Commit 9c9bb49b authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into fix-trace-archive-cron-worker-race-condition

parents 254134fb 969b7c56
......@@ -269,10 +269,10 @@ package-and-qa:
<<: *single-script-job-variables
SCRIPT_NAME: trigger-build-docs
environment:
name: review-docs/$CI_COMMIT_REF_NAME
name: review-docs/$CI_COMMIT_REF_SLUG
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
url: http://$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup
# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
......@@ -307,7 +307,7 @@ review-docs-cleanup:
<<: *review-docs
stage: post-cleanup
environment:
name: review-docs/$CI_COMMIT_REF_NAME
name: review-docs/$CI_COMMIT_REF_SLUG
action: stop
when: manual
script:
......@@ -325,11 +325,9 @@ cloud-native-image:
variables:
GIT_DEPTH: "1"
cache: {}
before_script:
- gem install gitlab --no-rdoc --no-ri
- chmod 755 ./scripts/trigger-build-cloud-native
script:
- ./scripts/trigger-build-cloud-native
- gem install gitlab --no-ri --no-rdoc
- ./trigger-build cng
only:
- tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee
......
......@@ -10,10 +10,6 @@
Capybara/CurrentPathExpectation:
Enabled: false
# Offense count: 956
Capybara/FeatureMethods:
Enabled: false
# Offense count: 23
FactoryBot/DynamicAttributeDefinedStatically:
Exclude:
......
......@@ -2,6 +2,26 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 11.0.3 (2018-07-05)
### Fixed (14 changes, 1 of them is from the community)
- Revert merge request widget button max height. !20175 (George Tsiolis)
- Implement upload copy when moving an issue with upload on object storage. !20191
- Fix broken '!' support to autocomplete MRs in GFM fields. !20204
- Restore showing Elasticsearch and Geo status on dashboard. !20276
- Fix merge request page rendering error when its target/source branch is missing. !20280
- Fix sidebar collapse breapoints for job and wiki pages.
- fix size of code blocks in headings.
- Fix loading screen for search autocomplete dropdown.
- Fix ambiguous due_date column for Issue scopes.
- Always serve favicon from main GitLab domain so that CI badge can be drawn over it.
- Fix tooltip flickering bug.
- Fix refreshing cache keys for open issues count.
- Replace deprecated bs.affix in merge request tabs with sticky polyfill.
- Prevent pipeline job tooltip from scrolling off dropdown container.
## 11.0.2 (2018-06-26)
### Fixed (8 changes, 1 of them is from the community)
......
......@@ -47,7 +47,7 @@ gem 'omniauth-google-oauth2', '~> 0.5.3'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3'
......
......@@ -568,7 +568,7 @@ GEM
omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0)
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
......@@ -1101,7 +1101,7 @@ DEPENDENCIES
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0)
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
......
......@@ -572,7 +572,7 @@ GEM
omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0)
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
......@@ -1111,7 +1111,7 @@ DEPENDENCIES
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0)
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
......
......@@ -120,6 +120,10 @@ All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/
Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help.
## Why?
[Read here](https://about.gitlab.com/why/)
## Is it any good?
[Yes](https://news.ycombinator.com/item?id=3067434)
......
......@@ -28,23 +28,29 @@ export default {
},
},
methods: {
buildUpdateRequest(list) {
return {
add_label_ids: [list.label.id],
};
},
addIssues() {
const firstListIndex = 1;
const list = this.modal.selectedList || this.state.lists[firstListIndex];
const selectedIssues = ModalStore.getSelectedIssues();
const issueIds = selectedIssues.map(issue => issue.id);
const req = this.buildUpdateRequest(list);
// Post the data to the backend
gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id],
}).catch(() => {
Flash(__('Failed to update issues, please try again.'));
gl.boardService
.bulkUpdate(issueIds, req)
.catch(() => {
Flash(__('Failed to update issues, please try again.'));
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
list.issuesSize -= 1;
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
list.issuesSize -= 1;
});
});
});
// Add the issues on the frontend
selectedIssues.forEach((issue) => {
......
......@@ -121,8 +121,7 @@
<div
v-if="issuesCount > 0 && issues.length === 0"
class="empty-state add-issues-empty-state-filter text-center">
<div
class="svg-content">
<div class="svg-content">
<img :src="emptyStateSvg" />
</div>
<div class="text-content">
......
......@@ -5,7 +5,7 @@
const Store = gl.issueBoards.BoardsStore;
export default {
export default Vue.extend({
props: {
issue: {
type: Object,
......@@ -25,19 +25,16 @@
removeIssue() {
const { issue } = this;
const lists = issue.getLists();
const listLabelIds = lists.map(list => list.label.id);
let labelIds = issue.labels.map(label => label.id).filter(id => !listLabelIds.includes(id));
if (labelIds.length === 0) {
labelIds = [''];
}
const req = this.buildPatchRequest(issue, lists);
const data = {
issue: {
label_ids: labelIds,
},
issue: this.seedPatchRequest(issue, req),
};
if (data.issue.label_ids.length === 0) {
data.issue.label_ids = [''];
}
// Post the remove data
Vue.http.patch(this.updateUrl, data).catch(() => {
Flash(__('Failed to remove issue from board, please try again.'));
......@@ -54,8 +51,30 @@
Store.detail.issue = {};
},
/**
* Build the default patch request.
*/
buildPatchRequest(issue, lists) {
const listLabelIds = lists.map(list => list.label.id);
const labelIds = issue.labels
.map(label => label.id)
.filter(id => !listLabelIds.includes(id));
return {
label_ids: labelIds,
};
},
/**
* Seed the given patch request.
*
* (This is overridden in EE)
*/
seedPatchRequest(issue, req) {
return req;
},
},
};
});
</script>
<template>
<div
......
......@@ -4,6 +4,7 @@
/* global ListAssignee */
import Vue from 'vue';
import '~/vue_shared/models/label';
import IssueProject from './project';
class ListIssue {
......
......@@ -7,6 +7,24 @@ import queryData from '../utils/query_data';
const PER_PAGE = 20;
const TYPES = {
backlog: {
isPreset: true,
isExpandable: true,
isBlank: false,
},
closed: {
isPreset: true,
isExpandable: true,
isBlank: false,
},
blank: {
isPreset: true,
isExpandable: false,
isBlank: true,
},
};
class List {
constructor(obj, defaultAvatar) {
this.id = obj.id;
......@@ -14,8 +32,10 @@ class List {
this.position = obj.position;
this.title = obj.title;
this.type = obj.list_type;
this.preset = ['backlog', 'closed', 'blank'].indexOf(this.type) > -1;
this.isExpandable = ['backlog', 'closed'].indexOf(this.type) > -1;
const typeInfo = this.getTypeInfo(this.type);
this.preset = !!typeInfo.isPreset;
this.isExpandable = !!typeInfo.isExpandable;
this.isExpanded = true;
this.page = 1;
this.loading = true;
......@@ -31,7 +51,7 @@ class List {
this.title = this.assignee.name;
}
if (this.type !== 'blank' && this.id) {
if (!typeInfo.isBlank && this.id) {
this.getIssues().catch(() => {
// TODO: handle request error
});
......@@ -107,7 +127,7 @@ class List {
return gl.boardService
.getIssuesForList(this.id, data)
.then(res => res.data)
.then((data) => {
.then(data => {
this.loading = false;
this.issuesSize = data.size;
......@@ -126,18 +146,7 @@ class List {
return gl.boardService
.newIssue(this.id, issue)
.then(res => res.data)
.then((data) => {
issue.id = data.id;
issue.iid = data.iid;
issue.project = data.project;
issue.path = data.real_path;
issue.referencePath = data.reference_path;
if (this.issuesSize > 1) {
const moveBeforeId = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
}
});
.then(data => this.onNewIssueResponse(issue, data));
}
createIssues(data) {
......@@ -217,6 +226,25 @@ class List {
return !matchesRemove;
});
}
getTypeInfo (type) {
return TYPES[type] || {};
}
onNewIssueResponse (issue, data) {
issue.id = data.id;
issue.iid = data.iid;
issue.project = data.project;
issue.path = data.real_path;
issue.referencePath = data.reference_path;
if (this.issuesSize > 1) {
const moveBeforeId = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
}
}
}
window.List = List;
export default List;
......@@ -2,6 +2,7 @@
import _ from 'underscore';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
......@@ -12,6 +13,7 @@ export default {
ClipboardButton,
EditButton,
Icon,
FileIcon,
},
directives: {
Tooltip,
......@@ -139,18 +141,20 @@ export default {
:name="collapseIcon"
:size="16"
aria-hidden="true"
class="diff-toggle-caret"
class="diff-toggle-caret append-right-5"
@click.stop="handleToggle"
/>
<a
ref="titleWrapper"
:href="titleLink"
class="append-right-4"
>
<i
:class="`fa-${icon}`"
class="fa fa-fw"
<file-icon
:file-name="filePath"
:size="18"
aria-hidden="true"
></i>
css-classes="js-file-icon append-right-5"
/>
<span v-if="diffFile.renamedFile">
<strong
v-tooltip
......
......@@ -24,19 +24,21 @@ export default {
...mapGetters(['commit']),
parallelDiffLines() {
return this.diffLines.map(line => {
const parallelLine = Object.assign({}, line);
if (line.left) {
Object.assign(line, { left: trimFirstCharOfLineContent(line.left) });
parallelLine.left = trimFirstCharOfLineContent(line.left);
} else {
Object.assign(line, { left: { type: EMPTY_CELL_TYPE } });
parallelLine.left = { type: EMPTY_CELL_TYPE };
}
if (line.right) {
Object.assign(line, { right: trimFirstCharOfLineContent(line.right) });
parallelLine.right = trimFirstCharOfLineContent(line.right);
} else {
Object.assign(line, { right: { type: EMPTY_CELL_TYPE } });
parallelLine.right = { type: EMPTY_CELL_TYPE };
}
return line;
return parallelLine;
});
},
diffLinesLength() {
......
......@@ -155,18 +155,21 @@ export function addContextLines(options) {
}
}
export function trimFirstCharOfLineContent(line) {
if (!line.richText) {
return line;
}
const firstChar = line.richText.charAt(0);
if (firstChar === ' ' || firstChar === '+' || firstChar === '-') {
Object.assign(line, {
richText: line.richText.substring(1),
});
/**
* Trims the first char of the `richText` property when it's either a space or a diff symbol.
* @param {Object} line
* @returns {Object}
*/
export function trimFirstCharOfLineContent(line = {}) {
const parsedLine = Object.assign({}, line);
if (line.richText) {
const firstChar = parsedLine.richText.charAt(0);
if (firstChar === ' ' || firstChar === '+' || firstChar === '-') {
parsedLine.richText = line.richText.substring(1);
}
}
return line;
return parsedLine;
}
......@@ -5,6 +5,7 @@
import $ from 'jquery';
import _ from 'underscore';
import { __ } from '~/locale';
import '~/gl_dropdown';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import ModalStore from './boards/stores/modal_store';
......@@ -251,3 +252,5 @@ export default class MilestoneSelect {
});
}
}
window.MilestoneSelect = MilestoneSelect;
<script>
import _ from 'underscore';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue';
......@@ -13,6 +15,7 @@ export default {
Graph,
GraphGroup,
EmptyState,
Icon,
},
props: {
hasMetrics: {
......@@ -80,6 +83,14 @@ export default {
type: String,
required: true,
},
environmentsEndpoint: {
type: String,
required: true,
},
currentEnvironmentName: {
type: String,
required: true,
},
},
data() {
return {
......@@ -96,6 +107,7 @@ export default {
this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint,
});
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged);
......@@ -122,7 +134,11 @@ export default {
this.service
.getDeploymentData()
.then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')),
.catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
this.service
.getEnvironmentsData()
.then((data) => this.store.storeEnvironmentsData(data))
.catch(() => Flash(s__('Metrics|There was an error getting environments information.'))),
])
.then(() => {
if (this.store.groups.length < 1) {
......@@ -155,8 +171,41 @@ export default {
<template>
<div
v-if="!showEmptyState"
class="prometheus-graphs"
class="prometheus-graphs prepend-top-10"
>
<div class="environments d-flex align-items-center">
{{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10">
<button
class="dropdown-menu-toggle"
data-toggle="dropdown"
type="button"
>
<span>
{{ currentEnvironmentName }}
</span>
<icon
name="chevron-down"
/>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="environment in store.environmentsData"
:key="environment.latest.id"
>
<a
:href="environment.latest.metrics_path"
:class="{ 'is-active': environment.latest.name == currentEnvironmentName }"
class="dropdown-item"
>
{{ environment.latest.name }}
</a>
</li>
</ul>
</div>
</div>
</div>
<graph-group
v-for="(groupData, index) in store.groups"
:key="index"
......
import axios from '../../lib/utils/axios_utils';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
const MAX_REQUESTS = 3;
......@@ -23,9 +24,10 @@ function backOffRequest(makeRequestCallback) {
}
export default class MonitoringService {
constructor({ metricsEndpoint, deploymentEndpoint }) {
constructor({ metricsEndpoint, deploymentEndpoint, environmentsEndpoint }) {
this.metricsEndpoint = metricsEndpoint;
this.deploymentEndpoint = deploymentEndpoint;
this.environmentsEndpoint = environmentsEndpoint;
}
getGraphsData() {
......@@ -33,7 +35,7 @@ export default class MonitoringService {
.then(resp => resp.data)
.then((response) => {
if (!response || !response.data) {
throw new Error('Unexpected metrics data response from prometheus endpoint');
throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
return response.data;
});
......@@ -47,9 +49,20 @@ export default class MonitoringService {
.then(resp => resp.data)
.then((response) => {
if (!response || !response.deployments) {
throw new Error('Unexpected deployment data response from prometheus endpoint');
throw new Error(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
}
return response.deployments;
});
}
getEnvironmentsData() {
return axios.get(this.environmentsEndpoint)
.then(resp => resp.data)
.then((response) => {
if (!response || !response.environments) {
throw new Error(s__('Metrics|There was an error fetching the environments data, please try again'));
}
return response.environments;
});
}
}
......@@ -24,6 +24,7 @@ export default class MonitoringStore {
constructor() {
this.groups = [];
this.deploymentData = [];
this.environmentsData = [];
}
storeMetrics(groups = []) {
......@@ -37,6 +38,10 @@ export default class MonitoringStore {
this.deploymentData = deploymentData;
}
storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData;
}
getMetricsCount() {
return this.groups.reduce((count, group) => count + group.metrics.length, 0);
}
......
<script>
import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
export default {
components: {
DiffFileHeader,
SkeletonLoadingContainer,
},
props: {
discussion: {
type: Object,
required: true,
export default {
components: {
DiffFileHeader,
SkeletonLoadingContainer,
},
},
data() {
return {
error: false,
};
},
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
}),
hasTruncatedDiffLines() {
return this.discussion.truncatedDiffLines && this.discussion.truncatedDiffLines.length !== 0;
props: {
discussion: {
type: Object,
required: true,
},
},
isDiscussionsExpanded() {
return true; // TODO: @fatihacet - Fix this.
data() {
return {
error: false,
};
},
isCollapsed() {
return this.diffFile.collapsed || false;
},
isImageDiff() {
return !this.diffFile.text;
},
diffFileClass() {
const { text } = this.diffFile;
return text ? 'text-file' : 'js-image-file';
},
diffFile() {
return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true });
},
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
currentUser() {
return this.noteableData.current_user;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
normalizedDiffLines() {
const lines = this.discussion.truncatedDiffLines || [];
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
}),
hasTruncatedDiffLines() {
return this.discussion.truncatedDiffLines &&
this.discussion.truncatedDiffLines.length !== 0;
},
isDiscussionsExpanded() {
return true; // TODO: @fatihacet - Fix this.
},
isCollapsed() {
return this.diffFile.collapsed || false;
},
isImageDiff() {
return !this.diffFile.text;
},
diffFileClass() {
const { text } = this.diffFile;
return text ? 'text-file' : 'js-image-file';
},
diffFile() {
return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true });
},
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
currentUser() {
return this.noteableData.current_user;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
normalizedDiffLines() {
if (this.discussion.truncatedDiffLines) {
return this.discussion.truncatedDiffLines.map(line =>
trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)),
);
}
return lines.map(line => trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)));
return [];
},
},
},
mounted() {
if (this.isImageDiff) {
const canCreateNote = false;
const renderCommentBadge = true;
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
} else if (!this.hasTruncatedDiffLines) {
this.fetchDiff();
}
},
methods: {
...mapActions(['fetchDiscussionDiffLines']),
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
mounted() {
if (this.isImageDiff) {
const canCreateNote = false;
const renderCommentBadge = true;
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
} else if (!this.hasTruncatedDiffLines) {
this.fetchDiff();
}
},
fetchDiff() {
this.error = false;
this.fetchDiscussionDiffLines(this.discussion)
.then(this.highlight)
.catch(() => {
this.error = true;
});
methods: {
...mapActions(['fetchDiscussionDiffLines']),
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
},
fetchDiff() {
this.error = false;
this.fetchDiscussionDiffLines(this.discussion)
.then(this.highlight)
.catch(() => {
this.error = true;
});
},
},
},
};
};
</script>
<template>
......
......@@ -85,9 +85,9 @@ export const allDiscussions = (state, getters) => {
export const resolvedDiscussionsById = state => {
const map = {};
state.discussions.forEach(n => {
state.discussions.filter(d => d.resolvable).forEach(n => {
if (n.notes) {
const resolved = n.notes.every(note => note.resolved && !note.system);
const resolved = n.notes.filter(note => note.resolvable).every(note => note.resolved);
if (resolved) {
map[n.id] = n;
......
......@@ -4,7 +4,7 @@ import Project from './project';
import ShortcutsNavigation from '../../shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => {
const page = document.body.dataset.page;
const { page } = document.body.dataset;
const newClusterViews = [
'projects:clusters:new',
'projects:clusters:create_gcp',
......
import initTerminal from '~/terminal/';
document.addEventListener('DOMContentLoaded', initTerminal);
......@@ -29,8 +29,8 @@
methods: {
isValid(form) {
return !form ||
form.find('.js-vue-markdown-field').length ||
$(this.$el).closest('form') === form[0];
form.find('.js-vue-markdown-field').length &&
$(this.$el).closest('form')[0] === form[0];
},
previewMarkdownTab(event, form) {
......
......@@ -15,7 +15,7 @@
}
svg {
vertical-align: text-bottom;
vertical-align: middle;
}
}
......
......@@ -222,6 +222,23 @@
}
}
.prometheus-graphs {
.environments {
.dropdown-menu-toggle {
svg {
position: absolute;
right: 5%;
top: 25%;
}
}
.dropdown-menu-toggle,
.dropdown-menu {
width: 240px;
}
}
}
.environments-actions {
.external-url,
.monitoring-url,
......
......@@ -2,11 +2,12 @@ class Projects::JobsController < Projects::ApplicationController
include SendFileUpload
before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!,
only: [:index, :show, :status, :raw, :trace]
before_action :authorize_read_build!
before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase]
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_workhorse_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
layout 'project'
......@@ -134,6 +135,15 @@ class Projects::JobsController < Projects::ApplicationController
end
end
def terminal
end
# GET .../terminal.ws : implemented in gitlab-workhorse
def terminal_websocket_authorize
set_workhorse_internal_api_content_type
render json: Gitlab::Workhorse.terminal_websocket(@build.terminal_specification)
end
private
def authorize_update_build!
......@@ -144,6 +154,14 @@ class Projects::JobsController < Projects::ApplicationController
return access_denied! unless can?(current_user, :erase_build, build)
end
def authorize_use_build_terminal!
return access_denied! unless can?(current_user, :create_build_terminal, build)
end
def verify_api_request!
Gitlab::Workhorse.verify_api_request!(request.headers)
end
def raw_send_params
{ type: 'text/plain; charset=utf-8', disposition: 'inline' }
end
......
......@@ -26,4 +26,8 @@ class Board < ActiveRecord::Base
def closed_list
lists.merge(List.closed).take
end
def scoped?
false
end
end
......@@ -27,7 +27,13 @@ module Ci
has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :metadata, class_name: 'Ci::BuildMetadata'
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
accepts_nested_attributes_for :runner_session
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project
##
......@@ -174,6 +180,10 @@ module Ci
after_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state
end
after_transition running: any do |build|
Ci::BuildRunnerSession.where(build: build).delete_all
end
end
def ensure_metadata
......@@ -584,6 +594,10 @@ module Ci
super(options).merge(when: read_attribute(:when))
end
def has_terminal?
running? && runner_session_url.present?
end
private
def update_artifacts_size
......
module Ci
# The purpose of this class is to store Build related runner session.
# Data will be removed after transitioning from running to any state.
class BuildRunnerSession < ActiveRecord::Base
extend Gitlab::Ci::Model
self.table_name = 'ci_builds_runner_session'
belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session
validates :build, presence: true
validates :url, url: { protocols: %w(https) }
def terminal_specification
return {} unless url.present?
{
subprotocols: ['terminal.gitlab.com'].freeze,
url: "#{url}/exec".sub("https://", "wss://"),
headers: { Authorization: authorization.presence }.compact,
ca_pem: certificate.presence
}
end
end
end
......@@ -240,7 +240,7 @@ class KubernetesService < DeploymentService
end
def deprecation_validation
return if active_changed?(from: true, to: false)
return if active_changed?(from: true, to: false) || (new_record? && !active?)
if deprecated?
errors[:base] << deprecation_message
......
......@@ -281,9 +281,9 @@ class Service < ActiveRecord::Base
def self.build_from_template(project_id, template)
service = template.dup
service.active = false unless service.valid?
service.template = false
service.project_id = project_id
service.active = false if service.active? && !service.valid?
service
end
......
......@@ -18,6 +18,10 @@ module Ci
@subject.project.branch_allows_collaboration?(@user, @subject.ref)
end
condition(:terminal, scope: :subject) do
@subject.has_terminal?
end
rule { protected_ref }.policy do
prevent :update_build
prevent :erase_build
......@@ -29,5 +33,7 @@ module Ci
enable :update_build
enable :update_commit_status
end
rule { can?(:update_build) & terminal }.enable :create_build_terminal
end
end
......@@ -25,6 +25,8 @@ class DiffFileEntity < Grape::Entity
expose :can_modify_blob do |diff_file|
merge_request = options[:merge_request]
next unless diff_file.blob
if merge_request&.source_project && current_user
can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch)
else
......@@ -108,6 +110,7 @@ class DiffFileEntity < Grape::Entity
project = merge_request.target_project
next unless project
next unless diff_file.content_sha
project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path))
end
......@@ -125,6 +128,8 @@ class DiffFileEntity < Grape::Entity
end
expose :context_lines_path, if: -> (diff_file, _) { diff_file.text? } do |diff_file|
next unless diff_file.content_sha
project_blob_diff_path(diff_file.repository.project, tree_join(diff_file.content_sha, diff_file.file_path))
end
......
......@@ -3,7 +3,7 @@ class DiscussionEntity < Grape::Entity
include NotesHelper
expose :id, :reply_id
expose :position, if: -> (d, _) { d.diff_discussion? }
expose :position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? }
expose :line_code, if: -> (d, _) { d.diff_discussion? }
expose :expanded?, as: :expanded
expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? }
......
......@@ -13,7 +13,7 @@ module Ci
@runner = runner
end
def execute
def execute(params = {})
builds =
if runner.instance_type?
builds_for_shared_runner
......@@ -41,6 +41,8 @@ module Ci
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
begin
build.runner_id = runner.id
build.runner_session_attributes = params[:session] if params[:session].present?
build.run!
register_success(build)
......
......@@ -32,8 +32,9 @@ module Issues
def filter_assignee(issuable)
return if params[:assignee_ids].blank?
# The number of assignees is limited by one for GitLab CE
params[:assignee_ids] = params[:assignee_ids][0, 1]
unless issuable.allows_multiple_assignees?
params[:assignee_ids] = params[:assignee_ids].take(1)
end
assignee_ids = params[:assignee_ids].select { |assignee_id| assignee_can_read?(issuable, assignee_id) }
......
# frozen_string_literal: true
class AttachmentUploader < GitlabUploader
include RecordsUploads::Concern
include ObjectStorage::Concern
......
# frozen_string_literal: true
class AvatarUploader < GitlabUploader
include UploaderHelper
include RecordsUploads::Concern
......
# frozen_string_literal: true
class FaviconUploader < AttachmentUploader
EXTENSION_WHITELIST = %w[png ico].freeze
......
# frozen_string_literal: true
class FileMover
attr_reader :secret, :file_name, :model, :update_field
......
# frozen_string_literal: true
# This class breaks the actual CarrierWave concept.
# Every uploader should use a base_dir that is model agnostic so we can build
# back URLs from base_dir-relative paths saved in the `Upload` model.
......@@ -117,7 +119,7 @@ class FileUploader < GitlabUploader
end
def markdown_link
markdown = "[#{markdown_name}](#{secure_url})"
markdown = +"[#{markdown_name}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous?
markdown
end
......
# frozen_string_literal: true
class GitlabUploader < CarrierWave::Uploader::Base
class_attribute :options
......
# frozen_string_literal: true
class JobArtifactUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
......
# frozen_string_literal: true
class LegacyArtifactUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
......
# frozen_string_literal: true
class LfsObjectUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
......
# frozen_string_literal: true
class NamespaceFileUploader < FileUploader
# Re-Override
def self.root
......
# frozen_string_literal: true
require 'fog/aws'
require 'carrierwave/storage/fog'
......
# frozen_string_literal: true
class PersonalFileUploader < FileUploader
# Re-Override
def self.root
......
# frozen_string_literal: true
module RecordsUploads
module Concern
extend ActiveSupport::Concern
......
# frozen_string_literal: true
# Extra methods for uploader
module UploaderHelper
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
......
# frozen_string_literal: true
module Workhorse
module UploadPath
def workhorse_upload_path
......
.modal{ id: "revoke-modal-#{token.id}" }
.modal{ id: "revoke-modal-#{token.id}", tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
......
......@@ -2,15 +2,9 @@
- page_title "Metrics for environment", @environment.name
.prometheus-container{ class: container_class }
.top-area
.row
.col-sm-6
%h3
Environment:
= link_to @environment.name, environment_path(@environment)
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"clusters-path": project_clusters_path(@project),
"current-environment-name": @environment.name,
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
"empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
"empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
......@@ -18,6 +12,7 @@
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'),
"metrics-endpoint": additional_metrics_project_environment_path(@project, @environment, format: :json),
"deployment-endpoint": project_environment_deployments_path(@project, @environment, format: :json),
"environments-endpoint": project_environments_path(@project, format: :json),
"project-path": project_path(@project),
"tags-path": project_tags_path(@project),
"has-metrics": "#{@environment.has_metrics?}" } }
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container
.blocks-container
- if can?(current_user, :create_build_terminal, @build)
.block
= link_to terminal_project_job_path(@project, @build), class: 'terminal-button pull-right btn visible-md-block visible-lg-block', title: 'Terminal' do
Terminal
#js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } }
......@@ -14,8 +18,8 @@
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
- elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
%span= time_ago_with_tooltip @build.artifacts_expire_at
The artifacts will be removed
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
- if @build.artifacts?
.btn-group.d-flex{ role: :group }
......
- @no_container = true
- add_to_breadcrumbs 'Jobs', project_jobs_path(@project)
- add_to_breadcrumbs "##{@build.id}", project_job_path(@project, @build)
- breadcrumb_title 'Terminal'
- page_title 'Terminal', "#{@build.name} (##{@build.id})", 'Jobs'
- content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm/xterm"
.terminal-container{ class: container_class }
#terminal{ data: { project_path: terminal_project_job_path(@project, @build, format: :ws) } }
......@@ -117,6 +117,9 @@
%li
JaCoCo (Java/Kotlin)
%code Total.*?([0-9]{1,3})%
%li
go test -cover (Go)
%code coverage: \d+.\d+% of statements
= f.submit _('Save changes'), class: "btn btn-save"
......
......@@ -3,34 +3,34 @@
.d-none.d-sm-block
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do
Edit
= _('Edit')
- if can?(current_user, :update_project_snippet, @snippet)
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
Delete
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
= _('Delete')
- if can?(current_user, :create_project_snippet, @project)
= link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
= link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: _("New snippet") do
= _('New snippet')
- if @snippet.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.d-block.d-sm-none.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
= _('Options')
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
- if can?(current_user, :create_project_snippet, @project)
%li
= link_to new_project_snippet_path(@project), title: "New snippet" do
New snippet
= link_to new_project_snippet_path(@project), title: _("New snippet") do
= _('New snippet')
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
= _('Delete')
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to edit_project_snippet_path(@project, @snippet) do
Edit
= _('Edit')
- if @snippet.submittable_as_spam_by?(current_user)
%li
= link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post
= link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post
- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
- add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title @snippet.to_reference
- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
- page_title _("Edit"), "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
%h3.page-title
Edit Snippet
= _("Edit Snippet")
%hr
= render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
- page_title "Snippets"
- page_title _("Snippets")
- if current_user
.top-area
......@@ -7,6 +7,6 @@
.nav-controls
- if can?(current_user, :create_project_snippet, @project)
= link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet"
= link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-new", title: _("New snippet")
= render 'snippets/snippets'
- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
- breadcrumb_title "New"
- page_title "New Snippets"
- add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title _("New")
- page_title _("New Snippets")
%h3.page-title
New Snippet
= _('New Snippet')
%hr
= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
- add_to_breadcrumbs "Snippets", project_snippets_path(@project)
- add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= render 'shared/snippets/header'
......
......@@ -2,8 +2,8 @@
- group = local_assigns.fetch(:group, false)
- @no_breadcrumb_container = true
- @no_container = true
- @content_class = "issue-boards-content"
- breadcrumb_title _("Issue Board")
- @content_class = "issue-boards-content js-focus-mode-board"
- breadcrumb_title _("Issue Boards")
- page_title _("Boards")
- content_for :page_specific_javascripts do
......@@ -11,10 +11,11 @@
-# haml-lint:disable InlineJavaScript
%script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal
%script#js-board-promotion{ type: "text/x-template" }= render_if_exists "shared/promotions/promote_issue_board"
#board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
.d-none.d-sm-none.d-md-block
= render 'shared/issuable/search_bar', type: :boards
= render 'shared/issuable/search_bar', type: :boards, board: board
.boards-list
.boards-app-loading.text-center{ "v-if" => "loading" }
......
- type = local_assigns.fetch(:type)
- board = local_assigns.fetch(:board, nil)
- block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
- full_path = @project.present? ? @project.full_path : @group.full_path
- user_can_admin_list = board && can?(current_user, :admin_list, board.parent)
.issues-filters
.issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal }
- if type == :boards
#js-multiple-boards-switcher.inline.boards-switcher{ "v-cloak" => true }
= render_if_exists "shared/boards/switcher", board: board
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
......@@ -99,13 +104,18 @@
%gl-emoji
%span.js-data-value.prepend-left-10
{{name}}
= render_if_exists 'shared/issuable/filter_weight', type: type
%button.clear-search.hidden{ type: 'button' }
= icon('times')
.filter-dropdown-container
- if type == :boards
- if can?(current_user, :admin_list, board.parent)
= render_if_exists 'shared/issuable/board_create_list_dropdown', board: board
.js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
- if user_can_admin_list
= render 'shared/issuable/board_create_list_dropdown', board: board
- if @project
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
#js-toggle-focus-btn
- elsif type != :boards_modal
= render 'shared/sort_dropdown'
---
title: Do not use '-f' with 'rm' in gitlab-basics docs
merge_request: 18027
author: Elias Werberich
type: changed
---
title: Fix double "in" in time to artifact deletion message
merge_request: 20357
author: "@bbodenmiller"
type: fixed
---
title: Fixed bug when editing a comment in an issue,the preview mode is toggled in
the main textarea
merge_request: 20112
author: Constance Okoghenun
type: fixed
---
title: Add environment dropdown for the metrics page
merge_request: 19833
author:
type: changed
---
title: Prevent pipeline job tooltip from scrolling off dropdown container
merge_request:
author:
type: fixed
---
title: Replace deprecated bs.affix in merge request tabs with sticky polyfill
merge_request:
author:
type: fixed
---
title: Fix ambiguous due_date column for Issue scopes
merge_request:
author:
type: fixed
---
title: Fix loading screen for search autocomplete dropdown
merge_request:
author:
type: fixed
---
title: Fix sidebar collapse breapoints for job and wiki pages
merge_request:
author:
type: fixed
---
title: Fix broken '!' support to autocomplete MRs in GFM fields
merge_request: 20204
author:
type: fixed
---
title: fix size of code blocks in headings
merge_request:
author:
type: fixed
---
title: Fix merge request page rendering error when its target/source branch is missing
merge_request: 20280
author:
type: fixed
---
title: Improves performance of mr code, by fixing the state being mutated outside
of the store in the util function trimFirstCharOfLineContent and in map operations.
Avoids map operation in an empty array. Adds specs to the trimFirstCharOfLineContent
function
merge_request: 20380
author: filipa
type: performance
---
title: Restore showing Elasticsearch and Geo status on dashboard
merge_request: 20276
author:
type: fixed
---
title: Close revoke deploy token modal on escape keypress
merge_request: 20347
author: George Tsiolis
type: changed
---
title: Always serve favicon from main GitLab domain so that CI badge can be drawn
over it
merge_request:
author:
type: fixed
---
title: Deactivate new KubernetesService created from active template to prevent project creation from failing
merge_request:
author:
type: fixed
---
title: Fix tooltip flickering bug
merge_request:
author:
type: fixed
---
title: Add Web Terminal for Ci Builds
merge_request:
author: Vicky Chijwani
type: added
---
title: Enable frozen string in apps/validators/*.rb
merge_request: 20382
author: gfyoung
type: other
---
title: Updated Gitaly fail-fast timeout values
merge_request: !20259
author:
type: performance
---
title: Fix refreshing cache keys for open issues count
merge_request:
author:
type: fixed
---
title: Revert merge request widget button max height
merge_request: 20175
author: George Tsiolis
type: fixed
---
title: Implement upload copy when moving an issue with upload on object storage
merge_request: 20191
author:
type: fixed
......@@ -279,6 +279,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :erase
get :trace, defaults: { format: 'json' }
get :raw
get :terminal
get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', constraints: { format: nil }
end
resource :artifacts, only: [] do
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateCiBuildsRunnerSession < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
create_table :ci_builds_runner_session, id: :bigserial do |t|
t.integer :build_id, null: false
t.string :url, null: false
t.string :certificate
t.string :authorization
t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade
t.index :build_id, unique: true
end
end
end
......@@ -358,6 +358,15 @@ ActiveRecord::Schema.define(version: 20180629191052) do
add_index "ci_builds_metadata", ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree
add_index "ci_builds_metadata", ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree
create_table "ci_builds_runner_session", id: :bigserial, force: :cascade do |t|
t.integer "build_id", null: false
t.string "url", null: false
t.string "certificate"
t.string "authorization"
end
add_index "ci_builds_runner_session", ["build_id"], name: "index_ci_builds_runner_session_on_build_id", unique: true, using: :btree
create_table "ci_group_variables", force: :cascade do |t|
t.string "key", null: false
t.text "value"
......@@ -2191,6 +2200,7 @@ ActiveRecord::Schema.define(version: 20180629191052) do
add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_builds_metadata", "projects", on_delete: :cascade
add_foreign_key "ci_builds_runner_session", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
......
......@@ -511,6 +511,39 @@ GET /projects/:id
}
```
If the project is a fork, and you provide a valid token to authenticate, the
`forked_from_project` field will appear in the response.
```json
{
"id":3,
...
"forked_from_project":{
"id":13083,
"description":"GitLab Community Edition",
"name":"GitLab Community Edition",
"name_with_namespace":"GitLab.org / GitLab Community Edition",
"path":"gitlab-ce",
"path_with_namespace":"gitlab-org/gitlab-ce",
"created_at":"2013-09-26T06:02:36.000Z",
"default_branch":"master",
"tag_list":[],
"ssh_url_to_repo":"git@gitlab.com:gitlab-org/gitlab-ce.git",
"http_url_to_repo":"https://gitlab.com/gitlab-org/gitlab-ce.git",
"web_url":"https://gitlab.com/gitlab-org/gitlab-ce",
"avatar_url":"https://assets.gitlab-static.net/uploads/-/system/project/avatar/13083/logo-extra-whitespace.png",
"star_count":3812,
"forks_count":3561,
"last_activity_at":"2018-01-02T11:40:26.570Z"
}
...
}
```
## Get project users
Get the users list of a project.
......
......@@ -57,9 +57,9 @@ so, the CI/CD job must be named `container_scanning` and the artifact path must
[Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
CAUTION: **Caution:**
Container Scanning was previously using `sast:container` for job name and
Before GitLab 11.0, Container Scanning was previously using `sast:container` for job name and
`gl-sast-container-report.json` for the artifact name. While these old names
are still maintained they have been deprecated with GitLab 11.0 and may be removed
are still maintained, they have been deprecated with GitLab 11.0 and may be removed
in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
configuration to reflect that change.
......
......@@ -43,7 +43,19 @@ exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibbo
RequestHeader set X_FORWARDED_PROTO 'https'
```
1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should adjust them to your need and environment. Add any other configuration you need.
1. Edit /etc/gitlab/gitlab.rb configuration file to enable OmniAuth and add
Shibboleth as an OmniAuth provider. User attributes will be sent from the
Apache reverse proxy to GitLab as headers with the names from the Shibboleth
attribute mapping. Therefore the values of the `args` hash
should be in the form of `"HTTP_ATTRIBUTE"`. The keys in the hash are arguments
to the [OmniAuth::Strategies::Shibboleth class](https://github.com/toyokazu/omniauth-shibboleth/blob/master/lib/omniauth/strategies/shibboleth.rb)
and are documented by the [omniauth-shibboleth gem](https://github.com/toyokazu/omniauth-shibboleth)
(take care to note the version of the gem packaged with GitLab). If some of
your users appear to be authenticated by Shibboleth and Apache, but GitLab
rejects their account with a URI that contains "e-mail is invalid" then your
Shibboleth Identity Provider or Attribute Authority may be asserting multiple
e-mail addresses. In this instance, you might consider setting the
`multi_values` argument to `first`.
File should look like this:
```
......@@ -58,14 +70,15 @@ gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_providers'] = [
{
"name" => 'shibboleth',
"args" => {
"shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
"name" => "'shibboleth"',
"label" => "Text for Login Button",
"args" => {
"shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
"shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID",
"uid_field" => 'HTTP_EPPN',
"name_field" => 'HTTP_CN',
"uid_field" => 'HTTP_EPPN',
"name_field" => 'HTTP_CN',
"info_fields" => { "email" => 'HTTP_MAIL'}
}
}
}
]
......
......@@ -1203,6 +1203,7 @@ module API
class RunnerInfo < Grape::Entity
expose :metadata_timeout, as: :timeout
expose :runner_session_url
end
class Step < Grape::Entity
......
......@@ -81,6 +81,11 @@ module API
requires :token, type: String, desc: %q(Runner's authentication token)
optional :last_update, type: String, desc: %q(Runner's queue last_update token)
optional :info, type: Hash, desc: %q(Runner's metadata)
optional :session, type: Hash, desc: %q(Runner's session data) do
optional :url, type: String, desc: %q(Session's url)
optional :certificate, type: String, desc: %q(Session's certificate)
optional :authorization, type: String, desc: %q(Session's authorization)
end
end
post '/request' do
authenticate_runner!
......@@ -90,14 +95,16 @@ module API
break no_content!
end
if current_runner.runner_queue_value_latest?(params[:last_update])
header 'X-GitLab-Last-Update', params[:last_update]
runner_params = declared_params(include_missing: false)
if current_runner.runner_queue_value_latest?(runner_params[:last_update])
header 'X-GitLab-Last-Update', runner_params[:last_update]
Gitlab::Metrics.add_event(:build_not_found_cached)
break no_content!
end
new_update = current_runner.ensure_runner_queue_value
result = ::Ci::RegisterJobService.new(current_runner).execute
result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params)
if result.valid?
if result.build
......
......@@ -247,6 +247,7 @@ module Gitlab
lines = highlighted_diff_lines
return if lines.empty?
return if blob.nil?
last_line = lines.last
......
......@@ -63,8 +63,12 @@ module Gitlab
# This saves us an RPC round trip.
return nil if commit_id.include?(':')
commit = repo.wrapped_gitaly_errors do
repo.gitaly_commit_client.find_commit(commit_id)
commit = repo.gitaly_migrate(:find_commit) do |is_enabled|
if is_enabled
repo.gitaly_commit_client.find_commit(commit_id)
else
rugged_find(repo, commit_id)
end
end
decorate(repo, commit) if commit
......@@ -74,6 +78,12 @@ module Gitlab
nil
end
def rugged_find(repo, commit_id)
obj = repo.rev_parse_target(commit_id)
obj.is_a?(Rugged::Commit) ? obj : nil
end
# Get last commit for HEAD
#
# Ex.
......
......@@ -12,8 +12,14 @@ module Gitlab
end
def conflicts
@conflicts ||= @target_repository.wrapped_gitaly_errors do
gitaly_conflicts_client(@target_repository).list_conflict_files.to_a
@conflicts ||= begin
@target_repository.gitaly_migrate(:conflicts_list_conflict_files) do |is_enabled|
if is_enabled
gitaly_conflicts_client(@target_repository).list_conflict_files.to_a
else
rugged_list_conflict_files
end
end
end
rescue GRPC::FailedPrecondition => e
raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message)
......@@ -22,8 +28,12 @@ module Gitlab
end
def resolve_conflicts(source_repository, resolution, source_branch:, target_branch:)
source_repository.wrapped_gitaly_errors do
gitaly_conflicts_client(source_repository).resolve_conflicts(@target_repository, resolution, source_branch, target_branch)
source_repository.gitaly_migrate(:conflicts_resolve_conflicts) do |is_enabled|
if is_enabled
gitaly_conflicts_client(source_repository).resolve_conflicts(@target_repository, resolution, source_branch, target_branch)
else
rugged_resolve_conflicts(source_repository, resolution, source_branch, target_branch)
end
end
end
......@@ -51,6 +61,57 @@ module Gitlab
def gitaly_conflicts_client(repository)
repository.gitaly_conflicts_client(@our_commit_oid, @their_commit_oid)
end
def write_resolved_file_to_index(repository, index, file, params)
if params[:sections]
resolved_lines = file.resolve_lines(params[:sections])
new_file = resolved_lines.map { |line| line[:full_line] }.join("\n")
new_file << "\n" if file.our_blob.data.end_with?("\n")
elsif params[:content]
new_file = file.resolve_content(params[:content])
end
our_path = file.our_path
oid = repository.rugged.write(new_file, :blob)
index.add(path: our_path, oid: oid, mode: file.our_mode)
index.conflict_remove(our_path)
end
def rugged_list_conflict_files
target_index = @target_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid)
# We don't need to do `with_repo_branch_commit` here, because the target
# project always fetches source refs when creating merge request diffs.
conflict_files(@target_repository, target_index)
end
def rugged_resolve_conflicts(source_repository, resolution, source_branch, target_branch)
source_repository.with_repo_branch_commit(@target_repository, target_branch) do
index = source_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid)
conflicts = conflict_files(source_repository, index)
resolution.files.each do |file_params|
conflict_file = conflict_for_path(conflicts, file_params[:old_path], file_params[:new_path])
write_resolved_file_to_index(source_repository, index, conflict_file, file_params)
end
unless index.conflicts.empty?
missing_files = index.conflicts.map { |file| file[:ours][:path] }
raise ResolutionError, "Missing resolutions for the following files: #{missing_files.join(', ')}"
end
commit_params = {
message: resolution.commit_message,
parents: [@our_commit_oid, @their_commit_oid]
}
source_repository.commit_index(resolution.user, source_branch, index, commit_params)
end
end
end
end
end
......
......@@ -555,8 +555,12 @@ module Gitlab
# diff options. The +options+ hash can also include :break_rewrites to
# split larger rewrites into delete/add pairs.
def diff(from, to, options = {}, *paths)
iterator = wrapped_gitaly_errors do
gitaly_commit_client.diff(from, to, options.merge(paths: paths))
iterator = gitaly_migrate(:diff_between) do |is_enabled|
if is_enabled
gitaly_commit_client.diff(from, to, options.merge(paths: paths))
else
diff_patches(from, to, options, *paths)
end
end
Gitlab::Git::DiffCollection.new(iterator, options)
......@@ -1587,6 +1591,17 @@ module Gitlab
tmp_entry
end
# Return the Rugged patches for the diff between +from+ and +to+.
def diff_patches(from, to, options = {}, *paths)
options ||= {}
break_rewrites = options[:break_rewrites]
actual_options = Gitlab::Git::Diff.filter_diff_options(options.merge(paths: paths))
diff = rugged.diff(from, to, actual_options)
diff.find_similar!(break_rewrites: break_rewrites)
diff.each_patch
end
def sort_branches(branches, sort_by)
case sort_by
when 'name'
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment