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: ...@@ -269,10 +269,10 @@ package-and-qa:
<<: *single-script-job-variables <<: *single-script-job-variables
SCRIPT_NAME: trigger-build-docs SCRIPT_NAME: trigger-build-docs
environment: 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 # 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 # 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 on_stop: review-docs-cleanup
# Trigger a manual docs build in gitlab-docs only on non docs-only branches. # Trigger a manual docs build in gitlab-docs only on non docs-only branches.
...@@ -307,7 +307,7 @@ review-docs-cleanup: ...@@ -307,7 +307,7 @@ review-docs-cleanup:
<<: *review-docs <<: *review-docs
stage: post-cleanup stage: post-cleanup
environment: environment:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_SLUG
action: stop action: stop
when: manual when: manual
script: script:
...@@ -325,11 +325,9 @@ cloud-native-image: ...@@ -325,11 +325,9 @@ cloud-native-image:
variables: variables:
GIT_DEPTH: "1" GIT_DEPTH: "1"
cache: {} cache: {}
before_script:
- gem install gitlab --no-rdoc --no-ri
- chmod 755 ./scripts/trigger-build-cloud-native
script: script:
- ./scripts/trigger-build-cloud-native - gem install gitlab --no-ri --no-rdoc
- ./trigger-build cng
only: only:
- tags@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee - tags@gitlab-org/gitlab-ee
......
...@@ -10,10 +10,6 @@ ...@@ -10,10 +10,6 @@
Capybara/CurrentPathExpectation: Capybara/CurrentPathExpectation:
Enabled: false Enabled: false
# Offense count: 956
Capybara/FeatureMethods:
Enabled: false
# Offense count: 23 # Offense count: 23
FactoryBot/DynamicAttributeDefinedStatically: FactoryBot/DynamicAttributeDefinedStatically:
Exclude: Exclude:
......
...@@ -2,6 +2,26 @@ ...@@ -2,6 +2,26 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. 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) ## 11.0.2 (2018-06-26)
### Fixed (8 changes, 1 of them is from the community) ### Fixed (8 changes, 1 of them is from the community)
......
...@@ -47,7 +47,7 @@ gem 'omniauth-google-oauth2', '~> 0.5.3' ...@@ -47,7 +47,7 @@ gem 'omniauth-google-oauth2', '~> 0.5.3'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2' gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.10' gem 'omniauth-saml', '~> 1.10'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4' gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3' gem 'omniauth-authentiq', '~> 0.3.3'
......
...@@ -568,7 +568,7 @@ GEM ...@@ -568,7 +568,7 @@ GEM
omniauth-saml (1.10.0) omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2) omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7) ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.4.0) omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
...@@ -1101,7 +1101,7 @@ DEPENDENCIES ...@@ -1101,7 +1101,7 @@ DEPENDENCIES
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4) omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
......
...@@ -572,7 +572,7 @@ GEM ...@@ -572,7 +572,7 @@ GEM
omniauth-saml (1.10.0) omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2) omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7) ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.4.0) omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1) omniauth-oauth (~> 1.1)
...@@ -1111,7 +1111,7 @@ DEPENDENCIES ...@@ -1111,7 +1111,7 @@ DEPENDENCIES
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4) omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
......
...@@ -120,6 +120,10 @@ All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/ ...@@ -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. 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? ## Is it any good?
[Yes](https://news.ycombinator.com/item?id=3067434) [Yes](https://news.ycombinator.com/item?id=3067434)
......
...@@ -28,16 +28,22 @@ export default { ...@@ -28,16 +28,22 @@ export default {
}, },
}, },
methods: { methods: {
buildUpdateRequest(list) {
return {
add_label_ids: [list.label.id],
};
},
addIssues() { addIssues() {
const firstListIndex = 1; const firstListIndex = 1;
const list = this.modal.selectedList || this.state.lists[firstListIndex]; const list = this.modal.selectedList || this.state.lists[firstListIndex];
const selectedIssues = ModalStore.getSelectedIssues(); const selectedIssues = ModalStore.getSelectedIssues();
const issueIds = selectedIssues.map(issue => issue.id); const issueIds = selectedIssues.map(issue => issue.id);
const req = this.buildUpdateRequest(list);
// Post the data to the backend // Post the data to the backend
gl.boardService.bulkUpdate(issueIds, { gl.boardService
add_label_ids: [list.label.id], .bulkUpdate(issueIds, req)
}).catch(() => { .catch(() => {
Flash(__('Failed to update issues, please try again.')); Flash(__('Failed to update issues, please try again.'));
selectedIssues.forEach((issue) => { selectedIssues.forEach((issue) => {
......
...@@ -121,8 +121,7 @@ ...@@ -121,8 +121,7 @@
<div <div
v-if="issuesCount > 0 && issues.length === 0" v-if="issuesCount > 0 && issues.length === 0"
class="empty-state add-issues-empty-state-filter text-center"> class="empty-state add-issues-empty-state-filter text-center">
<div <div class="svg-content">
class="svg-content">
<img :src="emptyStateSvg" /> <img :src="emptyStateSvg" />
</div> </div>
<div class="text-content"> <div class="text-content">
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
export default { export default Vue.extend({
props: { props: {
issue: { issue: {
type: Object, type: Object,
...@@ -25,19 +25,16 @@ ...@@ -25,19 +25,16 @@
removeIssue() { removeIssue() {
const { issue } = this; const { issue } = this;
const lists = issue.getLists(); const lists = issue.getLists();
const listLabelIds = lists.map(list => list.label.id); const req = this.buildPatchRequest(issue, lists);
let labelIds = issue.labels.map(label => label.id).filter(id => !listLabelIds.includes(id));
if (labelIds.length === 0) {
labelIds = [''];
}
const data = { const data = {
issue: { issue: this.seedPatchRequest(issue, req),
label_ids: labelIds,
},
}; };
if (data.issue.label_ids.length === 0) {
data.issue.label_ids = [''];
}
// Post the remove data // Post the remove data
Vue.http.patch(this.updateUrl, data).catch(() => { Vue.http.patch(this.updateUrl, data).catch(() => {
Flash(__('Failed to remove issue from board, please try again.')); Flash(__('Failed to remove issue from board, please try again.'));
...@@ -54,8 +51,30 @@ ...@@ -54,8 +51,30 @@
Store.detail.issue = {}; 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> </script>
<template> <template>
<div <div
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
/* global ListAssignee */ /* global ListAssignee */
import Vue from 'vue'; import Vue from 'vue';
import '~/vue_shared/models/label';
import IssueProject from './project'; import IssueProject from './project';
class ListIssue { class ListIssue {
......
...@@ -7,6 +7,24 @@ import queryData from '../utils/query_data'; ...@@ -7,6 +7,24 @@ import queryData from '../utils/query_data';
const PER_PAGE = 20; 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 { class List {
constructor(obj, defaultAvatar) { constructor(obj, defaultAvatar) {
this.id = obj.id; this.id = obj.id;
...@@ -14,8 +32,10 @@ class List { ...@@ -14,8 +32,10 @@ class List {
this.position = obj.position; this.position = obj.position;
this.title = obj.title; this.title = obj.title;
this.type = obj.list_type; 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.isExpanded = true;
this.page = 1; this.page = 1;
this.loading = true; this.loading = true;
...@@ -31,7 +51,7 @@ class List { ...@@ -31,7 +51,7 @@ class List {
this.title = this.assignee.name; this.title = this.assignee.name;
} }
if (this.type !== 'blank' && this.id) { if (!typeInfo.isBlank && this.id) {
this.getIssues().catch(() => { this.getIssues().catch(() => {
// TODO: handle request error // TODO: handle request error
}); });
...@@ -107,7 +127,7 @@ class List { ...@@ -107,7 +127,7 @@ class List {
return gl.boardService return gl.boardService
.getIssuesForList(this.id, data) .getIssuesForList(this.id, data)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then(data => {
this.loading = false; this.loading = false;
this.issuesSize = data.size; this.issuesSize = data.size;
...@@ -126,18 +146,7 @@ class List { ...@@ -126,18 +146,7 @@ class List {
return gl.boardService return gl.boardService
.newIssue(this.id, issue) .newIssue(this.id, issue)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then(data => this.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);
}
});
} }
createIssues(data) { createIssues(data) {
...@@ -217,6 +226,25 @@ class List { ...@@ -217,6 +226,25 @@ class List {
return !matchesRemove; 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; window.List = List;
export default List;
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import _ from 'underscore'; import _ from 'underscore';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.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 Tooltip from '~/vue_shared/directives/tooltip';
import { truncateSha } from '~/lib/utils/text_utility'; import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
...@@ -12,6 +13,7 @@ export default { ...@@ -12,6 +13,7 @@ export default {
ClipboardButton, ClipboardButton,
EditButton, EditButton,
Icon, Icon,
FileIcon,
}, },
directives: { directives: {
Tooltip, Tooltip,
...@@ -139,18 +141,20 @@ export default { ...@@ -139,18 +141,20 @@ export default {
:name="collapseIcon" :name="collapseIcon"
:size="16" :size="16"
aria-hidden="true" aria-hidden="true"
class="diff-toggle-caret" class="diff-toggle-caret append-right-5"
@click.stop="handleToggle" @click.stop="handleToggle"
/> />
<a <a
ref="titleWrapper" ref="titleWrapper"
:href="titleLink" :href="titleLink"
class="append-right-4"
> >
<i <file-icon
:class="`fa-${icon}`" :file-name="filePath"
class="fa fa-fw" :size="18"
aria-hidden="true" aria-hidden="true"
></i> css-classes="js-file-icon append-right-5"
/>
<span v-if="diffFile.renamedFile"> <span v-if="diffFile.renamedFile">
<strong <strong
v-tooltip v-tooltip
......
...@@ -24,19 +24,21 @@ export default { ...@@ -24,19 +24,21 @@ export default {
...mapGetters(['commit']), ...mapGetters(['commit']),
parallelDiffLines() { parallelDiffLines() {
return this.diffLines.map(line => { return this.diffLines.map(line => {
const parallelLine = Object.assign({}, line);
if (line.left) { if (line.left) {
Object.assign(line, { left: trimFirstCharOfLineContent(line.left) }); parallelLine.left = trimFirstCharOfLineContent(line.left);
} else { } else {
Object.assign(line, { left: { type: EMPTY_CELL_TYPE } }); parallelLine.left = { type: EMPTY_CELL_TYPE };
} }
if (line.right) { if (line.right) {
Object.assign(line, { right: trimFirstCharOfLineContent(line.right) }); parallelLine.right = trimFirstCharOfLineContent(line.right);
} else { } else {
Object.assign(line, { right: { type: EMPTY_CELL_TYPE } }); parallelLine.right = { type: EMPTY_CELL_TYPE };
} }
return line; return parallelLine;
}); });
}, },
diffLinesLength() { diffLinesLength() {
......
...@@ -155,18 +155,21 @@ export function addContextLines(options) { ...@@ -155,18 +155,21 @@ export function addContextLines(options) {
} }
} }
export function trimFirstCharOfLineContent(line) { /**
if (!line.richText) { * Trims the first char of the `richText` property when it's either a space or a diff symbol.
return line; * @param {Object} line
} * @returns {Object}
*/
export function trimFirstCharOfLineContent(line = {}) {
const parsedLine = Object.assign({}, line);
const firstChar = line.richText.charAt(0); if (line.richText) {
const firstChar = parsedLine.richText.charAt(0);
if (firstChar === ' ' || firstChar === '+' || firstChar === '-') { if (firstChar === ' ' || firstChar === '+' || firstChar === '-') {
Object.assign(line, { parsedLine.richText = line.richText.substring(1);
richText: line.richText.substring(1), }
});
} }
return line; return parsedLine;
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import { __ } from '~/locale'; import { __ } from '~/locale';
import '~/gl_dropdown';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility'; import { timeFor } from './lib/utils/datetime_utility';
import ModalStore from './boards/stores/modal_store'; import ModalStore from './boards/stores/modal_store';
...@@ -251,3 +252,5 @@ export default class MilestoneSelect { ...@@ -251,3 +252,5 @@ export default class MilestoneSelect {
}); });
} }
} }
window.MilestoneSelect = MilestoneSelect;
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash'; import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service'; import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
...@@ -13,6 +15,7 @@ export default { ...@@ -13,6 +15,7 @@ export default {
Graph, Graph,
GraphGroup, GraphGroup,
EmptyState, EmptyState,
Icon,
}, },
props: { props: {
hasMetrics: { hasMetrics: {
...@@ -80,6 +83,14 @@ export default { ...@@ -80,6 +83,14 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
environmentsEndpoint: {
type: String,
required: true,
},
currentEnvironmentName: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -96,6 +107,7 @@ export default { ...@@ -96,6 +107,7 @@ export default {
this.service = new MonitoringService({ this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint, metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint, deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint,
}); });
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio); eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged); eventHub.$on('hoverChanged', this.hoverChanged);
...@@ -122,7 +134,11 @@ export default { ...@@ -122,7 +134,11 @@ export default {
this.service this.service
.getDeploymentData() .getDeploymentData()
.then(data => this.store.storeDeploymentData(data)) .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(() => { .then(() => {
if (this.store.groups.length < 1) { if (this.store.groups.length < 1) {
...@@ -155,8 +171,41 @@ export default { ...@@ -155,8 +171,41 @@ export default {
<template> <template>
<div <div
v-if="!showEmptyState" 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 <graph-group
v-for="(groupData, index) in store.groups" v-for="(groupData, index) in store.groups"
:key="index" :key="index"
......
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import statusCodes from '../../lib/utils/http_status'; import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils'; import { backOff } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
const MAX_REQUESTS = 3; const MAX_REQUESTS = 3;
...@@ -23,9 +24,10 @@ function backOffRequest(makeRequestCallback) { ...@@ -23,9 +24,10 @@ function backOffRequest(makeRequestCallback) {
} }
export default class MonitoringService { export default class MonitoringService {
constructor({ metricsEndpoint, deploymentEndpoint }) { constructor({ metricsEndpoint, deploymentEndpoint, environmentsEndpoint }) {
this.metricsEndpoint = metricsEndpoint; this.metricsEndpoint = metricsEndpoint;
this.deploymentEndpoint = deploymentEndpoint; this.deploymentEndpoint = deploymentEndpoint;
this.environmentsEndpoint = environmentsEndpoint;
} }
getGraphsData() { getGraphsData() {
...@@ -33,7 +35,7 @@ export default class MonitoringService { ...@@ -33,7 +35,7 @@ export default class MonitoringService {
.then(resp => resp.data) .then(resp => resp.data)
.then((response) => { .then((response) => {
if (!response || !response.data) { 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; return response.data;
}); });
...@@ -47,9 +49,20 @@ export default class MonitoringService { ...@@ -47,9 +49,20 @@ export default class MonitoringService {
.then(resp => resp.data) .then(resp => resp.data)
.then((response) => { .then((response) => {
if (!response || !response.deployments) { 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; 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 { ...@@ -24,6 +24,7 @@ export default class MonitoringStore {
constructor() { constructor() {
this.groups = []; this.groups = [];
this.deploymentData = []; this.deploymentData = [];
this.environmentsData = [];
} }
storeMetrics(groups = []) { storeMetrics(groups = []) {
...@@ -37,6 +38,10 @@ export default class MonitoringStore { ...@@ -37,6 +38,10 @@ export default class MonitoringStore {
this.deploymentData = deploymentData; this.deploymentData = deploymentData;
} }
storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData;
}
getMetricsCount() { getMetricsCount() {
return this.groups.reduce((count, group) => count + group.metrics.length, 0); return this.groups.reduce((count, group) => count + group.metrics.length, 0);
} }
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index'; import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
export default { export default {
components: { components: {
DiffFileHeader, DiffFileHeader,
SkeletonLoadingContainer, SkeletonLoadingContainer,
...@@ -27,7 +27,8 @@ export default { ...@@ -27,7 +27,8 @@ export default {
noteableData: state => state.notes.noteableData, noteableData: state => state.notes.noteableData,
}), }),
hasTruncatedDiffLines() { hasTruncatedDiffLines() {
return this.discussion.truncatedDiffLines && this.discussion.truncatedDiffLines.length !== 0; return this.discussion.truncatedDiffLines &&
this.discussion.truncatedDiffLines.length !== 0;
}, },
isDiscussionsExpanded() { isDiscussionsExpanded() {
return true; // TODO: @fatihacet - Fix this. return true; // TODO: @fatihacet - Fix this.
...@@ -55,9 +56,13 @@ export default { ...@@ -55,9 +56,13 @@ export default {
return window.gon.user_color_scheme; return window.gon.user_color_scheme;
}, },
normalizedDiffLines() { normalizedDiffLines() {
const lines = this.discussion.truncatedDiffLines || []; if (this.discussion.truncatedDiffLines) {
return this.discussion.truncatedDiffLines.map(line =>
trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)),
);
}
return lines.map(line => trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line))); return [];
}, },
}, },
mounted() { mounted() {
...@@ -83,7 +88,7 @@ export default { ...@@ -83,7 +88,7 @@ export default {
}); });
}, },
}, },
}; };
</script> </script>
<template> <template>
......
...@@ -85,9 +85,9 @@ export const allDiscussions = (state, getters) => { ...@@ -85,9 +85,9 @@ export const allDiscussions = (state, getters) => {
export const resolvedDiscussionsById = state => { export const resolvedDiscussionsById = state => {
const map = {}; const map = {};
state.discussions.forEach(n => { state.discussions.filter(d => d.resolvable).forEach(n => {
if (n.notes) { 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) { if (resolved) {
map[n.id] = n; map[n.id] = n;
......
...@@ -4,7 +4,7 @@ import Project from './project'; ...@@ -4,7 +4,7 @@ import Project from './project';
import ShortcutsNavigation from '../../shortcuts_navigation'; import ShortcutsNavigation from '../../shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const page = document.body.dataset.page; const { page } = document.body.dataset;
const newClusterViews = [ const newClusterViews = [
'projects:clusters:new', 'projects:clusters:new',
'projects:clusters:create_gcp', 'projects:clusters:create_gcp',
......
import initTerminal from '~/terminal/';
document.addEventListener('DOMContentLoaded', initTerminal);
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
methods: { methods: {
isValid(form) { isValid(form) {
return !form || return !form ||
form.find('.js-vue-markdown-field').length || form.find('.js-vue-markdown-field').length &&
$(this.$el).closest('form') === form[0]; $(this.$el).closest('form')[0] === form[0];
}, },
previewMarkdownTab(event, form) { previewMarkdownTab(event, form) {
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
} }
svg { svg {
vertical-align: text-bottom; vertical-align: middle;
} }
} }
......
...@@ -222,6 +222,23 @@ ...@@ -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 { .environments-actions {
.external-url, .external-url,
.monitoring-url, .monitoring-url,
......
...@@ -2,11 +2,12 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -2,11 +2,12 @@ class Projects::JobsController < Projects::ApplicationController
include SendFileUpload include SendFileUpload
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, before_action :authorize_read_build!
only: [:index, :show, :status, :raw, :trace]
before_action :authorize_update_build!, before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase] except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase]
before_action :authorize_erase_build!, only: [: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' layout 'project'
...@@ -134,6 +135,15 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -134,6 +135,15 @@ class Projects::JobsController < Projects::ApplicationController
end end
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 private
def authorize_update_build! def authorize_update_build!
...@@ -144,6 +154,14 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -144,6 +154,14 @@ class Projects::JobsController < Projects::ApplicationController
return access_denied! unless can?(current_user, :erase_build, build) return access_denied! unless can?(current_user, :erase_build, build)
end 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 def raw_send_params
{ type: 'text/plain; charset=utf-8', disposition: 'inline' } { type: 'text/plain; charset=utf-8', disposition: 'inline' }
end end
......
...@@ -26,4 +26,8 @@ class Board < ActiveRecord::Base ...@@ -26,4 +26,8 @@ class Board < ActiveRecord::Base
def closed_list def closed_list
lists.merge(List.closed).take lists.merge(List.closed).take
end end
def scoped?
false
end
end end
...@@ -27,7 +27,13 @@ module Ci ...@@ -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 :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 :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 :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 delegate :gitlab_deploy_token, to: :project
## ##
...@@ -174,6 +180,10 @@ module Ci ...@@ -174,6 +180,10 @@ module Ci
after_transition pending: :running do |build| after_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state build.ensure_metadata.update_timeout_state
end end
after_transition running: any do |build|
Ci::BuildRunnerSession.where(build: build).delete_all
end
end end
def ensure_metadata def ensure_metadata
...@@ -584,6 +594,10 @@ module Ci ...@@ -584,6 +594,10 @@ module Ci
super(options).merge(when: read_attribute(:when)) super(options).merge(when: read_attribute(:when))
end end
def has_terminal?
running? && runner_session_url.present?
end
private private
def update_artifacts_size 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 ...@@ -240,7 +240,7 @@ class KubernetesService < DeploymentService
end end
def deprecation_validation def deprecation_validation
return if active_changed?(from: true, to: false) return if active_changed?(from: true, to: false) || (new_record? && !active?)
if deprecated? if deprecated?
errors[:base] << deprecation_message errors[:base] << deprecation_message
......
...@@ -281,9 +281,9 @@ class Service < ActiveRecord::Base ...@@ -281,9 +281,9 @@ class Service < ActiveRecord::Base
def self.build_from_template(project_id, template) def self.build_from_template(project_id, template)
service = template.dup service = template.dup
service.active = false unless service.valid?
service.template = false service.template = false
service.project_id = project_id service.project_id = project_id
service.active = false if service.active? && !service.valid?
service service
end end
......
...@@ -18,6 +18,10 @@ module Ci ...@@ -18,6 +18,10 @@ module Ci
@subject.project.branch_allows_collaboration?(@user, @subject.ref) @subject.project.branch_allows_collaboration?(@user, @subject.ref)
end end
condition(:terminal, scope: :subject) do
@subject.has_terminal?
end
rule { protected_ref }.policy do rule { protected_ref }.policy do
prevent :update_build prevent :update_build
prevent :erase_build prevent :erase_build
...@@ -29,5 +33,7 @@ module Ci ...@@ -29,5 +33,7 @@ module Ci
enable :update_build enable :update_build
enable :update_commit_status enable :update_commit_status
end end
rule { can?(:update_build) & terminal }.enable :create_build_terminal
end end
end end
...@@ -25,6 +25,8 @@ class DiffFileEntity < Grape::Entity ...@@ -25,6 +25,8 @@ class DiffFileEntity < Grape::Entity
expose :can_modify_blob do |diff_file| expose :can_modify_blob do |diff_file|
merge_request = options[:merge_request] merge_request = options[:merge_request]
next unless diff_file.blob
if merge_request&.source_project && current_user if merge_request&.source_project && current_user
can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch) can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch)
else else
...@@ -108,6 +110,7 @@ class DiffFileEntity < Grape::Entity ...@@ -108,6 +110,7 @@ class DiffFileEntity < Grape::Entity
project = merge_request.target_project project = merge_request.target_project
next unless project next unless project
next unless diff_file.content_sha
project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path)) project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path))
end end
...@@ -125,6 +128,8 @@ class DiffFileEntity < Grape::Entity ...@@ -125,6 +128,8 @@ class DiffFileEntity < Grape::Entity
end end
expose :context_lines_path, if: -> (diff_file, _) { diff_file.text? } do |diff_file| 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)) project_blob_diff_path(diff_file.repository.project, tree_join(diff_file.content_sha, diff_file.file_path))
end end
......
...@@ -3,7 +3,7 @@ class DiscussionEntity < Grape::Entity ...@@ -3,7 +3,7 @@ class DiscussionEntity < Grape::Entity
include NotesHelper include NotesHelper
expose :id, :reply_id 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 :line_code, if: -> (d, _) { d.diff_discussion? }
expose :expanded?, as: :expanded expose :expanded?, as: :expanded
expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? } expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? }
......
...@@ -13,7 +13,7 @@ module Ci ...@@ -13,7 +13,7 @@ module Ci
@runner = runner @runner = runner
end end
def execute def execute(params = {})
builds = builds =
if runner.instance_type? if runner.instance_type?
builds_for_shared_runner builds_for_shared_runner
...@@ -41,6 +41,8 @@ module Ci ...@@ -41,6 +41,8 @@ module Ci
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
begin begin
build.runner_id = runner.id build.runner_id = runner.id
build.runner_session_attributes = params[:session] if params[:session].present?
build.run! build.run!
register_success(build) register_success(build)
......
...@@ -32,8 +32,9 @@ module Issues ...@@ -32,8 +32,9 @@ module Issues
def filter_assignee(issuable) def filter_assignee(issuable)
return if params[:assignee_ids].blank? return if params[:assignee_ids].blank?
# The number of assignees is limited by one for GitLab CE unless issuable.allows_multiple_assignees?
params[:assignee_ids] = params[:assignee_ids][0, 1] params[:assignee_ids] = params[:assignee_ids].take(1)
end
assignee_ids = params[:assignee_ids].select { |assignee_id| assignee_can_read?(issuable, assignee_id) } assignee_ids = params[:assignee_ids].select { |assignee_id| assignee_can_read?(issuable, assignee_id) }
......
# frozen_string_literal: true
class AttachmentUploader < GitlabUploader class AttachmentUploader < GitlabUploader
include RecordsUploads::Concern include RecordsUploads::Concern
include ObjectStorage::Concern include ObjectStorage::Concern
......
# frozen_string_literal: true
class AvatarUploader < GitlabUploader class AvatarUploader < GitlabUploader
include UploaderHelper include UploaderHelper
include RecordsUploads::Concern include RecordsUploads::Concern
......
# frozen_string_literal: true
class FaviconUploader < AttachmentUploader class FaviconUploader < AttachmentUploader
EXTENSION_WHITELIST = %w[png ico].freeze EXTENSION_WHITELIST = %w[png ico].freeze
......
# frozen_string_literal: true
class FileMover class FileMover
attr_reader :secret, :file_name, :model, :update_field attr_reader :secret, :file_name, :model, :update_field
......
# frozen_string_literal: true
# This class breaks the actual CarrierWave concept. # This class breaks the actual CarrierWave concept.
# Every uploader should use a base_dir that is model agnostic so we can build # 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. # back URLs from base_dir-relative paths saved in the `Upload` model.
...@@ -117,7 +119,7 @@ class FileUploader < GitlabUploader ...@@ -117,7 +119,7 @@ class FileUploader < GitlabUploader
end end
def markdown_link def markdown_link
markdown = "[#{markdown_name}](#{secure_url})" markdown = +"[#{markdown_name}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous? markdown.prepend("!") if image_or_video? || dangerous?
markdown markdown
end end
......
# frozen_string_literal: true
class GitlabUploader < CarrierWave::Uploader::Base class GitlabUploader < CarrierWave::Uploader::Base
class_attribute :options class_attribute :options
......
# frozen_string_literal: true
class JobArtifactUploader < GitlabUploader class JobArtifactUploader < GitlabUploader
extend Workhorse::UploadPath extend Workhorse::UploadPath
include ObjectStorage::Concern include ObjectStorage::Concern
......
# frozen_string_literal: true
class LegacyArtifactUploader < GitlabUploader class LegacyArtifactUploader < GitlabUploader
extend Workhorse::UploadPath extend Workhorse::UploadPath
include ObjectStorage::Concern include ObjectStorage::Concern
......
# frozen_string_literal: true
class LfsObjectUploader < GitlabUploader class LfsObjectUploader < GitlabUploader
extend Workhorse::UploadPath extend Workhorse::UploadPath
include ObjectStorage::Concern include ObjectStorage::Concern
......
# frozen_string_literal: true
class NamespaceFileUploader < FileUploader class NamespaceFileUploader < FileUploader
# Re-Override # Re-Override
def self.root def self.root
......
# frozen_string_literal: true
require 'fog/aws' require 'fog/aws'
require 'carrierwave/storage/fog' require 'carrierwave/storage/fog'
......
# frozen_string_literal: true
class PersonalFileUploader < FileUploader class PersonalFileUploader < FileUploader
# Re-Override # Re-Override
def self.root def self.root
......
# frozen_string_literal: true
module RecordsUploads module RecordsUploads
module Concern module Concern
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
# Extra methods for uploader # Extra methods for uploader
module UploaderHelper module UploaderHelper
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
......
# frozen_string_literal: true
module Workhorse module Workhorse
module UploadPath module UploadPath
def workhorse_upload_path def workhorse_upload_path
......
.modal{ id: "revoke-modal-#{token.id}" } .modal{ id: "revoke-modal-#{token.id}", tabindex: -1 }
.modal-dialog .modal-dialog
.modal-content .modal-content
.modal-header .modal-header
......
...@@ -2,15 +2,9 @@ ...@@ -2,15 +2,9 @@
- page_title "Metrics for environment", @environment.name - page_title "Metrics for environment", @environment.name
.prometheus-container{ class: container_class } .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'), #prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"clusters-path": project_clusters_path(@project), "clusters-path": project_clusters_path(@project),
"current-environment-name": @environment.name,
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'), "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
"empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'), "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
"empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'), "empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
...@@ -18,6 +12,7 @@ ...@@ -18,6 +12,7 @@
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'), "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), "metrics-endpoint": additional_metrics_project_environment_path(@project, @environment, format: :json),
"deployment-endpoint": project_environment_deployments_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), "project-path": project_path(@project),
"tags-path": project_tags_path(@project), "tags-path": project_tags_path(@project),
"has-metrics": "#{@environment.has_metrics?}" } } "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" } } %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container .sidebar-container
.blocks-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? } } #js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } }
...@@ -14,8 +18,8 @@ ...@@ -14,8 +18,8 @@
#{time_ago_with_tooltip(@build.artifacts_expire_at)} #{time_ago_with_tooltip(@build.artifacts_expire_at)}
- elsif @build.has_expiring_artifacts? - elsif @build.has_expiring_artifacts?
%p.build-detail-row %p.build-detail-row
The artifacts will be removed in The artifacts will be removed
%span= time_ago_with_tooltip @build.artifacts_expire_at #{time_ago_with_tooltip(@build.artifacts_expire_at)}
- if @build.artifacts? - if @build.artifacts?
.btn-group.d-flex{ role: :group } .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 @@ ...@@ -117,6 +117,9 @@
%li %li
JaCoCo (Java/Kotlin) JaCoCo (Java/Kotlin)
%code Total.*?([0-9]{1,3})% %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" = f.submit _('Save changes'), class: "btn btn-save"
......
...@@ -3,34 +3,34 @@ ...@@ -3,34 +3,34 @@
.d-none.d-sm-block .d-none.d-sm-block
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do = link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do
Edit = _('Edit')
- if can?(current_user, :update_project_snippet, @snippet) - 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 = 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 = _('Delete')
- if can?(current_user, :create_project_snippet, @project) - 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 = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: _("New snippet") do
New snippet = _('New snippet')
- if @snippet.submittable_as_spam_by?(current_user) - 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) - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.d-block.d-sm-none.dropdown .d-block.d-sm-none.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options = _('Options')
= icon('caret-down') = icon('caret-down')
.dropdown-menu.dropdown-menu-full-width .dropdown-menu.dropdown-menu-full-width
%ul %ul
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
%li %li
= link_to new_project_snippet_path(@project), title: "New snippet" do = link_to new_project_snippet_path(@project), title: _("New snippet") do
New snippet = _('New snippet')
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
%li %li
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
Delete = _('Delete')
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
%li %li
= link_to edit_project_snippet_path(@project, @snippet) do = link_to edit_project_snippet_path(@project, @snippet) do
Edit = _('Edit')
- if @snippet.submittable_as_spam_by?(current_user) - if @snippet.submittable_as_spam_by?(current_user)
%li %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 - 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 %h3.page-title
Edit Snippet = _("Edit Snippet")
%hr %hr
= render "shared/snippets/form", url: project_snippet_path(@project, @snippet) = render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
- page_title "Snippets" - page_title _("Snippets")
- if current_user - if current_user
.top-area .top-area
...@@ -7,6 +7,6 @@ ...@@ -7,6 +7,6 @@
.nav-controls .nav-controls
- if can?(current_user, :create_project_snippet, @project) - 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' = render 'snippets/snippets'
- add_to_breadcrumbs "Snippets", project_snippets_path(@project) - add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title "New" - breadcrumb_title _("New")
- page_title "New Snippets" - page_title _("New Snippets")
%h3.page-title %h3.page-title
New Snippet = _('New Snippet')
%hr %hr
= render "shared/snippets/form", url: project_snippets_path(@project, @snippet) = render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout - @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 - 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' = render 'shared/snippets/header'
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
- group = local_assigns.fetch(:group, false) - group = local_assigns.fetch(:group, false)
- @no_breadcrumb_container = true - @no_breadcrumb_container = true
- @no_container = true - @no_container = true
- @content_class = "issue-boards-content" - @content_class = "issue-boards-content js-focus-mode-board"
- breadcrumb_title _("Issue Board") - breadcrumb_title _("Issue Boards")
- page_title _("Boards") - page_title _("Boards")
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
...@@ -11,10 +11,11 @@ ...@@ -11,10 +11,11 @@
-# haml-lint:disable InlineJavaScript -# haml-lint:disable InlineJavaScript
%script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board" %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-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 }" } #board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
.d-none.d-sm-none.d-md-block .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-list
.boards-app-loading.text-center{ "v-if" => "loading" } .boards-app-loading.text-center{ "v-if" => "loading" }
......
- type = local_assigns.fetch(:type) - type = local_assigns.fetch(:type)
- board = local_assigns.fetch(:board, nil)
- block_css_class = type != :boards_modal ? 'row-content-block second-block' : '' - block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
- full_path = @project.present? ? @project.full_path : @group.full_path - 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-filters
.issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal } .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 = 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? - if params[:search].present?
= hidden_field_tag :search, params[:search] = hidden_field_tag :search, params[:search]
...@@ -99,13 +104,18 @@ ...@@ -99,13 +104,18 @@
%gl-emoji %gl-emoji
%span.js-data-value.prepend-left-10 %span.js-data-value.prepend-left-10
{{name}} {{name}}
= render_if_exists 'shared/issuable/filter_weight', type: type
%button.clear-search.hidden{ type: 'button' } %button.clear-search.hidden{ type: 'button' }
= icon('times') = icon('times')
.filter-dropdown-container .filter-dropdown-container
- if type == :boards - if type == :boards
- if can?(current_user, :admin_list, board.parent) .js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
= render_if_exists 'shared/issuable/board_create_list_dropdown', board: board - if user_can_admin_list
= render 'shared/issuable/board_create_list_dropdown', board: board
- if @project - if @project
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @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 - elsif type != :boards_modal
= render 'shared/sort_dropdown' = 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 ...@@ -279,6 +279,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :erase post :erase
get :trace, defaults: { format: 'json' } get :trace, defaults: { format: 'json' }
get :raw get :raw
get :terminal
get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', constraints: { format: nil }
end end
resource :artifacts, only: [] do 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 ...@@ -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", ["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 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| create_table "ci_group_variables", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.text "value" t.text "value"
...@@ -2191,6 +2200,7 @@ ActiveRecord::Schema.define(version: 20180629191052) do ...@@ -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", "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", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_builds_metadata", "projects", 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_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", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
......
...@@ -511,6 +511,39 @@ GET /projects/:id ...@@ -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 project users
Get the users list of a project. 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 ...@@ -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). [Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
CAUTION: **Caution:** 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 `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` in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
configuration to reflect that change. configuration to reflect that change.
......
...@@ -43,7 +43,19 @@ exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibbo ...@@ -43,7 +43,19 @@ exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibbo
RequestHeader set X_FORWARDED_PROTO 'https' 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: File should look like this:
``` ```
...@@ -58,7 +70,8 @@ gitlab_rails['omniauth_block_auto_created_users'] = false ...@@ -58,7 +70,8 @@ gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_enabled'] = true gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_providers'] = [ gitlab_rails['omniauth_providers'] = [
{ {
"name" => 'shibboleth', "name" => "'shibboleth"',
"label" => "Text for Login Button",
"args" => { "args" => {
"shib_session_id_field" => "HTTP_SHIB_SESSION_ID", "shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
"shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID", "shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID",
......
...@@ -1203,6 +1203,7 @@ module API ...@@ -1203,6 +1203,7 @@ module API
class RunnerInfo < Grape::Entity class RunnerInfo < Grape::Entity
expose :metadata_timeout, as: :timeout expose :metadata_timeout, as: :timeout
expose :runner_session_url
end end
class Step < Grape::Entity class Step < Grape::Entity
......
...@@ -81,6 +81,11 @@ module API ...@@ -81,6 +81,11 @@ module API
requires :token, type: String, desc: %q(Runner's authentication token) requires :token, type: String, desc: %q(Runner's authentication token)
optional :last_update, type: String, desc: %q(Runner's queue last_update token) optional :last_update, type: String, desc: %q(Runner's queue last_update token)
optional :info, type: Hash, desc: %q(Runner's metadata) 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 end
post '/request' do post '/request' do
authenticate_runner! authenticate_runner!
...@@ -90,14 +95,16 @@ module API ...@@ -90,14 +95,16 @@ module API
break no_content! break no_content!
end end
if current_runner.runner_queue_value_latest?(params[:last_update]) runner_params = declared_params(include_missing: false)
header 'X-GitLab-Last-Update', params[:last_update]
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) Gitlab::Metrics.add_event(:build_not_found_cached)
break no_content! break no_content!
end end
new_update = current_runner.ensure_runner_queue_value 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.valid?
if result.build if result.build
......
...@@ -247,6 +247,7 @@ module Gitlab ...@@ -247,6 +247,7 @@ module Gitlab
lines = highlighted_diff_lines lines = highlighted_diff_lines
return if lines.empty? return if lines.empty?
return if blob.nil?
last_line = lines.last last_line = lines.last
......
...@@ -63,8 +63,12 @@ module Gitlab ...@@ -63,8 +63,12 @@ module Gitlab
# This saves us an RPC round trip. # This saves us an RPC round trip.
return nil if commit_id.include?(':') return nil if commit_id.include?(':')
commit = repo.wrapped_gitaly_errors do commit = repo.gitaly_migrate(:find_commit) do |is_enabled|
if is_enabled
repo.gitaly_commit_client.find_commit(commit_id) repo.gitaly_commit_client.find_commit(commit_id)
else
rugged_find(repo, commit_id)
end
end end
decorate(repo, commit) if commit decorate(repo, commit) if commit
...@@ -74,6 +78,12 @@ module Gitlab ...@@ -74,6 +78,12 @@ module Gitlab
nil nil
end 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 # Get last commit for HEAD
# #
# Ex. # Ex.
......
...@@ -12,8 +12,14 @@ module Gitlab ...@@ -12,8 +12,14 @@ module Gitlab
end end
def conflicts def conflicts
@conflicts ||= @target_repository.wrapped_gitaly_errors do @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 gitaly_conflicts_client(@target_repository).list_conflict_files.to_a
else
rugged_list_conflict_files
end
end
end end
rescue GRPC::FailedPrecondition => e rescue GRPC::FailedPrecondition => e
raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message) raise Gitlab::Git::Conflict::Resolver::ConflictSideMissing.new(e.message)
...@@ -22,8 +28,12 @@ module Gitlab ...@@ -22,8 +28,12 @@ module Gitlab
end end
def resolve_conflicts(source_repository, resolution, source_branch:, target_branch:) def resolve_conflicts(source_repository, resolution, source_branch:, target_branch:)
source_repository.wrapped_gitaly_errors do 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) 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
end end
...@@ -51,6 +61,57 @@ module Gitlab ...@@ -51,6 +61,57 @@ module Gitlab
def gitaly_conflicts_client(repository) def gitaly_conflicts_client(repository)
repository.gitaly_conflicts_client(@our_commit_oid, @their_commit_oid) repository.gitaly_conflicts_client(@our_commit_oid, @their_commit_oid)
end 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 end
end end
......
...@@ -555,8 +555,12 @@ module Gitlab ...@@ -555,8 +555,12 @@ module Gitlab
# diff options. The +options+ hash can also include :break_rewrites to # diff options. The +options+ hash can also include :break_rewrites to
# split larger rewrites into delete/add pairs. # split larger rewrites into delete/add pairs.
def diff(from, to, options = {}, *paths) def diff(from, to, options = {}, *paths)
iterator = wrapped_gitaly_errors do iterator = gitaly_migrate(:diff_between) do |is_enabled|
if is_enabled
gitaly_commit_client.diff(from, to, options.merge(paths: paths)) gitaly_commit_client.diff(from, to, options.merge(paths: paths))
else
diff_patches(from, to, options, *paths)
end
end end
Gitlab::Git::DiffCollection.new(iterator, options) Gitlab::Git::DiffCollection.new(iterator, options)
...@@ -1587,6 +1591,17 @@ module Gitlab ...@@ -1587,6 +1591,17 @@ module Gitlab
tmp_entry tmp_entry
end 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) def sort_branches(branches, sort_by)
case sort_by case sort_by
when 'name' 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