Commit c80e6b9c authored by Dylan Griffith's avatar Dylan Griffith

Merge branch 'master' into siemens-runner-per-group

parents 51cc01b6 55f07cc3
{ {
"presets": [["latest", { "es2015": { "modules": false } }], "stage-2"], "presets": [["latest", { "es2015": { "modules": false } }], "stage-2"],
"env": { "env": {
"karma": {
"plugins": ["rewire"]
},
"coverage": { "coverage": {
"plugins": [ "plugins": [
[ [
...@@ -14,7 +17,8 @@ ...@@ -14,7 +17,8 @@
{ {
"process.env.BABEL_ENV": "coverage" "process.env.BABEL_ENV": "coverage"
} }
] ],
"rewire"
] ]
} }
} }
......
...@@ -289,7 +289,6 @@ stages: ...@@ -289,7 +289,6 @@ stages:
# Trigger a package build in omnibus-gitlab repository # Trigger a package build in omnibus-gitlab repository
# #
package-and-qa: package-and-qa:
<<: *dedicated-runner
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: [] before_script: []
stage: build stage: build
......
...@@ -2,6 +2,34 @@ ...@@ -2,6 +2,34 @@
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.7.1 (2018-04-23)
### Fixed (11 changes)
- [API] Fix URLs in the `Link` header for `GET /projects/:id/repository/contributors` when no value is passed for `order_by` or `sort`. !18393
- Fix a case with secret variables being empty sometimes. !18400
- Fix `Trace::HttpIO` can not render multi-byte chars. !18417
- Fix specifying a non-default ref when requesting an archive using the legacy URL. !18468
- Respect visibility options and description when importing project from template. !18473
- Removes 'No Job log' message from build trace. !18523
- Align action icons in pipeline graph.
- Fix direct_upload when records with null file_store are used.
- Removed alert box in IDE when redirecting to new merge request.
- Fixed IDE not loading for sub groups.
- Fixed IDE not showing loading state when tree is loading.
### Performance (4 changes)
- Validate project path prior to hitting the database. !18322
- Add index to file_store on ci_job_artifacts. !18444
- Fix N+1 queries when loading participants for a commit note.
- Support Markdown rendering using multiple projects.
### Added (1 change)
- Add an API endpoint to download git repository snapshots. !18173
## 10.7.0 (2018-04-22) ## 10.7.0 (2018-04-22)
### Security (6 changes, 2 of them are from the community) ### Security (6 changes, 2 of them are from the community)
......
...@@ -178,7 +178,7 @@ GEM ...@@ -178,7 +178,7 @@ GEM
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20170404) domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.1) doorkeeper (4.3.2)
railties (>= 4.2) railties (>= 4.2)
doorkeeper-openid_connect (1.3.0) doorkeeper-openid_connect (1.3.0)
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
...@@ -483,10 +483,11 @@ GEM ...@@ -483,10 +483,11 @@ GEM
logging (2.2.2) logging (2.2.2)
little-plugger (~> 1.1) little-plugger (~> 1.1)
multi_json (~> 1.10) multi_json (~> 1.10)
lograge (0.5.1) lograge (0.10.0)
actionpack (>= 4, < 5.2) actionpack (>= 4)
activesupport (>= 4, < 5.2) activesupport (>= 4)
railties (>= 4, < 5.2) railties (>= 4)
request_store (~> 1.0)
loofah (2.2.2) loofah (2.2.2)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
......
10.7.0-pre 10.8.0-pre
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import VirtualList from 'vue-virtual-scroll-list';
import Item from './item.vue';
import router from '../../ide_router';
import {
MAX_FILE_FINDER_RESULTS,
FILE_FINDER_ROW_HEIGHT,
FILE_FINDER_EMPTY_ROW_HEIGHT,
} from '../../constants';
import {
UP_KEY_CODE,
DOWN_KEY_CODE,
ENTER_KEY_CODE,
ESC_KEY_CODE,
} from '../../../lib/utils/keycodes';
export default {
components: {
Item,
VirtualList,
},
data() {
return {
focusedIndex: 0,
searchText: '',
mouseOver: false,
cancelMouseOver: false,
};
},
computed: {
...mapGetters(['allBlobs']),
...mapState(['fileFindVisible', 'loading']),
filteredBlobs() {
const searchText = this.searchText.trim();
if (searchText === '') {
return this.allBlobs.slice(0, MAX_FILE_FINDER_RESULTS);
}
return fuzzaldrinPlus
.filter(this.allBlobs, searchText, {
key: 'path',
maxResults: MAX_FILE_FINDER_RESULTS,
})
.sort((a, b) => b.lastOpenedAt - a.lastOpenedAt);
},
filteredBlobsLength() {
return this.filteredBlobs.length;
},
listShowCount() {
return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1;
},
listHeight() {
return this.filteredBlobsLength ? FILE_FINDER_ROW_HEIGHT : FILE_FINDER_EMPTY_ROW_HEIGHT;
},
showClearInputButton() {
return this.searchText.trim() !== '';
},
},
watch: {
fileFindVisible() {
this.$nextTick(() => {
if (!this.fileFindVisible) {
this.searchText = '';
} else {
this.focusedIndex = 0;
if (this.$refs.searchInput) {
this.$refs.searchInput.focus();
}
}
});
},
searchText() {
this.focusedIndex = 0;
},
focusedIndex() {
if (!this.mouseOver) {
this.$nextTick(() => {
const el = this.$refs.virtualScrollList.$el;
const scrollTop = this.focusedIndex * FILE_FINDER_ROW_HEIGHT;
const bottom = this.listShowCount * FILE_FINDER_ROW_HEIGHT;
if (this.focusedIndex === 0) {
// if index is the first index, scroll straight to start
el.scrollTop = 0;
} else if (this.focusedIndex === this.filteredBlobsLength - 1) {
// if index is the last index, scroll to the end
el.scrollTop = this.filteredBlobsLength * FILE_FINDER_ROW_HEIGHT;
} else if (scrollTop >= bottom + el.scrollTop) {
// if element is off the bottom of the scroll list, scroll down one item
el.scrollTop = scrollTop - bottom + FILE_FINDER_ROW_HEIGHT;
} else if (scrollTop < el.scrollTop) {
// if element is off the top of the scroll list, scroll up one item
el.scrollTop = scrollTop;
}
});
}
},
},
methods: {
...mapActions(['toggleFileFinder']),
clearSearchInput() {
this.searchText = '';
this.$nextTick(() => {
this.$refs.searchInput.focus();
});
},
onKeydown(e) {
switch (e.keyCode) {
case UP_KEY_CODE:
e.preventDefault();
this.mouseOver = false;
this.cancelMouseOver = true;
if (this.focusedIndex > 0) {
this.focusedIndex -= 1;
} else {
this.focusedIndex = this.filteredBlobsLength - 1;
}
break;
case DOWN_KEY_CODE:
e.preventDefault();
this.mouseOver = false;
this.cancelMouseOver = true;
if (this.focusedIndex < this.filteredBlobsLength - 1) {
this.focusedIndex += 1;
} else {
this.focusedIndex = 0;
}
break;
default:
break;
}
},
onKeyup(e) {
switch (e.keyCode) {
case ENTER_KEY_CODE:
this.openFile(this.filteredBlobs[this.focusedIndex]);
break;
case ESC_KEY_CODE:
this.toggleFileFinder(false);
break;
default:
break;
}
},
openFile(file) {
this.toggleFileFinder(false);
router.push(`/project${file.url}`);
},
onMouseOver(index) {
if (!this.cancelMouseOver) {
this.mouseOver = true;
this.focusedIndex = index;
}
},
onMouseMove(index) {
this.cancelMouseOver = false;
this.onMouseOver(index);
},
},
};
</script>
<template>
<div
class="ide-file-finder-overlay"
@mousedown.self="toggleFileFinder(false)"
>
<div
class="dropdown-menu diff-file-changes ide-file-finder show"
>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
:placeholder="__('Search files')"
autocomplete="off"
v-model="searchText"
ref="searchInput"
@keydown="onKeydown($event)"
@keyup="onKeyup($event)"
/>
<i
aria-hidden="true"
class="fa fa-search dropdown-input-search"
:class="{
hidden: showClearInputButton
}"
></i>
<i
role="button"
:aria-label="__('Clear search input')"
class="fa fa-times dropdown-input-clear"
:class="{
show: showClearInputButton
}"
@click="clearSearchInput"
></i>
</div>
<div>
<virtual-list
:size="listHeight"
:remain="listShowCount"
wtag="ul"
ref="virtualScrollList"
>
<template v-if="filteredBlobsLength">
<li
v-for="(file, index) in filteredBlobs"
:key="file.key"
>
<item
class="disable-hover"
:file="file"
:search-text="searchText"
:focused="index === focusedIndex"
:index="index"
@click="openFile"
@mouseover="onMouseOver"
@mousemove="onMouseMove"
/>
</li>
</template>
<li
v-else
class="dropdown-menu-empty-item"
>
<div class="append-right-default prepend-left-default prepend-top-8 append-bottom-8">
<template v-if="loading">
{{ __('Loading...') }}
</template>
<template v-else>
{{ __('No files found.') }}
</template>
</div>
</li>
</virtual-list>
</div>
</div>
</div>
</template>
<script>
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import FileIcon from '../../../vue_shared/components/file_icon.vue';
import ChangedFileIcon from '../changed_file_icon.vue';
const MAX_PATH_LENGTH = 60;
export default {
components: {
ChangedFileIcon,
FileIcon,
},
props: {
file: {
type: Object,
required: true,
},
focused: {
type: Boolean,
required: true,
},
searchText: {
type: String,
required: true,
},
index: {
type: Number,
required: true,
},
},
computed: {
pathWithEllipsis() {
const path = this.file.path;
return path.length < MAX_PATH_LENGTH
? path
: `...${path.substr(path.length - MAX_PATH_LENGTH)}`;
},
nameSearchTextOccurences() {
return fuzzaldrinPlus.match(this.file.name, this.searchText);
},
pathSearchTextOccurences() {
return fuzzaldrinPlus.match(this.pathWithEllipsis, this.searchText);
},
},
methods: {
clickRow() {
this.$emit('click', this.file);
},
mouseOverRow() {
this.$emit('mouseover', this.index);
},
mouseMove() {
this.$emit('mousemove', this.index);
},
},
};
</script>
<template>
<button
type="button"
class="diff-changed-file"
:class="{
'is-focused': focused,
}"
@click.prevent="clickRow"
@mouseover="mouseOverRow"
@mousemove="mouseMove"
>
<file-icon
:file-name="file.name"
:size="16"
css-classes="diff-file-changed-icon append-right-8"
/>
<span class="diff-changed-file-content append-right-8">
<strong
class="diff-changed-file-name"
>
<span
v-for="(char, index) in file.name.split('')"
:key="index + char"
:class="{
highlighted: nameSearchTextOccurences.indexOf(index) >= 0,
}"
v-text="char"
>
</span>
</strong>
<span
class="diff-changed-file-path prepend-top-5"
>
<span
v-for="(char, index) in pathWithEllipsis.split('')"
:key="index + char"
:class="{
highlighted: pathSearchTextOccurences.indexOf(index) >= 0,
}"
v-text="char"
>
</span>
</span>
</span>
<span
v-if="file.changed || file.tempFile"
class="diff-changed-stats"
>
<changed-file-icon
:file="file"
/>
</span>
</button>
</template>
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue'; import Mousetrap from 'mousetrap';
import ideContextbar from './ide_context_bar.vue'; import ideSidebar from './ide_side_bar.vue';
import repoTabs from './repo_tabs.vue'; import ideContextbar from './ide_context_bar.vue';
import ideStatusBar from './ide_status_bar.vue'; import repoTabs from './repo_tabs.vue';
import repoEditor from './repo_editor.vue'; import ideStatusBar from './ide_status_bar.vue';
import repoEditor from './repo_editor.vue';
import FindFile from './file_finder/index.vue';
export default { const originalStopCallback = Mousetrap.stopCallback;
export default {
components: { components: {
ideSidebar, ideSidebar,
ideContextbar, ideContextbar,
repoTabs, repoTabs,
ideStatusBar, ideStatusBar,
repoEditor, repoEditor,
FindFile,
}, },
props: { props: {
emptyStateSvgPath: { emptyStateSvgPath: {
...@@ -29,7 +34,13 @@ export default { ...@@ -29,7 +34,13 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId']), ...mapState([
'changedFiles',
'openFiles',
'viewer',
'currentMergeRequestId',
'fileFindVisible',
]),
...mapGetters(['activeFile', 'hasChanges']), ...mapGetters(['activeFile', 'hasChanges']),
}, },
mounted() { mounted() {
...@@ -42,14 +53,39 @@ export default { ...@@ -42,14 +53,39 @@ export default {
}); });
return returnValue; return returnValue;
}; };
Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => {
if (e.preventDefault) {
e.preventDefault();
}
this.toggleFileFinder(!this.fileFindVisible);
});
Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo);
}, },
}; methods: {
...mapActions(['toggleFileFinder']),
mousetrapStopCallback(e, el, combo) {
if (combo === 't' && el.classList.contains('dropdown-input-field')) {
return true;
} else if (combo === 'command+p' || combo === 'ctrl+p') {
return false;
}
return originalStopCallback(e, el, combo);
},
},
};
</script> </script>
<template> <template>
<div <div
class="ide-view" class="ide-view"
> >
<find-file
v-show="fileFindVisible"
/>
<ide-sidebar /> <ide-sidebar />
<div <div
class="multi-file-edit-pane" class="multi-file-edit-pane"
......
// Fuzzy file finder // Fuzzy file finder
export const MAX_FILE_FINDER_RESULTS = 40;
export const FILE_FINDER_ROW_HEIGHT = 55;
export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33;
// Commit message textarea
export const MAX_TITLE_LENGTH = 50; export const MAX_TITLE_LENGTH = 50;
export const MAX_BODY_LENGTH = 72; export const MAX_BODY_LENGTH = 72;
import _ from 'underscore'; import _ from 'underscore';
import store from '../stores';
import DecorationsController from './decorations/controller'; import DecorationsController from './decorations/controller';
import DirtyDiffController from './diff/controller'; import DirtyDiffController from './diff/controller';
import Disposable from './common/disposable'; import Disposable from './common/disposable';
import ModelManager from './common/model_manager'; import ModelManager from './common/model_manager';
import editorOptions, { defaultEditorOptions } from './editor_options'; import editorOptions, { defaultEditorOptions } from './editor_options';
import gitlabTheme from './themes/gl_theme'; import gitlabTheme from './themes/gl_theme';
import keymap from './keymap.json';
export const clearDomElement = el => { export const clearDomElement = el => {
if (!el || !el.firstChild) return; if (!el || !el.firstChild) return;
...@@ -53,6 +55,8 @@ export default class Editor { ...@@ -53,6 +55,8 @@ export default class Editor {
)), )),
); );
this.addCommands();
window.addEventListener('resize', this.debouncedUpdate, false); window.addEventListener('resize', this.debouncedUpdate, false);
} }
} }
...@@ -73,6 +77,8 @@ export default class Editor { ...@@ -73,6 +77,8 @@ export default class Editor {
})), })),
); );
this.addCommands();
window.addEventListener('resize', this.debouncedUpdate, false); window.addEventListener('resize', this.debouncedUpdate, false);
} }
} }
...@@ -189,4 +195,31 @@ export default class Editor { ...@@ -189,4 +195,31 @@ export default class Editor {
static renderSideBySide(domElement) { static renderSideBySide(domElement) {
return domElement.offsetWidth >= 700; return domElement.offsetWidth >= 700;
} }
addCommands() {
const getKeyCode = key => {
const monacoKeyMod = key.indexOf('KEY_') === 0;
return monacoKeyMod ? this.monaco.KeyCode[key] : this.monaco.KeyMod[key];
};
keymap.forEach(command => {
const keybindings = command.bindings.map(binding => {
const keys = binding.split('+');
// eslint-disable-next-line no-bitwise
return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]);
});
this.instance.addAction({
id: command.id,
label: command.label,
keybindings,
run() {
store.dispatch(command.action.name, command.action.params);
return null;
},
});
});
}
} }
[
{
"id": "file-finder",
"label": "File finder",
"bindings": ["CtrlCmd+KEY_P"],
"action": {
"name": "toggleFileFinder",
"params": true
}
}
]
...@@ -137,7 +137,13 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { ...@@ -137,7 +137,13 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay); commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
}; };
export const toggleFileFinder = ({ commit }, fileFindVisible) =>
commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
export * from './actions/tree'; export * from './actions/tree';
export * from './actions/file'; export * from './actions/file';
export * from './actions/project'; export * from './actions/project';
export * from './actions/merge_request'; export * from './actions/merge_request';
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -42,4 +42,20 @@ export const collapseButtonTooltip = state => ...@@ -42,4 +42,20 @@ export const collapseButtonTooltip = state =>
export const hasMergeRequest = state => !!state.currentMergeRequestId; export const hasMergeRequest = state => !!state.currentMergeRequestId;
export const allBlobs = state =>
Object.keys(state.entries)
.reduce((acc, key) => {
const entry = state.entries[key];
if (entry.type === 'blob') {
acc.push(entry);
}
return acc;
}, [])
.sort((a, b) => b.lastOpenedAt - a.lastOpenedAt);
export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path); export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -185,3 +185,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) = ...@@ -185,3 +185,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) =
commit(types.UPDATE_LOADING, false); commit(types.UPDATE_LOADING, false);
}); });
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -27,3 +27,6 @@ export const branchName = (state, getters, rootState) => { ...@@ -27,3 +27,6 @@ export const branchName = (state, getters, rootState) => {
return rootState.currentBranchId; return rootState.currentBranchId;
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -58,3 +58,5 @@ export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE'; ...@@ -58,3 +58,5 @@ export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE';
export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
...@@ -100,6 +100,11 @@ export default { ...@@ -100,6 +100,11 @@ export default {
delayViewerUpdated, delayViewerUpdated,
}); });
}, },
[types.TOGGLE_FILE_FINDER](state, fileFindVisible) {
Object.assign(state, {
fileFindVisible,
});
},
[types.UPDATE_FILE_AFTER_COMMIT](state, { file, lastCommit }) { [types.UPDATE_FILE_AFTER_COMMIT](state, { file, lastCommit }) {
const changedFile = state.changedFiles.find(f => f.path === file.path); const changedFile = state.changedFiles.find(f => f.path === file.path);
......
...@@ -4,6 +4,7 @@ export default { ...@@ -4,6 +4,7 @@ export default {
[types.SET_FILE_ACTIVE](state, { path, active }) { [types.SET_FILE_ACTIVE](state, { path, active }) {
Object.assign(state.entries[path], { Object.assign(state.entries[path], {
active, active,
lastOpenedAt: new Date().getTime(),
}); });
if (active && !state.entries[path].pending) { if (active && !state.entries[path].pending) {
......
...@@ -18,4 +18,5 @@ export default () => ({ ...@@ -18,4 +18,5 @@ export default () => ({
entries: {}, entries: {},
viewer: 'editor', viewer: 'editor',
delayViewerUpdated: false, delayViewerUpdated: false,
fileFindVisible: false,
}); });
...@@ -42,6 +42,7 @@ export const dataStructure = () => ({ ...@@ -42,6 +42,7 @@ export const dataStructure = () => ({
viewMode: 'edit', viewMode: 'edit',
previewMode: null, previewMode: null,
size: 0, size: 0,
lastOpenedAt: 0,
}); });
export const decorateData = entity => { export const decorateData = entity => {
......
...@@ -45,7 +45,7 @@ export default { ...@@ -45,7 +45,7 @@ export default {
return timeIntervalInWords(this.job.queued); return timeIntervalInWords(this.job.queued);
}, },
runnerId() { runnerId() {
return `#${this.job.runner.id}`; return `${this.job.runner.description} (#${this.job.runner.id})`;
}, },
retryButtonClass() { retryButtonClass() {
let className = 'js-retry-button pull-right btn btn-retry visible-md-block visible-lg-block'; let className = 'js-retry-button pull-right btn btn-retry visible-md-block visible-lg-block';
......
export const UP_KEY_CODE = 38;
export const DOWN_KEY_CODE = 40;
export const ENTER_KEY_CODE = 13;
export const ESC_KEY_CODE = 27;
...@@ -315,3 +315,6 @@ export const scrollToNoteIfNeeded = (context, el) => { ...@@ -315,3 +315,6 @@ export const scrollToNoteIfNeeded = (context, el) => {
scrollToElement(el); scrollToElement(el);
} }
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -68,3 +68,6 @@ export const resolvedDiscussionCount = (state, getters) => { ...@@ -68,3 +68,6 @@ export const resolvedDiscussionCount = (state, getters) => {
return Object.keys(resolvedMap).length; return Object.keys(resolvedMap).length;
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -35,3 +35,6 @@ export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destr ...@@ -35,3 +35,6 @@ export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destr
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const isLoading = state => state.isLoading; export const isLoading = state => state.isLoading;
export const repos = state => state.repos; export const repos = state => state.repos;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -472,6 +472,7 @@ img.emoji { ...@@ -472,6 +472,7 @@ img.emoji {
.append-right-20 { margin-right: 20px; } .append-right-20 { margin-right: 20px; }
.append-bottom-0 { margin-bottom: 0; } .append-bottom-0 { margin-bottom: 0; }
.append-bottom-5 { margin-bottom: 5px; } .append-bottom-5 { margin-bottom: 5px; }
.append-bottom-8 { margin-bottom: $grid-size; }
.append-bottom-10 { margin-bottom: 10px; } .append-bottom-10 { margin-bottom: 10px; }
.append-bottom-15 { margin-bottom: 15px; } .append-bottom-15 { margin-bottom: 15px; }
.append-bottom-20 { margin-bottom: 20px; } .append-bottom-20 { margin-bottom: 20px; }
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
border-color: $gray-darkest; border-color: $gray-darkest;
} }
[data-toggle="dropdown"] { [data-toggle='dropdown'] {
outline: 0; outline: 0;
} }
} }
...@@ -172,7 +172,11 @@ ...@@ -172,7 +172,11 @@
color: $brand-danger; color: $brand-danger;
} }
&:hover, &.disable-hover {
text-decoration: none;
}
&:not(.disable-hover):hover,
&:active, &:active,
&:focus, &:focus,
&.is-focused { &.is-focused {
...@@ -508,17 +512,16 @@ ...@@ -508,17 +512,16 @@
} }
&.is-indeterminate::before { &.is-indeterminate::before {
content: "\f068"; content: '\f068';
} }
&.is-active::before { &.is-active::before {
content: "\f00c"; content: '\f00c';
} }
} }
} }
} }
.dropdown-title { .dropdown-title {
position: relative; position: relative;
padding: 2px 25px 10px; padding: 2px 25px 10px;
...@@ -724,7 +727,6 @@ ...@@ -724,7 +727,6 @@
} }
} }
.dropdown-menu-due-date { .dropdown-menu-due-date {
.dropdown-content { .dropdown-content {
max-height: 230px; max-height: 230px;
...@@ -854,9 +856,13 @@ header.header-content .dropdown-menu.projects-dropdown-menu { ...@@ -854,9 +856,13 @@ header.header-content .dropdown-menu.projects-dropdown-menu {
} }
.projects-list-frequent-container, .projects-list-frequent-container,
.projects-list-search-container, { .projects-list-search-container {
padding: 8px 0; padding: 8px 0;
overflow-y: auto; overflow-y: auto;
li.section-empty.section-failure {
color: $callout-danger-color;
}
} }
.section-header, .section-header,
...@@ -867,13 +873,6 @@ header.header-content .dropdown-menu.projects-dropdown-menu { ...@@ -867,13 +873,6 @@ header.header-content .dropdown-menu.projects-dropdown-menu {
font-size: $gl-font-size; font-size: $gl-font-size;
} }
.projects-list-frequent-container,
.projects-list-search-container {
li.section-empty.section-failure {
color: $callout-danger-color;
}
}
.search-input-container { .search-input-container {
position: relative; position: relative;
padding: 4px $gl-padding; padding: 4px $gl-padding;
...@@ -905,8 +904,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu { ...@@ -905,8 +904,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu {
} }
.projects-list-item-container { .projects-list-item-container {
.project-item-avatar-container .project-item-avatar-container .project-item-metadata-container {
.project-item-metadata-container {
float: left; float: left;
} }
......
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
.project-home-panel { .project-home-panel {
padding-left: 0 !important; padding-left: 0 !important;
.project-avatar {
display: block;
}
.project-repo-buttons, .project-repo-buttons,
.git-clone-holder { .git-clone-holder {
display: none; display: none;
......
...@@ -241,8 +241,6 @@ ...@@ -241,8 +241,6 @@
} }
.scrolling-tabs-container { .scrolling-tabs-container {
position: relative;
.merge-request-tabs-container & { .merge-request-tabs-container & {
overflow: hidden; overflow: hidden;
} }
...@@ -272,8 +270,6 @@ ...@@ -272,8 +270,6 @@
} }
.inner-page-scroll-tabs { .inner-page-scroll-tabs {
position: relative;
.fade-right { .fade-right {
@include fade(left, $white-light); @include fade(left, $white-light);
right: 0; right: 0;
......
...@@ -314,6 +314,10 @@ ...@@ -314,6 +314,10 @@
display: inline-flex; display: inline-flex;
vertical-align: top; vertical-align: top;
&:hover .color-label {
text-decoration: underline;
}
.label { .label {
vertical-align: inherit; vertical-align: inherit;
font-size: $label-font-size; font-size: $label-font-size;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
} }
.ide-view { .ide-view {
position: relative;
display: flex; display: flex;
height: calc(100vh - #{$header-height}); height: calc(100vh - #{$header-height});
margin-top: 0; margin-top: 0;
...@@ -876,6 +877,26 @@ ...@@ -876,6 +877,26 @@
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
.ide-file-finder-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
}
.ide-file-finder {
top: 10px;
left: 50%;
transform: translateX(-50%);
.highlighted {
color: $blue-500;
font-weight: $gl-font-weight-bold;
}
}
.ide-commit-message-field { .ide-commit-message-field {
height: 200px; height: 200px;
background-color: $white-light; background-color: $white-light;
......
...@@ -23,6 +23,9 @@ module AuthenticatesWithTwoFactor ...@@ -23,6 +23,9 @@ module AuthenticatesWithTwoFactor
# #
# Returns nil # Returns nil
def prompt_for_two_factor(user) def prompt_for_two_factor(user)
# Set @user for Devise views
@user = user # rubocop:disable Gitlab/ModuleWithInstanceVariables
return locked_user_redirect(user) unless user.can?(:log_in) return locked_user_redirect(user) unless user.can?(:log_in)
session[:otp_user_id] = user.id session[:otp_user_id] = user.id
......
...@@ -165,8 +165,8 @@ module IssuableCollections ...@@ -165,8 +165,8 @@ module IssuableCollections
[:project, :author, :assignees, :labels, :milestone, project: :namespace] [:project, :author, :assignees, :labels, :milestone, project: :namespace]
when 'MergeRequest' when 'MergeRequest'
[ [
:source_project, :target_project, :author, :assignee, :labels, :milestone, :target_project, :author, :assignee, :labels, :milestone,
head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits source_project: :route, head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits
] ]
end end
end end
......
class Ldap::OmniauthCallbacksController < OmniauthCallbacksController
extend ::Gitlab::Utils::Override
def self.define_providers!
return unless Gitlab::Auth::LDAP::Config.enabled?
Gitlab::Auth::LDAP::Config.available_servers.each do |server|
alias_method server['provider_name'], :ldap
end
end
# We only find ourselves here
# if the authentication to LDAP was successful.
def ldap
sign_in_user_flow(Gitlab::Auth::LDAP::User)
end
define_providers!
override :set_remember_me
def set_remember_me(user)
user.remember_me = params[:remember_me] if user.persisted?
end
override :fail_login
def fail_login(user)
flash[:alert] = 'Access denied for your LDAP account.'
redirect_to new_user_session_path
end
end
...@@ -4,18 +4,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -4,18 +4,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
protect_from_forgery except: [:kerberos, :saml, :cas3] protect_from_forgery except: [:kerberos, :saml, :cas3]
Gitlab.config.omniauth.providers.each do |provider| def handle_omniauth
define_method provider['name'] do omniauth_flow(Gitlab::Auth::OAuth)
handle_omniauth
end
end end
if Gitlab::Auth::LDAP::Config.enabled? Gitlab.config.omniauth.providers.each do |provider|
Gitlab::Auth::LDAP::Config.available_servers.each do |server| alias_method provider['name'], :handle_omniauth
define_method server['provider_name'] do
ldap
end
end
end end
# Extend the standard implementation to also increment # Extend the standard implementation to also increment
...@@ -37,51 +31,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -37,51 +31,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
error ||= exception.error if exception.respond_to?(:error) error ||= exception.error if exception.respond_to?(:error)
error ||= exception.message if exception.respond_to?(:message) error ||= exception.message if exception.respond_to?(:message)
error ||= env["omniauth.error.type"].to_s error ||= env["omniauth.error.type"].to_s
error.to_s.humanize if error
end
# We only find ourselves here
# if the authentication to LDAP was successful.
def ldap
ldap_user = Gitlab::Auth::LDAP::User.new(oauth)
ldap_user.save if ldap_user.changed? # will also save new users
@user = ldap_user.gl_user error.to_s.humanize if error
@user.remember_me = params[:remember_me] if ldap_user.persisted?
# Do additional LDAP checks for the user filter and EE features
if ldap_user.allowed?
if @user.two_factor_enabled?
prompt_for_two_factor(@user)
else
log_audit_event(@user, with: oauth['provider'])
sign_in_and_redirect(@user)
end
else
fail_ldap_login
end
end end
def saml def saml
if current_user omniauth_flow(Gitlab::Auth::Saml)
log_audit_event(current_user, with: :saml)
# Update SAML identity if data has changed.
identity = current_user.identities.with_extern_uid(:saml, oauth['uid']).take
if identity.nil?
current_user.identities.create(extern_uid: oauth['uid'], provider: :saml)
redirect_to profile_account_path, notice: 'Authentication method updated'
else
redirect_to after_sign_in_path_for(current_user)
end
else
saml_user = Gitlab::Auth::Saml::User.new(oauth)
saml_user.save if saml_user.changed?
@user = saml_user.gl_user
continue_login_process
end
rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error
end end
def omniauth_error def omniauth_error
...@@ -117,25 +72,36 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -117,25 +72,36 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
private private
def handle_omniauth def omniauth_flow(auth_module, identity_linker: nil)
if current_user if current_user
# Add new authentication method
current_user.identities
.with_extern_uid(oauth['provider'], oauth['uid'])
.first_or_create(extern_uid: oauth['uid'])
log_audit_event(current_user, with: oauth['provider']) log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
identity_linker ||= auth_module::IdentityLinker.new(current_user, oauth)
identity_linker.link
if identity_linker.changed?
redirect_identity_linked
elsif identity_linker.error_message.present?
redirect_identity_link_failed(identity_linker.error_message)
else
redirect_identity_exists
end
else else
oauth_user = Gitlab::Auth::OAuth::User.new(oauth) sign_in_user_flow(auth_module::User)
oauth_user.save end
@user = oauth_user.gl_user end
continue_login_process def redirect_identity_exists
redirect_to after_sign_in_path_for(current_user)
end end
rescue Gitlab::Auth::OAuth::User::SigninDisabledForProviderError
handle_disabled_provider def redirect_identity_link_failed(error_message)
rescue Gitlab::Auth::OAuth::User::SignupDisabledError redirect_to profile_account_path, notice: "Authentication failed: #{error_message}"
handle_signup_error end
def redirect_identity_linked
redirect_to profile_account_path, notice: 'Authentication method updated'
end end
def handle_service_ticket(provider, ticket) def handle_service_ticket(provider, ticket)
...@@ -144,21 +110,27 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -144,21 +110,27 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
session[:service_tickets][provider] = ticket session[:service_tickets][provider] = ticket
end end
def continue_login_process def sign_in_user_flow(auth_user_class)
# Only allow properly saved users to login. auth_user = auth_user_class.new(oauth)
if @user.persisted? && @user.valid? user = auth_user.find_and_update!
log_audit_event(@user, with: oauth['provider'])
if auth_user.valid_sign_in?
log_audit_event(user, with: oauth['provider'])
if @user.two_factor_enabled? set_remember_me(user)
params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user) if user.two_factor_enabled?
prompt_for_two_factor(user)
else else
remember_me(@user) if remember_me? sign_in_and_redirect(user)
sign_in_and_redirect(@user)
end end
else else
fail_login fail_login(user)
end end
rescue Gitlab::Auth::OAuth::User::SigninDisabledForProviderError
handle_disabled_provider
rescue Gitlab::Auth::OAuth::User::SignupDisabledError
handle_signup_error
end end
def handle_signup_error def handle_signup_error
...@@ -178,18 +150,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -178,18 +150,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
@oauth ||= request.env['omniauth.auth'] @oauth ||= request.env['omniauth.auth']
end end
def fail_login def fail_login(user)
error_message = @user.errors.full_messages.to_sentence error_message = user.errors.full_messages.to_sentence
return redirect_to omniauth_error_path(oauth['provider'], error: error_message) return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end end
def fail_ldap_login
flash[:alert] = 'Access denied for your LDAP account.'
redirect_to new_user_session_path
end
def fail_auth0_login def fail_auth0_login
flash[:alert] = 'Wrong extern UID provided. Make sure Auth0 is configured correctly.' flash[:alert] = 'Wrong extern UID provided. Make sure Auth0 is configured correctly.'
...@@ -208,6 +174,16 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -208,6 +174,16 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
.for_authentication.security_event .for_authentication.security_event
end end
def set_remember_me(user)
return unless remember_me?
if user.two_factor_enabled?
params[:remember_me] = '1'
else
remember_me(user)
end
end
def remember_me? def remember_me?
request_params = request.env['omniauth.params'] request_params = request.env['omniauth.params']
(request_params['remember_me'] == '1') if request_params.present? (request_params['remember_me'] == '1') if request_params.present?
......
...@@ -81,6 +81,14 @@ module GitlabRoutingHelper ...@@ -81,6 +81,14 @@ module GitlabRoutingHelper
end end
end end
def edit_milestone_path(entity, *args)
if entity.parent.is_a?(Group)
edit_group_milestone_path(entity.parent, entity, *args)
else
edit_project_milestone_path(entity.parent, entity, *args)
end
end
def toggle_subscription_path(entity, *args) def toggle_subscription_path(entity, *args)
if entity.is_a?(Issue) if entity.is_a?(Issue)
toggle_subscription_project_issue_path(entity.project, entity) toggle_subscription_project_issue_path(entity.project, entity)
......
...@@ -400,7 +400,8 @@ module ProjectsHelper ...@@ -400,7 +400,8 @@ module ProjectsHelper
exports_path = File.join(Settings.shared['path'], 'tmp/project_exports') exports_path = File.join(Settings.shared['path'], 'tmp/project_exports')
filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]") filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]")
filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") disk_path = Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path
filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]")
end end
def project_child_container_class(view_path) def project_child_container_class(view_path)
......
...@@ -27,6 +27,7 @@ module Ci ...@@ -27,6 +27,7 @@ module Ci
has_one :metadata, class_name: 'Ci::BuildMetadata' has_one :metadata, class_name: 'Ci::BuildMetadata'
delegate :timeout, to: :metadata, prefix: true, allow_nil: true delegate :timeout, to: :metadata, prefix: true, allow_nil: true
delegate :gitlab_deploy_token, to: :project
## ##
# The "environment" field for builds is a String, and is the unexpanded name! # The "environment" field for builds is a String, and is the unexpanded name!
...@@ -604,6 +605,7 @@ module Ci ...@@ -604,6 +605,7 @@ module Ci
.append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER) .append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
.append(key: 'CI_REGISTRY_PASSWORD', value: token, public: false) .append(key: 'CI_REGISTRY_PASSWORD', value: token, public: false)
.append(key: 'CI_REPOSITORY_URL', value: repo_url, public: false) .append(key: 'CI_REPOSITORY_URL', value: repo_url, public: false)
.concat(deploy_token_variables)
end end
end end
...@@ -654,6 +656,15 @@ module Ci ...@@ -654,6 +656,15 @@ module Ci
end end
end end
def deploy_token_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless gitlab_deploy_token
variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.name)
variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false)
end
end
def environment_url def environment_url
options&.dig(:environment, :url) || persisted_environment&.external_url options&.dig(:environment, :url) || persisted_environment&.external_url
end end
......
...@@ -31,12 +31,13 @@ module Avatarable ...@@ -31,12 +31,13 @@ module Avatarable
asset_host = ActionController::Base.asset_host asset_host = ActionController::Base.asset_host
use_asset_host = asset_host.present? use_asset_host = asset_host.present?
use_authentication = respond_to?(:public?) && !public?
# Avatars for private and internal groups and projects require authentication to be viewed, # Avatars for private and internal groups and projects require authentication to be viewed,
# which means they can only be served by Rails, on the regular GitLab host. # which means they can only be served by Rails, on the regular GitLab host.
# If an asset host is configured, we need to return the fully qualified URL # If an asset host is configured, we need to return the fully qualified URL
# instead of only the avatar path, so that Rails doesn't prefix it with the asset host. # instead of only the avatar path, so that Rails doesn't prefix it with the asset host.
if use_asset_host && respond_to?(:public?) && !public? if use_asset_host && use_authentication
use_asset_host = false use_asset_host = false
only_path = false only_path = false
end end
...@@ -49,6 +50,6 @@ module Avatarable ...@@ -49,6 +50,6 @@ module Avatarable
url_base << gitlab_config.relative_url_root url_base << gitlab_config.relative_url_root
end end
url_base + avatar.url url_base + avatar.local_url
end end
end end
...@@ -31,7 +31,7 @@ module ProtectedRef ...@@ -31,7 +31,7 @@ module ProtectedRef
end end
end end
def protected_ref_accessible_to?(ref, user, action:, protected_refs: nil) def protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level| access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level|
access_level.check_access(user) access_level.check_access(user)
end end
......
...@@ -45,25 +45,25 @@ module Storage ...@@ -45,25 +45,25 @@ module Storage
# Hooks # Hooks
# Save the storage paths before the projects are destroyed to use them on after destroy # Save the storages before the projects are destroyed to use them on after destroy
def prepare_for_destroy def prepare_for_destroy
old_repository_storage_paths old_repository_storages
end end
private private
def move_repositories def move_repositories
# Move the namespace directory in all storage paths used by member projects # Move the namespace directory in all storages used by member projects
repository_storage_paths.each do |repository_storage_path| repository_storages.each do |repository_storage|
# Ensure old directory exists before moving it # Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, full_path_was) gitlab_shell.add_namespace(repository_storage, full_path_was)
# Ensure new directory exists before moving it (if there's a parent) # Ensure new directory exists before moving it (if there's a parent)
gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path) unless gitlab_shell.mv_namespace(repository_storage, full_path_was, full_path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}" Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_was} to #{full_path}"
# if we cannot move namespace directory we should rollback # if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs # db changes in order to prevent out of sync between db and fs
...@@ -72,33 +72,33 @@ module Storage ...@@ -72,33 +72,33 @@ module Storage
end end
end end
def old_repository_storage_paths def old_repository_storages
@old_repository_storage_paths ||= repository_storage_paths @old_repository_storage_paths ||= repository_storages
end end
def repository_storage_paths def repository_storages
# We need to get the storage paths for all the projects, even the ones that are # We need to get the storage paths for all the projects, even the ones that are
# pending delete. Unscoping also get rids of the default order, which causes # pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT. # problems with SELECT DISTINCT.
Project.unscoped do Project.unscoped do
all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path) all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage)
end end
end end
def rm_dir def rm_dir
# Remove the namespace directory in all storages paths used by member projects # Remove the namespace directory in all storages paths used by member projects
old_repository_storage_paths.each do |repository_storage_path| old_repository_storages.each do |repository_storage|
# Move namespace directory into trash. # Move namespace directory into trash.
# We will remove it later async # We will remove it later async
new_path = "#{full_path}+#{id}+deleted" new_path = "#{full_path}+#{id}+deleted"
if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path) if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}") Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
# Remove namespace directroy async with delay so # Remove namespace directroy async with delay so
# GitLab has time to remove all projects first # GitLab has time to remove all projects first
run_after_commit do run_after_commit do
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
end end
end end
end end
......
...@@ -4,6 +4,7 @@ class DeployToken < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class DeployToken < ActiveRecord::Base
add_authentication_token_field :token add_authentication_token_field :token
AVAILABLE_SCOPES = %i(read_repository read_registry).freeze AVAILABLE_SCOPES = %i(read_repository read_registry).freeze
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'.freeze
default_value_for(:expires_at) { Forever.date } default_value_for(:expires_at) { Forever.date }
...@@ -17,6 +18,10 @@ class DeployToken < ActiveRecord::Base ...@@ -17,6 +18,10 @@ class DeployToken < ActiveRecord::Base
scope :active, -> { where("revoked = false AND expires_at >= NOW()") } scope :active, -> { where("revoked = false AND expires_at >= NOW()") }
def self.gitlab_deploy_token
active.find_by(name: GITLAB_DEPLOY_TOKEN_NAME)
end
def revoke! def revoke!
update!(revoked: true) update!(revoked: true)
end end
......
...@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true) CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end end
def commits_count
super || merge_request_diff_commits.size
end
private private
def create_merge_request_diff_files(diffs) def create_merge_request_diff_files(diffs)
......
...@@ -68,6 +68,11 @@ class Project < ActiveRecord::Base ...@@ -68,6 +68,11 @@ class Project < ActiveRecord::Base
after_save :update_project_statistics, if: :namespace_id_changed? after_save :update_project_statistics, if: :namespace_id_changed?
after_create :create_project_feature, unless: :project_feature after_create :create_project_feature, unless: :project_feature
after_create :create_ci_cd_settings,
unless: :ci_cd_settings,
if: proc { ProjectCiCdSetting.available? }
after_create :set_last_activity_at after_create :set_last_activity_at
after_create :set_last_repository_updated_at after_create :set_last_repository_updated_at
after_update :update_forks_visibility_level after_update :update_forks_visibility_level
...@@ -229,6 +234,7 @@ class Project < ActiveRecord::Base ...@@ -229,6 +234,7 @@ class Project < ActiveRecord::Base
has_many :custom_attributes, class_name: 'ProjectCustomAttribute' has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
has_many :project_badges, class_name: 'ProjectBadge' has_many :project_badges, class_name: 'ProjectBadge'
has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting'
has_one :settings, -> (project) { has_one :settings, -> (project) {
query = where(project_id: project) query = where(project_id: project)
...@@ -519,10 +525,6 @@ class Project < ActiveRecord::Base ...@@ -519,10 +525,6 @@ class Project < ActiveRecord::Base
repository.empty? repository.empty?
end end
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]&.legacy_disk_path
end
def team def team
@team ||= ProjectTeam.new(self) @team ||= ProjectTeam.new(self)
end end
...@@ -1048,13 +1050,6 @@ class Project < ActiveRecord::Base ...@@ -1048,13 +1050,6 @@ class Project < ActiveRecord::Base
"#{web_url}.git" "#{web_url}.git"
end end
def user_can_push_to_empty_repo?(user)
return false unless empty_repo?
return false unless Ability.allowed?(user, :push_code, self)
!ProtectedBranch.default_branch_protected? || team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
end
def forked? def forked?
return true if fork_network && fork_network.root_project != self return true if fork_network && fork_network.root_project != self
...@@ -1113,7 +1108,7 @@ class Project < ActiveRecord::Base ...@@ -1113,7 +1108,7 @@ class Project < ActiveRecord::Base
# Check if repository already exists on disk # Check if repository already exists on disk
def check_repository_path_availability def check_repository_path_availability
return true if skip_disk_validation return true if skip_disk_validation
return false unless repository_storage_path return false unless repository_storage
expires_full_path_cache # we need to clear cache to validate renames correctly expires_full_path_cache # we need to clear cache to validate renames correctly
...@@ -1888,6 +1883,10 @@ class Project < ActiveRecord::Base ...@@ -1888,6 +1883,10 @@ class Project < ActiveRecord::Base
settings.toggle!(settings_attribute) settings.toggle!(settings_attribute)
end end
def gitlab_deploy_token
@gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token
end
private private
def storage def storage
...@@ -1916,14 +1915,14 @@ class Project < ActiveRecord::Base ...@@ -1916,14 +1915,14 @@ class Project < ActiveRecord::Base
def check_repository_absence! def check_repository_absence!
return if skip_disk_validation return if skip_disk_validation
if repository_storage_path.blank? || repository_with_same_path_already_exists? if repository_storage.blank? || repository_with_same_path_already_exists?
errors.add(:base, 'There is already a repository with that name on disk') errors.add(:base, 'There is already a repository with that name on disk')
throw :abort throw :abort
end end
end end
def repository_with_same_path_already_exists? def repository_with_same_path_already_exists?
gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git") gitlab_shell.exists?(repository_storage, "#{disk_path}.git")
end end
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
...@@ -2013,10 +2012,11 @@ class Project < ActiveRecord::Base ...@@ -2013,10 +2012,11 @@ class Project < ActiveRecord::Base
def fetch_branch_allows_maintainer_push?(user, branch_name) def fetch_branch_allows_maintainer_push?(user, branch_name)
check_access = -> do check_access = -> do
next false if empty_repo?
merge_request = source_of_merge_requests.opened merge_request = source_of_merge_requests.opened
.where(allow_maintainer_to_push: true) .where(allow_maintainer_to_push: true)
.find_by(source_branch: branch_name) .find_by(source_branch: branch_name)
merge_request&.can_be_merged_by?(user) merge_request&.can_be_merged_by?(user)
end end
......
class ProjectCiCdSetting < ActiveRecord::Base
belongs_to :project
# The version of the schema that first introduced this model/table.
MINIMUM_SCHEMA_VERSION = 20180403035759
def self.available?
@available ||=
ActiveRecord::Migrator.current_version >= MINIMUM_SCHEMA_VERSION
end
def self.reset_column_information
@available = nil
super
end
end
...@@ -21,7 +21,7 @@ class ProjectWiki ...@@ -21,7 +21,7 @@ class ProjectWiki
end end
delegate :empty?, to: :pages delegate :empty?, to: :pages
delegate :repository_storage_path, :hashed_storage?, to: :project delegate :repository_storage, :hashed_storage?, to: :project
def path def path
@project.path + '.wiki' @project.path + '.wiki'
......
...@@ -4,6 +4,15 @@ class ProtectedBranch < ActiveRecord::Base ...@@ -4,6 +4,15 @@ class ProtectedBranch < ActiveRecord::Base
protected_ref_access_levels :merge, :push protected_ref_access_levels :merge, :push
def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
# Masters, owners and admins are allowed to create the default branch
if default_branch_protected? && project.empty_repo?
return true if user.admin? || project.team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
end
super
end
# Check if branch name is marked as protected in the system # Check if branch name is marked as protected in the system
def self.protected?(project, ref_name) def self.protected?(project, ref_name)
return true if project.empty_repo? && default_branch_protected? return true if project.empty_repo? && default_branch_protected?
......
...@@ -84,10 +84,15 @@ class Repository ...@@ -84,10 +84,15 @@ class Repository
# Return absolute path to repository # Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.expand_path( @path_to_repo ||=
File.join(repository_storage_path, disk_path + '.git') begin
storage = Gitlab.config.repositories.storages[@project.repository_storage]
File.expand_path(
File.join(storage.legacy_disk_path, disk_path + '.git')
) )
end end
end
def inspect def inspect
"#<#{self.class.name}:#{@disk_path}>" "#<#{self.class.name}:#{@disk_path}>"
...@@ -915,10 +920,6 @@ class Repository ...@@ -915,10 +920,6 @@ class Repository
raw_repository.fetch_ref(source_repository.raw_repository, source_ref: source_ref, target_ref: target_ref) raw_repository.fetch_ref(source_repository.raw_repository, source_ref: source_ref, target_ref: target_ref)
end end
def repository_storage_path
@project.repository_storage_path
end
def rebase(user, merge_request) def rebase(user, merge_request)
raw.rebase(user, merge_request.id, branch: merge_request.source_branch, raw.rebase(user, merge_request.id, branch: merge_request.source_branch,
branch_sha: merge_request.source_branch_sha, branch_sha: merge_request.source_branch_sha,
......
module Storage module Storage
class HashedProject class HashedProject
attr_accessor :project attr_accessor :project
delegate :gitlab_shell, :repository_storage_path, to: :project delegate :gitlab_shell, :repository_storage, to: :project
ROOT_PATH_PREFIX = '@hashed'.freeze ROOT_PATH_PREFIX = '@hashed'.freeze
...@@ -24,7 +24,7 @@ module Storage ...@@ -24,7 +24,7 @@ module Storage
end end
def ensure_storage_path_exists def ensure_storage_path_exists
gitlab_shell.add_namespace(repository_storage_path, base_dir) gitlab_shell.add_namespace(repository_storage, base_dir)
end end
def rename_repo def rename_repo
......
module Storage module Storage
class LegacyProject class LegacyProject
attr_accessor :project attr_accessor :project
delegate :namespace, :gitlab_shell, :repository_storage_path, to: :project delegate :namespace, :gitlab_shell, :repository_storage, to: :project
def initialize(project) def initialize(project)
@project = project @project = project
...@@ -24,18 +24,18 @@ module Storage ...@@ -24,18 +24,18 @@ module Storage
def ensure_storage_path_exists def ensure_storage_path_exists
return unless namespace return unless namespace
gitlab_shell.add_namespace(repository_storage_path, base_dir) gitlab_shell.add_namespace(repository_storage, base_dir)
end end
def rename_repo def rename_repo
new_full_path = project.build_full_path new_full_path = project.build_full_path
if gitlab_shell.mv_repository(repository_storage_path, project.full_path_was, new_full_path) if gitlab_shell.mv_repository(repository_storage, project.full_path_was, new_full_path)
# If repository moved successfully we need to send update instructions to users. # If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository # However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions # So we basically we mute exceptions in next actions
begin begin
gitlab_shell.mv_repository(repository_storage_path, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki") gitlab_shell.mv_repository(repository_storage, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki")
return true return true
rescue => e rescue => e
Rails.logger.error "Exception renaming #{project.full_path_was} -> #{new_full_path}: #{e}" Rails.logger.error "Exception renaming #{project.full_path_was} -> #{new_full_path}: #{e}"
......
...@@ -22,7 +22,7 @@ class GroupPolicy < BasePolicy ...@@ -22,7 +22,7 @@ class GroupPolicy < BasePolicy
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) } condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
condition(:has_projects) do condition(:has_projects) do
GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any? GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true }).execute.any?
end end
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
...@@ -43,7 +43,11 @@ class GroupPolicy < BasePolicy ...@@ -43,7 +43,11 @@ class GroupPolicy < BasePolicy
end end
rule { admin } .enable :read_group rule { admin } .enable :read_group
rule { has_projects } .enable :read_group
rule { has_projects }.policy do
enable :read_group
enable :read_label
end
rule { has_access }.enable :read_namespace rule { has_access }.enable :read_namespace
......
...@@ -4,6 +4,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -4,6 +4,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper include GitlabRoutingHelper
include StorageHelper include StorageHelper
include TreeHelper include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
presents :project presents :project
...@@ -170,9 +171,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -170,9 +171,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def can_current_user_push_to_branch?(branch) def can_current_user_push_to_branch?(branch)
return false unless repository.branch_exists?(branch) user_access(project).can_push_to_branch?(branch)
end
::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch) def can_current_user_push_to_default_branch?
can_current_user_push_to_branch?(default_branch)
end end
def files_anchor_data def files_anchor_data
...@@ -200,7 +203,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -200,7 +203,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def new_file_anchor_data def new_file_anchor_data
if current_user && can_current_user_push_code? if current_user && can_current_user_push_to_default_branch?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('New file'), label: _('New file'),
link: project_new_blob_path(project, default_branch || 'master'), link: project_new_blob_path(project, default_branch || 'master'),
...@@ -209,7 +212,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -209,7 +212,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def readme_anchor_data def readme_anchor_data
if current_user && can_current_user_push_code? && repository.readme.blank? if current_user && can_current_user_push_to_default_branch? && repository.readme.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add Readme'), label: _('Add Readme'),
link: add_readme_path) link: add_readme_path)
...@@ -221,7 +224,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -221,7 +224,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def changelog_anchor_data def changelog_anchor_data
if current_user && can_current_user_push_code? && repository.changelog.blank? if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add Changelog'), label: _('Add Changelog'),
link: add_changelog_path) link: add_changelog_path)
...@@ -233,7 +236,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -233,7 +236,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def license_anchor_data def license_anchor_data
if current_user && can_current_user_push_code? && repository.license_blob.blank? if current_user && can_current_user_push_to_default_branch? && repository.license_blob.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add License'), label: _('Add License'),
link: add_license_path) link: add_license_path)
...@@ -245,7 +248,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -245,7 +248,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def contribution_guide_anchor_data def contribution_guide_anchor_data
if current_user && can_current_user_push_code? && repository.contribution_guide.blank? if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add Contribution guide'), label: _('Add Contribution guide'),
link: add_contribution_guide_path) link: add_contribution_guide_path)
......
...@@ -91,7 +91,7 @@ module Projects ...@@ -91,7 +91,7 @@ module Projects
project.run_after_commit do project.run_after_commit do
# self is now project # self is now project
GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage_path, new_path) GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage, new_path)
end end
else else
false false
...@@ -100,9 +100,9 @@ module Projects ...@@ -100,9 +100,9 @@ module Projects
def mv_repository(from_path, to_path) def mv_repository(from_path, to_path)
# There is a possibility project does not have repository or wiki # There is a possibility project does not have repository or wiki
return true unless gitlab_shell.exists?(project.repository_storage_path, from_path + '.git') return true unless gitlab_shell.exists?(project.repository_storage, from_path + '.git')
gitlab_shell.mv_repository(project.repository_storage_path, from_path, to_path) gitlab_shell.mv_repository(project.repository_storage, from_path, to_path)
end end
def attempt_rollback(project, message) def attempt_rollback(project, message)
......
...@@ -47,8 +47,8 @@ module Projects ...@@ -47,8 +47,8 @@ module Projects
private private
def move_repository(from_name, to_name) def move_repository(from_name, to_name)
from_exists = gitlab_shell.exists?(project.repository_storage_path, "#{from_name}.git") from_exists = gitlab_shell.exists?(project.repository_storage, "#{from_name}.git")
to_exists = gitlab_shell.exists?(project.repository_storage_path, "#{to_name}.git") to_exists = gitlab_shell.exists?(project.repository_storage, "#{to_name}.git")
# If we don't find the repository on either original or target we should log that as it could be an issue if the # If we don't find the repository on either original or target we should log that as it could be an issue if the
# project was not originally empty. # project was not originally empty.
...@@ -60,7 +60,7 @@ module Projects ...@@ -60,7 +60,7 @@ module Projects
return true return true
end end
gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) gitlab_shell.mv_repository(project.repository_storage, from_name, to_name)
end end
def rollback_folder_move def rollback_folder_move
......
...@@ -127,7 +127,7 @@ module Projects ...@@ -127,7 +127,7 @@ module Projects
end end
def move_repo_folder(from_name, to_name) def move_repo_folder(from_name, to_name)
gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) gitlab_shell.mv_repository(project.repository_storage, from_name, to_name)
end end
def execute_system_hooks def execute_system_hooks
......
...@@ -138,8 +138,10 @@ module QuickActions ...@@ -138,8 +138,10 @@ module QuickActions
'Remove assignee' 'Remove assignee'
end end
end end
explanation do explanation do |users = nil|
"Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}." assignees = issuable.assignees
assignees &= users if users.present? && issuable.allows_multiple_assignees?
"Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
end end
params do params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '' issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
...@@ -268,6 +270,26 @@ module QuickActions ...@@ -268,6 +270,26 @@ module QuickActions
end end
end end
desc 'Copy labels and milestone from other issue or merge request'
explanation do |source_issuable|
"Copy labels and milestone from #{source_issuable.to_reference}."
end
params '#issue | !merge_request'
condition do
issuable.persisted? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
parse_params do |issuable_param|
extract_references(issuable_param, :issue).first ||
extract_references(issuable_param, :merge_request).first
end
command :copy_metadata do |source_issuable|
if source_issuable.present? && source_issuable.project.id == issuable.project.id
@updates[:add_label_ids] = source_issuable.labels.map(&:id)
@updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
end
end
desc 'Add a todo' desc 'Add a todo'
explanation 'Adds a todo.' explanation 'Adds a todo.'
condition do condition do
......
...@@ -65,6 +65,10 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -65,6 +65,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
!!model !!model
end end
def local_url
File.join('/', self.class.base_dir, dynamic_segment, filename)
end
private private
# Designed to be overridden by child uploaders that have a dynamic path # Designed to be overridden by child uploaders that have a dynamic path
......
- breadcrumb_title "General Settings" - breadcrumb_title "General Settings"
- @content_class = "limit-container-width" unless fluid_layout
.panel.panel-default.prepend-top-default .panel.panel-default.prepend-top-default
.panel-heading .panel-heading
Group settings Group settings
......
...@@ -58,6 +58,8 @@ ...@@ -58,6 +58,8 @@
touch README.md touch README.md
git add README.md git add README.md
git commit -m "add README" git commit -m "add README"
- if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin master git push -u origin master
%fieldset %fieldset
...@@ -69,6 +71,8 @@ ...@@ -69,6 +71,8 @@
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git add . git add .
git commit -m "Initial commit" git commit -m "Initial commit"
- if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin master git push -u origin master
%fieldset %fieldset
...@@ -78,6 +82,8 @@ ...@@ -78,6 +82,8 @@
cd existing_repo cd existing_repo
git remote rename origin old-origin git remote rename origin old-origin
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin --all git push -u origin --all
git push -u origin --tags git push -u origin --tags
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- if @namespaces.present? - if @namespaces.present?
.fork-thumbnail-container.js-fork-content .fork-thumbnail-container.js-fork-content
%h5.prepend-top-0.append-bottom-0.prepend-left-default.append-right-default %h5.prepend-top-0.append-bottom-0.prepend-left-default.append-right-default
Click to fork the project = _("Select a namespace to fork the project")
- @namespaces.each do |namespace| - @namespaces.each do |namespace|
= render 'fork_button', namespace: namespace = render 'fork_button', namespace: namespace
- else - else
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
= link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do = link_to project_ci_lint_path(@project), class: 'btn btn-default' do
%span CI lint %span CI lint
.content-list.builds-content-list .content-list.builds-content-list
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
%ul %ul
- pipeline.yaml_errors.split(",").each do |error| - pipeline.yaml_errors.split(",").each do |error|
%li= error %li= error
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} You can also test your .gitlab-ci.yml in the #{link_to "Lint", project_ci_lint_path(@project)}
- if pipeline.project.builds_enabled? && !pipeline.ci_yaml_file - if pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
.bs-callout.bs-callout-warning .bs-callout.bs-callout-warning
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
docker login #{Gitlab.config.registry.host_port} docker login #{Gitlab.config.registry.host_port}
%br %br
%p %p
- deploy_token = link_to(_('deploy token'), help_page_path('user/projects/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank') - deploy_token = link_to(_('deploy token'), help_page_path('user/project/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank')
= s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token } = s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token }
%br %br
%p %p
......
...@@ -32,6 +32,13 @@ ...@@ -32,6 +32,13 @@
required: true, required: true,
title: 'You can choose a descriptive name different from the path.' title: 'You can choose a descriptive name different from the path.'
- if @group.persisted?
.form-group.group-name-holder
= f.label :id, class: 'control-label' do
= _("Group ID")
.col-sm-10
= f.text_field :id, class: 'form-control', readonly: true
.form-group.group-description-holder .form-group.group-description-holder
= f.label :description, class: 'control-label' = f.label :description, class: 'control-label'
.col-sm-10 .col-sm-10
......
...@@ -13,7 +13,9 @@ class RepositoryForkWorker ...@@ -13,7 +13,9 @@ class RepositoryForkWorker
# See https://gitlab.com/gitlab-org/gitaly/issues/1110 # See https://gitlab.com/gitlab-org/gitaly/issues/1110
if args.empty? if args.empty?
source_project = target_project.forked_from_project source_project = target_project.forked_from_project
return target_project.mark_import_as_failed('Source project cannot be found.') unless source_project unless source_project
return target_project.mark_import_as_failed('Source project cannot be found.')
end
fork_repository(target_project, source_project.repository_storage, source_project.disk_path) fork_repository(target_project, source_project.repository_storage, source_project.disk_path)
else else
......
---
title: Introduce new ProjectCiCdSetting model with group_runners_enabled
merge_request: 18144
author:
type: performance
---
title: Add an API endpoint to download git repository snapshots
merge_request: 18173
author:
type: added
---
title: Reduce queries on merge requests list page for merge requests from forks
merge_request: 18561
author:
type: performance
---
title: Expose Deploy Token data as environment varialbes on CI/CD jobs
merge_request: 18414
author:
type: added
---
title: Fixed wrong avatar URL when the avatar is on object storage.
merge_request: 18092
author:
type: fixed
---
title: Fix `Trace::HttpIO` can not render multi-byte chars
merge_request: 18417
author:
type: fixed
---
title: Align action icons in pipeline graph
merge_request:
author:
type: fixed
---
title: '[API] Fix URLs in the `Link` header for `GET /projects/:id/repository/contributors`
when no value is passed for `order_by` or `sort`'
merge_request: 18393
author:
type: fixed
---
title: Fix tabs container styles to make RSS button clickable
merge_request: 18559
author:
type: fixed
---
title: Add index to file_store on ci_job_artifacts
merge_request: 18444
author:
type: performance
---
title: Fix specifying a non-default ref when requesting an archive using the legacy
URL
merge_request: 18468
author:
type: fixed
---
title: Removes 'No Job log' message from build trace
merge_request: 18523
author:
type: fixed
---
title: Update links to /ci/lint with ones to project ci/lint
merge_request: 18539
author: Takuya Noguchi
type: fixed
---
title: Fix unassign slash command preview
merge_request: 18447
author:
type: fixed
---
title: Validate project path prior to hitting the database.
merge_request: 18322
author:
type: performance
---
title: Replace "Click" with "Select" to be more inclusive of people with accessibility
requirements
merge_request: 18386
author: Mark Lapierre
type: other
---
title: Add Copy metadata quick action
merge_request: 16473
author: Mateusz Bajorski
type: added
---
title: Align project avatar on small viewports
merge_request: 18513
author: George Tsiolis
type: changed
---
title: Fix errors on pushing to an empty repository
merge_request: 18462
author:
type: fixed
---
title: Fix direct_upload when records with null file_store are used
merge_request:
author:
type: fixed
---
title: Fix a case with secret variables being empty sometimes
merge_request: 18400
author:
type: fixed
---
title: Respect visibility options and description when importing project from template
merge_request: 18473
author:
type: fixed
---
title: Added Webhook SSRF prevention to documentation
merge_request: 18532
author:
type: other
--- ---
title: Fixed IDE not loading for sub groups title: Added fuzzy file finder to web IDE
merge_request: merge_request:
author: author:
type: fixed type: added
---
title: Removed alert box in IDE when redirecting to new merge request
merge_request:
author:
type: fixed
---
title: Fixed IDE not showing loading state when tree is loading
merge_request:
author:
type: fixed
---
title: Fix users not seeing labels from private groups when being a member of a child project
merge_request:
author:
type: fixed
---
title: Support Markdown rendering using multiple projects
merge_request:
author:
type: performance
---
title: Restore label underline color
merge_request: 18407
author: George Tsiolis
type: fixed
---
title: Bump lograge to 0.10.0 and remove monkey patch
merge_request:
author:
type: other
---
title: Fix N+1 queries when loading participants for a commit note
merge_request:
author:
type: performance
---
title: Show group id in group settings
merge_request: 18482
author: George Tsiolis
type: added
---
title: Show Runner's description on job's page
merge_request: 17321
author:
type: added
---
title: Update doorkeeper to 4.3.2 to fix GitLab OAuth authentication
merge_request: 18543
author:
type: fixed
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.
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