Commit 88cc9d52 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into feature/sm/35954-create-kubernetes-cluster-on-gke-from-k8s-service

parents d6e22e83 8921af39
...@@ -5,3 +5,4 @@ app/policies/project_policy.rb ...@@ -5,3 +5,4 @@ app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb
...@@ -195,6 +195,10 @@ entry. ...@@ -195,6 +195,10 @@ entry.
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi) - Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
- [BUGIFX] Improves subgroup creation permissions. !13418 - [BUGIFX] Improves subgroup creation permissions. !13418
## 9.5.7 (2017-10-03)
- Fix gitlab rake:import:repos task.
## 9.5.6 (2017-09-29) ## 9.5.6 (2017-09-29)
- [FIXED] Fix MR ready to merge buttons/controls at mobile breakpoint. !14242 - [FIXED] Fix MR ready to merge buttons/controls at mobile breakpoint. !14242
......
...@@ -910,7 +910,7 @@ GEM ...@@ -910,7 +910,7 @@ GEM
json (>= 1.8.0) json (>= 1.8.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.2) unf_ext (0.0.7.4)
unicode-display_width (1.3.0) unicode-display_width (1.3.0)
unicorn (5.1.0) unicorn (5.1.0)
kgio (~> 2.6) kgio (~> 2.6)
......
import Jed from 'jed'; import Jed from 'jed';
import sprintf from './sprintf'; import sprintf from './sprintf';
/**
This is required to require all the translation folders in the current directory
this saves us having to do this manually & keep up to date with new languages
**/
function requireAll(requireContext) { return requireContext.keys().map(requireContext); }
const allLocales = requireAll(require.context('./', true, /^(?!.*(?:index.js$)).*\.js$/));
const locales = allLocales.reduce((d, obj) => {
const data = d;
const localeKey = Object.keys(obj)[0];
data[localeKey] = obj[localeKey];
return data;
}, {});
const langAttribute = document.querySelector('html').getAttribute('lang'); const langAttribute = document.querySelector('html').getAttribute('lang');
const lang = (langAttribute || 'en').replace(/-/g, '_'); const lang = (langAttribute || 'en').replace(/-/g, '_');
const locale = new Jed(locales[lang]); const locale = new Jed(window.translations || {});
/** /**
Translates `text` Translates `text`
@param text The text to be translated @param text The text to be translated
@returns {String} The translated text @returns {String} The translated text
**/ **/
......
...@@ -127,6 +127,21 @@ import IssuablesHelper from './helpers/issuables_helper'; ...@@ -127,6 +127,21 @@ import IssuablesHelper from './helpers/issuables_helper';
$el.text(gl.text.addDelimiter(count)); $el.text(gl.text.addDelimiter(count));
}; };
MergeRequest.prototype.hideCloseButton = function() {
const el = document.querySelector('.merge-request .issuable-actions');
const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) {
closeDropdownItem.classList.add('hidden');
// Selects the next dropdown item
el.querySelector('li.report-item').click();
} else {
// No dropdown just hide the Close button
el.querySelector('.btn-close').classList.add('hidden');
}
// Dropdown for mobile screen
el.querySelector('li.js-close-item').classList.add('hidden');
};
return MergeRequest; return MergeRequest;
})(); })();
}).call(window); }).call(window);
<template>
<div class="cell">
<code-cell
type="input"
:raw-code="rawInputCode"
:count="cell.execution_count"
:code-css-class="codeCssClass" />
<output-cell
v-if="hasOutput"
:count="cell.execution_count"
:output="output"
:code-css-class="codeCssClass" />
</div>
</template>
<script> <script>
import CodeCell from './code/index.vue'; import CodeCell from './code/index.vue';
import OutputCell from './output/index.vue'; import OutputCell from './output/index.vue';
...@@ -51,6 +36,21 @@ export default { ...@@ -51,6 +36,21 @@ export default {
}; };
</script> </script>
<template>
<div class="cell">
<code-cell
type="input"
:raw-code="rawInputCode"
:count="cell.execution_count"
:code-css-class="codeCssClass" />
<output-cell
v-if="hasOutput"
:count="cell.execution_count"
:output="output"
:code-css-class="codeCssClass" />
</div>
</template>
<style scoped> <style scoped>
.cell { .cell {
flex-direction: column; flex-direction: column;
......
<template>
<div :class="type">
<prompt
:type="promptType"
:count="count" />
<pre
class="language-python"
:class="codeCssClass"
ref="code"
v-text="code">
</pre>
</div>
</template>
<script> <script>
import Prism from '../../lib/highlight'; import Prism from '../../lib/highlight';
import Prompt from '../prompt.vue'; import Prompt from '../prompt.vue';
...@@ -55,3 +41,17 @@ ...@@ -55,3 +41,17 @@
}, },
}; };
</script> </script>
<template>
<div :class="type">
<prompt
:type="promptType"
:count="count" />
<pre
class="language-python"
:class="codeCssClass"
ref="code"
v-text="code">
</pre>
</div>
</template>
<template>
<div class="cell text-cell">
<prompt />
<div class="markdown" v-html="markdown"></div>
</div>
</template>
<script> <script>
/* global katex */ /* global katex */
import marked from 'marked'; import marked from 'marked';
...@@ -95,6 +88,13 @@ ...@@ -95,6 +88,13 @@
}; };
</script> </script>
<template>
<div class="cell text-cell">
<prompt />
<div class="markdown" v-html="markdown"></div>
</div>
</template>
<style> <style>
.markdown .katex { .markdown .katex {
display: block; display: block;
......
<template>
<div class="output">
<prompt />
<div v-html="rawCode"></div>
</div>
</template>
<script> <script>
import Prompt from '../prompt.vue'; import Prompt from '../prompt.vue';
...@@ -20,3 +13,10 @@ export default { ...@@ -20,3 +13,10 @@ export default {
}, },
}; };
</script> </script>
<template>
<div class="output">
<prompt />
<div v-html="rawCode"></div>
</div>
</template>
<template>
<div class="output">
<prompt />
<img
:src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>
<script> <script>
import Prompt from '../prompt.vue'; import Prompt from '../prompt.vue';
...@@ -25,3 +17,11 @@ export default { ...@@ -25,3 +17,11 @@ export default {
}, },
}; };
</script> </script>
<template>
<div class="output">
<prompt />
<img
:src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>
<template>
<component :is="componentName"
type="output"
:outputType="outputType"
:count="count"
:raw-code="rawCode"
:code-css-class="codeCssClass" />
</template>
<script> <script>
import CodeCell from '../code/index.vue'; import CodeCell from '../code/index.vue';
import Html from './html.vue'; import Html from './html.vue';
...@@ -81,3 +72,12 @@ export default { ...@@ -81,3 +72,12 @@ export default {
}, },
}; };
</script> </script>
<template>
<component :is="componentName"
type="output"
:outputType="outputType"
:count="count"
:raw-code="rawCode"
:code-css-class="codeCssClass" />
</template>
<template>
<div class="prompt">
<span v-if="type && count">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<script> <script>
export default { export default {
props: { props: {
...@@ -21,6 +13,14 @@ ...@@ -21,6 +13,14 @@
}; };
</script> </script>
<template>
<div class="prompt">
<span v-if="type && count">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<style scoped> <style scoped>
.prompt { .prompt {
padding: 0 10px; padding: 0 10px;
......
<template>
<div v-if="hasNotebook">
<component
v-for="(cell, index) in cells"
:is="cellType(cell.cell_type)"
:cell="cell"
:key="index"
:code-css-class="codeCssClass" />
</div>
</template>
<script> <script>
import { import {
MarkdownCell, MarkdownCell,
...@@ -59,6 +48,17 @@ ...@@ -59,6 +48,17 @@
}; };
</script> </script>
<template>
<div v-if="hasNotebook">
<component
v-for="(cell, index) in cells"
:is="cellType(cell.cell_type)"
:cell="cell"
:key="index"
:code-css-class="codeCssClass" />
</div>
</template>
<style> <style>
.cell, .cell,
.input, .input,
......
...@@ -272,6 +272,7 @@ ...@@ -272,6 +272,7 @@
v-model="note" v-model="note"
ref="textarea" ref="textarea"
slot="textarea" slot="textarea"
:disabled="isSubmitting"
placeholder="Write a comment or drag your files here..." placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()" @keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()"> @keydown.meta.enter="handleSave()">
......
<template>
<div class="pdf-viewer" v-if="hasPDF">
<page v-for="(page, index) in pages"
:key="index"
:v-if="!loading"
:page="page"
:number="index + 1" />
</div>
</template>
<script> <script>
import pdfjsLib from 'vendor/pdf'; import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min'; import workerSrc from 'vendor/pdf.worker.min';
...@@ -64,6 +54,16 @@ ...@@ -64,6 +54,16 @@
}; };
</script> </script>
<template>
<div class="pdf-viewer" v-if="hasPDF">
<page v-for="(page, index) in pages"
:key="index"
:v-if="!loading"
:page="page"
:number="index + 1" />
</div>
</template>
<style> <style>
.pdf-viewer { .pdf-viewer {
background: url('./assets/img/bg.gif'); background: url('./assets/img/bg.gif');
......
<template>
<canvas
class="pdf-page"
ref="canvas"
:data-page="number" />
</template>
<script> <script>
export default { export default {
props: { props: {
...@@ -48,6 +41,13 @@ ...@@ -48,6 +41,13 @@
}; };
</script> </script>
<template>
<canvas
class="pdf-page"
ref="canvas"
:data-page="number" />
</template>
<style> <style>
.pdf-page { .pdf-page {
margin: 8px auto 0 auto; margin: 8px auto 0 auto;
......
export default () => { export default () => {
$('.fork-thumbnail a').on('click', function forkThumbnailClicked() { $('.js-fork-thumbnail').on('click', function forkThumbnailClicked() {
if ($(this).hasClass('disabled')) return false; if ($(this).hasClass('disabled')) return false;
$('.fork-namespaces').hide(); return $('.js-fork-content').toggle();
return $('.save-project-loader').show();
}); });
}; };
<script>
/* globals Flash */
import { mapGetters, mapActions } from 'vuex';
import '../../flash';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import store from '../stores';
import collapsibleContainer from './collapsible_container.vue';
import { errorMessages, errorMessagesTypes } from '../constants';
export default {
name: 'registryListApp',
props: {
endpoint: {
type: String,
required: true,
},
},
store,
components: {
collapsibleContainer,
loadingIcon,
},
computed: {
...mapGetters([
'isLoading',
'repos',
]),
},
methods: {
...mapActions([
'setMainEndpoint',
'fetchRepos',
]),
},
created() {
this.setMainEndpoint(this.endpoint);
},
mounted() {
this.fetchRepos()
.catch(() => Flash(errorMessages[errorMessagesTypes.FETCH_REPOS]));
},
};
</script>
<template>
<div>
<loading-icon
v-if="isLoading"
size="3"
/>
<collapsible-container
v-else-if="!isLoading && repos.length"
v-for="(item, index) in repos"
:key="index"
:repo="item"
/>
<p v-else-if="!isLoading && !repos.length">
{{__("No container images stored for this project. Add one by following the instructions above.")}}
</p>
</div>
</template>
<script>
/* globals Flash */
import { mapActions } from 'vuex';
import '../../flash';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import tableRegistry from './table_registry.vue';
import { errorMessages, errorMessagesTypes } from '../constants';
export default {
name: 'collapsibeContainerRegisty',
props: {
repo: {
type: Object,
required: true,
},
},
components: {
clipboardButton,
loadingIcon,
tableRegistry,
},
directives: {
tooltip,
},
data() {
return {
isOpen: false,
};
},
computed: {
clipboardText() {
return `docker pull ${this.repo.location}`;
},
},
methods: {
...mapActions([
'fetchRepos',
'fetchList',
'deleteRepo',
]),
toggleRepo() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.fetchList({ repo: this.repo })
.catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY));
}
},
handleDeleteRepository() {
this.deleteRepo(this.repo)
.then(() => this.fetchRepos())
.catch(() => this.showError(errorMessagesTypes.DELETE_REPO));
},
showError(message) {
Flash((errorMessages[message]));
},
},
};
</script>
<template>
<div class="container-image">
<div
class="container-image-head">
<button
type="button"
@click="toggleRepo"
class="js-toggle-repo btn-link">
<i
class="fa"
:class="{
'fa-chevron-right': !isOpen,
'fa-chevron-up': isOpen,
}"
aria-hidden="true">
</i>
{{repo.name}}
</button>
<clipboard-button
v-if="repo.location"
:text="clipboardText"
:title="repo.location"
/>
<div class="controls hidden-xs pull-right">
<button
v-if="repo.canDelete"
type="button"
class="js-remove-repo btn btn-danger"
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
v-tooltip
@click="handleDeleteRepository">
<i
class="fa fa-trash"
aria-hidden="true">
</i>
</button>
</div>
</div>
<loading-icon
v-if="repo.isLoading"
class="append-bottom-20"
size="2"
/>
<div
v-else-if="!repo.isLoading && isOpen"
class="container-image-tags">
<table-registry
v-if="repo.list.length"
:repo="repo"
/>
<div
v-else
class="nothing-here-block">
{{s__("ContainerRegistry|No tags in Container Registry for this container image.")}}
</div>
</div>
</div>
</template>
<script>
/* globals Flash */
import { mapActions } from 'vuex';
import { n__ } from '../../locale';
import '../../flash';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import { errorMessages, errorMessagesTypes } from '../constants';
export default {
props: {
repo: {
type: Object,
required: true,
},
},
components: {
clipboardButton,
tablePagination,
},
mixins: [
timeagoMixin,
],
directives: {
tooltip,
},
computed: {
shouldRenderPagination() {
return this.repo.pagination.total > this.repo.pagination.perPage;
},
},
methods: {
...mapActions([
'fetchList',
'deleteRegistry',
]),
layers(item) {
return item.layers ? n__('%d layer', '%d layers', item.layers) : '';
},
handleDeleteRegistry(registry) {
this.deleteRegistry(registry)
.then(() => this.fetchList({ repo: this.repo }))
.catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY));
},
onPageChange(pageNumber) {
this.fetchList({ repo: this.repo, page: pageNumber })
.catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY));
},
clipboardText(text) {
return `docker pull ${text}`;
},
showError(message) {
Flash((errorMessages[message]));
},
},
};
</script>
<template>
<div>
<table class="table tags">
<thead>
<tr>
<th>{{s__('ContainerRegistry|Tag')}}</th>
<th>{{s__('ContainerRegistry|Tag ID')}}</th>
<th>{{s__("ContainerRegistry|Size")}}</th>
<th>{{s__("ContainerRegistry|Created")}}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, i) in repo.list"
:key="i">
<td>
{{item.tag}}
<clipboard-button
v-if="item.location"
:title="item.location"
:text="clipboardText(item.location)"
/>
</td>
<td>
<span
v-tooltip
:title="item.revision"
data-placement="bottom">
{{item.shortRevision}}
</span>
</td>
<td>
{{item.size}}
<template v-if="item.size && item.layers">
&middot;
</template>
{{layers(item)}}
</td>
<td>
{{timeFormated(item.createdAt)}}
</td>
<td class="content">
<button
v-if="item.canDelete"
type="button"
class="js-delete-registry btn btn-danger hidden-xs pull-right"
:title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')"
data-container="body"
v-tooltip
@click="handleDeleteRegistry(item)">
<i
class="fa fa-trash"
aria-hidden="true">
</i>
</button>
</td>
</tr>
</tbody>
</table>
<table-pagination
v-if="shouldRenderPagination"
:change="onPageChange"
:page-info="repo.pagination"
/>
</div>
</template>
import { __ } from '../locale';
export const errorMessagesTypes = {
FETCH_REGISTRY: 'FETCH_REGISTRY',
FETCH_REPOS: 'FETCH_REPOS',
DELETE_REPO: 'DELETE_REPO',
DELETE_REGISTRY: 'DELETE_REGISTRY',
};
export const errorMessages = {
[errorMessagesTypes.FETCH_REGISTRY]: __('Something went wrong while fetching the registry list.'),
[errorMessagesTypes.FETCH_REPOS]: __('Something went wrong while fetching the projects.'),
[errorMessagesTypes.DELETE_REPO]: __('Something went wrong on our end.'),
[errorMessagesTypes.DELETE_REGISTRY]: __('Something went wrong on our end.'),
};
import Vue from 'vue';
import registryApp from './components/app.vue';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-registry-images',
components: {
registryApp,
},
data() {
const dataset = document.querySelector(this.$options.el).dataset;
return {
endpoint: dataset.endpoint,
};
},
render(createElement) {
return createElement('registry-app', {
props: {
endpoint: this.endpoint,
},
});
},
}));
import Vue from 'vue';
import VueResource from 'vue-resource';
import * as types from './mutation_types';
Vue.use(VueResource);
export const fetchRepos = ({ commit, state }) => {
commit(types.TOGGLE_MAIN_LOADING);
return Vue.http.get(state.endpoint)
.then(res => res.json())
.then((response) => {
commit(types.TOGGLE_MAIN_LOADING);
commit(types.SET_REPOS_LIST, response);
});
};
export const fetchList = ({ commit }, { repo, page }) => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
return Vue.http.get(repo.tagsPath, { params: { page } })
.then((response) => {
const headers = response.headers;
return response.json().then((resp) => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
commit(types.SET_REGISTRY_LIST, { repo, resp, headers });
});
});
};
export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath)
.then(res => res.json());
export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath)
.then(res => res.json());
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
export const isLoading = state => state.isLoading;
export const repos = state => state.repos;
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
isLoading: false,
endpoint: '', // initial endpoint to fetch the repos list
/**
* Each object in `repos` has the following strucure:
* {
* name: String,
* isLoading: Boolean,
* tagsPath: String // endpoint to request the list
* destroyPath: String // endpoit to delete the repo
* list: Array // List of the registry images
* }
*
* Each registry image inside `list` has the following structure:
* {
* tag: String,
* revision: String
* shortRevision: String
* size: Number
* layers: Number
* createdAt: String
* destroyPath: String // endpoit to delete each image
* }
*/
repos: [],
},
actions,
getters,
mutations,
});
export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT';
export const SET_REPOS_LIST = 'SET_REPOS_LIST';
export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING';
export const SET_REGISTRY_LIST = 'SET_REGISTRY_LIST';
export const TOGGLE_REGISTRY_LIST_LOADING = 'TOGGLE_REGISTRY_LIST_LOADING';
import * as types from './mutation_types';
import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
export default {
[types.SET_MAIN_ENDPOINT](state, endpoint) {
Object.assign(state, { endpoint });
},
[types.SET_REPOS_LIST](state, list) {
Object.assign(state, {
repos: list.map(el => ({
canDelete: !!el.destroy_path,
destroyPath: el.destroy_path,
id: el.id,
isLoading: false,
list: [],
location: el.location,
name: el.path,
tagsPath: el.tags_path,
})),
});
},
[types.TOGGLE_MAIN_LOADING](state) {
Object.assign(state, { isLoading: !state.isLoading });
},
[types.SET_REGISTRY_LIST](state, { repo, resp, headers }) {
const listToUpdate = state.repos.find(el => el.id === repo.id);
const normalizedHeaders = normalizeHeaders(headers);
const pagination = parseIntPagination(normalizedHeaders);
listToUpdate.pagination = pagination;
listToUpdate.list = resp.map(element => ({
tag: element.name,
revision: element.revision,
shortRevision: element.short_revision,
size: element.size,
layers: element.layers,
location: element.location,
createdAt: element.created_at,
destroyPath: element.destroy_path,
canDelete: !!element.destroy_path,
}));
},
[types.TOGGLE_REGISTRY_LIST_LOADING](state, list) {
const listToUpdate = state.repos.find(el => el.id === list.id);
listToUpdate.isLoading = !listToUpdate.isLoading;
},
};
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="loading" showDisabledButton /> <status-icon status="loading" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
Checking ability to merge automatically Checking ability to merge automatically
......
...@@ -12,7 +12,7 @@ export default { ...@@ -12,7 +12,7 @@ export default {
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon <status-icon
status="failed" status="failed"
showDisabledButton /> :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span <span
v-if="mr.shouldBeRebased" v-if="mr.shouldBeRebased"
......
...@@ -51,7 +51,7 @@ export default { ...@@ -51,7 +51,7 @@ export default {
</span> </span>
</template> </template>
<template v-else> <template v-else>
<status-icon status="failed" showDisabledButton /> <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
<span <span
......
...@@ -24,7 +24,7 @@ export default { ...@@ -24,7 +24,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="failed" showDisabledButton /> <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold js-branch-text"> <span class="bold js-branch-text">
<span class="capitalize"> <span class="capitalize">
......
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="success" showDisabledButton /> <status-icon status="success" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
Ready to be merged automatically. Ready to be merged automatically.
......
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="failed" showDisabledButton /> <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
Pipeline blocked. The pipeline for this merge request requires a manual action to proceed Pipeline blocked. The pipeline for this merge request requires a manual action to proceed
......
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="failed" showDisabledButton /> <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
......
...@@ -38,24 +38,40 @@ export default { ...@@ -38,24 +38,40 @@ export default {
return this.useCommitMessageWithDescription ? withoutDesc : withDesc; return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
}, },
mergeButtonClass() { status() {
const defaultClass = 'btn btn-sm btn-success accept-merge-request';
const failedClass = `${defaultClass} btn-danger`;
const inActionClass = `${defaultClass} btn-info`;
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr; const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
if (hasCI && !ciStatus) { if (hasCI && !ciStatus) {
return failedClass; return 'failed';
} else if (!pipeline) { } else if (!pipeline) {
return defaultClass; return 'success';
} else if (isPipelineActive) { } else if (isPipelineActive) {
return inActionClass; return 'pending';
} else if (isPipelineFailed) { } else if (isPipelineFailed) {
return 'failed';
}
return 'success';
},
mergeButtonClass() {
const defaultClass = 'btn btn-sm btn-success accept-merge-request';
const failedClass = `${defaultClass} btn-danger`;
const inActionClass = `${defaultClass} btn-info`;
if (this.status === 'failed') {
return failedClass; return failedClass;
} else if (this.status === 'pending') {
return inActionClass;
} }
return defaultClass; return defaultClass;
}, },
iconClass() {
if (this.status === 'failed' || !this.commitMessage.length || !this.isMergeAllowed() || this.mr.preventMerge) {
return 'failed';
}
return 'success';
},
mergeButtonText() { mergeButtonText() {
if (this.isMergingImmediately) { if (this.isMergingImmediately) {
return 'Merge in progress'; return 'Merge in progress';
...@@ -156,6 +172,7 @@ export default { ...@@ -156,6 +172,7 @@ export default {
eventHub.$emit('FetchActionsContent'); eventHub.$emit('FetchActionsContent');
if (window.mergeRequest) { if (window.mergeRequest) {
window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged');
window.mergeRequest.hideCloseButton();
window.mergeRequest.decreaseCounter(); window.mergeRequest.decreaseCounter();
} }
stopPolling(); stopPolling();
...@@ -208,7 +225,7 @@ export default { ...@@ -208,7 +225,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="success" /> <status-icon :status="iconClass" />
<div class="media-body"> <div class="media-body">
<div class="mr-widget-body-controls media space-children"> <div class="mr-widget-body-controls media space-children">
<span class="btn-group append-bottom-5"> <span class="btn-group append-bottom-5">
......
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="failed" showDisabledButton /> <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
The source branch HEAD has recently changed. Please reload the page and review the changes before merging The source branch HEAD has recently changed. Please reload the page and review the changes before merging
......
...@@ -10,7 +10,7 @@ export default { ...@@ -10,7 +10,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="failed" showDisabledButton /> <status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
There are unresolved discussions. Please resolve these discussions There are unresolved discussions. Please resolve these discussions
......
...@@ -38,7 +38,7 @@ export default { ...@@ -38,7 +38,7 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="failed" :showDisabledButton="Boolean(mr.removeWIPPath)" /> <status-icon status="failed" :show-disabled-button="Boolean(mr.removeWIPPath)" />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
This is a Work in Progress This is a Work in Progress
......
<script>
/**
* Falls back to the code used in `copy_to_clipboard.js`
*/
export default {
name: 'clipboardButton',
props: {
text: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
},
};
</script>
<template>
<button
type="button"
class="btn btn-transparent btn-clipboard"
:data-title="title"
:data-clipboard-text="text">
<i
aria-hidden="true"
class="fa fa-clipboard">
</i>
</button>
</template>
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
&.s60 { @include avatar-size(60px, 12px); } &.s60 { @include avatar-size(60px, 12px); }
&.s70 { @include avatar-size(70px, 14px); } &.s70 { @include avatar-size(70px, 14px); }
&.s90 { @include avatar-size(90px, 15px); } &.s90 { @include avatar-size(90px, 15px); }
&.s100 { @include avatar-size(100px, 15px); }
&.s110 { @include avatar-size(110px, 15px); } &.s110 { @include avatar-size(110px, 15px); }
&.s140 { @include avatar-size(140px, 15px); } &.s140 { @include avatar-size(140px, 15px); }
&.s160 { @include avatar-size(160px, 20px); } &.s160 { @include avatar-size(160px, 20px); }
...@@ -78,6 +79,7 @@ ...@@ -78,6 +79,7 @@
&.s60 { font-size: 32px; line-height: 58px; } &.s60 { font-size: 32px; line-height: 58px; }
&.s70 { font-size: 34px; line-height: 70px; } &.s70 { font-size: 34px; line-height: 70px; }
&.s90 { font-size: 36px; line-height: 88px; } &.s90 { font-size: 36px; line-height: 88px; }
&.s100 { font-size: 36px; line-height: 98px; }
&.s110 { font-size: 40px; line-height: 108px; font-weight: $gl-font-weight-normal; } &.s110 { font-size: 40px; line-height: 108px; font-weight: $gl-font-weight-normal; }
&.s140 { font-size: 72px; line-height: 138px; } &.s140 { font-size: 72px; line-height: 138px; }
&.s160 { font-size: 96px; line-height: 158px; } &.s160 { font-size: 96px; line-height: 158px; }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
.prepend-top-10 { margin-top: 10px; } .prepend-top-10 { margin-top: 10px; }
.prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px; } .prepend-top-20 { margin-top: 20px; }
.prepend-left-4 { margin-left: 4px; }
.prepend-left-5 { margin-left: 5px; } .prepend-left-5 { margin-left: 5px; }
.prepend-left-10 { margin-left: 10px; } .prepend-left-10 { margin-left: 10px; }
.prepend-left-default { margin-left: $gl-padding; } .prepend-left-default { margin-left: $gl-padding; }
...@@ -129,11 +130,6 @@ span.update-author { ...@@ -129,11 +130,6 @@ span.update-author {
} }
} }
.user-mention {
color: $user-mention-color;
font-weight: $gl-font-weight-bold;
}
.field_with_errors { .field_with_errors {
display: inline; display: inline;
} }
......
...@@ -6,3 +6,14 @@ ...@@ -6,3 +6,14 @@
.gfm-commit_range { .gfm-commit_range {
@extend .commit-sha; @extend .commit-sha;
} }
.gfm-project_member {
padding: 0 2px;
border-radius: #{$border-radius-default / 2};
background-color: $user-mention-bg;
&:hover {
background-color: $user-mention-bg-hover;
text-decoration: none;
}
}
...@@ -48,31 +48,24 @@ ...@@ -48,31 +48,24 @@
} }
&:hover { &:hover {
background-color: $white-normal; border-color: $gray-darkest;
border-color: $border-white-normal;
color: $gl-text-color; color: $gl-text-color;
} }
} }
} }
.select2-drop { .select2-drop,
box-shadow: $select2-drop-shadow1 0 0 1px 0, $select2-drop-shadow2 0 2px 18px 0; .select2-drop.select2-drop-above {
border-radius: $border-radius-default; box-shadow: 0 2px 4px $dropdown-shadow-color;
border: none; border-radius: $border-radius-base;
border: 1px solid $dropdown-border-color;
min-width: 175px; min-width: 175px;
color: $gl-text-color;
} }
.select2-results .select2-result-label, .select2-drop.select2-drop-above.select2-drop-active {
.select2-more-results { border-top: 1px solid $dropdown-border-color;
padding: 10px 15px; margin-top: -6px;
}
.select2-drop {
color: $gl-grayish-blue;
}
.select2-highlighted {
background: $gl-link-color !important;
} }
.select2-results li.select2-result-with-children > .select2-result-label { .select2-results li.select2-result-with-children > .select2-result-label {
...@@ -87,13 +80,11 @@ ...@@ -87,13 +80,11 @@
} }
} }
.select2-dropdown-open { .select2-dropdown-open,
.select2-dropdown-open.select2-drop-above {
.select2-choice { .select2-choice {
border-color: $border-white-normal; border-color: $gray-darkest;
outline: 0; outline: 0;
background-image: none;
background-color: $white-dark;
box-shadow: $gl-btn-active-gradient;
} }
} }
...@@ -131,28 +122,14 @@ ...@@ -131,28 +122,14 @@
} }
} }
} }
&.select2-container-active .select2-choices,
&.select2-dropdown-open .select2-choices {
border-color: $border-white-normal;
box-shadow: $gl-btn-active-gradient;
}
} }
.select2-drop-active { .select2-drop-active {
margin-top: 6px; margin-top: $dropdown-vertical-offset;
font-size: 14px; font-size: 14px;
&.select2-drop-above {
margin-bottom: 8px;
}
.select2-results { .select2-results {
max-height: 350px; max-height: 350px;
.select2-highlighted {
background: $gl-primary;
}
} }
} }
...@@ -186,19 +163,35 @@ ...@@ -186,19 +163,35 @@
background-size: 16px 16px !important; background-size: 16px 16px !important;
} }
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-ajax-error,
.select2-results .select2-selection-limit {
background: $gray-light;
display: list-item;
padding: 10px 15px;
}
.select2-results { .select2-results {
margin: 0; margin: 0;
padding: 10px 0; padding: #{$gl-padding / 2} 0;
.select2-no-results,
.select2-searching,
.select2-ajax-error,
.select2-selection-limit {
background: transparent;
padding: #{$gl-padding / 2} $gl-padding;
}
.select2-result-label,
.select2-more-results {
padding: #{$gl-padding / 2} $gl-padding;
}
.select2-highlighted {
background: transparent;
color: $gl-text-color;
.select2-result-label {
background: $dropdown-item-hover-bg;
}
}
.select2-result {
padding: 0 1px;
}
} }
.ajax-users-select { .ajax-users-select {
...@@ -265,56 +258,10 @@ ...@@ -265,56 +258,10 @@
min-width: 250px !important; min-width: 250px !important;
} }
// TODO: change global style .select2-result-selectable,
.ajax-project-dropdown, .select2-result-unselectable {
.ajax-users-dropdown,
body[data-page="projects:edit"] #select2-drop,
body[data-page="projects:new"] #select2-drop,
body[data-page="projects:merge_requests:edit"] #select2-drop,
body[data-page="projects:blob:new"] #select2-drop,
body[data-page="profiles:show"] #select2-drop,
body[data-page="admin:groups:show"] #select2-drop,
body[data-page="projects:issues:show"] #select2-drop,
body[data-page="projects:blob:edit"] #select2-drop {
&.select2-drop {
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
color: $gl-text-color;
}
&.select2-drop-above {
border-top: none;
margin-top: -4px;
}
.select2-results {
.select2-no-results,
.select2-searching,
.select2-ajax-error,
.select2-selection-limit {
background: transparent;
}
.select2-result {
padding: 0 1px;
.select2-match { .select2-match {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
text-decoration: none; text-decoration: none;
} }
.select2-result-label {
padding: #{$gl-padding / 2} $gl-padding;
}
&.select2-highlighted {
background-color: transparent !important;
color: $gl-text-color;
.select2-result-label {
background-color: $dropdown-item-hover-bg;
}
}
}
}
} }
...@@ -262,7 +262,8 @@ $well-pre-bg: #eee; ...@@ -262,7 +262,8 @@ $well-pre-bg: #eee;
$well-pre-color: #555; $well-pre-color: #555;
$loading-color: #555; $loading-color: #555;
$update-author-color: #999; $update-author-color: #999;
$user-mention-color: #2fa0bb; $user-mention-bg: rgba($blue-500, 0.044);
$user-mention-bg-hover: rgba($blue-500, 0.15);
$time-color: #999; $time-color: #999;
$project-member-show-color: #aaa; $project-member-show-color: #aaa;
$gl-promo-color: #aaa; $gl-promo-color: #aaa;
......
...@@ -9,6 +9,14 @@ ...@@ -9,6 +9,14 @@
.container-image-head { .container-image-head {
padding: 0 16px; padding: 0 16px;
line-height: 4em; line-height: 4em;
.btn-link {
padding: 0;
&:focus {
outline: none;
}
}
} }
.table.tags { .table.tags {
......
...@@ -499,22 +499,17 @@ a.deploy-project-label { ...@@ -499,22 +499,17 @@ a.deploy-project-label {
} }
} }
.fork-namespaces { .fork-thumbnail {
.row { height: 200px;
-webkit-flex-wrap: wrap; width: calc((100% / 2) - #{$gl-padding * 2});
display: -webkit-flex;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.fork-thumbnail { @media (min-width: $screen-md-min) {
border-radius: $border-radius-base; width: calc((100% / 4) - #{$gl-padding * 2});
background-color: $white-light; }
border: 1px solid $border-white-light;
height: 202px; @media (min-width: $screen-lg-min) {
margin: $gl-padding; width: calc((100% / 5) - #{$gl-padding * 2});
text-align: center; }
width: 169px;
&:hover:not(.disabled), &:hover:not(.disabled),
&.forked { &.forked {
...@@ -522,18 +517,11 @@ a.deploy-project-label { ...@@ -522,18 +517,11 @@ a.deploy-project-label {
border-color: $row-hover-border; border-color: $row-hover-border;
} }
.no-avatar { .avatar-container,
width: 100px; .identicon {
height: 100px; float: none;
background-color: $gray-light; margin-left: auto;
border: 1px solid $white-normal; margin-right: auto;
margin: 0 auto;
border-radius: 50%;
i {
font-size: 100px;
color: $white-normal;
}
} }
a { a {
...@@ -541,28 +529,23 @@ a.deploy-project-label { ...@@ -541,28 +529,23 @@ a.deploy-project-label {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding-top: $gl-padding; padding-top: $gl-padding;
color: $gl-text-color; text-decoration: none;
&.disabled { &.disabled {
opacity: .3; opacity: .3;
cursor: not-allowed; cursor: not-allowed;
&:hover {
text-decoration: none;
} }
} }
}
.caption { .fork-thumbnail-container {
min-height: 30px; display: flex;
padding: $gl-padding 0; flex-wrap: wrap;
} margin-left: -$gl-padding;
} margin-right: -$gl-padding;
img { > h5 {
border-radius: 50%; width: 100%;
max-width: 100px;
}
}
} }
} }
......
...@@ -12,3 +12,7 @@ ...@@ -12,3 +12,7 @@
margin-left: 10px; margin-left: 10px;
} }
} }
.registry-placeholder {
min-height: 60px;
}
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def index def index
set_index_vars set_index_vars
@personal_access_token = finder.build
end end
def create def create
...@@ -40,7 +41,6 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -40,7 +41,6 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def set_index_vars def set_index_vars
@scopes = Gitlab::Auth.available_scopes @scopes = Gitlab::Auth.available_scopes
@personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute @inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at) @active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
end end
......
...@@ -9,7 +9,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -9,7 +9,7 @@ class Projects::BranchesController < Projects::ApplicationController
def index def index
@sort = params[:sort].presence || sort_value_recently_updated @sort = params[:sort].presence || sort_value_recently_updated
@branches = BranchesFinder.new(@repository, params).execute @branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page]) @branches = Kaminari.paginate_array(@branches).page(params[:page])
respond_to do |format| respond_to do |format|
......
...@@ -6,17 +6,26 @@ module Projects ...@@ -6,17 +6,26 @@ module Projects
def index def index
@images = project.container_repositories @images = project.container_repositories
respond_to do |format|
format.html
format.json do
render json: ContainerRepositoriesSerializer
.new(project: project, current_user: current_user)
.represent(@images)
end
end
end end
def destroy def destroy
if image.destroy if image.destroy
redirect_to project_container_registry_index_path(@project), respond_to do |format|
status: 302, format.json { head :no_content }
notice: 'Image repository has been removed successfully!' end
else else
redirect_to project_container_registry_index_path(@project), respond_to do |format|
status: 302, format.json { head :bad_request }
alert: 'Failed to remove image repository!' end
end end
end end
......
...@@ -3,20 +3,35 @@ module Projects ...@@ -3,20 +3,35 @@ module Projects
class TagsController < ::Projects::Registry::ApplicationController class TagsController < ::Projects::Registry::ApplicationController
before_action :authorize_update_container_image!, only: [:destroy] before_action :authorize_update_container_image!, only: [:destroy]
def index
respond_to do |format|
format.json do
render json: ContainerTagsSerializer
.new(project: @project, current_user: @current_user)
.with_pagination(request, response)
.represent(tags)
end
end
end
def destroy def destroy
if tag.delete if tag.delete
redirect_to project_container_registry_index_path(@project), respond_to do |format|
status: 302, format.json { head :no_content }
notice: 'Registry tag has been removed successfully!' end
else else
redirect_to project_container_registry_index_path(@project), respond_to do |format|
status: 302, format.json { head :bad_request }
alert: 'Failed to remove registry tag!' end
end end
end end
private private
def tags
Kaminari::PaginatableArray.new(image.tags, limit: 15)
end
def image def image
@image ||= project.container_repositories @image ||= project.container_repositories
.find(params[:repository_id]) .find(params[:repository_id])
......
module EventsHelper module EventsHelper
ICON_NAMES_BY_EVENT_TYPE = { ICON_NAMES_BY_EVENT_TYPE = {
'pushed to' => 'icon_commit', 'pushed to' => 'commit',
'pushed new' => 'icon_commit', 'pushed new' => 'commit',
'created' => 'icon_status_open', 'created' => 'status_open',
'opened' => 'icon_status_open', 'opened' => 'status_open',
'closed' => 'icon_status_closed', 'closed' => 'status_closed',
'accepted' => 'icon_code_fork', 'accepted' => 'fork',
'commented on' => 'icon_comment_o', 'commented on' => 'comment',
'deleted' => 'icon_trash_o' 'deleted' => 'remove',
'imported' => 'import',
'joined' => 'users'
}.freeze }.freeze
def link_to_author(event, self_added: false) def link_to_author(event, self_added: false)
...@@ -197,7 +199,7 @@ module EventsHelper ...@@ -197,7 +199,7 @@ module EventsHelper
def icon_for_event(note) def icon_for_event(note)
icon_name = ICON_NAMES_BY_EVENT_TYPE[note] icon_name = ICON_NAMES_BY_EVENT_TYPE[note]
custom_icon(icon_name) if icon_name sprite_icon(icon_name) if icon_name
end end
def icon_for_profile_event(event) def icon_for_profile_event(event)
......
...@@ -34,6 +34,7 @@ class Key < ActiveRecord::Base ...@@ -34,6 +34,7 @@ class Key < ActiveRecord::Base
value&.delete!("\n\r") value&.delete!("\n\r")
value.strip! unless value.blank? value.strip! unless value.blank?
write_attribute(:key, value) write_attribute(:key, value)
@public_key = nil
end end
def publishable_key def publishable_key
......
...@@ -560,14 +560,20 @@ class MergeRequest < ActiveRecord::Base ...@@ -560,14 +560,20 @@ class MergeRequest < ActiveRecord::Base
commits_for_notes_limit = 100 commits_for_notes_limit = 100
commit_ids = commit_shas.take(commits_for_notes_limit) commit_ids = commit_shas.take(commits_for_notes_limit)
Note.where( commit_notes = Note
"(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" + .except(:order)
"((project_id = :source_project_id OR project_id = :target_project_id) AND noteable_type = 'Commit' AND commit_id IN (:commit_ids))", .where(project_id: [source_project_id, target_project_id])
mr_id: id, .where(noteable_type: 'Commit', commit_id: commit_ids)
commit_ids: commit_ids,
target_project_id: target_project_id, # We're using a UNION ALL here since this results in better performance
source_project_id: source_project_id # compared to using OR statements. We're using UNION ALL since the queries
) # used won't produce any duplicates (e.g. a note for a commit can't also be
# a note for an MR).
union = Gitlab::SQL::Union
.new([notes, commit_notes], remove_duplicates: false)
.to_sql
Note.from("(#{union}) #{Note.table_name}")
end end
alias_method :discussion_notes, :related_notes alias_method :discussion_notes, :related_notes
...@@ -742,10 +748,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -742,10 +748,9 @@ class MergeRequest < ActiveRecord::Base
end end
def has_ci? def has_ci?
has_ci_integration = source_project.try(:ci_service) return false if has_no_commits?
uses_gitlab_ci = all_pipelines.any?
(has_ci_integration || uses_gitlab_ci) && commits.any? !!(head_pipeline_id || all_pipelines.any? || source_project&.ci_service)
end end
def branch_missing? def branch_missing?
......
...@@ -17,6 +17,8 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -17,6 +17,8 @@ class PersonalAccessToken < ActiveRecord::Base
validates :scopes, presence: true validates :scopes, presence: true
validate :validate_scopes validate :validate_scopes
after_initialize :set_default_scopes, if: :persisted?
def revoke! def revoke!
update!(revoked: true) update!(revoked: true)
end end
...@@ -32,4 +34,8 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -32,4 +34,8 @@ class PersonalAccessToken < ActiveRecord::Base
errors.add :scopes, "can only contain available scopes" errors.add :scopes, "can only contain available scopes"
end end
end end
def set_default_scopes
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end
end end
...@@ -989,7 +989,7 @@ class Repository ...@@ -989,7 +989,7 @@ class Repository
end end
def create_ref(ref, ref_path) def create_ref(ref, ref_path)
fetch_ref(path_to_repo, ref, ref_path) raw_repository.write_ref(ref_path, ref)
end end
def ls_files(ref) def ls_files(ref)
......
...@@ -31,7 +31,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -31,7 +31,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
def remove_wip_path def remove_wip_path
if can?(current_user, :update_merge_request, merge_request.project) if work_in_progress? && can?(current_user, :update_merge_request, merge_request.project)
remove_wip_project_merge_request_path(project, merge_request) remove_wip_project_merge_request_path(project, merge_request)
end end
end end
......
class ContainerRepositoriesSerializer < BaseSerializer
entity ContainerRepositoryEntity
end
class ContainerRepositoryEntity < Grape::Entity
include RequestAwareEntity
expose :id, :path, :location
expose :tags_path do |repository|
project_registry_repository_tags_path(project, repository, format: :json)
end
expose :destroy_path, if: -> (*) { can_destroy? } do |repository|
project_container_registry_path(project, repository, format: :json)
end
private
alias_method :repository, :object
def project
request.project
end
def can_destroy?
can?(request.current_user, :update_container_image, project)
end
end
class ContainerTagEntity < Grape::Entity
include RequestAwareEntity
expose :name, :location, :revision, :total_size, :created_at
expose :destroy_path, if: -> (*) { can_destroy? } do |tag|
project_registry_repository_tag_path(project, tag.repository, tag.name, format: :json)
end
private
alias_method :tag, :object
def project
request.project
end
def can_destroy?
# TODO: We check permission against @project, not tag,
# as tag is no AR object that is attached to project
can?(request.current_user, :update_container_image, project)
end
end
class ContainerTagsSerializer < BaseSerializer
entity ContainerTagEntity
def with_pagination(request, response)
tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) }
end
def paginated?
@paginator.present?
end
def represent(resource, opts = {})
resource = @paginator.paginate(resource) if paginated?
super(resource, opts)
end
end
...@@ -23,7 +23,6 @@ class MergeRequestEntity < IssuableEntity ...@@ -23,7 +23,6 @@ class MergeRequestEntity < IssuableEntity
expose :closed_event, using: EventEntity expose :closed_event, using: EventEntity
# User entities # User entities
expose :author, using: UserEntity
expose :merge_user, using: UserEntity expose :merge_user, using: UserEntity
# Diff sha's # Diff sha's
...@@ -31,7 +30,6 @@ class MergeRequestEntity < IssuableEntity ...@@ -31,7 +30,6 @@ class MergeRequestEntity < IssuableEntity
merge_request.diff_head_sha if merge_request.diff_head_commit merge_request.diff_head_sha if merge_request.diff_head_commit
end end
expose :merge_commit_sha
expose :merge_commit_message expose :merge_commit_message
expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline
......
...@@ -37,9 +37,9 @@ ...@@ -37,9 +37,9 @@
- if content_for?(:library_javascripts) - if content_for?(:library_javascripts)
= yield :library_javascripts = yield :library_javascripts
= javascript_include_tag asset_path("locale/#{I18n.locale.to_s || I18n.default_locale.to_s}/app.js")
= webpack_bundle_tag "webpack_runtime" = webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common" = webpack_bundle_tag "common"
= webpack_bundle_tag "locale"
= webpack_bundle_tag "main" = webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled = webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test? = webpack_bundle_tag "test" if Rails.env.test?
......
...@@ -9,50 +9,36 @@ ...@@ -9,50 +9,36 @@
%br %br
Forking a repository allows you to make changes without affecting the original project. Forking a repository allows you to make changes without affecting the original project.
.col-lg-9 .col-lg-9
.fork-namespaces
- if @namespaces.present? - if @namespaces.present?
%label.label-light .fork-thumbnail-container.js-fork-content
%span %h5.prepend-top-0.append-bottom-0.prepend-left-default.append-right-default
Click to fork the project Click to fork the project
- @namespaces.in_groups_of(6, false) do |group| - @namespaces.each do |namespace|
.row
- group.each do |namespace|
- avatar = namespace_icon(namespace, 100) - avatar = namespace_icon(namespace, 100)
- if fork = namespace.find_fork_of(@project)
.fork-thumbnail.forked
= link_to project_path(fork) do
- if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar
= icon 'question'
- else
= image_tag avatar
.caption
= namespace.human_name
- else
- can_create_project = current_user.can?(:create_projects, namespace) - can_create_project = current_user.can?(:create_projects, namespace)
.fork-thumbnail{ class: ("disabled" unless can_create_project) } - forked_project = namespace.find_fork_of(@project)
= link_to project_forks_path(@project, namespace_key: namespace.id), - fork_path = forked_project ? project_path(forked_project) : project_forks_path(@project, namespace_key: namespace.id)
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default{ class: [("disabled" unless can_create_project), ("forked" if forked_project)] }
= link_to fork_path,
method: "POST", method: "POST",
class: ("disabled has-tooltip" unless can_create_project), class: [("js-fork-thumbnail" unless forked_project), ("disabled has-tooltip" unless can_create_project)],
title: (_('You have reached your project limit') unless can_create_project) do title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar) - if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar = project_identicon(namespace, class: "avatar s100 identicon")
= icon 'question'
- else - else
= image_tag avatar .avatar-container.s100
.caption = image_tag(avatar, class: "avatar s100")
%h5.prepend-top-default
= namespace.human_name = namespace.human_name
- else - else
%label.label-light %strong
%span
No available namespaces to fork the project. No available namespaces to fork the project.
%br %p.prepend-top-default
%small
You must have permission to create a project in a namespace before forking. You must have permission to create a project in a namespace before forking.
.save-project-loader.hide .save-project-loader.hide.js-fork-content
.center %h2.text-center
%h2 = icon('spinner spin')
%i.fa.fa-spinner.fa-spin
Forking repository Forking repository
%p Please wait a moment, this page will automatically refresh when ready. %p.text-center
Please wait a moment, this page will automatically refresh when ready.
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
%h2.merge-requests-title %h2.merge-requests-title
= pluralize(@merge_requests.count, 'Related Merge Request') = pluralize(@merge_requests.count, 'Related Merge Request')
%ul.unstyled-list.related-merge-requests %ul.unstyled-list.related-merge-requests
- has_any_ci = @merge_requests.any?(&:head_pipeline) - has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
- @merge_requests.each do |merge_request| - @merge_requests.each do |merge_request|
%li %li
%span.merge-request-ci-status %span.merge-request-ci-status
- if merge_request.head_pipeline - if merge_request.head_pipeline
= render_pipeline_status(merge_request.head_pipeline) = render_pipeline_status(merge_request.head_pipeline)
- elsif has_any_ci - elsif has_any_head_pipeline
= icon('blank fw') = icon('blank fw')
%span.merge-request-id %span.merge-request-id
= merge_request.to_reference = merge_request.to_reference
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
- unless current_user == @merge_request.author - unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request)) %li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request - if can_update_merge_request
%li{ class: merge_request_button_visibility(@merge_request, true) } %li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
%li{ class: merge_request_button_visibility(@merge_request, false) } %li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
......
.container-image.js-toggle-container
.container-image-head
= link_to "#", class: "js-toggle-button" do
= icon('chevron-down', 'aria-hidden': 'true')
= escape_once(image.path)
= clipboard_button(clipboard_text: "docker pull #{image.location}")
- if can?(current_user, :update_container_image, @project)
.controls.hidden-xs.pull-right
= link_to project_container_registry_path(@project, image),
class: 'btn btn-remove has-tooltip',
title: 'Remove repository',
data: { confirm: 'Are you sure?' },
method: :delete do
= icon('trash cred', 'aria-hidden': 'true')
.container-image-tags.js-toggle-content.hide
- if image.has_tags?
.table-holder
%table.table.tags
%thead
%tr
%th Tag
%th Tag ID
%th Size
%th Created
- if can?(current_user, :update_container_image, @project)
%th
= render partial: 'tag', collection: image.tags
- else
.nothing-here-block No tags in Container Registry for this container image.
- page_title "Container Registry" - page_title "Container Registry"
.row.prepend-top-default.append-bottom-default %section
.col-lg-3 .settings-header
%h4.prepend-top-0 %h4
= page_title = page_title
%p %p
With the Docker Container Registry integrated into GitLab, every project = s_('ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images.')
can have its own space to store its Docker images.
%p.append-bottom-0 %p.append-bottom-0
= succeed '.' do = succeed '.' do
Learn more about = s_('ContainerRegistry|Learn more about')
= link_to 'Container Registry', help_page_path('user/project/container_registry'), target: '_blank' = link_to _('Container Registry'), help_page_path('user/project/container_registry'), target: '_blank'
.row.registry-placeholder.prepend-bottom-10
.col-lg-12
#js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json) } }
.col-lg-9 = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('registry_list')
.row.prepend-top-10
.col-lg-12
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h4.panel-title %h4.panel-title
How to use the Container Registry = s_('ContainerRegistry|How to use the Container Registry')
.panel-body .panel-body
%p %p
First log in to GitLab&rsquo;s Container Registry using your GitLab username - link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank')
and password. If you have - link_2fa = link_to(_('2FA enabled'), help_page_path('user/profile/account/two_factor_authentication'), target: '_blank')
= link_to '2FA enabled', help_page_path('user/profile/account/two_factor_authentication'), target: '_blank' = s_('ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:').html_safe % { link_2fa: link_2fa, link_token: link_token }
you need to use a
= succeed ':' do
= link_to 'personal access token', help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank'
%pre %pre
docker login #{Gitlab.config.registry.host_port} docker login #{Gitlab.config.registry.host_port}
%br %br
%p %p
Once you log in, you&rsquo;re free to create and upload a container image = s_('ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands').html_safe % { build: "<code>build</code>".html_safe, push: "<code>push</code>".html_safe }
using the common
%code build
and
%code push
commands:
%pre %pre
:plain :plain
docker build -t #{escape_once(@project.container_registry_url)} . docker build -t #{escape_once(@project.container_registry_url)} .
docker push #{escape_once(@project.container_registry_url)} docker push #{escape_once(@project.container_registry_url)}
%hr %hr
%h5.prepend-top-default %h5.prepend-top-default
Use different image names = s_('ContainerRegistry|Use different image names')
%p.light %p.light
GitLab supports up to 3 levels of image names. The following = s_('ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:')
examples of images are valid for your project:
%pre %pre
:plain :plain
#{escape_once(@project.container_registry_url)}:tag #{escape_once(@project.container_registry_url)}:tag
#{escape_once(@project.container_registry_url)}/optional-image-name:tag #{escape_once(@project.container_registry_url)}/optional-image-name:tag
#{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag #{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag
- if @images.blank?
%p.settings-message.text-center.append-bottom-default
No container images stored for this project. Add one by following the
instructions above.
- else
= render partial: 'image', collection: @images
...@@ -2,12 +2,11 @@ ...@@ -2,12 +2,11 @@
- release = @releases.find { |release| release.tag == tag.name } - release = @releases.find { |release| release.tag == tag.name }
%li.flex-row %li.flex-row
.row-main-content.str-truncated .row-main-content.str-truncated
= link_to project_tag_path(@project, tag.name), class: 'item-title ref-name' do
= icon('tag') = icon('tag')
= tag.name = link_to tag.name, project_tag_path(@project, tag.name), class: 'item-title ref-name prepend-left-4'
- if protected_tag?(@project, tag) - if protected_tag?(@project, tag)
%span.label.label-success %span.label.label-success.prepend-left-4
protected protected
- if tag.message.present? - if tag.message.present?
......
- type = impersonation ? "impersonation" : "personal access" - type = impersonation ? "impersonation" : "personal access"
%h5.prepend-top-0 %h5.prepend-top-0
Add a #{type} Token Add a #{type} token
%p.profile-settings-content %p.profile-settings-content
Pick a name for the application, and we'll give you a unique #{type} Token. Pick a name for the application, and we'll give you a unique #{type} token.
= form_for token, url: path, method: :post, html: { class: 'js-requires-input' } do |f| = form_for token, url: path, method: :post, html: { class: 'js-requires-input' } do |f|
......
---
title: "Add missing space in Sidekiq memory killer log message"
merge_request: 14553
author: Benjamin Drung
type: fixed
---
title: Fix the default branches sorting to actually be 'Last updated'
merge_request: 14295
author:
type: fixed
---
title: Re-arrange <script> tags before <template> tags in .vue files
merge_request: 14671
author:
type: changed
---
title: Hide close MR button after merge without reloading page
merge_request: 14122
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: fix merge request widget status icon for failed CI
merge_request:
author:
type: fixed
---
title: Use explicit boolean true attribute for show-disabled-button in Vue files
merge_request: 14672
author:
type: fixed
---
title: Set default scope on PATs that don't have one set to allow them to be revoked
merge_request:
author:
type: fixed
---
title: Add link to OpenID Connect documentation
merge_request: 14368
author: Markus Koller
type: other
---
title: Fix edit project service cancel button position
merge_request: 14596
author: Matt Coleman
type: fixed
---
title: Makes @mentions links have a different styling for better separation
merge_request:
author:
type: added
---
title: Use a UNION ALL for getting merge request notes
merge_request:
author:
type: other
---
title: Adjusts tag link to avoid underlining spaces
merge_request: 14544
author: Guilherme Vieira
type: fixed
...@@ -105,6 +105,7 @@ module Gitlab ...@@ -105,6 +105,7 @@ module Gitlab
config.assets.precompile << "lib/ace.js" config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*" config.assets.precompile << "vendor/assets/fonts/*"
config.assets.precompile << "test.css" config.assets.precompile << "test.css"
config.assets.precompile << "locale/**/app.js"
# Version of your assets, change this if you want to expire all your assets # Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0' config.assets.version = '1.0'
......
...@@ -499,6 +499,8 @@ production: &base ...@@ -499,6 +499,8 @@ production: &base
# Gitaly settings # Gitaly settings
gitaly: gitaly:
# Path to the directory containing Gitaly client executables.
client_path: /home/git/gitaly
# Default Gitaly authentication token. Can be overriden per storage. Can # Default Gitaly authentication token. Can be overriden per storage. Can
# be left blank when Gitaly is running locally on a Unix socket, which # be left blank when Gitaly is running locally on a Unix socket, which
# is the normal way to deploy Gitaly. # is the normal way to deploy Gitaly.
...@@ -664,7 +666,7 @@ test: ...@@ -664,7 +666,7 @@ test:
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
gitaly: gitaly:
enabled: true client_path: tmp/tests/gitaly
token: secret token: secret
backup: backup:
path: tmp/tests/backups path: tmp/tests/backups
......
...@@ -39,3 +39,17 @@ module GettextI18nRailsJs ...@@ -39,3 +39,17 @@ module GettextI18nRailsJs
end end
end end
end end
class PoToJson
# This is required to modify the JS locale file output to our import needs
# Overwrites: https://github.com/webhippie/po_to_json/blob/master/lib/po_to_json.rb#L46
def generate_for_jed(language, overwrite = {})
@options = parse_options(overwrite.merge(language: language))
@parsed ||= inject_meta(parse_document)
generated = build_json_for(build_jed_for(@parsed))
[
"window.translations = #{generated};"
].join(" ")
end
end
...@@ -281,7 +281,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -281,7 +281,7 @@ constraints(ProjectUrlConstrainer.new) do
namespace :registry do namespace :registry do
resources :repository, only: [] do resources :repository, only: [] do
resources :tags, only: [:destroy], resources :tags, only: [:index, :destroy],
constraints: { id: Gitlab::Regex.container_registry_tag_regex } constraints: { id: Gitlab::Regex.container_registry_tag_regex }
end end
end end
......
...@@ -68,6 +68,7 @@ var config = { ...@@ -68,6 +68,7 @@ var config = {
prometheus_metrics: './prometheus_metrics', prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches', protected_branches: './protected_branches',
protected_tags: './protected_tags', protected_tags: './protected_tags',
registry_list: './registry/index.js',
repo: './repo/index.js', repo: './repo/index.js',
sidebar: './sidebar/sidebar_bundle.js', sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js', schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
...@@ -121,10 +122,6 @@ var config = { ...@@ -121,10 +122,6 @@ var config = {
name: '[name].[hash].[ext]', name: '[name].[hash].[ext]',
} }
}, },
{
test: /locale\/\w+\/(.*)\.js$/,
loader: 'exports-loader?locales',
},
{ {
test: /monaco-editor\/\w+\/vs\/loader\.js$/, test: /monaco-editor\/\w+\/vs\/loader\.js$/,
use: [ use: [
...@@ -200,6 +197,7 @@ var config = { ...@@ -200,6 +197,7 @@ var config = {
'pdf_viewer', 'pdf_viewer',
'pipelines', 'pipelines',
'pipelines_details', 'pipelines_details',
'registry_list',
'repo', 'repo',
'schedule_form', 'schedule_form',
'schedules_index', 'schedules_index',
...@@ -222,7 +220,7 @@ var config = { ...@@ -222,7 +220,7 @@ var config = {
// create cacheable common library bundles // create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'locale', 'common', 'webpack_runtime'], names: ['main', 'common', 'webpack_runtime'],
}), }),
// enable scope hoisting // enable scope hoisting
......
...@@ -7,11 +7,13 @@ class AddFastForwardOptionToProject < ActiveRecord::Migration ...@@ -7,11 +7,13 @@ class AddFastForwardOptionToProject < ActiveRecord::Migration
disable_ddl_transaction! disable_ddl_transaction!
def add def up
add_column_with_default(:projects, :merge_requests_ff_only_enabled, :boolean, default: false) add_column_with_default(:projects, :merge_requests_ff_only_enabled, :boolean, default: false)
end end
def down def down
if column_exists?(:projects, :merge_requests_ff_only_enabled)
remove_column(:projects, :merge_requests_ff_only_enabled) remove_column(:projects, :merge_requests_ff_only_enabled)
end end
end
end end
# rubocop:disable all
class MakeSureFastForwardOptionExists < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
# We had to fix the migration db/migrate/20150827121444_add_fast_forward_option_to_project.rb
# And this is why it's possible that someone has ran the migrations but does
# not have the merge_requests_ff_only_enabled column. This migration makes sure it will
# be added
unless column_exists?(:projects, :merge_requests_ff_only_enabled)
add_column_with_default(:projects, :merge_requests_ff_only_enabled, :boolean, default: false)
end
end
def down
if column_exists?(:projects, :merge_requests_ff_only_enabled)
remove_column(:projects, :merge_requests_ff_only_enabled)
end
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: 20170928100231) do ActiveRecord::Schema.define(version: 20171004121444) 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"
......
...@@ -121,6 +121,15 @@ GET /projects/:id/merge_requests?labels=bug,reproduced ...@@ -121,6 +121,15 @@ GET /projects/:id/merge_requests?labels=bug,reproduced
GET /projects/:id/merge_requests?my_reaction_emoji=star GET /projects/:id/merge_requests?my_reaction_emoji=star
``` ```
`project_id` represents the ID of the project where the MR resides.
`project_id` will always equal `target_project_id`.
In the case of a merge request from the same project,
`source_project_id`, `target_project_id` and `project_id`
will be the same. In the case of a merge request from a fork,
`target_project_id` and `project_id` will be the same and
`source_project_id` will be the fork project's ID.
Parameters: Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
......
## Enable or disable GitLab CI ## Enable or disable GitLab CI/CD
_To effectively use GitLab CI, you need a valid [`.gitlab-ci.yml`](yaml/README.md) To effectively use GitLab CI/CD, you need a valid [`.gitlab-ci.yml`](yaml/README.md)
file present at the root directory of your project and a file present at the root directory of your project and a
[runner](runners/README.md) properly set up. You can read our [runner](runners/README.md) properly set up. You can read our
[quick start guide](quick_start/README.md) to get you started._ [quick start guide](quick_start/README.md) to get you started.
If you are using an external CI server like Jenkins or Drone CI, it is advised If you are using an external CI/CD server like Jenkins or Drone CI, it is advised
to disable GitLab CI in order to not have any conflicts with the commits status to disable GitLab CI/CD in order to not have any conflicts with the commits status
API. API.
--- ---
GitLab CI is exposed via the `/pipelines` and `/builds` pages of a project. GitLab CI/CD is exposed via the `/pipelines` and `/jobs` pages of a project.
Disabling GitLab CI in a project does not delete any previous jobs. Disabling GitLab CI/CD in a project does not delete any previous jobs.
In fact, the `/pipelines` and `/builds` pages can still be accessed, although In fact, the `/pipelines` and `/jobs` pages can still be accessed, although
it's hidden from the left sidebar menu. it's hidden from the left sidebar menu.
GitLab CI is enabled by default on new installations and can be disabled either GitLab CI/CD is enabled by default on new installations and can be disabled either
individually under each project's settings, or site-wide by modifying the individually under each project's settings, or site-wide by modifying the
settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations
respectively. respectively.
### Per-project user setting ### Per-project user setting
The setting to enable or disable GitLab CI can be found with the name **Pipelines** The setting to enable or disable GitLab CI/CD can be found under your project's
under the **Sharing & Permissions** area of a project's settings along with **Settings > General > Permissions**. Choose one of "Disabled", "Only team members"
**Merge Requests**. Choose one of **Disabled**, **Only team members** and or "Everyone with access" and hit **Save changes** for the settings to take effect.
**Everyone with access** and hit **Save changes** for the settings to take effect.
![Sharing & Permissions settings](img/permissions_settings.png) ![Sharing & Permissions settings](../user/project/settings/img/sharing_and_permissions_settings.png)
--- ### Site-wide admin setting
### Site-wide administrator setting
You can disable GitLab CI site-wide, by modifying the settings in `gitlab.yml` You can disable GitLab CI/CD site-wide, by modifying the settings in `gitlab.yml`
and `gitlab.rb` for source and Omnibus installations respectively. and `gitlab.rb` for source and Omnibus installations respectively.
Two things to note: Two things to note:
1. Disabling GitLab CI, will affect only newly-created projects. Projects that 1. Disabling GitLab CI/CD, will affect only newly-created projects. Projects that
had it enabled prior to this modification, will work as before. had it enabled prior to this modification, will work as before.
1. Even if you disable GitLab CI, users will still be able to enable it in the 1. Even if you disable GitLab CI/CD, users will still be able to enable it in the
project's settings. project's settings.
---
For installations from source, open `gitlab.yml` with your editor and set For installations from source, open `gitlab.yml` with your editor and set
`builds` to `false`: `builds` to `false`:
......
...@@ -26,7 +26,7 @@ so every environment can have one or more deployments. GitLab keeps track of ...@@ -26,7 +26,7 @@ so every environment can have one or more deployments. GitLab keeps track of
your deployments, so you always know what is currently being deployed on your your deployments, so you always know what is currently being deployed on your
servers. If you have a deployment service such as [Kubernetes][kubernetes-service] servers. If you have a deployment service such as [Kubernetes][kubernetes-service]
enabled for your project, you can use it to assist with your deployments, and enabled for your project, you can use it to assist with your deployments, and
can even access a web terminal for your environment from within GitLab! can even access a [web terminal](#web-terminals) for your environment from within GitLab!
To better understand how environments and deployments work, let's consider an To better understand how environments and deployments work, let's consider an
example. We assume that you have already created a project in GitLab and set up example. We assume that you have already created a project in GitLab and set up
...@@ -119,7 +119,7 @@ where you can find information of the last deployment status of an environment. ...@@ -119,7 +119,7 @@ where you can find information of the last deployment status of an environment.
Here's how the Environments page looks so far. Here's how the Environments page looks so far.
![Staging environment view](img/environments_available_staging.png) ![Environment view](img/environments_available.png)
There's a bunch of information there, specifically you can see: There's a bunch of information there, specifically you can see:
...@@ -229,7 +229,7 @@ You can find it in the pipeline, job, environment, and deployment views. ...@@ -229,7 +229,7 @@ You can find it in the pipeline, job, environment, and deployment views.
| Pipelines | Single pipeline | Environments | Deployments | jobs | | Pipelines | Single pipeline | Environments | Deployments | jobs |
| --------- | ----------------| ------------ | ----------- | -------| | --------- | ----------------| ------------ | ----------- | -------|
| ![Pipelines manual action](img/environments_manual_action_pipelines.png) | ![Pipelines manual action](img/environments_manual_action_single_pipeline.png) | ![Environments manual action](img/environments_manual_action_environments.png) | ![Deployments manual action](img/environments_manual_action_deployments.png) | ![Builds manual action](img/environments_manual_action_builds.png) | | ![Pipelines manual action](img/environments_manual_action_pipelines.png) | ![Pipelines manual action](img/environments_manual_action_single_pipeline.png) | ![Environments manual action](img/environments_manual_action_environments.png) | ![Deployments manual action](img/environments_manual_action_deployments.png) | ![Builds manual action](img/environments_manual_action_jobs.png) |
Clicking on the play button in either of these places will trigger the Clicking on the play button in either of these places will trigger the
`deploy_prod` job, and the deployment will be recorded under a new `deploy_prod` job, and the deployment will be recorded under a new
...@@ -402,7 +402,7 @@ places within GitLab. ...@@ -402,7 +402,7 @@ places within GitLab.
| In a merge request widget as a link | In the Environments view as a button | In the Deployments view as a button | | In a merge request widget as a link | In the Environments view as a button | In the Deployments view as a button |
| -------------------- | ------------ | ----------- | | -------------------- | ------------ | ----------- |
| ![Environment URL in merge request](img/environments_mr_review_app.png) | ![Environment URL in environments](img/environments_link_url.png) | ![Environment URL in deployments](img/environments_link_url_deployments.png) | | ![Environment URL in merge request](img/environments_mr_review_app.png) | ![Environment URL in environments](img/environments_available.png) | ![Environment URL in deployments](img/deployments_view.png) |
If a merge request is eventually merged to the default branch (in our case If a merge request is eventually merged to the default branch (in our case
`master`) and that branch also deploys to an environment (in our case `staging` `master`) and that branch also deploys to an environment (in our case `staging`
...@@ -574,7 +574,7 @@ Once configured, GitLab will attempt to retrieve [supported performance metrics] ...@@ -574,7 +574,7 @@ Once configured, GitLab will attempt to retrieve [supported performance metrics]
environment which has had a successful deployment. If monitoring data was environment which has had a successful deployment. If monitoring data was
successfully retrieved, a Monitoring button will appear for each environment. successfully retrieved, a Monitoring button will appear for each environment.
![Environment Detail with Metrics](img/prometheus_environment_detail_with_metrics.png) ![Environment Detail with Metrics](img/deployments_view.png)
Clicking on the Monitoring button will display a new page, showing up to the last Clicking on the Monitoring button will display a new page, showing up to the last
8 hours of performance data. It may take a minute or two for data to appear 8 hours of performance data. It may take a minute or two for data to appear
...@@ -593,10 +593,11 @@ Web terminals were added in GitLab 8.15 and are only available to project ...@@ -593,10 +593,11 @@ Web terminals were added in GitLab 8.15 and are only available to project
masters and owners. masters and owners.
If you deploy to your environments with the help of a deployment service (e.g., If you deploy to your environments with the help of a deployment service (e.g.,
the [Kubernetes service][kubernetes-service], GitLab can open the [Kubernetes service][kubernetes-service]), GitLab can open
a terminal session to your environment! This is a very powerful feature that a terminal session to your environment! This is a very powerful feature that
allows you to debug issues without leaving the comfort of your web browser. To allows you to debug issues without leaving the comfort of your web browser. To
enable it, just follow the instructions given in the service documentation. enable it, just follow the instructions given in the service integration
documentation.
Once enabled, your environments will gain a "terminal" button: Once enabled, your environments will gain a "terminal" button:
......
doc/ci/img/deployments_view.png

19.5 KB | W: | H:

doc/ci/img/deployments_view.png

59.7 KB | W: | H:

doc/ci/img/deployments_view.png
doc/ci/img/deployments_view.png
doc/ci/img/deployments_view.png
doc/ci/img/deployments_view.png
  • 2-up
  • Swipe
  • Onion skin
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