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

This commit is contained in:
Shinya Maeda 2017-10-05 14:49:16 +09:00
commit 88cc9d5294
215 changed files with 2745 additions and 965 deletions

View file

@ -5,3 +5,4 @@ app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb

View file

@ -195,6 +195,10 @@ entry.
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
- [BUGIFX] Improves subgroup creation permissions. !13418
## 9.5.7 (2017-10-03)
- Fix gitlab rake:import:repos task.
## 9.5.6 (2017-09-29)
- [FIXED] Fix MR ready to merge buttons/controls at mobile breakpoint. !14242

View file

@ -910,7 +910,7 @@ GEM
json (>= 1.8.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unf_ext (0.0.7.4)
unicode-display_width (1.3.0)
unicorn (5.1.0)
kgio (~> 2.6)

View file

@ -1,30 +1,12 @@
import Jed from 'jed';
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 lang = (langAttribute || 'en').replace(/-/g, '_');
const locale = new Jed(locales[lang]);
const locale = new Jed(window.translations || {});
/**
Translates `text`
@param text The text to be translated
@returns {String} The translated text
**/

View file

@ -127,6 +127,21 @@ import IssuablesHelper from './helpers/issuables_helper';
$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;
})();
}).call(window);

View file

@ -1,18 +1,3 @@
<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>
import CodeCell from './code/index.vue';
import OutputCell from './output/index.vue';
@ -51,6 +36,21 @@ export default {
};
</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>
.cell {
flex-direction: column;

View file

@ -1,17 +1,3 @@
<template>
<div :class="type">
<prompt
:type="promptType"
:count="count" />
<pre
class="language-python"
:class="codeCssClass"
ref="code"
v-text="code">
</pre>
</div>
</template>
<script>
import Prism from '../../lib/highlight';
import Prompt from '../prompt.vue';
@ -55,3 +41,17 @@
},
};
</script>
<template>
<div :class="type">
<prompt
:type="promptType"
:count="count" />
<pre
class="language-python"
:class="codeCssClass"
ref="code"
v-text="code">
</pre>
</div>
</template>

View file

@ -1,10 +1,3 @@
<template>
<div class="cell text-cell">
<prompt />
<div class="markdown" v-html="markdown"></div>
</div>
</template>
<script>
/* global katex */
import marked from 'marked';
@ -95,6 +88,13 @@
};
</script>
<template>
<div class="cell text-cell">
<prompt />
<div class="markdown" v-html="markdown"></div>
</div>
</template>
<style>
.markdown .katex {
display: block;

View file

@ -1,10 +1,3 @@
<template>
<div class="output">
<prompt />
<div v-html="rawCode"></div>
</div>
</template>
<script>
import Prompt from '../prompt.vue';
@ -20,3 +13,10 @@ export default {
},
};
</script>
<template>
<div class="output">
<prompt />
<div v-html="rawCode"></div>
</div>
</template>

View file

@ -1,11 +1,3 @@
<template>
<div class="output">
<prompt />
<img
:src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>
<script>
import Prompt from '../prompt.vue';
@ -25,3 +17,11 @@ export default {
},
};
</script>
<template>
<div class="output">
<prompt />
<img
:src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>

View file

@ -1,12 +1,3 @@
<template>
<component :is="componentName"
type="output"
:outputType="outputType"
:count="count"
:raw-code="rawCode"
:code-css-class="codeCssClass" />
</template>
<script>
import CodeCell from '../code/index.vue';
import Html from './html.vue';
@ -81,3 +72,12 @@ export default {
},
};
</script>
<template>
<component :is="componentName"
type="output"
:outputType="outputType"
:count="count"
:raw-code="rawCode"
:code-css-class="codeCssClass" />
</template>

View file

@ -1,11 +1,3 @@
<template>
<div class="prompt">
<span v-if="type && count">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<script>
export default {
props: {
@ -21,6 +13,14 @@
};
</script>
<template>
<div class="prompt">
<span v-if="type && count">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<style scoped>
.prompt {
padding: 0 10px;

View file

@ -1,14 +1,3 @@
<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>
import {
MarkdownCell,
@ -59,6 +48,17 @@
};
</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>
.cell,
.input,

View file

@ -272,6 +272,7 @@
v-model="note"
ref="textarea"
slot="textarea"
:disabled="isSubmitting"
placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()">

View file

@ -1,13 +1,3 @@
<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>
import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min';
@ -64,6 +54,16 @@
};
</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>
.pdf-viewer {
background: url('./assets/img/bg.gif');

View file

@ -1,10 +1,3 @@
<template>
<canvas
class="pdf-page"
ref="canvas"
:data-page="number" />
</template>
<script>
export default {
props: {
@ -48,6 +41,13 @@
};
</script>
<template>
<canvas
class="pdf-page"
ref="canvas"
:data-page="number" />
</template>
<style>
.pdf-page {
margin: 8px auto 0 auto;

View file

@ -1,8 +1,7 @@
export default () => {
$('.fork-thumbnail a').on('click', function forkThumbnailClicked() {
$('.js-fork-thumbnail').on('click', function forkThumbnailClicked() {
if ($(this).hasClass('disabled')) return false;
$('.fork-namespaces').hide();
return $('.save-project-loader').show();
return $('.js-fork-content').toggle();
});
};

View file

@ -0,0 +1,62 @@
<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>

View file

@ -0,0 +1,131 @@
<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>

View file

@ -0,0 +1,137 @@
<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>

View file

@ -0,0 +1,15 @@
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.'),
};

View file

@ -0,0 +1,25 @@
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,
},
});
},
}));

View file

@ -0,0 +1,39 @@
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);

View file

@ -0,0 +1,2 @@
export const isLoading = state => state.isLoading;
export const repos = state => state.repos;

View file

@ -0,0 +1,39 @@
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,
});

View file

@ -0,0 +1,7 @@
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';

View file

@ -0,0 +1,54 @@
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;
},
};

View file

@ -7,7 +7,7 @@ export default {
},
template: `
<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">
<span class="bold">
Checking ability to merge automatically

View file

@ -12,7 +12,7 @@ export default {
<div class="mr-widget-body media">
<status-icon
status="failed"
showDisabledButton />
:show-disabled-button="true" />
<div class="media-body space-children">
<span
v-if="mr.shouldBeRebased"

View file

@ -51,7 +51,7 @@ export default {
</span>
</template>
<template v-else>
<status-icon status="failed" showDisabledButton />
<status-icon status="failed" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
<span

View file

@ -24,7 +24,7 @@ export default {
},
template: `
<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">
<span class="bold js-branch-text">
<span class="capitalize">

View file

@ -7,7 +7,7 @@ export default {
},
template: `
<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">
<span class="bold">
Ready to be merged automatically.

View file

@ -7,7 +7,7 @@ export default {
},
template: `
<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">
<span class="bold">
Pipeline blocked. The pipeline for this merge request requires a manual action to proceed

View file

@ -7,7 +7,7 @@ export default {
},
template: `
<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">
<span class="bold">
The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure

View file

@ -38,24 +38,40 @@ export default {
return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
},
status() {
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
if (hasCI && !ciStatus) {
return 'failed';
} else if (!pipeline) {
return 'success';
} else if (isPipelineActive) {
return 'pending';
} 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`;
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
if (hasCI && !ciStatus) {
if (this.status === 'failed') {
return failedClass;
} else if (!pipeline) {
return defaultClass;
} else if (isPipelineActive) {
} else if (this.status === 'pending') {
return inActionClass;
} else if (isPipelineFailed) {
return failedClass;
}
return defaultClass;
},
iconClass() {
if (this.status === 'failed' || !this.commitMessage.length || !this.isMergeAllowed() || this.mr.preventMerge) {
return 'failed';
}
return 'success';
},
mergeButtonText() {
if (this.isMergingImmediately) {
return 'Merge in progress';
@ -156,6 +172,7 @@ export default {
eventHub.$emit('FetchActionsContent');
if (window.mergeRequest) {
window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged');
window.mergeRequest.hideCloseButton();
window.mergeRequest.decreaseCounter();
}
stopPolling();
@ -208,7 +225,7 @@ export default {
},
template: `
<div class="mr-widget-body media">
<status-icon status="success" />
<status-icon :status="iconClass" />
<div class="media-body">
<div class="mr-widget-body-controls media space-children">
<span class="btn-group append-bottom-5">

View file

@ -7,7 +7,7 @@ export default {
},
template: `
<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">
<span class="bold">
The source branch HEAD has recently changed. Please reload the page and review the changes before merging

View file

@ -10,7 +10,7 @@ export default {
},
template: `
<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">
<span class="bold">
There are unresolved discussions. Please resolve these discussions

View file

@ -38,7 +38,7 @@ export default {
},
template: `
<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">
<span class="bold">
This is a Work in Progress

View file

@ -0,0 +1,32 @@
<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>

View file

@ -23,6 +23,7 @@
&.s60 { @include avatar-size(60px, 12px); }
&.s70 { @include avatar-size(70px, 14px); }
&.s90 { @include avatar-size(90px, 15px); }
&.s100 { @include avatar-size(100px, 15px); }
&.s110 { @include avatar-size(110px, 15px); }
&.s140 { @include avatar-size(140px, 15px); }
&.s160 { @include avatar-size(160px, 20px); }
@ -78,6 +79,7 @@
&.s60 { font-size: 32px; line-height: 58px; }
&.s70 { font-size: 34px; line-height: 70px; }
&.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; }
&.s140 { font-size: 72px; line-height: 138px; }
&.s160 { font-size: 96px; line-height: 158px; }

View file

@ -11,6 +11,7 @@
.prepend-top-10 { margin-top: 10px; }
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px; }
.prepend-left-4 { margin-left: 4px; }
.prepend-left-5 { margin-left: 5px; }
.prepend-left-10 { margin-left: 10px; }
.prepend-left-default { margin-left: $gl-padding; }
@ -129,11 +130,6 @@ span.update-author {
}
}
.user-mention {
color: $user-mention-color;
font-weight: $gl-font-weight-bold;
}
.field_with_errors {
display: inline;
}

View file

@ -6,3 +6,14 @@
.gfm-commit_range {
@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;
}
}

View file

@ -48,31 +48,24 @@
}
&:hover {
background-color: $white-normal;
border-color: $border-white-normal;
border-color: $gray-darkest;
color: $gl-text-color;
}
}
}
.select2-drop {
box-shadow: $select2-drop-shadow1 0 0 1px 0, $select2-drop-shadow2 0 2px 18px 0;
border-radius: $border-radius-default;
border: none;
.select2-drop,
.select2-drop.select2-drop-above {
box-shadow: 0 2px 4px $dropdown-shadow-color;
border-radius: $border-radius-base;
border: 1px solid $dropdown-border-color;
min-width: 175px;
color: $gl-text-color;
}
.select2-results .select2-result-label,
.select2-more-results {
padding: 10px 15px;
}
.select2-drop {
color: $gl-grayish-blue;
}
.select2-highlighted {
background: $gl-link-color !important;
.select2-drop.select2-drop-above.select2-drop-active {
border-top: 1px solid $dropdown-border-color;
margin-top: -6px;
}
.select2-results li.select2-result-with-children > .select2-result-label {
@ -87,13 +80,11 @@
}
}
.select2-dropdown-open {
.select2-dropdown-open,
.select2-dropdown-open.select2-drop-above {
.select2-choice {
border-color: $border-white-normal;
border-color: $gray-darkest;
outline: 0;
background-image: none;
background-color: $white-dark;
box-shadow: $gl-btn-active-gradient;
}
}
@ -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 {
margin-top: 6px;
margin-top: $dropdown-vertical-offset;
font-size: 14px;
&.select2-drop-above {
margin-bottom: 8px;
}
.select2-results {
max-height: 350px;
.select2-highlighted {
background: $gl-primary;
}
}
}
@ -186,19 +163,35 @@
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 {
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 {
@ -265,56 +258,10 @@
min-width: 250px !important;
}
// TODO: change global style
.ajax-project-dropdown,
.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 {
font-weight: $gl-font-weight-bold;
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;
}
}
}
.select2-result-selectable,
.select2-result-unselectable {
.select2-match {
font-weight: $gl-font-weight-bold;
text-decoration: none;
}
}

View file

@ -262,7 +262,8 @@ $well-pre-bg: #eee;
$well-pre-color: #555;
$loading-color: #555;
$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;
$project-member-show-color: #aaa;
$gl-promo-color: #aaa;

View file

@ -9,6 +9,14 @@
.container-image-head {
padding: 0 16px;
line-height: 4em;
.btn-link {
padding: 0;
&:focus {
outline: none;
}
}
}
.table.tags {

View file

@ -499,73 +499,56 @@ a.deploy-project-label {
}
}
.fork-namespaces {
.row {
-webkit-flex-wrap: wrap;
display: -webkit-flex;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.fork-thumbnail {
height: 200px;
width: calc((100% / 2) - #{$gl-padding * 2});
.fork-thumbnail {
border-radius: $border-radius-base;
background-color: $white-light;
border: 1px solid $border-white-light;
height: 202px;
margin: $gl-padding;
text-align: center;
width: 169px;
@media (min-width: $screen-md-min) {
width: calc((100% / 4) - #{$gl-padding * 2});
}
&:hover:not(.disabled),
&.forked {
background-color: $row-hover;
border-color: $row-hover-border;
}
@media (min-width: $screen-lg-min) {
width: calc((100% / 5) - #{$gl-padding * 2});
}
.no-avatar {
width: 100px;
height: 100px;
background-color: $gray-light;
border: 1px solid $white-normal;
margin: 0 auto;
border-radius: 50%;
&:hover:not(.disabled),
&.forked {
background-color: $row-hover;
border-color: $row-hover-border;
}
i {
font-size: 100px;
color: $white-normal;
}
}
.avatar-container,
.identicon {
float: none;
margin-left: auto;
margin-right: auto;
}
a {
display: block;
width: 100%;
height: 100%;
padding-top: $gl-padding;
color: $gl-text-color;
a {
display: block;
width: 100%;
height: 100%;
padding-top: $gl-padding;
text-decoration: none;
&.disabled {
opacity: .3;
cursor: not-allowed;
&:hover {
text-decoration: none;
}
}
.caption {
min-height: 30px;
padding: $gl-padding 0;
}
}
img {
border-radius: 50%;
max-width: 100px;
}
&.disabled {
opacity: .3;
cursor: not-allowed;
}
}
}
.fork-thumbnail-container {
display: flex;
flex-wrap: wrap;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
> h5 {
width: 100%;
}
}
.project-template,
.project-import {
.form-group {

View file

@ -12,3 +12,7 @@
margin-left: 10px;
}
}
.registry-placeholder {
min-height: 60px;
}

View file

@ -1,6 +1,7 @@
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def index
set_index_vars
@personal_access_token = finder.build
end
def create
@ -40,7 +41,6 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def set_index_vars
@scopes = Gitlab::Auth.available_scopes
@personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
end

View file

@ -9,7 +9,7 @@ class Projects::BranchesController < Projects::ApplicationController
def index
@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])
respond_to do |format|

View file

@ -6,17 +6,26 @@ module Projects
def index
@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
def destroy
if image.destroy
redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Image repository has been removed successfully!'
respond_to do |format|
format.json { head :no_content }
end
else
redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove image repository!'
respond_to do |format|
format.json { head :bad_request }
end
end
end

View file

@ -3,20 +3,35 @@ module Projects
class TagsController < ::Projects::Registry::ApplicationController
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
if tag.delete
redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Registry tag has been removed successfully!'
respond_to do |format|
format.json { head :no_content }
end
else
redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove registry tag!'
respond_to do |format|
format.json { head :bad_request }
end
end
end
private
def tags
Kaminari::PaginatableArray.new(image.tags, limit: 15)
end
def image
@image ||= project.container_repositories
.find(params[:repository_id])

View file

@ -1,13 +1,15 @@
module EventsHelper
ICON_NAMES_BY_EVENT_TYPE = {
'pushed to' => 'icon_commit',
'pushed new' => 'icon_commit',
'created' => 'icon_status_open',
'opened' => 'icon_status_open',
'closed' => 'icon_status_closed',
'accepted' => 'icon_code_fork',
'commented on' => 'icon_comment_o',
'deleted' => 'icon_trash_o'
'pushed to' => 'commit',
'pushed new' => 'commit',
'created' => 'status_open',
'opened' => 'status_open',
'closed' => 'status_closed',
'accepted' => 'fork',
'commented on' => 'comment',
'deleted' => 'remove',
'imported' => 'import',
'joined' => 'users'
}.freeze
def link_to_author(event, self_added: false)
@ -197,7 +199,7 @@ module EventsHelper
def icon_for_event(note)
icon_name = ICON_NAMES_BY_EVENT_TYPE[note]
custom_icon(icon_name) if icon_name
sprite_icon(icon_name) if icon_name
end
def icon_for_profile_event(event)

View file

@ -34,6 +34,7 @@ class Key < ActiveRecord::Base
value&.delete!("\n\r")
value.strip! unless value.blank?
write_attribute(:key, value)
@public_key = nil
end
def publishable_key

View file

@ -560,14 +560,20 @@ class MergeRequest < ActiveRecord::Base
commits_for_notes_limit = 100
commit_ids = commit_shas.take(commits_for_notes_limit)
Note.where(
"(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" +
"((project_id = :source_project_id OR project_id = :target_project_id) AND noteable_type = 'Commit' AND commit_id IN (:commit_ids))",
mr_id: id,
commit_ids: commit_ids,
target_project_id: target_project_id,
source_project_id: source_project_id
)
commit_notes = Note
.except(:order)
.where(project_id: [source_project_id, target_project_id])
.where(noteable_type: 'Commit', commit_id: commit_ids)
# We're using a UNION ALL here since this results in better performance
# 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
alias_method :discussion_notes, :related_notes
@ -742,10 +748,9 @@ class MergeRequest < ActiveRecord::Base
end
def has_ci?
has_ci_integration = source_project.try(:ci_service)
uses_gitlab_ci = all_pipelines.any?
return false if has_no_commits?
(has_ci_integration || uses_gitlab_ci) && commits.any?
!!(head_pipeline_id || all_pipelines.any? || source_project&.ci_service)
end
def branch_missing?

View file

@ -17,6 +17,8 @@ class PersonalAccessToken < ActiveRecord::Base
validates :scopes, presence: true
validate :validate_scopes
after_initialize :set_default_scopes, if: :persisted?
def revoke!
update!(revoked: true)
end
@ -32,4 +34,8 @@ class PersonalAccessToken < ActiveRecord::Base
errors.add :scopes, "can only contain available scopes"
end
end
def set_default_scopes
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end
end

View file

@ -989,7 +989,7 @@ class Repository
end
def create_ref(ref, ref_path)
fetch_ref(path_to_repo, ref, ref_path)
raw_repository.write_ref(ref_path, ref)
end
def ls_files(ref)

View file

@ -31,7 +31,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
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)
end
end

View file

@ -0,0 +1,3 @@
class ContainerRepositoriesSerializer < BaseSerializer
entity ContainerRepositoryEntity
end

View file

@ -0,0 +1,25 @@
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

View file

@ -0,0 +1,23 @@
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

View file

@ -0,0 +1,17 @@
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

View file

@ -23,7 +23,6 @@ class MergeRequestEntity < IssuableEntity
expose :closed_event, using: EventEntity
# User entities
expose :author, using: UserEntity
expose :merge_user, using: UserEntity
# Diff sha's
@ -31,7 +30,6 @@ class MergeRequestEntity < IssuableEntity
merge_request.diff_head_sha if merge_request.diff_head_commit
end
expose :merge_commit_sha
expose :merge_commit_message
expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline

View file

@ -37,9 +37,9 @@
- if content_for?(: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 "common"
= webpack_bundle_tag "locale"
= webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?

View file

@ -9,50 +9,36 @@
%br
Forking a repository allows you to make changes without affecting the original project.
.col-lg-9
.fork-namespaces
- if @namespaces.present?
%label.label-light
%span
Click to fork the project
- @namespaces.in_groups_of(6, false) do |group|
.row
- group.each do |namespace|
- 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)
.fork-thumbnail{ class: ("disabled" unless can_create_project) }
= link_to project_forks_path(@project, namespace_key: namespace.id),
method: "POST",
class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar
= icon 'question'
- else
= image_tag avatar
.caption
= namespace.human_name
- else
%label.label-light
%span
No available namespaces to fork the project.
%br
%small
You must have permission to create a project in a namespace before forking.
- if @namespaces.present?
.fork-thumbnail-container.js-fork-content
%h5.prepend-top-0.append-bottom-0.prepend-left-default.append-right-default
Click to fork the project
- @namespaces.each do |namespace|
- avatar = namespace_icon(namespace, 100)
- can_create_project = current_user.can?(:create_projects, namespace)
- forked_project = namespace.find_fork_of(@project)
- 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",
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
- if /no_((\w*)_)*avatar/.match(avatar)
= project_identicon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
%h5.prepend-top-default
= namespace.human_name
- else
%strong
No available namespaces to fork the project.
%p.prepend-top-default
You must have permission to create a project in a namespace before forking.
.save-project-loader.hide
.center
%h2
%i.fa.fa-spinner.fa-spin
Forking repository
%p Please wait a moment, this page will automatically refresh when ready.
.save-project-loader.hide.js-fork-content
%h2.text-center
= icon('spinner spin')
Forking repository
%p.text-center
Please wait a moment, this page will automatically refresh when ready.

View file

@ -2,13 +2,13 @@
%h2.merge-requests-title
= pluralize(@merge_requests.count, 'Related Merge Request')
%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|
%li
%span.merge-request-ci-status
- if merge_request.head_pipeline
= render_pipeline_status(merge_request.head_pipeline)
- elsif has_any_ci
- elsif has_any_head_pipeline
= icon('blank fw')
%span.merge-request-id
= merge_request.to_reference

View file

@ -29,7 +29,7 @@
- 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))
- 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'
%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'

View file

@ -1,32 +0,0 @@
.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.

View file

@ -1,60 +1,49 @@
- page_title "Container Registry"
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
%section
.settings-header
%h4
= page_title
%p
With the Docker Container Registry integrated into GitLab, every project
can have its own space to store its Docker images.
= s_('ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images.')
%p.append-bottom-0
= succeed '.' do
Learn more about
= link_to 'Container Registry', help_page_path('user/project/container_registry'), target: '_blank'
= s_('ContainerRegistry|Learn more about')
= 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
.panel.panel-default
.panel-heading
%h4.panel-title
How to use the Container Registry
.panel-body
%p
First log in to GitLab&rsquo;s Container Registry using your GitLab username
and password. If you have
= link_to '2FA enabled', help_page_path('user/profile/account/two_factor_authentication'), target: '_blank'
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
docker login #{Gitlab.config.registry.host_port}
%br
%p
Once you log in, you&rsquo;re free to create and upload a container image
using the common
%code build
and
%code push
commands:
%pre
:plain
docker build -t #{escape_once(@project.container_registry_url)} .
docker push #{escape_once(@project.container_registry_url)}
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('registry_list')
%hr
%h5.prepend-top-default
Use different image names
%p.light
GitLab supports up to 3 levels of image names. The following
examples of images are valid for your project:
%pre
:plain
#{escape_once(@project.container_registry_url)}:tag
#{escape_once(@project.container_registry_url)}/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
.row.prepend-top-10
.col-lg-12
.panel.panel-default
.panel-heading
%h4.panel-title
= s_('ContainerRegistry|How to use the Container Registry')
.panel-body
%p
- link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank')
- link_2fa = 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 }
%pre
docker login #{Gitlab.config.registry.host_port}
%br
%p
= 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 }
%pre
:plain
docker build -t #{escape_once(@project.container_registry_url)} .
docker push #{escape_once(@project.container_registry_url)}
%hr
%h5.prepend-top-default
= s_('ContainerRegistry|Use different image names')
%p.light
= s_('ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:')
%pre
:plain
#{escape_once(@project.container_registry_url)}:tag
#{escape_once(@project.container_registry_url)}/optional-image-name:tag
#{escape_once(@project.container_registry_url)}/optional-name/optional-image-name:tag

View file

@ -23,7 +23,7 @@
- disabled_class = 'disabled'
- disabled_title = @service.disabled_title
= link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
= link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr

View file

@ -2,12 +2,11 @@
- release = @releases.find { |release| release.tag == tag.name }
%li.flex-row
.row-main-content.str-truncated
= link_to project_tag_path(@project, tag.name), class: 'item-title ref-name' do
= icon('tag')
= tag.name
= icon('tag')
= link_to tag.name, project_tag_path(@project, tag.name), class: 'item-title ref-name prepend-left-4'
- if protected_tag?(@project, tag)
%span.label.label-success
%span.label.label-success.prepend-left-4
protected
- if tag.message.present?

View file

@ -1,9 +1,9 @@
- type = impersonation ? "impersonation" : "personal access"
%h5.prepend-top-0
Add a #{type} Token
Add a #{type} token
%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|

View file

@ -0,0 +1,5 @@
---
title: "Add missing space in Sidekiq memory killer log message"
merge_request: 14553
author: Benjamin Drung
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix the default branches sorting to actually be 'Last updated'
merge_request: 14295
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Re-arrange <script> tags before <template> tags in .vue files
merge_request: 14671
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Hide close MR button after merge without reloading page
merge_request: 14122
author: Jacopo Beschi @jacopo-beschi
type: added

View file

@ -0,0 +1,5 @@
---
title: fix merge request widget status icon for failed CI
merge_request:
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Use explicit boolean true attribute for show-disabled-button in Vue files
merge_request: 14672
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Set default scope on PATs that don't have one set to allow them to be revoked
merge_request:
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add link to OpenID Connect documentation
merge_request: 14368
author: Markus Koller
type: other

View file

@ -0,0 +1,5 @@
---
title: Fix edit project service cancel button position
merge_request: 14596
author: Matt Coleman
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Makes @mentions links have a different styling for better separation
merge_request:
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Use a UNION ALL for getting merge request notes
merge_request:
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Adjusts tag link to avoid underlining spaces
merge_request: 14544
author: Guilherme Vieira
type: fixed

View file

@ -105,6 +105,7 @@ module Gitlab
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*"
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
config.assets.version = '1.0'

View file

@ -499,6 +499,8 @@ production: &base
# Gitaly settings
gitaly:
# Path to the directory containing Gitaly client executables.
client_path: /home/git/gitaly
# Default Gitaly authentication token. Can be overriden per storage. Can
# be left blank when Gitaly is running locally on a Unix socket, which
# is the normal way to deploy Gitaly.
@ -664,7 +666,7 @@ test:
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
gitaly:
enabled: true
client_path: tmp/tests/gitaly
token: secret
backup:
path: tmp/tests/backups

View file

@ -39,3 +39,17 @@ module GettextI18nRailsJs
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

View file

@ -281,7 +281,7 @@ constraints(ProjectUrlConstrainer.new) do
namespace :registry do
resources :repository, only: [] do
resources :tags, only: [:destroy],
resources :tags, only: [:index, :destroy],
constraints: { id: Gitlab::Regex.container_registry_tag_regex }
end
end

View file

@ -68,6 +68,7 @@ var config = {
prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
registry_list: './registry/index.js',
repo: './repo/index.js',
sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
@ -121,10 +122,6 @@ var config = {
name: '[name].[hash].[ext]',
}
},
{
test: /locale\/\w+\/(.*)\.js$/,
loader: 'exports-loader?locales',
},
{
test: /monaco-editor\/\w+\/vs\/loader\.js$/,
use: [
@ -200,6 +197,7 @@ var config = {
'pdf_viewer',
'pipelines',
'pipelines_details',
'registry_list',
'repo',
'schedule_form',
'schedules_index',
@ -222,7 +220,7 @@ var config = {
// create cacheable common library bundles
new webpack.optimize.CommonsChunkPlugin({
names: ['main', 'locale', 'common', 'webpack_runtime'],
names: ['main', 'common', 'webpack_runtime'],
}),
// enable scope hoisting

View file

@ -7,11 +7,13 @@ class AddFastForwardOptionToProject < ActiveRecord::Migration
disable_ddl_transaction!
def add
def up
add_column_with_default(:projects, :merge_requests_ff_only_enabled, :boolean, default: false)
end
def down
remove_column(:projects, :merge_requests_ff_only_enabled)
if column_exists?(:projects, :merge_requests_ff_only_enabled)
remove_column(:projects, :merge_requests_ff_only_enabled)
end
end
end

View file

@ -0,0 +1,25 @@
# 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

View file

@ -11,7 +11,7 @@
#
# 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
enable_extension "plpgsql"

View file

@ -121,6 +121,15 @@ GET /projects/:id/merge_requests?labels=bug,reproduced
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:
| Attribute | Type | Required | Description |

View file

@ -1,51 +1,46 @@
## 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
[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
to disable GitLab CI in order to not have any conflicts with the commits status
If you are using an external CI/CD server like Jenkins or Drone CI, it is advised
to disable GitLab CI/CD in order to not have any conflicts with the commits status
API.
---
GitLab CI is exposed via the `/pipelines` and `/builds` pages of a project.
Disabling GitLab CI in a project does not delete any previous jobs.
In fact, the `/pipelines` and `/builds` pages can still be accessed, although
GitLab CI/CD is exposed via the `/pipelines` and `/jobs` pages of a project.
Disabling GitLab CI/CD in a project does not delete any previous jobs.
In fact, the `/pipelines` and `/jobs` pages can still be accessed, although
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
settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations
respectively.
### Per-project user setting
The setting to enable or disable GitLab CI can be found with the name **Pipelines**
under the **Sharing & Permissions** area of a project's settings along with
**Merge Requests**. Choose one of **Disabled**, **Only team members** and
**Everyone with access** and hit **Save changes** for the settings to take effect.
The setting to enable or disable GitLab CI/CD can be found under your project's
**Settings > General > Permissions**. Choose one of "Disabled", "Only team members"
or "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.
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.
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.
---
For installations from source, open `gitlab.yml` with your editor and set
`builds` to `false`:

View file

@ -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
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
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
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.
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:
@ -229,7 +229,7 @@ You can find it in the pipeline, job, environment, and deployment views.
| 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
`deploy_prod` job, and the deployment will be recorded under a new
@ -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 |
| -------------------- | ------------ | ----------- |
| ![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
`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]
environment which has had a successful deployment. If monitoring data was
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
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
masters and owners.
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
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:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show more