Commit ddf74e51 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into live-trace-v2

parents 7297a06c e9e800f5
...@@ -10,12 +10,6 @@ engines: ...@@ -10,12 +10,6 @@ engines:
- javascript - javascript
exclude_paths: exclude_paths:
- "lib/api/v3/*" - "lib/api/v3/*"
eslint:
enabled: true
channel: "eslint-4"
rubocop:
enabled: true
channel: "gitlab-rubocop-0-52-1"
ratings: ratings:
paths: paths:
- Gemfile.lock - Gemfile.lock
......
...@@ -78,6 +78,19 @@ stages: ...@@ -78,6 +78,19 @@ stages:
- mysql:latest - mysql:latest
- redis:alpine - redis:alpine
.rails5-variables: &rails5-variables
script:
- export RAILS5=${RAILS5}
- export BUNDLE_GEMFILE=${BUNDLE_GEMFILE}
.rails5: &rails5
allow_failure: true
only:
- /rails5/
variables:
BUNDLE_GEMFILE: "Gemfile.rails5"
RAILS5: "true"
# Skip all jobs except the ones that begin with 'docs/'. # Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes. # Used for commits including ONLY documentation changes.
# https://docs.gitlab.com/ce/development/writing_documentation.html#testing # https://docs.gitlab.com/ce/development/writing_documentation.html#testing
...@@ -118,6 +131,7 @@ stages: ...@@ -118,6 +131,7 @@ stages:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
<<: *pull-cache <<: *pull-cache
<<: *rails5-variables
stage: test stage: test
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
...@@ -148,14 +162,23 @@ stages: ...@@ -148,14 +162,23 @@ stages:
<<: *rspec-metadata <<: *rspec-metadata
<<: *use-pg <<: *use-pg
.rspec-metadata-pg-rails5: &rspec-metadata-pg-rails5
<<: *rspec-metadata-pg
<<: *rails5
.rspec-metadata-mysql: &rspec-metadata-mysql .rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata <<: *rspec-metadata
<<: *use-mysql <<: *use-mysql
.rspec-metadata-mysql-rails5: &rspec-metadata-mysql-rails5
<<: *rspec-metadata-mysql
<<: *rails5
.spinach-metadata: &spinach-metadata .spinach-metadata: &spinach-metadata
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs-and-qa <<: *except-docs-and-qa
<<: *pull-cache <<: *pull-cache
<<: *rails5-variables
stage: test stage: test
script: script:
- JOB_NAME=( $CI_JOB_NAME ) - JOB_NAME=( $CI_JOB_NAME )
...@@ -179,10 +202,18 @@ stages: ...@@ -179,10 +202,18 @@ stages:
<<: *spinach-metadata <<: *spinach-metadata
<<: *use-pg <<: *use-pg
.spinach-metadata-pg-rails5: &spinach-metadata-pg-rails5
<<: *spinach-metadata-pg
<<: *rails5
.spinach-metadata-mysql: &spinach-metadata-mysql .spinach-metadata-mysql: &spinach-metadata-mysql
<<: *spinach-metadata <<: *spinach-metadata
<<: *use-mysql <<: *use-mysql
.spinach-metadata-mysql-rails5: &spinach-metadata-mysql-rails5
<<: *spinach-metadata-mysql
<<: *rails5
.only-canonical-masters: &only-canonical-masters .only-canonical-masters: &only-canonical-masters
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
...@@ -468,6 +499,70 @@ spinach-pg 1 2: *spinach-metadata-pg ...@@ -468,6 +499,70 @@ spinach-pg 1 2: *spinach-metadata-pg
spinach-mysql 0 2: *spinach-metadata-mysql spinach-mysql 0 2: *spinach-metadata-mysql
spinach-mysql 1 2: *spinach-metadata-mysql spinach-mysql 1 2: *spinach-metadata-mysql
rspec-pg-rails5 0 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 1 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 2 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 3 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 4 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 5 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 6 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 7 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 8 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 9 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 10 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 11 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 12 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 13 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 14 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 15 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 16 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 17 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 18 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 19 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 20 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 21 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 22 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 23 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 24 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 25 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 26 28: *rspec-metadata-pg-rails5
rspec-pg-rails5 27 28: *rspec-metadata-pg-rails5
rspec-mysql-rails5 0 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 1 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 2 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 3 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 4 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 5 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 6 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 7 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 8 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 9 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 10 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 11 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 12 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 13 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 14 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 15 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 16 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 17 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 18 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 19 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 20 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 21 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 22 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 23 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 24 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 25 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 26 28: *rspec-metadata-mysql-rails5
rspec-mysql-rails5 27 28: *rspec-metadata-mysql-rails5
spinach-pg-rails5 0 2: *spinach-metadata-pg-rails5
spinach-pg-rails5 1 2: *spinach-metadata-pg-rails5
spinach-mysql-rails5 0 2: *spinach-metadata-mysql-rails5
spinach-mysql-rails5 1 2: *spinach-metadata-mysql-rails5
static-analysis: static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-no-db-pull-cache-job
dependencies: dependencies:
...@@ -632,9 +727,6 @@ codequality: ...@@ -632,9 +727,6 @@ codequality:
cache: {} cache: {}
dependencies: [] dependencies: []
script: script:
# Get the custom rubocop codeclimate image (https://gitlab.com/gitlab-org/codeclimate-rubocop/wikis/home)
- docker pull dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1
- docker tag dev.gitlab.org:5005/gitlab/gitlab-build-images:gitlab-codeclimate-rubocop-0-52-1 codeclimate/codeclimate-rubocop:gitlab-codeclimate-rubocop-0-52-1
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code - docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
...@@ -670,7 +762,13 @@ qa:selectors: ...@@ -670,7 +762,13 @@ qa:selectors:
- bundle exec bin/qa Test::Sanity::Selectors - bundle exec bin/qa Test::Sanity::Selectors
coverage: coverage:
<<: *dedicated-no-docs-no-db-pull-cache-job # Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
# download artifacts from all the rspec jobs instead of from setup-test-env only
<<: *dedicated-runner
<<: *except-docs-and-qa
<<: *pull-cache
variables:
SETUP_DB: "false"
stage: post-test stage: post-test
script: script:
- bundle exec scripts/merge-simplecov - bundle exec scripts/merge-simplecov
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.6.3 (2018-04-03)
### Security (2 changes)
- Fix XSS on diff view stored on filenames.
- Adds confidential notes channel for Slack/Mattermost.
## 10.6.2 (2018-03-29) ## 10.6.2 (2018-03-29)
### Fixed (2 changes, 1 of them is from the community) ### Fixed (2 changes, 1 of them is from the community)
...@@ -217,6 +225,14 @@ entry. ...@@ -217,6 +225,14 @@ entry.
- Use host URL to build JIRA remote link icon. - Use host URL to build JIRA remote link icon.
## 10.5.7 (2018-04-03)
### Security (2 changes)
- Fix XSS on diff view stored on filenames.
- Adds confidential notes channel for Slack/Mattermost.
## 10.5.6 (2018-03-16) ## 10.5.6 (2018-03-16)
### Security (2 changes) ### Security (2 changes)
...@@ -484,6 +500,14 @@ entry. ...@@ -484,6 +500,14 @@ entry.
- Adds empty state illustration for pending job. - Adds empty state illustration for pending job.
## 10.4.7 (2018-04-03)
### Security (2 changes)
- Fix XSS on diff view stored on filenames.
- Adds confidential notes channel for Slack/Mattermost.
## 10.4.6 (2018-03-16) ## 10.4.6 (2018-03-16)
### Security (2 changes) ### Security (2 changes)
......
...@@ -19,7 +19,7 @@ export default class BoardService { ...@@ -19,7 +19,7 @@ export default class BoardService {
} }
static generateIssuePath(boardId, id) { static generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`; return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
} }
all() { all() {
......
...@@ -3,7 +3,6 @@ import { mapState, mapGetters } from 'vuex'; ...@@ -3,7 +3,6 @@ import { mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue'; import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue'; import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue'; import repoTabs from './repo_tabs.vue';
import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue'; import ideStatusBar from './ide_status_bar.vue';
import repoEditor from './repo_editor.vue'; import repoEditor from './repo_editor.vue';
...@@ -12,7 +11,6 @@ export default { ...@@ -12,7 +11,6 @@ export default {
ideSidebar, ideSidebar,
ideContextbar, ideContextbar,
repoTabs, repoTabs,
repoFileButtons,
ideStatusBar, ideStatusBar,
repoEditor, repoEditor,
}, },
...@@ -70,9 +68,6 @@ export default { ...@@ -70,9 +68,6 @@ export default {
class="multi-file-edit-pane-content" class="multi-file-edit-pane-content"
:file="activeFile" :file="activeFile"
/> />
<repo-file-buttons
:file="activeFile"
/>
<ide-status-bar <ide-status-bar
:file="activeFile" :file="activeFile"
/> />
......
<script>
import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Icon,
},
directives: {
tooltip,
},
props: {
file: {
type: Object,
required: true,
},
},
computed: {
showButtons() {
return (
this.file.rawPath || this.file.blamePath || this.file.commitsPath || this.file.permalink
);
},
rawDownloadButtonLabel() {
return this.file.binary ? __('Download') : __('Raw');
},
},
};
</script>
<template>
<div
v-if="showButtons"
class="pull-right ide-btn-group"
>
<a
v-tooltip
:href="file.blamePath"
:title="__('Blame')"
class="btn btn-xs btn-transparent blame"
>
<icon
name="blame"
:size="16"
/>
</a>
<a
v-tooltip
:href="file.commitsPath"
:title="__('History')"
class="btn btn-xs btn-transparent history"
>
<icon
name="history"
:size="16"
/>
</a>
<a
v-tooltip
:href="file.permalink"
:title="__('Permalink')"
class="btn btn-xs btn-transparent permalink"
>
<icon
name="link"
:size="16"
/>
</a>
<a
v-tooltip
:href="file.rawPath"
target="_blank"
class="btn btn-xs btn-transparent prepend-left-10 raw"
rel="noopener noreferrer"
:title="rawDownloadButtonLabel">
<icon
name="download"
:size="16"
/>
</a>
</div>
</template>
...@@ -2,10 +2,16 @@ ...@@ -2,10 +2,16 @@
/* global monaco */ /* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import flash from '~/flash'; import flash from '~/flash';
import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import monacoLoader from '../monaco_loader'; import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor'; import Editor from '../lib/editor';
import IdeFileButtons from './ide_file_buttons.vue';
export default { export default {
components: {
ContentViewer,
IdeFileButtons,
},
props: { props: {
file: { file: {
type: Object, type: Object,
...@@ -13,11 +19,21 @@ export default { ...@@ -13,11 +19,21 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['leftPanelCollapsed', 'rightPanelCollapsed', 'viewer', 'delayViewerUpdated']), ...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']),
...mapGetters(['currentMergeRequest']), ...mapGetters(['currentMergeRequest']),
shouldHideEditor() { shouldHideEditor() {
return this.file && this.file.binary && !this.file.raw; return this.file && this.file.binary && !this.file.raw;
}, },
editTabCSS() {
return {
active: this.file.viewMode === 'edit',
};
},
previewTabCSS() {
return {
active: this.file.viewMode === 'preview',
};
},
}, },
watch: { watch: {
file(oldVal, newVal) { file(oldVal, newVal) {
...@@ -26,15 +42,17 @@ export default { ...@@ -26,15 +42,17 @@ export default {
this.initMonaco(); this.initMonaco();
} }
}, },
leftPanelCollapsed() {
this.editor.updateDimensions();
},
rightPanelCollapsed() { rightPanelCollapsed() {
this.editor.updateDimensions(); this.editor.updateDimensions();
}, },
viewer() { viewer() {
this.createEditorInstance(); this.createEditorInstance();
}, },
panelResizing() {
if (!this.panelResizing) {
this.editor.updateDimensions();
}
},
}, },
beforeDestroy() { beforeDestroy() {
this.editor.dispose(); this.editor.dispose();
...@@ -56,6 +74,7 @@ export default { ...@@ -56,6 +74,7 @@ export default {
'changeFileContent', 'changeFileContent',
'setFileLanguage', 'setFileLanguage',
'setEditorPosition', 'setEditorPosition',
'setFileViewMode',
'setFileEOL', 'setFileEOL',
'updateViewer', 'updateViewer',
'updateDelayViewerUpdated', 'updateDelayViewerUpdated',
...@@ -153,15 +172,47 @@ export default { ...@@ -153,15 +172,47 @@ export default {
class="blob-viewer-container blob-editor-container" class="blob-viewer-container blob-editor-container"
> >
<div <div
v-if="shouldHideEditor" class="ide-mode-tabs clearfix"
v-html="file.html" v-if="!shouldHideEditor">
> <ul class="nav-links pull-left">
<li :class="editTabCSS">
<a
href="javascript:void(0);"
role="button"
@click.prevent="setFileViewMode({ file, viewMode: 'edit' })">
<template v-if="viewer === 'editor'">
{{ __('Edit') }}
</template>
<template v-else>
{{ __('Review') }}
</template>
</a>
</li>
<li
v-if="file.previewMode"
:class="previewTabCSS">
<a
href="javascript:void(0);"
role="button"
@click.prevent="setFileViewMode({ file, viewMode:'preview' })">
{{ file.previewMode.previewTitle }}
</a>
</li>
</ul>
<ide-file-buttons
:file="file"
/>
</div> </div>
<div <div
v-show="!shouldHideEditor" v-show="!shouldHideEditor && file.viewMode === 'edit'"
ref="editor" ref="editor"
class="multi-file-editor-holder" class="multi-file-editor-holder"
> >
</div> </div>
<content-viewer
v-if="!shouldHideEditor && file.viewMode === 'preview'"
:content="file.content || file.raw"
:path="file.path"
:project-path="file.projectId"/>
</div> </div>
</template> </template>
<script>
export default {
props: {
file: {
type: Object,
required: true,
},
},
computed: {
showButtons() {
return this.file.rawPath ||
this.file.blamePath ||
this.file.commitsPath ||
this.file.permalink;
},
rawDownloadButtonLabel() {
return this.file.binary ? 'Download' : 'Raw';
},
},
};
</script>
<template>
<div
v-if="showButtons"
class="multi-file-editor-btn-group"
>
<a
:href="file.rawPath"
target="_blank"
class="btn btn-default btn-sm raw"
rel="noopener noreferrer">
{{ rawDownloadButtonLabel }}
</a>
<div
class="btn-group"
role="group"
aria-label="File actions"
>
<a
:href="file.blamePath"
class="btn btn-default btn-sm blame"
>
Blame
</a>
<a
:href="file.commitsPath"
class="btn btn-default btn-sm history"
>
History
</a>
<a
:href="file.permalink"
class="btn btn-default btn-sm permalink"
>
Permalink
</a>
</div>
</div>
</template>
<script> <script>
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import PanelResizer from '~/vue_shared/components/panel_resizer.vue'; import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
export default { export default {
components: { components: {
PanelResizer, PanelResizer,
}, },
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
minSize: { minSize: {
type: Number, type: Number,
required: false, required: false,
default: 200, default: 340,
}, },
side: { side: {
type: String, type: String,
...@@ -47,10 +47,7 @@ ...@@ -47,10 +47,7 @@
}, },
}, },
methods: { methods: {
...mapActions([ ...mapActions(['setPanelCollapsedStatus', 'setResizingStatus']),
'setPanelCollapsedStatus',
'setResizingStatus',
]),
toggleFullbarCollapsed() { toggleFullbarCollapsed() {
if (this.collapsed && this.collapsible) { if (this.collapsed && this.collapsible) {
this.setPanelCollapsedStatus({ this.setPanelCollapsedStatus({
...@@ -60,8 +57,8 @@ ...@@ -60,8 +57,8 @@
} }
}, },
}, },
maxSize: (window.innerWidth / 2), maxSize: window.innerWidth / 2,
}; };
</script> </script>
<template> <template>
......
...@@ -69,6 +69,7 @@ export default class Editor { ...@@ -69,6 +69,7 @@ export default class Editor {
occurrencesHighlight: false, occurrencesHighlight: false,
renderLineHighlight: 'none', renderLineHighlight: 'none',
hideCursorInOverviewRuler: true, hideCursorInOverviewRuler: true,
renderSideBySide: Editor.renderSideBySide(domElement),
})), })),
); );
...@@ -81,7 +82,7 @@ export default class Editor { ...@@ -81,7 +82,7 @@ export default class Editor {
} }
attachModel(model) { attachModel(model) {
if (this.instance.getEditorType() === 'vs.editor.IDiffEditor') { if (this.isDiffEditorType) {
this.instance.setModel({ this.instance.setModel({
original: model.getOriginalModel(), original: model.getOriginalModel(),
modified: model.getModel(), modified: model.getModel(),
...@@ -153,6 +154,7 @@ export default class Editor { ...@@ -153,6 +154,7 @@ export default class Editor {
updateDimensions() { updateDimensions() {
this.instance.layout(); this.instance.layout();
this.updateDiffView();
} }
setPosition({ lineNumber, column }) { setPosition({ lineNumber, column }) {
...@@ -171,4 +173,20 @@ export default class Editor { ...@@ -171,4 +173,20 @@ export default class Editor {
this.disposable.add(this.instance.onDidChangeCursorPosition(e => cb(this.instance, e))); this.disposable.add(this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)));
} }
updateDiffView() {
if (!this.isDiffEditorType) return;
this.instance.updateOptions({
renderSideBySide: Editor.renderSideBySide(this.instance.getDomNode()),
});
}
get isDiffEditorType() {
return this.instance.getEditorType() === 'vs.editor.IDiffEditor';
}
static renderSideBySide(domElement) {
return domElement.offsetWidth >= 700;
}
} }
...@@ -6,7 +6,7 @@ export const defaultEditorOptions = { ...@@ -6,7 +6,7 @@ export const defaultEditorOptions = {
minimap: { minimap: {
enabled: false, enabled: false,
}, },
wordWrap: 'bounded', wordWrap: 'on',
}; };
export default [ export default [
......
...@@ -149,6 +149,10 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn ...@@ -149,6 +149,10 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn
} }
}; };
export const setFileViewMode = ({ state, commit }, { file, viewMode }) => {
commit(types.SET_FILE_VIEWMODE, { file, viewMode });
};
export const discardFileChanges = ({ state, commit }, path) => { export const discardFileChanges = ({ state, commit }, path) => {
const file = state.entries[path]; const file = state.entries[path];
......
...@@ -38,6 +38,7 @@ export const SET_FILE_BASE_RAW_DATA = 'SET_FILE_BASE_RAW_DATA'; ...@@ -38,6 +38,7 @@ export const SET_FILE_BASE_RAW_DATA = 'SET_FILE_BASE_RAW_DATA';
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT'; export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE'; export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
export const SET_FILE_POSITION = 'SET_FILE_POSITION'; export const SET_FILE_POSITION = 'SET_FILE_POSITION';
export const SET_FILE_VIEWMODE = 'SET_FILE_VIEWMODE';
export const SET_FILE_EOL = 'SET_FILE_EOL'; export const SET_FILE_EOL = 'SET_FILE_EOL';
export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES'; export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
export const ADD_FILE_TO_CHANGED = 'ADD_FILE_TO_CHANGED'; export const ADD_FILE_TO_CHANGED = 'ADD_FILE_TO_CHANGED';
......
...@@ -42,6 +42,7 @@ export default { ...@@ -42,6 +42,7 @@ export default {
renderError: data.render_error, renderError: data.render_error,
raw: null, raw: null,
baseRaw: null, baseRaw: null,
html: data.html,
}); });
}, },
[types.SET_FILE_RAW_DATA](state, { file, raw }) { [types.SET_FILE_RAW_DATA](state, { file, raw }) {
...@@ -83,6 +84,11 @@ export default { ...@@ -83,6 +84,11 @@ export default {
mrChange, mrChange,
}); });
}, },
[types.SET_FILE_VIEWMODE](state, { file, viewMode }) {
Object.assign(state.entries[file.path], {
viewMode,
});
},
[types.DISCARD_FILE_CHANGES](state, path) { [types.DISCARD_FILE_CHANGES](state, path) {
Object.assign(state.entries[path], { Object.assign(state.entries[path], {
content: state.entries[path].raw, content: state.entries[path].raw,
......
...@@ -38,6 +38,8 @@ export const dataStructure = () => ({ ...@@ -38,6 +38,8 @@ export const dataStructure = () => ({
editorColumn: 1, editorColumn: 1,
fileLanguage: '', fileLanguage: '',
eol: '', eol: '',
viewMode: 'edit',
previewMode: null,
}); });
export const decorateData = entity => { export const decorateData = entity => {
...@@ -57,8 +59,9 @@ export const decorateData = entity => { ...@@ -57,8 +59,9 @@ export const decorateData = entity => {
changed = false, changed = false,
parentTreeUrl = '', parentTreeUrl = '',
base64 = false, base64 = false,
previewMode,
file_lock, file_lock,
html,
} = entity; } = entity;
return { return {
...@@ -79,8 +82,9 @@ export const decorateData = entity => { ...@@ -79,8 +82,9 @@ export const decorateData = entity => {
renderError, renderError,
content, content,
base64, base64,
previewMode,
file_lock, file_lock,
html,
}; };
}; };
......
import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import { decorateData, sortTree } from '../utils'; import { decorateData, sortTree } from '../utils';
self.addEventListener('message', e => { self.addEventListener('message', e => {
const { const { data, projectId, branchId, tempFile = false, content = '', base64 = false } = e.data;
data,
projectId,
branchId,
tempFile = false,
content = '',
base64 = false,
} = e.data;
const treeList = []; const treeList = [];
let file; let file;
...@@ -19,9 +13,7 @@ self.addEventListener('message', e => { ...@@ -19,9 +13,7 @@ self.addEventListener('message', e => {
if (pathSplit.length > 0) { if (pathSplit.length > 0) {
pathSplit.reduce((pathAcc, folderName) => { pathSplit.reduce((pathAcc, folderName) => {
const parentFolder = acc[pathAcc[pathAcc.length - 1]]; const parentFolder = acc[pathAcc[pathAcc.length - 1]];
const folderPath = `${ const folderPath = `${parentFolder ? `${parentFolder.path}/` : ''}${folderName}`;
parentFolder ? `${parentFolder.path}/` : ''
}${folderName}`;
const foundEntry = acc[folderPath]; const foundEntry = acc[folderPath];
if (!foundEntry) { if (!foundEntry) {
...@@ -33,9 +25,7 @@ self.addEventListener('message', e => { ...@@ -33,9 +25,7 @@ self.addEventListener('message', e => {
path: folderPath, path: folderPath,
url: `/${projectId}/tree/${branchId}/${folderPath}/`, url: `/${projectId}/tree/${branchId}/${folderPath}/`,
type: 'tree', type: 'tree',
parentTreeUrl: parentFolder parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
? parentFolder.url
: `/${projectId}/tree/${branchId}/`,
tempFile, tempFile,
changed: tempFile, changed: tempFile,
opened: tempFile, opened: tempFile,
...@@ -70,13 +60,12 @@ self.addEventListener('message', e => { ...@@ -70,13 +60,12 @@ self.addEventListener('message', e => {
path, path,
url: `/${projectId}/blob/${branchId}/${path}`, url: `/${projectId}/blob/${branchId}/${path}`,
type: 'blob', type: 'blob',
parentTreeUrl: fileFolder parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
? fileFolder.url
: `/${projectId}/blob/${branchId}`,
tempFile, tempFile,
changed: tempFile, changed: tempFile,
content, content,
base64, base64,
previewMode: viewerInformationForPath(blobName),
}); });
Object.assign(acc, { Object.assign(acc, {
......
...@@ -94,10 +94,10 @@ export default class MilestoneSelect { ...@@ -94,10 +94,10 @@ export default class MilestoneSelect {
if (showMenuAbove) { if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove(); $dropdown.data('glDropdown').positionMenuAbove();
} }
$(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active'); $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`).addClass('is-active');
}), }),
renderRow: milestone => ` renderRow: milestone => `
<li data-milestone-id="${milestone.name}"> <li data-milestone-id="${_.escape(milestone.name)}">
<a href='#' class='dropdown-menu-milestone-link'> <a href='#' class='dropdown-menu-milestone-link'>
${_.escape(milestone.title)} ${_.escape(milestone.title)}
</a> </a>
...@@ -125,7 +125,6 @@ export default class MilestoneSelect { ...@@ -125,7 +125,6 @@ export default class MilestoneSelect {
return milestone.id; return milestone.id;
} }
}, },
isSelected: milestone => milestone.name === selectedMilestone,
hidden: () => { hidden: () => {
$selectBox.hide(); $selectBox.hide();
// display:block overrides the hide-collapse rule // display:block overrides the hide-collapse rule
...@@ -137,7 +136,7 @@ export default class MilestoneSelect { ...@@ -137,7 +136,7 @@ export default class MilestoneSelect {
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
} }
$('a.is-active', $el).removeClass('is-active'); $('a.is-active', $el).removeClass('is-active');
$(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active');
}, },
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: (clickEvent) => { clicked: (clickEvent) => {
...@@ -158,6 +157,7 @@ export default class MilestoneSelect { ...@@ -158,6 +157,7 @@ export default class MilestoneSelect {
const isMRIndex = (page === page && page === 'projects:merge_requests:index'); const isMRIndex = (page === page && page === 'projects:merge_requests:index');
const isSelecting = (selected.name !== selectedMilestone); const isSelecting = (selected.name !== selectedMilestone);
selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
e.preventDefault(); e.preventDefault();
return; return;
......
...@@ -1190,12 +1190,12 @@ export default class Notes { ...@@ -1190,12 +1190,12 @@ export default class Notes {
addForm = false; addForm = false;
let lineTypeSelector = ''; let lineTypeSelector = '';
rowCssToAdd = rowCssToAdd =
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>'; '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content discussion-notes"></div></td></tr>';
// In parallel view, look inside the correct left/right pane // In parallel view, look inside the correct left/right pane
if (this.isParallelView()) { if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`; lineTypeSelector = `.${lineType}`;
rowCssToAdd = rowCssToAdd =
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>'; '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content discussion-notes"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content discussion-notes"></div></td></tr>';
} }
const notesContentSelector = `.notes_content${lineTypeSelector} .content`; const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector); let notesContent = targetRow.find(notesContentSelector);
......
...@@ -258,9 +258,7 @@ Please check your network connection and try again.`; ...@@ -258,9 +258,7 @@ Please check your network connection and try again.`;
:key="note.id" :key="note.id"
/> />
</ul> </ul>
<div <div class="discussion-reply-holder">
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder">
<template v-if="!isReplying && canReply"> <template v-if="!isReplying && canReply">
<div <div
class="btn-group-justified discussion-with-resolve-btn" class="btn-group-justified discussion-with-resolve-btn"
......
<script>
import { viewerInformationForPath } from './lib/viewer_utils';
import MarkdownViewer from './viewers/markdown_viewer.vue';
export default {
props: {
content: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
projectPath: {
type: String,
required: false,
default: '',
},
},
computed: {
viewer() {
const previewInfo = viewerInformationForPath(this.path);
switch (previewInfo.id) {
case 'markdown':
return MarkdownViewer;
default:
return null;
}
},
},
};
</script>
<template>
<div class="preview-container">
<component
:is="viewer"
:project-path="projectPath"
:content="content"
/>
</div>
</template>
const viewers = {
markdown: {
id: 'markdown',
previewTitle: 'Preview Markdown',
},
};
const fileNameViewers = {};
const fileExtensionViewers = {
md: 'markdown',
markdown: 'markdown',
};
export function viewerInformationForPath(path) {
if (!path) return null;
const name = path.split('/').pop();
const viewerName =
fileNameViewers[name] || fileExtensionViewers[name ? name.split('.').pop() : ''] || '';
return viewers[viewerName];
}
export default viewers;
<script>
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import $ from 'jquery';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
const CancelToken = axios.CancelToken;
let axiosSource;
export default {
components: {
SkeletonLoadingContainer,
},
props: {
content: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
},
data() {
return {
previewContent: null,
isLoading: false,
};
},
watch: {
content() {
this.previewContent = null;
},
},
created() {
axiosSource = CancelToken.source();
this.fetchMarkdownPreview();
},
updated() {
this.fetchMarkdownPreview();
},
destroyed() {
if (this.isLoading) axiosSource.cancel('Cancelling Preview');
},
methods: {
fetchMarkdownPreview() {
if (this.content && this.previewContent === null) {
this.isLoading = true;
const postBody = {
text: this.content,
};
const postOptions = {
cancelToken: axiosSource.token,
};
axios
.post(
`${gon.relative_url_root}/${this.projectPath}/preview_markdown`,
postBody,
postOptions,
)
.then(({ data }) => {
this.previewContent = data.body;
this.isLoading = false;
this.$nextTick(() => {
$(this.$refs['markdown-preview']).renderGFM();
});
})
.catch(() => {
this.previewContent = __('An error occurred while fetching markdown preview');
this.isLoading = false;
});
}
},
},
};
</script>
<template>
<div
ref="markdown-preview"
class="md md-previewer">
<skeleton-loading-container v-if="isLoading" />
<div
v-else
v-html="previewContent">
</div>
</div>
</template>
...@@ -813,6 +813,7 @@ ...@@ -813,6 +813,7 @@
} }
.discussion-notes { .discussion-notes {
padding: 0 $gl-padding $gl-padding;
min-height: 35px; min-height: 35px;
&:first-child { &:first-child {
......
...@@ -173,11 +173,7 @@ ...@@ -173,11 +173,7 @@
} }
.discussion-form { .discussion-form {
background-color: $white-light; padding-top: $gl-padding-top;
}
.discussion-form-container {
padding: $gl-padding-top $gl-padding $gl-padding;
} }
.discussion-notes .disabled-comment { .discussion-notes .disabled-comment {
...@@ -237,12 +233,7 @@ ...@@ -237,12 +233,7 @@
.discussion-body, .discussion-body,
.diff-file { .diff-file {
.discussion-reply-holder { .discussion-reply-holder {
background-color: $white-light; padding-top: $gl-padding;
padding: 10px 16px;
&.is-replying {
padding-bottom: $gl-padding;
}
} }
} }
......
...@@ -47,7 +47,7 @@ ul.notes { ...@@ -47,7 +47,7 @@ ul.notes {
} }
.timeline-entry-inner { .timeline-entry-inner {
padding: $gl-padding $gl-btn-padding; padding: $gl-padding 0;
border-bottom: 1px solid $white-normal; border-bottom: 1px solid $white-normal;
} }
...@@ -94,12 +94,6 @@ ul.notes { ...@@ -94,12 +94,6 @@ ul.notes {
} }
} }
&.note-discussion {
.timeline-entry-inner {
padding: $gl-padding 10px;
}
}
.editing-spinner { .editing-spinner {
display: none; display: none;
} }
...@@ -352,6 +346,8 @@ ul.notes { ...@@ -352,6 +346,8 @@ ul.notes {
} }
.discussion-notes { .discussion-notes {
background-color: $white-light;
&:not(:first-child) { &:not(:first-child) {
border-top: 1px solid $white-normal; border-top: 1px solid $white-normal;
margin-top: 20px; margin-top: 20px;
...@@ -363,10 +359,6 @@ ul.notes { ...@@ -363,10 +359,6 @@ ul.notes {
} }
} }
.notes {
background-color: $white-light;
}
a code { a code {
top: 0; top: 0;
margin-right: 0; margin-right: 0;
...@@ -647,8 +639,6 @@ ul.notes { ...@@ -647,8 +639,6 @@ ul.notes {
border-bottom: 1px solid $white-normal; border-bottom: 1px solid $white-normal;
.timeline-entry-inner { .timeline-entry-inner {
padding-left: $gl-padding;
padding-right: $gl-padding;
border-bottom: 0; border-bottom: 0;
} }
} }
......
...@@ -308,14 +308,34 @@ ...@@ -308,14 +308,34 @@
height: 100%; height: 100%;
} }
.multi-file-editor-btn-group { .preview-container {
padding: $gl-bar-padding $gl-padding; height: 100%;
border-top: 1px solid $white-dark; overflow: auto;
.md-previewer {
padding: $gl-padding;
}
}
.ide-mode-tabs {
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
background: $white-light;
.nav-links {
border-bottom: 0;
li a {
padding: $gl-padding-8 $gl-padding;
line-height: $gl-btn-line-height;
}
}
}
.ide-btn-group {
padding: $gl-padding-4 $gl-vert-padding;
} }
.ide-status-bar { .ide-status-bar {
border-top: 1px solid $white-dark;
padding: $gl-bar-padding $gl-padding; padding: $gl-bar-padding $gl-padding;
background: $white-light; background: $white-light;
display: flex; display: flex;
......
...@@ -4,8 +4,8 @@ class Projects::DiscussionsController < Projects::ApplicationController ...@@ -4,8 +4,8 @@ class Projects::DiscussionsController < Projects::ApplicationController
before_action :check_merge_requests_available! before_action :check_merge_requests_available!
before_action :merge_request before_action :merge_request
before_action :discussion before_action :discussion, only: [:resolve, :unresolve]
before_action :authorize_resolve_discussion! before_action :authorize_resolve_discussion!, only: [:resolve, :unresolve]
def resolve def resolve
Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion) Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion)
......
...@@ -7,6 +7,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -7,6 +7,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
attr_reader :authentication_result, :redirected_path attr_reader :authentication_result, :redirected_path
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
alias_method :user, :actor alias_method :user, :actor
alias_method :authenticated_user, :actor alias_method :authenticated_user, :actor
......
...@@ -64,7 +64,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -64,7 +64,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
@access ||= access_klass.new(access_actor, project, @access ||= access_klass.new(access_actor, project,
'http', authentication_abilities: authentication_abilities, 'http', authentication_abilities: authentication_abilities,
namespace_path: params[:namespace_id], project_path: project_path, namespace_path: params[:namespace_id], project_path: project_path,
redirected_path: redirected_path) redirected_path: redirected_path, auth_result_type: auth_result_type)
end end
def access_actor def access_actor
......
...@@ -128,7 +128,7 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -128,7 +128,7 @@ class Projects::JobsController < Projects::ApplicationController
if stream.file? if stream.file?
send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline' send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
else else
send_data stream.raw, type: 'text/plain; charset=utf-8', disposition: 'inline' send_data stream.raw, type: 'text/plain; charset=utf-8', disposition: 'inline', filename: 'job.log'
end end
end end
end end
......
...@@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def existing_oids def existing_oids
@existing_oids ||= begin @existing_oids ||= begin
storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end end
end end
......
...@@ -4,41 +4,4 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -4,41 +4,4 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def show def show
redirect_to project_settings_ci_cd_path(@project, params: params) redirect_to project_settings_ci_cd_path(@project, params: params)
end end
def update
Projects::UpdateService.new(project, current_user, update_params).tap do |service|
if service.execute
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
run_autodevops_pipeline(service)
redirect_to project_settings_ci_cd_path(@project)
else
render 'show'
end
end
end
private
def run_autodevops_pipeline(service)
return unless service.run_auto_devops_pipeline?
if @project.empty_repo?
flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
return
end
CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe
end
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_in_minutes, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
auto_devops_attributes: [:id, :domain, :enabled]
)
end
end end
...@@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController
when "graphs_commits" when "graphs_commits"
commits_project_graph_path(@project, @id) commits_project_graph_path(@project, @id)
when "badges" when "badges"
project_pipelines_settings_path(@project, ref: @id) project_settings_ci_cd_path(@project, ref: @id)
else else
project_commits_path(@project, @id) project_commits_path(@project, @id)
end end
......
...@@ -2,13 +2,24 @@ module Projects ...@@ -2,13 +2,24 @@ module Projects
module Settings module Settings
class CiCdController < Projects::ApplicationController class CiCdController < Projects::ApplicationController
before_action :authorize_admin_pipeline! before_action :authorize_admin_pipeline!
before_action :define_variables
def show def show
define_runners_variables end
define_secret_variables
define_triggers_variables def update
define_badges_variables Projects::UpdateService.new(project, current_user, update_params).tap do |service|
define_auto_devops_variables result = service.execute
if result[:status] == :success
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
run_autodevops_pipeline(service)
redirect_to project_settings_ci_cd_path(@project)
else
render 'show'
end
end
end end
def reset_cache def reset_cache
...@@ -25,6 +36,35 @@ module Projects ...@@ -25,6 +36,35 @@ module Projects
private private
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_human_readable, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
auto_devops_attributes: [:id, :domain, :enabled]
)
end
def run_autodevops_pipeline(service)
return unless service.run_auto_devops_pipeline?
if @project.empty_repo?
flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
return
end
CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe
end
def define_variables
define_runners_variables
define_secret_variables
define_triggers_variables
define_badges_variables
define_auto_devops_variables
end
def define_runners_variables def define_runners_variables
@project_runners = @project.runners.ordered @project_runners = @project.runners.ordered
@assignable_runners = current_user.ci_authorized_runners @assignable_runners = current_user.ci_authorized_runners
......
...@@ -324,7 +324,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -324,7 +324,7 @@ class ProjectsController < Projects::ApplicationController
:avatar, :avatar,
:build_allow_git_fetch, :build_allow_git_fetch,
:build_coverage_regex, :build_coverage_regex,
:build_timeout_in_minutes, :build_timeout_human_readable,
:resolve_outdated_diff_discussions, :resolve_outdated_diff_discussions,
:container_registry_enabled, :container_registry_enabled,
:default_branch, :default_branch,
......
module ServicesHelper module ServicesHelper
def service_event_description(event)
case event
when "push", "push_events"
"Event will be triggered by a push to the repository"
when "tag_push", "tag_push_events"
"Event will be triggered when a new tag is pushed to the repository"
when "note", "note_events"
"Event will be triggered when someone adds a comment"
when "confidential_note", "confidential_note_events"
"Event will be triggered when someone adds a comment on a confidential issue"
when "issue", "issue_events"
"Event will be triggered when an issue is created/updated/closed"
when "confidential_issue", "confidential_issues_events"
"Event will be triggered when a confidential issue is created/updated/closed"
when "merge_request", "merge_request_events"
"Event will be triggered when a merge request is created/updated/merged"
when "pipeline", "pipeline_events"
"Event will be triggered when a pipeline status changes"
when "wiki_page", "wiki_page_events"
"Event will be triggered when a wiki page is created/updated"
when "commit", "commit_events"
"Event will be triggered when a commit is created/updated"
end
end
def service_event_field_name(event) def service_event_field_name(event)
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event) event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events" "#{event}_events"
......
...@@ -8,14 +8,14 @@ module ChronicDurationAttribute ...@@ -8,14 +8,14 @@ module ChronicDurationAttribute
end end
end end
def chronic_duration_attr_writer(virtual_attribute, source_attribute) def chronic_duration_attr_writer(virtual_attribute, source_attribute, parameters = {})
chronic_duration_attr_reader(virtual_attribute, source_attribute) chronic_duration_attr_reader(virtual_attribute, source_attribute)
define_method("#{virtual_attribute}=") do |value| define_method("#{virtual_attribute}=") do |value|
chronic_duration_attributes[virtual_attribute] = value.presence || '' chronic_duration_attributes[virtual_attribute] = value.presence || parameters[:default].presence.to_s
begin begin
new_value = ChronicDuration.parse(value).to_i if value.present? new_value = value.present? ? ChronicDuration.parse(value).to_i : parameters[:default].presence
assign_attributes(source_attribute => new_value) assign_attributes(source_attribute => new_value)
rescue ChronicDuration::DurationParseError rescue ChronicDuration::DurationParseError
# ignore error as it will be caught by validation # ignore error as it will be caught by validation
......
...@@ -7,6 +7,7 @@ class ProjectHook < WebHook ...@@ -7,6 +7,7 @@ class ProjectHook < WebHook
:issue_hooks, :issue_hooks,
:confidential_issue_hooks, :confidential_issue_hooks,
:note_hooks, :note_hooks,
:confidential_note_hooks,
:merge_request_hooks, :merge_request_hooks,
:job_hooks, :job_hooks,
:pipeline_hooks, :pipeline_hooks,
......
...@@ -268,6 +268,10 @@ class Note < ActiveRecord::Base ...@@ -268,6 +268,10 @@ class Note < ActiveRecord::Base
self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR
end end
def confidential?
noteable.try(:confidential?)
end
def editable? def editable?
!system? !system?
end end
...@@ -313,6 +317,10 @@ class Note < ActiveRecord::Base ...@@ -313,6 +317,10 @@ class Note < ActiveRecord::Base
!system? && !for_snippet? !system? && !for_snippet?
end end
def can_create_notification?
true
end
def discussion_class(noteable = nil) def discussion_class(noteable = nil)
# When commit notes are rendered on an MR's Discussion page, they are # When commit notes are rendered on an MR's Discussion page, they are
# displayed in one discussion instead of individually. # displayed in one discussion instead of individually.
......
...@@ -21,6 +21,7 @@ class Project < ActiveRecord::Base ...@@ -21,6 +21,7 @@ class Project < ActiveRecord::Base
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include DeploymentPlatform include DeploymentPlatform
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
include ChronicDurationAttribute
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
...@@ -325,6 +326,12 @@ class Project < ActiveRecord::Base ...@@ -325,6 +326,12 @@ class Project < ActiveRecord::Base
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600,
message: 'needs to be at least 10 minutes' }
# Returns a collection of projects that is either public or visible to the # Returns a collection of projects that is either public or visible to the
# logged in user. # logged in user.
def self.public_or_visible_to_user(user = nil) def self.public_or_visible_to_user(user = nil)
...@@ -630,7 +637,7 @@ class Project < ActiveRecord::Base ...@@ -630,7 +637,7 @@ class Project < ActiveRecord::Base
end end
def create_or_update_import_data(data: nil, credentials: nil) def create_or_update_import_data(data: nil, credentials: nil)
return unless import_url.present? && valid_import_url? return if data.nil? && credentials.nil?
project_import_data = import_data || build_import_data project_import_data = import_data || build_import_data
if data if data
...@@ -1066,6 +1073,16 @@ class Project < ActiveRecord::Base ...@@ -1066,6 +1073,16 @@ class Project < ActiveRecord::Base
end end
end end
# This will return all `lfs_objects` that are accessible to the project.
# So this might be `self.lfs_objects` if the project is not part of a fork
# network, or it is the base of the fork network.
#
# TODO: refactor this to get the correct lfs objects when implementing
# https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
def all_lfs_objects
lfs_storage_project.lfs_objects
end
def personal? def personal?
!group !group
end end
...@@ -1299,14 +1316,6 @@ class Project < ActiveRecord::Base ...@@ -1299,14 +1316,6 @@ class Project < ActiveRecord::Base
self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end end
def build_timeout_in_minutes
build_timeout / 60
end
def build_timeout_in_minutes=(value)
self.build_timeout = value.to_i * 60
end
def open_issues_count def open_issues_count
Projects::OpenIssuesCountService.new(self).count Projects::OpenIssuesCountService.new(self).count
end end
...@@ -1478,6 +1487,7 @@ class Project < ActiveRecord::Base ...@@ -1478,6 +1487,7 @@ class Project < ActiveRecord::Base
remove_import_jid remove_import_jid
update_project_counter_caches update_project_counter_caches
after_create_default_branch after_create_default_branch
refresh_markdown_cache!
end end
def update_project_counter_caches def update_project_counter_caches
......
...@@ -21,8 +21,16 @@ class ChatNotificationService < Service ...@@ -21,8 +21,16 @@ class ChatNotificationService < Service
end end
end end
def confidential_issue_channel
properties['confidential_issue_channel'].presence || properties['issue_channel']
end
def confidential_note_channel
properties['confidential_note_channel'].presence || properties['note_channel']
end
def self.supported_events def self.supported_events
%w[push issue confidential_issue merge_request note tag_push %w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page] pipeline wiki_page]
end end
...@@ -55,7 +63,9 @@ class ChatNotificationService < Service ...@@ -55,7 +63,9 @@ class ChatNotificationService < Service
return false unless message return false unless message
channel_name = get_channel_field(object_kind).presence || channel event_type = data[:event_type] || object_kind
channel_name = get_channel_field(event_type).presence || channel
opts = {} opts = {}
opts[:channel] = channel_name if channel_name opts[:channel] = channel_name if channel_name
......
...@@ -46,7 +46,7 @@ class HipchatService < Service ...@@ -46,7 +46,7 @@ class HipchatService < Service
end end
def self.supported_events def self.supported_events
%w(push issue confidential_issue merge_request note tag_push pipeline) %w(push issue confidential_issue merge_request note confidential_note tag_push pipeline)
end end
def execute(data) def execute(data)
......
...@@ -14,6 +14,7 @@ class Service < ActiveRecord::Base ...@@ -14,6 +14,7 @@ class Service < ActiveRecord::Base
default_value_for :merge_requests_events, true default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true default_value_for :tag_push_events, true
default_value_for :note_events, true default_value_for :note_events, true
default_value_for :confidential_note_events, true
default_value_for :job_events, true default_value_for :job_events, true
default_value_for :pipeline_events, true default_value_for :pipeline_events, true
default_value_for :wiki_page_events, true default_value_for :wiki_page_events, true
...@@ -42,6 +43,7 @@ class Service < ActiveRecord::Base ...@@ -42,6 +43,7 @@ class Service < ActiveRecord::Base
scope :confidential_issue_hooks, -> { where(confidential_issues_events: true, active: true) } scope :confidential_issue_hooks, -> { where(confidential_issues_events: true, active: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) }
scope :confidential_note_hooks, -> { where(confidential_note_events: true, active: true) }
scope :job_hooks, -> { where(job_events: true, active: true) } scope :job_hooks, -> { where(job_events: true, active: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) } scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
...@@ -168,9 +170,11 @@ class Service < ActiveRecord::Base ...@@ -168,9 +170,11 @@ class Service < ActiveRecord::Base
def self.prop_accessor(*args) def self.prop_accessor(*args)
args.each do |arg| args.each do |arg|
class_eval %{ class_eval %{
unless method_defined?(arg)
def #{arg} def #{arg}
properties['#{arg}'] properties['#{arg}']
end end
end
def #{arg}=(value) def #{arg}=(value)
self.properties ||= {} self.properties ||= {}
......
...@@ -164,12 +164,15 @@ class User < ActiveRecord::Base ...@@ -164,12 +164,15 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed? before_validation :set_notification_email, if: :email_changed?
before_save :set_notification_email, if: :email_changed? # in case validation is skipped
before_validation :set_public_email, if: :public_email_changed? before_validation :set_public_email, if: :public_email_changed?
before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
before_save :ensure_incoming_email_token before_save :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? } before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? } before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
before_validation :ensure_namespace_correct before_validation :ensure_namespace_correct
before_save :ensure_namespace_correct # in case validation is skipped
after_validation :set_username_errors after_validation :set_username_errors
after_update :username_changed_hook, if: :username_changed? after_update :username_changed_hook, if: :username_changed?
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
...@@ -408,7 +411,6 @@ class User < ActiveRecord::Base ...@@ -408,7 +411,6 @@ class User < ActiveRecord::Base
unique_internal(where(ghost: true), 'ghost', email) do |u| unique_internal(where(ghost: true), 'ghost', email) do |u|
u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
u.name = 'Ghost User' u.name = 'Ghost User'
u.notification_email = email
end end
end end
end end
......
...@@ -2,20 +2,6 @@ class IssuablePolicy < BasePolicy ...@@ -2,20 +2,6 @@ class IssuablePolicy < BasePolicy
delegate { @subject.project } delegate { @subject.project }
condition(:locked, scope: :subject, score: 0) { @subject.discussion_locked? } condition(:locked, scope: :subject, score: 0) { @subject.discussion_locked? }
# We aren't checking `:read_issue` or `:read_merge_request` in this case
# because it could be possible for a user to see an issuable-iid
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be allowed
# to read the actual issue after a more expensive `:read_issue` check.
#
# `:read_issue` & `:read_issue_iid` could diverge in gitlab-ee.
condition(:visible_to_user, score: 4) do
Project.where(id: @subject.project)
.public_or_visible_to_user(@user)
.with_feature_available_for_user(@subject, @user)
.any?
end
condition(:is_project_member) { @user && @subject.project && @subject.project.team.member?(@user) } condition(:is_project_member) { @user && @subject.project && @subject.project.team.member?(@user) }
desc "User is the assignee or author" desc "User is the assignee or author"
......
...@@ -17,6 +17,4 @@ class IssuePolicy < IssuablePolicy ...@@ -17,6 +17,4 @@ class IssuePolicy < IssuablePolicy
prevent :update_issue prevent :update_issue
prevent :admin_issue prevent :admin_issue
end end
rule { can?(:read_issue) | visible_to_user }.enable :read_issue_iid
end end
class MergeRequestPolicy < IssuablePolicy class MergeRequestPolicy < IssuablePolicy
rule { can?(:read_merge_request) | visible_to_user }.enable :read_merge_request_iid
end end
...@@ -66,6 +66,22 @@ class ProjectPolicy < BasePolicy ...@@ -66,6 +66,22 @@ class ProjectPolicy < BasePolicy
project.merge_requests_allowing_push_to_user(user).any? project.merge_requests_allowing_push_to_user(user).any?
end end
# We aren't checking `:read_issue` or `:read_merge_request` in this case
# because it could be possible for a user to see an issuable-iid
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
# allowed to read the actual issue after a more expensive `:read_issue`
# check. These checks are intended to be used alongside
# `:read_project_for_iids`.
#
# `:read_issue` & `:read_issue_iid` could diverge in gitlab-ee.
condition(:issues_visible_to_user, score: 4) do
@subject.feature_available?(:issues, @user)
end
condition(:merge_requests_visible_to_user, score: 4) do
@subject.feature_available?(:merge_requests, @user)
end
features = %w[ features = %w[
merge_requests merge_requests
issues issues
...@@ -81,6 +97,10 @@ class ProjectPolicy < BasePolicy ...@@ -81,6 +97,10 @@ class ProjectPolicy < BasePolicy
condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) } condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
end end
# `:read_project` may be prevented in EE, but `:read_project_for_iids` should
# not.
rule { guest | admin }.enable :read_project_for_iids
rule { guest }.enable :guest_access rule { guest }.enable :guest_access
rule { reporter }.enable :reporter_access rule { reporter }.enable :reporter_access
rule { developer }.enable :developer_access rule { developer }.enable :developer_access
...@@ -150,6 +170,7 @@ class ProjectPolicy < BasePolicy ...@@ -150,6 +170,7 @@ class ProjectPolicy < BasePolicy
# where we enable or prevent it based on other coditions. # where we enable or prevent it based on other coditions.
rule { (~anonymous & public_project) | internal_access }.policy do rule { (~anonymous & public_project) | internal_access }.policy do
enable :public_user_access enable :public_user_access
enable :read_project_for_iids
end end
rule { can?(:public_user_access) }.policy do rule { can?(:public_user_access) }.policy do
...@@ -255,7 +276,11 @@ class ProjectPolicy < BasePolicy ...@@ -255,7 +276,11 @@ class ProjectPolicy < BasePolicy
end end
rule { anonymous & ~public_project }.prevent_all rule { anonymous & ~public_project }.prevent_all
rule { public_project }.enable(:public_access)
rule { public_project }.policy do
enable :public_access
enable :read_project_for_iids
end
rule { can?(:public_access) }.policy do rule { can?(:public_access) }.policy do
enable :read_project enable :read_project
...@@ -305,6 +330,14 @@ class ProjectPolicy < BasePolicy ...@@ -305,6 +330,14 @@ class ProjectPolicy < BasePolicy
enable :update_pipeline enable :update_pipeline
end end
rule do
(can?(:read_project_for_iids) & issues_visible_to_user) | can?(:read_issue)
end.enable :read_issue_iid
rule do
(can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
end.enable :read_merge_request_iid
private private
def team_member? def team_member?
......
...@@ -42,7 +42,10 @@ module Boards ...@@ -42,7 +42,10 @@ module Boards
) )
end end
attrs[:move_between_ids] = move_between_ids if move_between_ids if move_between_ids
attrs[:move_between_ids] = move_between_ids
attrs[:board_group_id] = board.group&.id
end
attrs attrs
end end
......
...@@ -4,6 +4,9 @@ module Ci ...@@ -4,6 +4,9 @@ module Ci
class RegisterJobService class RegisterJobService
attr_reader :runner attr_reader :runner
JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
Result = Struct.new(:build, :valid?) Result = Struct.new(:build, :valid?)
def initialize(runner) def initialize(runner)
...@@ -104,10 +107,22 @@ module Ci ...@@ -104,10 +107,22 @@ module Ci
end end
def register_success(job) def register_success(job)
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at) labels = { shared_runner: runner.shared?,
jobs_running_for_project: jobs_running_for_project(job) }
job_queue_duration_seconds.observe(labels, Time.now - job.queued_at)
attempt_counter.increment attempt_counter.increment
end end
def jobs_running_for_project(job)
return '+Inf' unless runner.shared?
# excluding currently started job
running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.shared)
.limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
end
def failed_attempt_counter def failed_attempt_counter
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job") @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
end end
...@@ -117,7 +132,7 @@ module Ci ...@@ -117,7 +132,7 @@ module Ci
end end
def job_queue_duration_seconds def job_queue_duration_seconds
@job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time') @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS)
end end
end end
end end
...@@ -55,9 +55,10 @@ module Issues ...@@ -55,9 +55,10 @@ module Issues
return unless params[:move_between_ids] return unless params[:move_between_ids]
after_id, before_id = params.delete(:move_between_ids) after_id, before_id = params.delete(:move_between_ids)
board_group_id = params.delete(:board_group_id)
issue_before = get_issue_if_allowed(issue.project, before_id) if before_id issue_before = get_issue_if_allowed(before_id, board_group_id)
issue_after = get_issue_if_allowed(issue.project, after_id) if after_id issue_after = get_issue_if_allowed(after_id, board_group_id)
issue.move_between(issue_before, issue_after) issue.move_between(issue_before, issue_after)
end end
...@@ -84,8 +85,16 @@ module Issues ...@@ -84,8 +85,16 @@ module Issues
private private
def get_issue_if_allowed(project, id) def get_issue_if_allowed(id, board_group_id = nil)
issue = project.issues.find(id) return unless id
issue =
if board_group_id
IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id)
else
project.issues.find(id)
end
issue if can?(current_user, :update_issue, issue) issue if can?(current_user, :update_issue, issue)
end end
......
...@@ -11,7 +11,7 @@ module Notes ...@@ -11,7 +11,7 @@ module Notes
unless @note.system? unless @note.system?
EventCreateService.new.leave_note(@note, @note.author) EventCreateService.new.leave_note(@note, @note.author)
return unless @note.for_project_noteable? return if @note.for_personal_snippet?
@note.create_cross_references! @note.create_cross_references!
execute_note_hooks execute_note_hooks
...@@ -23,9 +23,13 @@ module Notes ...@@ -23,9 +23,13 @@ module Notes
end end
def execute_note_hooks def execute_note_hooks
return unless @note.project
note_data = hook_data note_data = hook_data
@note.project.execute_hooks(note_data, :note_hooks) hooks_scope = @note.confidential? ? :confidential_note_hooks : :note_hooks
@note.project.execute_services(note_data, :note_hooks)
@note.project.execute_hooks(note_data, hooks_scope)
@note.project.execute_services(note_data, hooks_scope)
end end
end end
end end
...@@ -5,8 +5,8 @@ module Projects ...@@ -5,8 +5,8 @@ module Projects
class GitlabProjectsImportService class GitlabProjectsImportService
attr_reader :current_user, :params attr_reader :current_user, :params
def initialize(user, params) def initialize(user, import_params, override_params = nil)
@current_user, @params = user, params.dup @current_user, @params, @override_params = user, import_params.dup, override_params
end end
def execute def execute
...@@ -17,6 +17,7 @@ module Projects ...@@ -17,6 +17,7 @@ module Projects
params[:import_type] = 'gitlab_project' params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path params[:import_source] = import_upload_path
params[:import_data] = { data: { override_params: @override_params } } if @override_params
::Projects::CreateService.new(current_user, params).execute ::Projects::CreateService.new(current_user, params).execute
end end
......
...@@ -28,7 +28,7 @@ module Projects ...@@ -28,7 +28,7 @@ module Projects
end end
def save_services def save_services
[version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver].all?(&:save)
end end
def version_saver def version_saver
...@@ -55,6 +55,10 @@ module Projects ...@@ -55,6 +55,10 @@ module Projects
Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared) Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared)
end end
def lfs_saver
Gitlab::ImportExport::LfsSaver.new(project: project, shared: @shared)
end
def cleanup_and_notify_error def cleanup_and_notify_error
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}") Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
......
...@@ -429,7 +429,7 @@ module SystemNoteService ...@@ -429,7 +429,7 @@ module SystemNoteService
def cross_reference(noteable, mentioner, author) def cross_reference(noteable, mentioner, author)
return if cross_reference_disallowed?(noteable, mentioner) return if cross_reference_disallowed?(noteable, mentioner)
gfm_reference = mentioner.gfm_reference(noteable.project) gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
body = cross_reference_note_content(gfm_reference) body = cross_reference_note_content(gfm_reference)
if noteable.is_a?(ExternalIssue) if noteable.is_a?(ExternalIssue)
...@@ -582,7 +582,7 @@ module SystemNoteService ...@@ -582,7 +582,7 @@ module SystemNoteService
text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}" text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}"
notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize) notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize)
else else
gfm_reference = mentioner.gfm_reference(noteable.project) gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
text = cross_reference_note_content(gfm_reference) text = cross_reference_note_content(gfm_reference)
notes.where(note: [text, text.capitalize]) notes.where(note: [text, text.capitalize])
end end
......
...@@ -128,7 +128,7 @@ module ObjectStorage ...@@ -128,7 +128,7 @@ module ObjectStorage
end end
def direct_upload_enabled? def direct_upload_enabled?
object_store_options.direct_upload object_store_options&.direct_upload
end end
def background_upload_enabled? def background_upload_enabled?
...@@ -184,6 +184,14 @@ module ObjectStorage ...@@ -184,6 +184,14 @@ module ObjectStorage
StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options) StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options)
} }
end end
def default_object_store
if self.object_store_enabled? && self.direct_upload_enabled?
Store::REMOTE
else
Store::LOCAL
end
end
end end
# allow to configure and overwrite the filename # allow to configure and overwrite the filename
...@@ -204,12 +212,12 @@ module ObjectStorage ...@@ -204,12 +212,12 @@ module ObjectStorage
end end
def object_store def object_store
@object_store ||= model.try(store_serialization_column) || Store::LOCAL @object_store ||= model.try(store_serialization_column) || self.class.default_object_store
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def object_store=(value) def object_store=(value)
@object_store = value || Store::LOCAL @object_store = value || self.class.default_object_store
@storage = storage_for(object_store) @storage = storage_for(object_store)
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
......
Unfortunately, your email message to GitLab could not be processed. Unfortunately, your email message to GitLab could not be processed.
\
= @reason = @reason
...@@ -21,11 +21,11 @@ ...@@ -21,11 +21,11 @@
%li Project uploads %li Project uploads
%li Project configuration including web hooks and services %li Project configuration including web hooks and services
%li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
%li LFS objects
%p %p
The following items will NOT be exported: The following items will NOT be exported:
%ul %ul
%li Job traces and artifacts %li Job traces and artifacts
%li LFS objects
%li Container registry images %li Container registry images
%li CI variables %li CI variables
%li Any encrypted tokens %li Any encrypted tokens
......
.row.prepend-top-default .row.prepend-top-default
.col-lg-12 .col-lg-12
= form_for @project, url: project_pipelines_settings_path(@project) do |f| = form_for @project, url: project_settings_ci_cd_path(@project) do |f|
= form_errors(@project)
%fieldset.builds-feature %fieldset.builds-feature
.form-group .form-group
%h5 Auto DevOps (Beta) %h5 Auto DevOps (Beta)
...@@ -73,10 +74,10 @@ ...@@ -73,10 +74,10 @@
%hr %hr
.form-group .form-group
= f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light' = f.label :build_timeout_human_readable, 'Timeout', class: 'label-light'
= f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0' = f.text_field :build_timeout_human_readable, class: 'form-control'
%p.help-block %p.help-block
Per job in minutes. If a job passes this threshold, it will be marked as failed Per job. If a job passes this threshold, it will be marked as failed
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank' = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank'
%hr %hr
...@@ -151,10 +152,13 @@ ...@@ -151,10 +152,13 @@
%li %li
excoveralls (Elixir) - excoveralls (Elixir) -
%code \[TOTAL\]\s+(\d+\.\d+)% %code \[TOTAL\]\s+(\d+\.\d+)%
%li
JaCoCo (Java/Kotlin)
%code Total.*?([0-9]{1,3})%
= f.submit 'Save changes', class: "btn btn-save" = f.submit 'Save changes', class: "btn btn-save"
%hr %hr
.row.prepend-top-default .row.prepend-top-default
= render partial: 'projects/pipelines_settings/badge', collection: @badges = render partial: 'badge', collection: @badges
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
- page_title "CI / CD" - page_title "CI / CD"
- expanded = Rails.env.test? - expanded = Rails.env.test?
- general_expanded = @project.errors.empty? ? expanded : true
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if expanded) } %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
.settings-header .settings-header
%h4 %h4
General pipelines settings General pipelines settings
...@@ -13,7 +14,7 @@ ...@@ -13,7 +14,7 @@
%p %p
Update your CI/CD configuration, like job timeout or Auto DevOps. Update your CI/CD configuration, like job timeout or Auto DevOps.
.settings-content .settings-content
= render 'projects/pipelines_settings/show' = render 'form'
%section.settings.no-animate{ class: ('expanded' if expanded) } %section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
-# DiffNote -# DiffNote
= f.hidden_field :position = f.hidden_field :position
.discussion-form-container
= render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
= render 'projects/zen', f: f, = render 'projects/zen', f: f,
attr: :note, attr: :note,
......
...@@ -32,6 +32,13 @@ ...@@ -32,6 +32,13 @@
%strong Comments %strong Comments
%p.light %p.light
This URL will be triggered when someone adds a comment This URL will be triggered when someone adds a comment
%li
= form.check_box :confidential_note_events, class: 'pull-left'
.prepend-left-20
= form.label :confidential_note_events, class: 'list-label' do
%strong Confidential Comments
%p.light
This URL will be triggered when someone adds a comment on a confidential issue
%li %li
= form.check_box :issues_events, class: 'pull-left' = form.check_box :issues_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
......
...@@ -5,7 +5,7 @@ class NewNoteWorker ...@@ -5,7 +5,7 @@ class NewNoteWorker
# old `NewNoteWorker` jobs (can remove later) # old `NewNoteWorker` jobs (can remove later)
def perform(note_id, _params = {}) def perform(note_id, _params = {})
if note = Note.find_by(id: note_id) if note = Note.find_by(id: note_id)
NotificationService.new.new_note(note) NotificationService.new.new_note(note) if note.can_create_notification?
Notes::PostProcessService.new(note).execute Notes::PostProcessService.new(note).execute
else else
Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job") Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
......
---
title: Fix XSS on diff view stored on filenames
merge_request:
author:
type: security
---
title: Improve performance of loading issues with lots of references to merge requests
merge_request: 17986
author:
type: performance
---
title: Allow HTTP(s) when git request is made by GitLab CI
merge_request: 18021
author:
type: changed
---
title: Fix `JobsController#raw` endpoint can not read traces in database
merge_request: 18101
author:
type: fixed
---
title: Refactor and tweak margin for note forms on Issuable
merge_request: 18120
author: Takuya Noguchi
type: fixed
---
title: Support LFS objects when importing/exporting GitLab project archives
merge_request: 18115
author:
type: added
---
title: Allow overriding params on project import through API
merge_request: 18086
author:
type: added
---
title: Allow to store uploads by default on Object Storage
merge_request:
author:
type: added
---
title: Ensure internal users (ghost, support bot) get assigned a namespace
merge_request:
author:
type: fixed
---
title: Partition job_queue_duration_seconds with jobs_running_for_project
merge_request: 17730
author:
type: changed
---
title: Fix 404 in group boards when moving issue between lists
merge_request:
author:
type: fixed
---
title: Adds confidential notes channel for Slack/Mattermost
merge_request:
author:
type: security
---
title: Adjust 404's for LegacyDiffNote discussion rendering
merge_request: 18201
author:
type: fixed
---
title: Use human readable value build_timeout in Project
merge_request: 17386
author:
type: changed
---
title: ListCommitsByOid is executed by Gitaly by default
merge_request:
author:
type: performance
...@@ -154,7 +154,7 @@ production: &base ...@@ -154,7 +154,7 @@ production: &base
# provider: AWS # Only AWS supported at the moment # provider: AWS # Only AWS supported at the moment
# aws_access_key_id: AWS_ACCESS_KEY_ID # aws_access_key_id: AWS_ACCESS_KEY_ID
# aws_secret_access_key: AWS_SECRET_ACCESS_KEY # aws_secret_access_key: AWS_SECRET_ACCESS_KEY
# region: eu-central-1 # region: us-east-1
## Git LFS ## Git LFS
lfs: lfs:
...@@ -164,13 +164,14 @@ production: &base ...@@ -164,13 +164,14 @@ production: &base
object_store: object_store:
enabled: false enabled: false
remote_directory: lfs-objects # Bucket name remote_directory: lfs-objects # Bucket name
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
# background_upload: false # Temporary option to limit automatic upload (Default: true) # background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
connection: connection:
provider: AWS provider: AWS
aws_access_key_id: AWS_ACCESS_KEY_ID aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: eu-central-1 region: us-east-1
# Use the following options to configure an AWS compatible host # Use the following options to configure an AWS compatible host
# host: 'localhost' # default: s3.amazonaws.com # host: 'localhost' # default: s3.amazonaws.com
# endpoint: 'http://127.0.0.1:9000' # default: nil # endpoint: 'http://127.0.0.1:9000' # default: nil
...@@ -184,13 +185,14 @@ production: &base ...@@ -184,13 +185,14 @@ production: &base
object_store: object_store:
enabled: false enabled: false
# remote_directory: uploads # Bucket name # remote_directory: uploads # Bucket name
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
# background_upload: false # Temporary option to limit automatic upload (Default: true) # background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
# connection: connection:
# provider: AWS provider: AWS
# aws_access_key_id: AWS_ACCESS_KEY_ID aws_access_key_id: AWS_ACCESS_KEY_ID
# aws_secret_access_key: AWS_SECRET_ACCESS_KEY aws_secret_access_key: AWS_SECRET_ACCESS_KEY
# region: eu-central-1 region: us-east-1
# host: 'localhost' # default: s3.amazonaws.com # host: 'localhost' # default: s3.amazonaws.com
# endpoint: 'http://127.0.0.1:9000' # default: nil # endpoint: 'http://127.0.0.1:9000' # default: nil
# path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object' # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
......
...@@ -365,6 +365,7 @@ Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system ...@@ -365,6 +365,7 @@ Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system
Settings.uploads['object_store'] ||= Settingslogic.new({}) Settings.uploads['object_store'] ||= Settingslogic.new({})
Settings.uploads['object_store']['enabled'] = false if Settings.uploads['object_store']['enabled'].nil? Settings.uploads['object_store']['enabled'] = false if Settings.uploads['object_store']['enabled'].nil?
Settings.uploads['object_store']['remote_directory'] ||= 'uploads' Settings.uploads['object_store']['remote_directory'] ||= 'uploads'
Settings.uploads['object_store']['direct_upload'] = false if Settings.uploads['object_store']['direct_upload'].nil?
Settings.uploads['object_store']['background_upload'] = true if Settings.uploads['object_store']['background_upload'].nil? Settings.uploads['object_store']['background_upload'] = true if Settings.uploads['object_store']['background_upload'].nil?
Settings.uploads['object_store']['proxy_download'] = false if Settings.uploads['object_store']['proxy_download'].nil? Settings.uploads['object_store']['proxy_download'] = false if Settings.uploads['object_store']['proxy_download'].nil?
# Convert upload connection settings to use string keys, to make Fog happy # Convert upload connection settings to use string keys, to make Fog happy
......
...@@ -420,7 +420,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -420,7 +420,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
namespace :settings do namespace :settings do
get :members, to: redirect("%{namespace_id}/%{project_id}/project_members") get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
resource :ci_cd, only: [:show], controller: 'ci_cd' do resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
post :reset_cache post :reset_cache
end end
resource :integrations, only: [:show] resource :integrations, only: [:show]
......
class AddConfidentialNoteEventsToWebHooks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column :web_hooks, :confidential_note_events, :boolean
end
def down
remove_column :web_hooks, :confidential_note_events
end
end
class AddConfidentialNoteEventsToServices < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column :services, :confidential_note_events, :boolean
change_column_default :services, :confidential_note_events, true
end
def down
remove_column :services, :confidential_note_events
end
end
class ScheduleSetConfidentialNoteEventsOnWebhooks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 1_000
INTERVAL = 5.minutes
disable_ddl_transaction!
def up
migration = Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks
migration_name = migration.to_s.demodulize
relation = migration::WebHook.hooks_to_update
queue_background_migration_jobs_by_range_at_intervals(relation,
migration_name,
INTERVAL,
batch_size: BATCH_SIZE)
end
def down
end
end
class ScheduleSetConfidentialNoteEventsOnServices < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 1_000
INTERVAL = 20.minutes
disable_ddl_transaction!
def up
migration = Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices
migration_name = migration.to_s.demodulize
relation = migration::Service.services_to_update
queue_background_migration_jobs_by_range_at_intervals(relation,
migration_name,
INTERVAL,
batch_size: BATCH_SIZE)
end
def down
end
end
class ScheduleBuildStageMigration < ActiveRecord::Migration class ScheduleBuildStageMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers ##
# This migration has been rescheduled to run again, see
DOWNTIME = false # `20180405101928_reschedule_builds_stages_migration.rb`
MIGRATION = 'MigrateBuildStage'.freeze #
BATCH_SIZE = 500
disable_ddl_transaction!
class Build < ActiveRecord::Base
include EachBatch
self.table_name = 'ci_builds'
end
def up def up
disable_statement_timeout # noop
Build.where('stage_id IS NULL').tap do |relation|
queue_background_migration_jobs_by_range_at_intervals(relation,
MIGRATION,
5.minutes,
batch_size: BATCH_SIZE)
end
end end
def down def down
......
class RescheduleBuildsStagesMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
##
# Rescheduled `20180212101928_schedule_build_stage_migration.rb`
#
DOWNTIME = false
MIGRATION = 'MigrateBuildStage'.freeze
BATCH_SIZE = 500
disable_ddl_transaction!
class Build < ActiveRecord::Base
include EachBatch
self.table_name = 'ci_builds'
end
def up
disable_statement_timeout
Build.where('stage_id IS NULL').tap do |relation|
queue_background_migration_jobs_by_range_at_intervals(relation,
MIGRATION,
5.minutes,
batch_size: BATCH_SIZE)
end
end
def down
# noop
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180327101207) do ActiveRecord::Schema.define(version: 20180405101928) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1693,6 +1693,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do ...@@ -1693,6 +1693,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do
t.boolean "confidential_issues_events", default: true, null: false t.boolean "confidential_issues_events", default: true, null: false
t.boolean "commit_events", default: true, null: false t.boolean "commit_events", default: true, null: false
t.boolean "job_events", default: false, null: false t.boolean "job_events", default: false, null: false
t.boolean "confidential_note_events", default: true
end end
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
...@@ -2031,6 +2032,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do ...@@ -2031,6 +2032,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do
t.boolean "confidential_issues_events", default: false, null: false t.boolean "confidential_issues_events", default: false, null: false
t.boolean "repository_update_events", default: false, null: false t.boolean "repository_update_events", default: false, null: false
t.boolean "job_events", default: false, null: false t.boolean "job_events", default: false, null: false
t.boolean "confidential_note_events"
end end
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
......
...@@ -85,7 +85,6 @@ created in snippets, wikis, and repos. ...@@ -85,7 +85,6 @@ created in snippets, wikis, and repos.
- [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a - [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a
basic Postfix mail server with IMAP authentication on Ubuntu for incoming basic Postfix mail server with IMAP authentication on Ubuntu for incoming
emails. emails.
server with IMAP authentication on Ubuntu, to be used with Reply by email.
- [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time. - [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time.
[reply by email]: reply_by_email.md [reply by email]: reply_by_email.md
......
...@@ -65,6 +65,7 @@ For source installations the following settings are nested under `uploads:` and ...@@ -65,6 +65,7 @@ For source installations the following settings are nested under `uploads:` and
|---------|-------------|---------| |---------|-------------|---------|
| `enabled` | Enable/disable object storage | `false` | | `enabled` | Enable/disable object storage | `false` |
| `remote_directory` | The bucket name where Uploads will be stored| | | `remote_directory` | The bucket name where Uploads will be stored| |
| `direct_upload` | Set to true to enable direct upload of Uploads without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. This is beta option as it uses inefficient way of uploading data (via Unicorn). The accelerated uploads gonna be implemented in future releases | `false` |
| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | | `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` |
| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
| `connection` | Various connection options described below | | | `connection` | Various connection options described below | |
......
...@@ -539,6 +539,8 @@ Example response: ...@@ -539,6 +539,8 @@ Example response:
## List Merge Requests associated with a commit ## List Merge Requests associated with a commit
> [Introduced][ce-18004] in GitLab 10.7.
Get a list of Merge Requests related to the specified commit. Get a list of Merge Requests related to the specified commit.
``` ```
...@@ -608,3 +610,4 @@ Example response: ...@@ -608,3 +610,4 @@ Example response:
[ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit" [ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit"
[ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047 [ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047
[ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026 [ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026
[ce-18004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18004
...@@ -111,6 +111,9 @@ POST /projects/import ...@@ -111,6 +111,9 @@ POST /projects/import
| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace | | `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
| `file` | string | yes | The file to be uploaded | | `file` | string | yes | The file to be uploaded |
| `path` | string | yes | Name and path for new project | | `path` | string | yes | Name and path for new project |
| `override_params` | Hash | no | Supports all fields defined in the [Project API](projects.md)] |
The override params passed will take precendence over all values defined inside the export file.
To upload a file from your filesystem, use the `--form` argument. This causes To upload a file from your filesystem, use the `--form` argument. This causes
cURL to post data using the header `Content-Type: multipart/form-data`. cURL to post data using the header `Content-Type: multipart/form-data`.
......
# Email
## Custom logo
The logo in the header of some emails can be customized, see the [logo customization section](../../../customization/branded_page_and_email_header.md).
...@@ -32,9 +32,15 @@ When you choose to allow only one of the protocols, a couple of things will happ ...@@ -32,9 +32,15 @@ When you choose to allow only one of the protocols, a couple of things will happ
On top of these UI restrictions, GitLab will deny all Git actions on the protocol On top of these UI restrictions, GitLab will deny all Git actions on the protocol
not selected. not selected.
CAUTION: **Important:**
Starting with [GitLab 10.7][ce-18021], HTTP(s) protocol will be allowed for
git clone/fetch requests done by GitLab Runner from CI/CD Jobs, even if
_Only SSH_ was selected.
> **Note:** Please keep in mind that disabling an access protocol does not actually > **Note:** Please keep in mind that disabling an access protocol does not actually
block access to the server itself. The ports used for the protocol, be it SSH or block access to the server itself. The ports used for the protocol, be it SSH or
HTTP, will still be accessible. What GitLab does is restrict access on the HTTP, will still be accessible. What GitLab does is restrict access on the
application level. application level.
[ce-4696]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4696 [ce-4696]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4696
[ce-18021]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18021
...@@ -57,11 +57,11 @@ The following items will be exported: ...@@ -57,11 +57,11 @@ The following items will be exported:
- Project configuration including web hooks and services - Project configuration including web hooks and services
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, - Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
and other project entities and other project entities
- LFS objects
The following items will NOT be exported: The following items will NOT be exported:
- Build traces and artifacts - Build traces and artifacts
- LFS objects
- Container registry images - Container registry images
- CI variables - CI variables
- Any encrypted tokens - Any encrypted tokens
......
...@@ -72,7 +72,7 @@ module API ...@@ -72,7 +72,7 @@ module API
class ProjectHook < Hook class ProjectHook < Hook
expose :project_id, :issues_events, :confidential_issues_events expose :project_id, :issues_events, :confidential_issues_events
expose :note_events, :pipeline_events, :wiki_page_events expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events
expose :job_events expose :job_events
end end
...@@ -794,7 +794,7 @@ module API ...@@ -794,7 +794,7 @@ module API
expose :id, :title, :created_at, :updated_at, :active expose :id, :title, :created_at, :updated_at, :active
expose :push_events, :issues_events, :confidential_issues_events expose :push_events, :issues_events, :confidential_issues_events
expose :merge_requests_events, :tag_push_events, :note_events expose :merge_requests_events, :tag_push_events, :note_events
expose :pipeline_events, :wiki_page_events expose :confidential_note_events, :pipeline_events, :wiki_page_events
expose :job_events expose :job_events
# Expose serialized properties # Expose serialized properties
expose :properties do |service, options| expose :properties do |service, options|
......
module API
module Helpers
module ProjectsHelpers
extend ActiveSupport::Concern
included do
helpers do
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'
optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
optional :public_builds, type: Boolean, desc: 'Perform public builds'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
optional :avatar, type: File, desc: 'Avatar image for project'
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
end
params :optional_project_params do
use :optional_project_params_ce
end
end
end
end
end
end
...@@ -14,6 +14,7 @@ module API ...@@ -14,6 +14,7 @@ module API
optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events" optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events" optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events" optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
optional :confidential_note_events, type: Boolean, desc: "Trigger hook on confidential note(comment) events"
optional :job_events, type: Boolean, desc: "Trigger hook on job events" optional :job_events, type: Boolean, desc: "Trigger hook on job events"
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events" optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events" optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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