Commit 459a4c86 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-04-17

# Conflicts:
#	doc/README.md
#	doc/user/project/merge_requests/index.md
#	doc/user/project/web_ide/index.md
#	locale/gitlab.pot

[ci skip]
parents 6e58c573 2cc78c0b
<!--
# Read me first!
Create this issue under https://dev.gitlab.org/gitlab/gitlabhq
Set the title to: `[Security] Description of the original issue`
-->
### Prior to the security release
- [ ] Read the [security process for developers] if you are not familiar with it.
- [ ] Link to the original issue adding it to the [links section](#links)
- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org`
- [ ] Create an MR targetting `org` `master`, prefixing your branch with `security-`
- [ ] Label your MR with the ~security label, prefix the title with `WIP: [master]`
- [ ] Add a link to the MR to the [links section](#links)
- [ ] Add a link to an EE MR if required
- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**.
- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping.
#### Backports
- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
- [ ] At this point, it might be easy to squash the commits from the MR into one
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation]
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
- [ ] Create each MR targetting the security branch `security-X-Y`
- [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/process.md#secpick-script
#### Documentation and final details
- [ ] Check the topic on #security to see when the next release is going ot happen and add a link to the [links section](#links)
- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
### Summary
#### Links
| Description | Link |
| -------- | -------- |
| Original issue | #TODO |
| Security release issue | #TODO |
| `master` MR | !TODO |
| `master` MR (EE) | !TODO |
| `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
#### Details
| Description | Details | Further details|
| -------- | -------- | -------- |
| Versions affected | X.Y | |
| Upgrade notes | | |
| GitLab Settings updated | Yes/No| |
| Migration required | Yes/No | |
[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/process.md
[RM list]: https://about.gitlab.com/release-managers/
/label ~security
...@@ -65,7 +65,7 @@ gem 'akismet', '~> 2.0' ...@@ -65,7 +65,7 @@ gem 'akismet', '~> 2.0'
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 3.0.0' gem 'devise-two-factor', '~> 3.0.0'
gem 'rqrcode-rails3', '~> 0.1.7' gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 3.0.0' gem 'attr_encrypted', '~> 3.1.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# GitLab Pages # GitLab Pages
......
...@@ -66,7 +66,7 @@ GEM ...@@ -66,7 +66,7 @@ GEM
unf unf
ast (2.4.0) ast (2.4.0)
atomic (1.1.99) atomic (1.1.99)
attr_encrypted (3.0.3) attr_encrypted (3.1.0)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.0) attr_required (1.0.0)
autoprefixer-rails (6.2.3) autoprefixer-rails (6.2.3)
...@@ -1033,7 +1033,7 @@ DEPENDENCIES ...@@ -1033,7 +1033,7 @@ DEPENDENCIES
asciidoctor (~> 1.5.6) asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8) asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.2.0) asset_sync (~> 2.2.0)
attr_encrypted (~> 3.0.0) attr_encrypted (~> 3.1.0)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
aws-sdk aws-sdk
babosa (~> 1.0.2) babosa (~> 1.0.2)
......
...@@ -16,6 +16,7 @@ class DeleteModal { ...@@ -16,6 +16,7 @@ class DeleteModal {
bindEvents() { bindEvents() {
this.$toggleBtns.on('click', this.setModalData.bind(this)); this.$toggleBtns.on('click', this.setModalData.bind(this));
this.$confirmInput.on('input', this.setDeleteDisabled.bind(this)); this.$confirmInput.on('input', this.setDeleteDisabled.bind(this));
this.$deleteBtn.on('click', this.setDisableDeleteButton.bind(this));
} }
setModalData(e) { setModalData(e) {
...@@ -30,6 +31,16 @@ class DeleteModal { ...@@ -30,6 +31,16 @@ class DeleteModal {
this.$deleteBtn.attr('disabled', e.currentTarget.value !== this.branchName); this.$deleteBtn.attr('disabled', e.currentTarget.value !== this.branchName);
} }
setDisableDeleteButton(e) {
if (this.$deleteBtn.is('[disabled]')) {
e.preventDefault();
e.stopPropagation();
return false;
}
return true;
}
updateModal() { updateModal() {
this.$branchName.text(this.branchName); this.$branchName.text(this.branchName);
this.$confirmInput.val(''); this.$confirmInput.val('');
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { s__, sprintf } from '../../locale'; import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue'; import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import { import { APPLICATION_INSTALLED, INGRESS } from '../constants';
APPLICATION_INSTALLED,
INGRESS,
} from '../constants';
export default { export default {
components: { components: {
applicationRow, applicationRow,
clipboardButton, clipboardButton,
},
props: {
applications: {
type: Object,
required: false,
default: () => ({}),
}, },
props: { helpPath: {
applications: { type: String,
type: Object, required: false,
required: false, default: '',
default: () => ({}),
},
helpPath: {
type: String,
required: false,
default: '',
},
ingressHelpPath: {
type: String,
required: false,
default: '',
},
ingressDnsHelpPath: {
type: String,
required: false,
default: '',
},
managePrometheusPath: {
type: String,
required: false,
default: '',
},
}, },
computed: { ingressHelpPath: {
generalApplicationDescription() { type: String,
return sprintf( required: false,
_.escape(s__( default: '',
},
ingressDnsHelpPath: {
type: String,
required: false,
default: '',
},
managePrometheusPath: {
type: String,
required: false,
default: '',
},
},
computed: {
generalApplicationDescription() {
return sprintf(
_.escape(
s__(
`ClusterIntegration|Install applications on your Kubernetes cluster. `ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`, Read more about %{helpLink}`,
)), { ),
helpLink: `<a href="${this.helpPath}"> ),
{
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))} ${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`, </a>`,
}, },
false, false,
); );
}, },
ingressId() { ingressId() {
return INGRESS; return INGRESS;
}, },
ingressInstalled() { ingressInstalled() {
return this.applications.ingress.status === APPLICATION_INSTALLED; return this.applications.ingress.status === APPLICATION_INSTALLED;
}, },
ingressExternalIp() { ingressExternalIp() {
return this.applications.ingress.externalIp; return this.applications.ingress.externalIp;
}, },
ingressDescription() { ingressDescription() {
const extraCostParagraph = sprintf( const extraCostParagraph = sprintf(
_.escape(s__( _.escape(
s__(
`ClusterIntegration|%{boldNotice} This will add some extra resources `ClusterIntegration|%{boldNotice} This will add some extra resources
like a load balancer, which may incur additional costs depending on like a load balancer, which may incur additional costs depending on
the hosting provider your Kubernetes cluster is installed on. If you are using GKE, the hosting provider your Kubernetes cluster is installed on. If you are using
you can %{pricingLink}.`, Google Kubernetes Engine, you can %{pricingLink}.`,
)), { ),
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`, ),
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer"> {
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`, ${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
}, },
false, false,
); );
const externalIpParagraph = sprintf( const externalIpParagraph = sprintf(
_.escape(s__( _.escape(
s__(
`ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS `ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS
at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}`, at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}`,
)), { ),
ingressHelpLink: `<a href="${this.ingressHelpPath}"> ),
{
ingressHelpLink: `<a href="${this.ingressHelpPath}">
${_.escape(s__('ClusterIntegration|More information'))} ${_.escape(s__('ClusterIntegration|More information'))}
</a>`, </a>`,
}, },
false, false,
); );
return ` return `
<p> <p>
${extraCostParagraph} ${extraCostParagraph}
</p> </p>
...@@ -98,22 +104,25 @@ ...@@ -98,22 +104,25 @@
${externalIpParagraph} ${externalIpParagraph}
</p> </p>
`; `;
}, },
prometheusDescription() { prometheusDescription() {
return sprintf( return sprintf(
_.escape(s__( _.escape(
s__(
`ClusterIntegration|Prometheus is an open-source monitoring system `ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`, with %{gitlabIntegrationLink} to monitor deployed applications.`,
)), { ),
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html" ),
{
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`, ${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
}, },
false, false,
); );
},
}, },
}; },
};
</script> </script>
<template> <template>
...@@ -205,7 +214,7 @@ ...@@ -205,7 +214,7 @@
> >
{{ s__(`ClusterIntegration|The IP address is in {{ s__(`ClusterIntegration|The IP address is in
the process of being assigned. Please check your Kubernetes the process of being assigned. Please check your Kubernetes
cluster or Quotas on GKE if it takes a long time.`) }} cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) }}
<a <a
:href="ingressHelpPath" :href="ingressHelpPath"
......
...@@ -36,11 +36,11 @@ const router = new VueRouter({ ...@@ -36,11 +36,11 @@ const router = new VueRouter({
base: `${gon.relative_url_root}/-/ide/`, base: `${gon.relative_url_root}/-/ide/`,
routes: [ routes: [
{ {
path: '/project/:namespace/:project', path: '/project/:namespace/:project+',
component: EmptyRouterComponent, component: EmptyRouterComponent,
children: [ children: [
{ {
path: ':targetmode/:branch/*', path: ':targetmode(edit|tree|blob)/:branch/*',
component: EmptyRouterComponent, component: EmptyRouterComponent,
}, },
{ {
......
...@@ -17,12 +17,8 @@ export default { ...@@ -17,12 +17,8 @@ export default {
}); });
}, },
[types.SET_DIRECTORY_DATA](state, { data, treePath }) { [types.SET_DIRECTORY_DATA](state, { data, treePath }) {
Object.assign(state, { Object.assign(state.trees[treePath], {
trees: Object.assign(state.trees, { tree: data,
[treePath]: {
tree: data,
},
}),
}); });
}, },
[types.SET_LAST_COMMIT_URL](state, { tree = state, url }) { [types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
......
...@@ -19,7 +19,6 @@ import AjaxCache from '~/lib/utils/ajax_cache'; ...@@ -19,7 +19,6 @@ import AjaxCache from '~/lib/utils/ajax_cache';
import Vue from 'vue'; import Vue from 'vue';
import syntaxHighlight from '~/syntax_highlight'; import syntaxHighlight from '~/syntax_highlight';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { __ } from '~/locale';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility'; import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash'; import Flash from './flash';
...@@ -198,6 +197,8 @@ export default class Notes { ...@@ -198,6 +197,8 @@ export default class Notes {
); );
this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff); this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff);
this.$wrapperEl.on('click', '.js-toggle-lazy-diff-retry-button', this.onClickRetryLazyLoad.bind(this));
// fetch notes when tab becomes visible // fetch notes when tab becomes visible
this.$wrapperEl.on('visibilitychange', this.visibilityChange); this.$wrapperEl.on('visibilitychange', this.visibilityChange);
// when issue status changes, we need to refresh data // when issue status changes, we need to refresh data
...@@ -244,6 +245,7 @@ export default class Notes { ...@@ -244,6 +245,7 @@ export default class Notes {
this.$wrapperEl.off('click', '.js-comment-resolve-button'); this.$wrapperEl.off('click', '.js-comment-resolve-button');
this.$wrapperEl.off('click', '.system-note-commit-list-toggler'); this.$wrapperEl.off('click', '.system-note-commit-list-toggler');
this.$wrapperEl.off('click', '.js-toggle-lazy-diff'); this.$wrapperEl.off('click', '.js-toggle-lazy-diff');
this.$wrapperEl.off('click', '.js-toggle-lazy-diff-retry-button');
this.$wrapperEl.off('ajax:success', '.js-main-target-form'); this.$wrapperEl.off('ajax:success', '.js-main-target-form');
this.$wrapperEl.off('ajax:success', '.js-discussion-note-form'); this.$wrapperEl.off('ajax:success', '.js-discussion-note-form');
this.$wrapperEl.off('ajax:complete', '.js-main-target-form'); this.$wrapperEl.off('ajax:complete', '.js-main-target-form');
...@@ -1431,16 +1433,15 @@ export default class Notes { ...@@ -1431,16 +1433,15 @@ export default class Notes {
syntaxHighlight(fileHolder); syntaxHighlight(fileHolder);
} }
static renderDiffError($container) { onClickRetryLazyLoad(e) {
$container.find('.line_content').html( const $retryButton = $(e.currentTarget);
$(`
<div class="nothing-here-block"> $retryButton.prop('disabled', true);
${__(
'Unable to load the diff.', return this.loadLazyDiff(e)
)} <a class="js-toggle-lazy-diff" href="javascript:void(0)">Try again</a>? .then(() => {
</div> $retryButton.prop('disabled', false);
`), });
);
} }
loadLazyDiff(e) { loadLazyDiff(e) {
...@@ -1449,20 +1450,35 @@ export default class Notes { ...@@ -1449,20 +1450,35 @@ export default class Notes {
$container.find('.js-toggle-lazy-diff').removeClass('js-toggle-lazy-diff'); $container.find('.js-toggle-lazy-diff').removeClass('js-toggle-lazy-diff');
const tableEl = $container.find('tbody'); const $tableEl = $container.find('tbody');
if (tableEl.length === 0) return; if ($tableEl.length === 0) return;
const fileHolder = $container.find('.file-holder'); const fileHolder = $container.find('.file-holder');
const url = fileHolder.data('linesPath'); const url = fileHolder.data('linesPath');
axios const $errorContainer = $container.find('.js-error-lazy-load-diff');
const $successContainer = $container.find('.js-success-lazy-load');
/**
* We only fetch resolved discussions.
* Unresolved discussions don't have an endpoint being provided.
*/
if (url) {
return axios
.get(url) .get(url)
.then(({ data }) => { .then(({ data }) => {
// Reset state in case last request returned error
$successContainer.removeClass('hidden');
$errorContainer.addClass('hidden');
Notes.renderDiffContent($container, data); Notes.renderDiffContent($container, data);
}) })
.catch(() => { .catch(() => {
Notes.renderDiffError($container); $successContainer.addClass('hidden');
$errorContainer.removeClass('hidden');
}); });
}
return Promise.resolve();
} }
toggleCommitList(e) { toggleCommitList(e) {
......
...@@ -10,29 +10,25 @@ export default class PerformanceBarService { ...@@ -10,29 +10,25 @@ export default class PerformanceBarService {
} }
static registerInterceptor(peekUrl, callback) { static registerInterceptor(peekUrl, callback) {
vueResourceInterceptor = (request, next) => { const interceptor = response => {
next(response => {
const requestId = response.headers['x-request-id'];
const requestUrl = response.url;
if (requestUrl !== peekUrl && requestId) {
callback(requestId, requestUrl);
}
});
};
Vue.http.interceptors.push(vueResourceInterceptor);
return axios.interceptors.response.use(response => {
const requestId = response.headers['x-request-id']; const requestId = response.headers['x-request-id'];
const requestUrl = response.config.url; // Get the request URL from response.config for Axios, and response for
// Vue Resource.
const requestUrl = (response.config || response).url;
const cachedResponse = response.headers['x-gitlab-from-cache'] === 'true';
if (requestUrl !== peekUrl && requestId) { if (requestUrl !== peekUrl && requestId && !cachedResponse) {
callback(requestId, requestUrl); callback(requestId, requestUrl);
} }
return response; return response;
}); };
vueResourceInterceptor = (request, next) => next(interceptor);
Vue.http.interceptors.push(vueResourceInterceptor);
return axios.interceptors.response.use(interceptor);
} }
static removeInterceptor(interceptor) { static removeInterceptor(interceptor) {
......
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetPipelineBlocked',
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
</span>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'PipelineFailed',
components: {
statusIcon,
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
{{ s__(`mrWidget|The pipeline for this merge request failed.
Please retry the job or push a new commit to fix the failure`) }}
</span>
</div>
</div>
</template>
...@@ -31,7 +31,7 @@ export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/compon ...@@ -31,7 +31,7 @@ export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/compon
export { default as ShaMismatchState } from './components/states/sha_mismatch.vue'; export { default as ShaMismatchState } from './components/states/sha_mismatch.vue';
export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue'; export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as PipelineFailedState } from './components/states/pipeline_failed.vue';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue'; export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue'; export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue'; export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue';
......
...@@ -175,7 +175,7 @@ ...@@ -175,7 +175,7 @@
</a> </a>
</span> </span>
<span v-else> <span v-else>
Cant find HEAD commit for this branch Can't find HEAD commit for this branch
</span> </span>
</div> </div>
</div> </div>
......
...@@ -503,3 +503,7 @@ fieldset[disabled] .btn, ...@@ -503,3 +503,7 @@ fieldset[disabled] .btn,
@extend %disabled; @extend %disabled;
} }
} }
.btn-no-padding {
padding: 0;
}
...@@ -160,6 +160,11 @@ ...@@ -160,6 +160,11 @@
} }
} }
} }
.diff-loading-error-block {
padding: $gl-padding * 2 $gl-padding;
text-align: center;
}
} }
.image { .image {
......
...@@ -821,3 +821,20 @@ ...@@ -821,3 +821,20 @@
max-width: 100%; max-width: 100%;
} }
} }
// Hack alert: we've rewritten `btn` class in a way that
// we've broken it and it is not possible to use with `btn-link`
// which causes a blank button when it's disabled and hovering
// The css in here is the boostrap one
.btn-link-retry {
&[disabled] {
cursor: not-allowed;
box-shadow: none;
opacity: .65;
&:hover {
color: $file-mode-changed;
text-decoration: none;
}
}
}
...@@ -41,6 +41,11 @@ ...@@ -41,6 +41,11 @@
.commit-title { .commit-title {
margin: 0; margin: 0;
white-space: normal;
@media (max-width: $screen-sm-max) {
justify-content: flex-end;
}
} }
.ci-table { .ci-table {
......
...@@ -90,7 +90,7 @@ module TreeHelper ...@@ -90,7 +90,7 @@ module TreeHelper
end end
def commit_in_single_accessible_branch def commit_in_single_accessible_branch
branch_name = html_escape(selected_branch) branch_name = ERB::Util.html_escape(selected_branch)
message = _("Your changes can be committed to %{branch_name} because a merge "\ message = _("Your changes can be committed to %{branch_name} because a merge "\
"request is open.") % { branch_name: "<strong>#{branch_name}</strong>" } "request is open.") % { branch_name: "<strong>#{branch_name}</strong>" }
......
...@@ -4,7 +4,7 @@ module Ci ...@@ -4,7 +4,7 @@ module Ci
include HasVariable include HasVariable
include Presentable include Presentable
belongs_to :group belongs_to :group, class_name: "::Group"
alias_attribute :secret_value, :value alias_attribute :secret_value, :value
......
...@@ -338,6 +338,7 @@ class Repository ...@@ -338,6 +338,7 @@ class Repository
return unless empty? return unless empty?
expire_method_caches(%i(has_visible_content?)) expire_method_caches(%i(has_visible_content?))
raw_repository.expire_has_local_branches_cache
end end
def lookup_cache def lookup_cache
......
...@@ -13,7 +13,7 @@ module Clusters ...@@ -13,7 +13,7 @@ module Clusters
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
provider.make_errored!("Failed to configure GKE Cluster: #{e.message}") provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}")
end end
private private
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- unless expanded - unless expanded
- diff_data = { lines_path: project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion) } - diff_data = { lines_path: project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion) }
.diff-file.file-holder{ class: diff_file_class, data: diff_data } .diff-file.file-holder.js-lazy-load-discussion{ class: diff_file_class, data: diff_data }
.js-file-title.file-title.file-title-flex-parent .js-file-title.file-title.file-title-flex-parent
.file-header-content .file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
...@@ -28,8 +28,11 @@ ...@@ -28,8 +28,11 @@
%tr.line_holder.line-holder-placeholder %tr.line_holder.line-holder-placeholder
%td.old_line.diff-line-num %td.old_line.diff-line-num
%td.new_line.diff-line-num %td.new_line.diff-line-num
%td.line_content %td.line_content.js-success-lazy-load
.js-code-placeholder .js-code-placeholder
%td.js-error-lazy-load-diff.hidden.diff-loading-error-block
- button = button_tag(_("Try again"), class: "btn-link btn-link-retry btn-no-padding js-toggle-lazy-diff-retry-button")
= _("Unable to load the diff. %{button_try_again}").html_safe % { button_try_again: button}
= render "discussions/diff_discussion", discussions: [discussion], expanded: true = render "discussions/diff_discussion", discussions: [discussion], expanded: true
- else - else
- partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff' - partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff'
......
...@@ -8,6 +8,6 @@ ...@@ -8,6 +8,6 @@
%h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up Kubernetes cluster integration') %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up Kubernetes cluster integration')
%p= s_('ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab') %p= s_('ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab')
= link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' = link_to s_('ClusterIntegration|Create on Google Kubernetes Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
%p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster') %p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster')
= link_to s_('ClusterIntegration|Add an existing Kubernetes cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' = link_to s_('ClusterIntegration|Add an existing Kubernetes cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20'
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
= author_avatar(commit, size: 36) = author_avatar(commit, size: 36)
.commit-detail.flex-list .commit-detail.flex-list
.commit-content .commit-content.qa-commit-content
- if view_details && merge_request - if view_details && merge_request
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title" = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- else - else
......
- breadcrumb_title "Pipelines" - breadcrumb_title "Pipelines"
- page_title "New Pipeline" - page_title = s_("Pipeline|Run Pipeline")
%h3.page-title %h3.page-title
New Pipeline = s_("Pipeline|Run Pipeline")
%hr %hr
= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f| = form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
= form_errors(@pipeline) = form_errors(@pipeline)
.form-group .form-group
= f.label :ref, 'Create for', class: 'control-label' = f.label :ref, s_('Pipeline|Run on'), class: 'control-label'
.col-sm-10 .col-sm-10
= hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
= dropdown_tag(params[:ref] || @project.default_branch, = dropdown_tag(params[:ref] || @project.default_branch,
options: { toggle_class: 'js-branch-select wide git-revision-dropdown-toggle', options: { toggle_class: 'js-branch-select wide git-revision-dropdown-toggle',
filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: "Search branches", filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: s_("Pipeline|Search branches"),
data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } }) data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
.help-block Existing branch name, tag .help-block
= s_("Pipeline|Existing branch name, tag")
.form-actions .form-actions
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3 = f.submit s_('Pipeline|Run pipeline'), class: 'btn btn-success', tabindex: 3
= link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel' = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-default pull-right'
-# haml-lint:disable InlineJavaScript -# haml-lint:disable InlineJavaScript
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
- content_for :push_access_levels do - content_for :push_access_levels do
.push_access_levels-container .push_access_levels-container
= dropdown_tag('Select', = dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-push wide', options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push-select wide',
dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown capitalize-header',
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
= render 'projects/protected_branches/shared/create_protected_branch' = render 'projects/protected_branches/shared/create_protected_branch'
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
%td %td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level
= dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') , = dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header', options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }}) data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }})
.protected-branches-list.js-protected-branches-list .protected-branches-list.js-protected-branches-list.qa-protected-branches-list
- if @protected_branches.empty? - if @protected_branches.empty?
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
= f.hidden_field(:name) = f.hidden_field(:name)
= dropdown_tag('Select branch or create wildcard', = dropdown_tag('Select branch or create wildcard',
options: { toggle_class: 'js-protected-branch-select js-filter-submit wide git-revision-dropdown-toggle', options: { toggle_class: 'js-protected-branch-select js-filter-submit wide git-revision-dropdown-toggle qa-protected-branch-select',
filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: "Search protected branches", filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown qa-protected-branch-dropdown", placeholder: "Search protected branches",
footer_content: true, footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true, data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_branch_name], selected: params[:protected_branch_name],
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.settings-header .settings-header
%h4 %h4
Protected Branches Protected Branches
%button.btn.js-settings-toggle{ type: 'button' } %button.btn.js-settings-toggle.qa-expand-protected-branches{ type: 'button' }
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Keep stable branches secure and force developers to use merge requests. Keep stable branches secure and force developers to use merge requests.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } %tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
%td %td
%span.ref-name= protected_branch.name %span.ref-name.qa-protected-branch-name= protected_branch.name
- if @project.root_ref?(protected_branch.name) - if @project.root_ref?(protected_branch.name)
%span.label.label-info.prepend-left-5 default %span.label.label-info.prepend-left-5 default
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
- @options && @options.each do |key, value| - @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil = hidden_field_tag key, value, id: nil
.dropdown .dropdown
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown" } = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
.dropdown-page-one .dropdown-page-one
= dropdown_title _("Switch branch/tag") = dropdown_title _("Switch branch/tag")
= dropdown_filter _("Search branches and tags") = dropdown_filter _("Search branches and tags")
......
#!/usr/bin/env ruby
require 'optparse'
require 'open3'
require 'rainbow/refinement'
using Rainbow
BRANCH_PREFIX = 'security'.freeze
STABLE_BRANCH_SUFFIX = 'stable'.freeze
REMOTE = 'dev'.freeze
options = { version: nil, branch: nil, sha: nil }
parser = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on('-v', '--version 10.0', 'Version') do |version|
options[:version] = version&.tr('.', '-')
end
opts.on('-b', '--branch security-fix-branch', 'Original branch name') do |branch|
options[:branch] = branch
end
opts.on('-s', '--sha abcd', 'SHA to cherry pick') do |sha|
options[:sha] = sha
end
opts.on('-h', '--help', 'Displays Help') do
puts opts
exit
end
end
parser.parse!
abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
branch = [BRANCH_PREFIX, options[:branch], options[:version]].join('-').freeze
stable_branch = "#{options[:version]}-#{STABLE_BRANCH_SUFFIX}".freeze
command = "git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch}"
_stdin, stdout, stderr = Open3.popen3(command)
puts stdout.read&.green
puts stderr.read&.red
---
title: Improves wording in new pipeline page
merge_request:
author:
type: other
---
title: Breaks commit not found message in pipelines table
merge_request:
author:
type: fixed
---
title: Replace GKE acronym with Google Kubernetes Engine
merge_request:
author:
type: other
---
title: Fix confirmation modal for deleting a protected branch
merge_request: 18176
author: Paul Bonaud @PaulRbR
type: fixed
---
title: Fixes unresolved discussions rendering the error state instead of the diff
merge_request:
author:
type: fixed
---
title: '[API] Fix URLs in the `Link` header for `GET /projects/:id/repository/contributors`
when no value is passed for `order_by` or `sort`'
merge_request: 18393
author:
type: fixed
---
title: Fix undefined `html_escape` method during markdown rendering
merge_request: 18418
author:
type: fixed
---
title: Fix a case with secret variables being empty sometimes
merge_request: 18400
author:
type: fixed
---
title: Fixed IDE not loading for sub groups
merge_request:
author:
type: fixed
---
title: Fixed IDE not showing loading state when tree is loading
merge_request:
author:
type: fixed
---
title: Move PipelineFailed vue component
merge_request: 18277
author: George Tsiolis
type: performance
---
title: Memoize Git::Repository#has_visible_content?
merge_request:
author:
type: performance
---
title: Check if a ref exists is done by Gitaly by default
merge_request:
author:
type: performance
...@@ -27,16 +27,8 @@ module Sidekiq ...@@ -27,16 +27,8 @@ module Sidekiq
Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead. Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
MSG MSG
rescue Sidekiq::Worker::EnqueueFromTransactionError => e rescue Sidekiq::Worker::EnqueueFromTransactionError => e
if Rails.env.production? Rails.logger.error(e.message) if Rails.env.production?
Rails.logger.error(e.message) Gitlab::Sentry.track_exception(e)
if Gitlab::Sentry.enabled?
Gitlab::Sentry.context
Raven.capture_exception(e)
end
else
raise
end
end end
end end
......
...@@ -89,7 +89,10 @@ on projects and code. ...@@ -89,7 +89,10 @@ on projects and code.
- [Advanced Syntax Search](user/search/advanced_search_syntax.md): Use advanced queries for more targeted search results. **[STARTER]** - [Advanced Syntax Search](user/search/advanced_search_syntax.md): Use advanced queries for more targeted search results. **[STARTER]**
- [Snippets](user/snippets.md): Snippets allow you to create little bits of code. - [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
- [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis. - [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis.
<<<<<<< HEAD
- [GitLab Service Desk](user/project/service_desk.md): A simple way to allow people to create issues in your GitLab instance without needing their own user account. **[PREMIUM]** - [GitLab Service Desk](user/project/service_desk.md): A simple way to allow people to create issues in your GitLab instance without needing their own user account. **[PREMIUM]**
=======
>>>>>>> upstream/master
- [Web IDE](user/project/web_ide/index.md) - [Web IDE](user/project/web_ide/index.md)
#### Repositories #### Repositories
......
...@@ -183,7 +183,7 @@ GET /projects/:id/repository/contributors ...@@ -183,7 +183,7 @@ GET /projects/:id/repository/contributors
Parameters: Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `order_by` (optional) - Return contributors ordered by `name`, `email`, or `commits` fields. If not given contributors are ordered by commit date. - `order_by` (optional) - Return contributors ordered by `name`, `email`, or `commits` (orders by commit date) fields. Default is `commits`
- `sort` (optional) - Return contributors sorted in `asc` or `desc` order. Default is `asc` - `sort` (optional) - Return contributors sorted in `asc` or `desc` order. Default is `asc`
Response: Response:
......
...@@ -523,7 +523,7 @@ export default new Vuex.Store({ ...@@ -523,7 +523,7 @@ export default new Vuex.Store({
``` ```
_Note:_ If the state of the application is too complex, an individual file for the state may be better. _Note:_ If the state of the application is too complex, an individual file for the state may be better.
#### `actions.js` ##### `actions.js`
An action commits a mutatation. In this file, we will write the actions that will call the respective mutation: An action commits a mutatation. In this file, we will write the actions that will call the respective mutation:
```javascript ```javascript
...@@ -550,7 +550,7 @@ import { mapActions } from 'vuex'; ...@@ -550,7 +550,7 @@ import { mapActions } from 'vuex';
}; };
``` ```
#### `getters.js` ##### `getters.js`
Sometimes we may need to get derived state based on store state, like filtering for a specific prop. This can be done through the `getters`: Sometimes we may need to get derived state based on store state, like filtering for a specific prop. This can be done through the `getters`:
```javascript ```javascript
...@@ -573,7 +573,7 @@ import { mapGetters } from 'vuex'; ...@@ -573,7 +573,7 @@ import { mapGetters } from 'vuex';
}; };
``` ```
#### `mutations.js` ##### `mutations.js`
The only way to actually change state in a Vuex store is by committing a mutation. The only way to actually change state in a Vuex store is by committing a mutation.
```javascript ```javascript
...@@ -586,7 +586,7 @@ The only way to actually change state in a Vuex store is by committing a mutatio ...@@ -586,7 +586,7 @@ The only way to actually change state in a Vuex store is by committing a mutatio
}; };
``` ```
#### `mutations_types.js` ##### `mutations_types.js`
From [vuex mutations docs][vuex-mutations]: From [vuex mutations docs][vuex-mutations]:
> It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application. > It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
......
...@@ -23,6 +23,7 @@ are very appreciative of the work done by translators and proofreaders! ...@@ -23,6 +23,7 @@ are very appreciative of the work done by translators and proofreaders!
- Italian - Italian
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo) - Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
- Japanese - Japanese
- Yamana Tokiuji - [GitLab](https://gitlab.com/tokiuji), [Crowdin](https://crowdin.com/profile/yamana)
- Korean - Korean
- Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang) - Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
......
...@@ -31,8 +31,8 @@ the hardware requirements. ...@@ -31,8 +31,8 @@ the hardware requirements.
- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/) via [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/) - [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/) via [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/)
- [Install GitLab on Azure](azure/index.md) - [Install GitLab on Azure](azure/index.md)
- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md) - [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md)
- [Install GitLab on Google Container Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on - [Install GitLab on Google Kubernetes Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on
the full process of installing GitLab on Google Container Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production. the full process of installing GitLab on Google Kubernetes Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production.
- [Install on AWS](https://about.gitlab.com/aws/) - [Install on AWS](https://about.gitlab.com/aws/)
- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) - - _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) -
Quickly test any version of GitLab on DigitalOcean using Docker Machine. Quickly test any version of GitLab on DigitalOcean using Docker Machine.
......
...@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source. ...@@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source # Download and compile from source
cd /tmp cd /tmp
curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.2.tar.gz curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.3.tar.gz
echo '9acc4339b7a2ab484eea69d705923271682b7058015219cf5a7e6ed8dee5b5fb git-2.16.2.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.2.tar.gz echo 'dda229e9c73f4fbb7d4324e0d993e11311673df03f73b194c554c2e9451e17cd git-2.16.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.3.tar.gz
cd git-2.16.2/ cd git-2.16.3/
./configure ./configure
make prefix=/usr/local all make prefix=/usr/local all
......
...@@ -36,11 +36,14 @@ With **[GitLab Enterprise Edition][ee]**, you can also: ...@@ -36,11 +36,14 @@ With **[GitLab Enterprise Edition][ee]**, you can also:
- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers **[STARTER]** - Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers **[STARTER]**
- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history **[STARTER]** - [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history **[STARTER]**
- Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) **[STARTER]** - Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) **[STARTER]**
<<<<<<< HEAD
- Analyze your source code for vulnerabilities with [Static Application Security Testing](#static-application-security-testing) **[ULTIMATE]** - Analyze your source code for vulnerabilities with [Static Application Security Testing](#static-application-security-testing) **[ULTIMATE]**
- Analyze your dependencies for vulnerabilities with [Dependency Scanning](#dependency-scanning) **[ULTIMATE]** - Analyze your dependencies for vulnerabilities with [Dependency Scanning](#dependency-scanning) **[ULTIMATE]**
- Analyze your Docker images for vulnerabilities with [Container Scanning](#container-scanning) **[ULTIMATE]** - Analyze your Docker images for vulnerabilities with [Container Scanning](#container-scanning) **[ULTIMATE]**
- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](#dynamic-application-security-testing) **[ULTIMATE]** - Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](#dynamic-application-security-testing) **[ULTIMATE]**
- Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing) **[PREMIUM]** - Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing) **[PREMIUM]**
=======
>>>>>>> upstream/master
## Use cases ## Use cases
......
# Web IDE # Web IDE
> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) [GitLab Ultimate][ee] 10.4. > [Introduced in](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) [GitLab Ultimate][ee] 10.4.
<<<<<<< HEAD
> [Brought to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7. > [Brought to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7.
=======
> [Brought to GitLab Core][core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7.
>>>>>>> upstream/master
The Web IDE editor makes it faster and easier to contribute changes to your The Web IDE editor makes it faster and easier to contribute changes to your
projects by providing an advanced editor with commit staging. projects by providing an advanced editor with commit staging.
......
Feature: Project Builds Permissions
Background:
Given I sign in as a user
And project exists in some group namespace
And project has CI enabled
And project has a recent build
Scenario: I try to visit build details as guest
Given I am member of a project with a guest role
When I visit recent build details page
Then page status code should be 404
Scenario: I try to visit project builds page as guest
Given I am member of a project with a guest role
When I visit project builds page
Then page status code should be 404
Scenario: I try to visit build details of internal project without access to builds
Given The project is internal
And public access for builds is disabled
When I visit recent build details page
Then page status code should be 404
Scenario: I try to visit internal project builds page without access to builds
Given The project is internal
And public access for builds is disabled
When I visit project builds page
Then page status code should be 404
@javascript
Scenario: I try to visit build details of internal project with access to builds
Given The project is internal
And public access for builds is enabled
When I visit recent build details page
Then I see details of a build
And I see build trace
Scenario: I try to visit internal project builds page with access to builds
Given The project is internal
And public access for builds is enabled
When I visit project builds page
Then I see the build
Scenario: I try to download build artifacts as guest
Given I am member of a project with a guest role
And recent build has artifacts available
When I access artifacts download page
Then page status code should be 404
Scenario: I try to download build artifacts as reporter
Given I am member of a project with a reporter role
And recent build has artifacts available
When I access artifacts download page
Then download of build artifacts archive starts
class Spinach::Features::ProjectBuildsPermissions < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedBuilds
include SharedPaths
include RepoHelpers
end
...@@ -30,10 +30,6 @@ module SharedBuilds ...@@ -30,10 +30,6 @@ module SharedBuilds
visit project_job_path(@project, @build) visit project_job_path(@project, @build)
end end
step 'I visit project builds page' do
visit project_jobs_path(@project)
end
step 'recent build has artifacts available' do step 'recent build has artifacts available' do
artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip' artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
archive = fixture_file_upload(artifacts, 'application/zip') archive = fixture_file_upload(artifacts, 'application/zip')
...@@ -54,25 +50,4 @@ module SharedBuilds ...@@ -54,25 +50,4 @@ module SharedBuilds
expect(page.response_headers['Content-Type']).to eq 'application/zip' expect(page.response_headers['Content-Type']).to eq 'application/zip'
expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary' expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
end end
step 'I access artifacts download page' do
visit download_project_job_artifacts_path(@project, @build)
end
step 'I see details of a build' do
expect(page).to have_content "Job ##{@build.id}"
end
step 'I see build trace' do
expect(page).to have_css '#build-trace'
end
step 'I see the build' do
page.within('.build') do
expect(page).to have_content "##{@build.id}"
expect(page).to have_content @build.sha[0..7]
expect(page).to have_content @build.ref
expect(page).to have_content @build.name
end
end
end end
...@@ -455,12 +455,4 @@ module SharedPaths ...@@ -455,12 +455,4 @@ module SharedPaths
mr = MergeRequest.find_by(title: title) mr = MergeRequest.find_by(title: title)
project_merge_request_path(mr.target_project, mr) project_merge_request_path(mr.target_project, mr)
end end
# ----------------------------------------
# Errors
# ----------------------------------------
step 'page status code should be 404' do
expect(status_code).to eq 404
end
end end
...@@ -13,11 +13,6 @@ module SharedProject ...@@ -13,11 +13,6 @@ module SharedProject
@project.add_master(@user) @project.add_master(@user)
end end
step "project exists in some group namespace" do
@group = create(:group, name: 'some group')
@project = create(:project, :repository, namespace: @group, public_builds: false)
end
# Create a specific project called "Shop" # Create a specific project called "Shop"
step 'I own project "Shop"' do step 'I own project "Shop"' do
@project = Project.find_by(name: "Shop") @project = Project.find_by(name: "Shop")
...@@ -29,18 +24,6 @@ module SharedProject ...@@ -29,18 +24,6 @@ module SharedProject
@project ||= Project.first @project ||= Project.first
end end
# ----------------------------------------
# Project permissions
# ----------------------------------------
step 'I am member of a project with a guest role' do
@project.add_guest(@user)
end
step 'I am member of a project with a reporter role' do
@project.add_reporter(@user)
end
# ---------------------------------------- # ----------------------------------------
# Visibility of archived project # Visibility of archived project
# ---------------------------------------- # ----------------------------------------
...@@ -140,18 +123,6 @@ module SharedProject ...@@ -140,18 +123,6 @@ module SharedProject
create(:label, project: project, title: 'enhancement') create(:label, project: project, title: 'enhancement')
end end
step 'The project is internal' do
@project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
step 'public access for builds is enabled' do
@project.update(public_builds: true)
end
step 'public access for builds is disabled' do
@project.update(public_builds: false)
end
def user_owns_project(user_name:, project_name:, visibility: :private) def user_owns_project(user_name:, project_name:, visibility: :private)
user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore) user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore)
project = Project.find_by(name: project_name) project = Project.find_by(name: project_name)
......
...@@ -111,8 +111,8 @@ module API ...@@ -111,8 +111,8 @@ module API
end end
params do params do
use :pagination use :pagination
optional :order_by, type: String, values: %w[email name commits], default: nil, desc: 'Return contributors ordered by `name` or `email` or `commits`' optional :order_by, type: String, values: %w[email name commits], default: 'commits', desc: 'Return contributors ordered by `name` or `email` or `commits`'
optional :sort, type: String, values: %w[asc desc], default: nil, desc: 'Sort by asc (ascending) or desc (descending)' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
end end
get ':id/repository/contributors' do get ':id/repository/contributors' do
begin begin
......
...@@ -9,7 +9,7 @@ module Banzai ...@@ -9,7 +9,7 @@ module Banzai
lang_attr = lang.present? ? %Q{ lang="#{lang}"} : '' lang_attr = lang.present? ? %Q{ lang="#{lang}"} : ''
result = result =
"<pre>" \ "<pre>" \
"<code#{lang_attr}>#{html_escape(code)}</code>" \ "<code#{lang_attr}>#{ERB::Util.html_escape(code)}</code>" \
"</pre>" "</pre>"
out(result) out(result)
......
...@@ -6,7 +6,7 @@ module Banzai ...@@ -6,7 +6,7 @@ module Banzai
lang_attr = lang ? %Q{ lang="#{lang}"} : '' lang_attr = lang ? %Q{ lang="#{lang}"} : ''
"\n<pre>" \ "\n<pre>" \
"<code#{lang_attr}>#{html_escape(code)}</code>" \ "<code#{lang_attr}>#{ERB::Util.html_escape(code)}</code>" \
"</pre>" "</pre>"
end end
end end
......
...@@ -33,10 +33,7 @@ module Gitlab ...@@ -33,10 +33,7 @@ module Gitlab
# match the blob, which is a bug. But we shouldn't fail to render # match the blob, which is a bug. But we shouldn't fail to render
# completely in that case, even though we want to report the error. # completely in that case, even though we want to report the error.
rescue RangeError => e rescue RangeError => e
if Gitlab::Sentry.enabled? Gitlab::Sentry.track_exception(e, issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/45441')
Gitlab::Sentry.context
Raven.capture_exception(e)
end
end end
end end
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
CHECK_DIR = Rails.root.join('ee_compat_check') CHECK_DIR = Rails.root.join('ee_compat_check')
IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb}i.freeze IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb|locale/gitlab\.pot}i.freeze
PLEASE_READ_THIS_BANNER = %Q{ PLEASE_READ_THIS_BANNER = %Q{
============================================================ ============================================================
===================== PLEASE READ THIS ===================== ===================== PLEASE READ THIS =====================
......
...@@ -50,7 +50,7 @@ module Gitlab ...@@ -50,7 +50,7 @@ module Gitlab
status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429 status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429
[status_code, { 'ETag' => etag }, []] [status_code, { 'ETag' => etag, 'X-Gitlab-From-Cache' => 'true' }, []]
end end
def track_cache_miss(if_none_match, cached_value_present, route) def track_cache_miss(if_none_match, cached_value_present, route)
......
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
include Gitlab::Git::RepositoryMirroring include Gitlab::Git::RepositoryMirroring
include Gitlab::Git::Popen include Gitlab::Git::Popen
include Gitlab::EncodingHelper include Gitlab::EncodingHelper
include Gitlab::Utils::StrongMemoize
ALLOWED_OBJECT_DIRECTORIES_VARIABLES = %w[ ALLOWED_OBJECT_DIRECTORIES_VARIABLES = %w[
GIT_OBJECT_DIRECTORY GIT_OBJECT_DIRECTORY
...@@ -231,13 +232,13 @@ module Gitlab ...@@ -231,13 +232,13 @@ module Gitlab
end end
end end
def expire_has_local_branches_cache
clear_memoization(:has_local_branches)
end
def has_local_branches? def has_local_branches?
gitaly_migrate(:has_local_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| strong_memoize(:has_local_branches) do
if is_enabled uncached_has_local_branches?
gitaly_repository_client.has_local_branches?
else
has_local_branches_rugged?
end
end end
end end
...@@ -299,7 +300,8 @@ module Gitlab ...@@ -299,7 +300,8 @@ module Gitlab
# #
# Ref names must start with `refs/`. # Ref names must start with `refs/`.
def ref_exists?(ref_name) def ref_exists?(ref_name)
gitaly_migrate(:ref_exists) do |is_enabled| gitaly_migrate(:ref_exists,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled if is_enabled
gitaly_ref_exists?(ref_name) gitaly_ref_exists?(ref_name)
else else
...@@ -1573,6 +1575,16 @@ module Gitlab ...@@ -1573,6 +1575,16 @@ module Gitlab
private private
def uncached_has_local_branches?
gitaly_migrate(:has_local_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_repository_client.has_local_branches?
else
has_local_branches_rugged?
end
end
end
def local_write_ref(ref_path, ref, old_ref: nil, shell: true) def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
if shell if shell
shell_write_ref(ref_path, ref, old_ref) shell_write_ref(ref_path, ref, old_ref)
......
...@@ -18,6 +18,25 @@ module Gitlab ...@@ -18,6 +18,25 @@ module Gitlab
end end
end end
# This can be used for investigating exceptions that can be recovered from in
# code. The exception will still be raised in development and test
# environments.
#
# That way we can track down these exceptions with as much information as we
# need to resolve them.
#
# Provide an issue URL for follow up.
def self.track_exception(exception, issue_url: nil, extra: {})
if enabled?
extra[:issue_url] = issue_url if issue_url
context # Make sure we've set everything we know in the context
Raven.capture_exception(exception, extra: extra)
end
raise exception if should_raise?
end
def self.program_context def self.program_context
if Sidekiq.server? if Sidekiq.server?
'sidekiq' 'sidekiq'
...@@ -25,5 +44,9 @@ module Gitlab ...@@ -25,5 +44,9 @@ module Gitlab
'rails' 'rails'
end end
end end
def self.should_raise?
Rails.env.development? || Rails.env.test?
end
end end
end end
This diff is collapsed.
...@@ -31,6 +31,7 @@ module QA ...@@ -31,6 +31,7 @@ module QA
autoload :Project, 'qa/factory/resource/project' autoload :Project, 'qa/factory/resource/project'
autoload :MergeRequest, 'qa/factory/resource/merge_request' autoload :MergeRequest, 'qa/factory/resource/merge_request'
autoload :DeployKey, 'qa/factory/resource/deploy_key' autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :Branch, 'qa/factory/resource/branch'
autoload :SecretVariable, 'qa/factory/resource/secret_variable' autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner' autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token' autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
...@@ -132,6 +133,7 @@ module QA ...@@ -132,6 +133,7 @@ module QA
autoload :Repository, 'qa/page/project/settings/repository' autoload :Repository, 'qa/page/project/settings/repository'
autoload :CICD, 'qa/page/project/settings/ci_cd' autoload :CICD, 'qa/page/project/settings/ci_cd'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys' autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
autoload :ProtectedBranches, 'qa/page/project/settings/protected_branches'
autoload :SecretVariables, 'qa/page/project/settings/secret_variables' autoload :SecretVariables, 'qa/page/project/settings/secret_variables'
autoload :Runners, 'qa/page/project/settings/runners' autoload :Runners, 'qa/page/project/settings/runners'
autoload :MergeRequest, 'qa/page/project/settings/merge_request' autoload :MergeRequest, 'qa/page/project/settings/merge_request'
......
module QA
module Factory
module Resource
class Branch < Factory::Base
attr_accessor :project, :branch_name, :allow_to_push, :protected
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'protected-branch-project'
end
product :name do
Page::Project::Settings::Repository.act do
expand_protected_branches(&:last_branch_name)
end
end
product :push_allowance do
Page::Project::Settings::Repository.act do
expand_protected_branches(&:last_push_allowance)
end
end
def initialize
@branch_name = 'test/branch'
@allow_to_push = true
@protected = false
end
def fabricate!
project.visit!
Factory::Repository::Push.fabricate! do |resource|
resource.project = project
resource.file_name = 'kick-off.txt'
resource.commit_message = 'First commit'
end
branch = Factory::Repository::Push.fabricate! do |resource|
resource.project = project
resource.file_name = 'README.md'
resource.commit_message = 'Add readme'
resource.branch_name = "master:#{@branch_name}"
end
Page::Project::Show.act { wait_for_push }
# The upcoming process will make it access the Protected Branches page,
# select the already created branch and protect it according
# to `allow_to_push` variable.
return branch unless @protected
Page::Menu::Side.act do
click_repository_settings
end
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
page.select_branch(branch_name)
if allow_to_push
page.allow_devs_and_masters_to_push
else
page.allow_no_one_to_push
end
page.protect_branch
end
end
end
end
end
end
end
require 'cgi' require 'cgi'
require 'uri' require 'uri'
require 'open3'
module QA module QA
module Git module Git
class Repository class Repository
include Scenario::Actable include Scenario::Actable
attr_reader :push_error
def self.perform(*args) def self.perform(*args)
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
Dir.chdir(dir) { super } Dir.chdir(dir) { super }
...@@ -65,7 +68,8 @@ module QA ...@@ -65,7 +68,8 @@ module QA
end end
def push_changes(branch = 'master') def push_changes(branch = 'master')
`git push #{@uri.to_s} #{branch} #{suppress_output}` # capture3 returns stdout, stderr and status.
_, @push_error, _ = Open3.capture3("git push #{@uri} #{branch} #{suppress_output}")
end end
def commits def commits
......
module QA
module Page
module Project
module Settings
class ProtectedBranches < Page::Base
view 'app/views/projects/protected_branches/shared/_dropdown.html.haml' do
element :protected_branch_select
element :protected_branch_dropdown
end
view 'app/views/projects/protected_branches/_create_protected_branch.html.haml' do
element :allowed_to_push_select
element :allowed_to_push_dropdown
end
view 'app/views/projects/protected_branches/shared/_branches_list.html.haml' do
element :protected_branches_list
end
view 'app/views/projects/protected_branches/shared/_protected_branch.html.haml' do
element :protected_branch_name
end
def select_branch(branch_name)
click_element :protected_branch_select
within_element(:protected_branch_dropdown) do
click_on branch_name
end
end
def allow_no_one_to_push
allow_to_push('No one')
end
def allow_devs_and_masters_to_push
allow_to_push('Developers + Masters')
end
def protect_branch
click_on 'Protect'
end
def last_branch_name
within_element(:protected_branches_list) do
all('.qa-protected-branch-name').last
end
end
def last_push_allowance
within_element(:protected_branches_list) do
all('.qa-allowed-to-push').last
end
end
private
def allow_to_push(text)
click_element :allowed_to_push_select
within_element(:allowed_to_push_dropdown) do
click_on text
end
end
end
end
end
end
end
...@@ -14,6 +14,12 @@ module QA ...@@ -14,6 +14,12 @@ module QA
DeployKeys.perform(&block) DeployKeys.perform(&block)
end end
end end
def expand_protected_branches(&block)
expand_section('Protected Branches') do
ProtectedBranches.perform(&block)
end
end
end end
end end
end end
......
...@@ -21,6 +21,11 @@ module QA ...@@ -21,6 +21,11 @@ module QA
element :new_issue_link, "link_to 'New issue', new_project_issue_path(@project)" element :new_issue_link, "link_to 'New issue', new_project_issue_path(@project)"
end end
view 'app/views/shared/_ref_switcher.html.haml' do
element :branches_select
element :branches_dropdown
end
def choose_repository_clone_http def choose_repository_clone_http
choose_repository_clone('HTTP', 'http') choose_repository_clone('HTTP', 'http')
end end
...@@ -44,6 +49,18 @@ module QA ...@@ -44,6 +49,18 @@ module QA
find('.qa-project-name').text find('.qa-project-name').text
end end
def switch_to_branch(branch_name)
find_element(:branches_select).click
within_element(:branches_dropdown) do
click_on branch_name
end
end
def last_commit_content
find_element(:commit_content).text
end
def new_merge_request def new_merge_request
wait(reload: true) do wait(reload: true) do
has_css?(element_selector_css(:create_merge_request)) has_css?(element_selector_css(:create_merge_request))
......
module QA
feature 'branch protection support', :core do
given(:branch_name) { 'protected-branch' }
given(:commit_message) { 'Protected push commit message' }
given(:project) do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end
given(:location) do
Page::Project::Show.act do
choose_repository_clone_http
repository_location
end
end
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
scenario 'user is able to protect a branch' do
protected_branch = Factory::Resource::Branch.fabricate! do |resource|
resource.branch_name = branch_name
resource.project = project
resource.allow_to_push = true
resource.protected = true
end
expect(protected_branch.name).to have_content(branch_name)
expect(protected_branch.push_allowance).to have_content('Developers + Masters')
end
scenario 'users without authorization cannot push to protected branch' do
Factory::Resource::Branch.fabricate! do |resource|
resource.branch_name = branch_name
resource.project = project
resource.allow_to_push = false
resource.protected = true
end
project.visit!
Git::Repository.perform do |repository|
repository.location = location
repository.use_default_credentials
repository.act do
clone
configure_identity('GitLab QA', 'root@gitlab.com')
checkout('protected-branch')
commit_file('README.md', 'readme content', 'Add a readme')
push_changes('protected-branch')
end
expect(repository.push_error)
.to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
expect(repository.push_error)
.to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
end
end
require 'spec_helper'
describe 'IDE', :js do
describe 'sub-groups' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, :repository, namespace: subgroup) }
before do
subgroup_project.add_master(user)
sign_in(user)
visit project_path(subgroup_project)
click_link('Web IDE')
wait_for_requests
end
it 'loads project in web IDE' do
expect(page).to have_selector('.context-header', text: subgroup_project.name)
end
end
end
...@@ -27,6 +27,23 @@ describe 'Merge request > User scrolls to note on load', :js do ...@@ -27,6 +27,23 @@ describe 'Merge request > User scrolls to note on load', :js do
expect(fragment_position_top).to be < (page_scroll_y + page_height) expect(fragment_position_top).to be < (page_scroll_y + page_height)
end end
it 'renders un-collapsed notes with diff' do
page.current_window.resize_to(1000, 1000)
visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
page.execute_script "window.scrollTo(0,0)"
note_element = find(fragment_id)
note_container = note_element.ancestor('.js-toggle-container')
expect(note_element.visible?).to eq true
page.within note_container do
expect(page).not_to have_selector('.js-error-lazy-load-diff')
end
end
it 'expands collapsed notes' do it 'expands collapsed notes' do
visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}" visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
note_element = find(collapsed_fragment_id) note_element = find(collapsed_fragment_id)
......
...@@ -33,7 +33,7 @@ feature 'Gcp Cluster', :js do ...@@ -33,7 +33,7 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add Kubernetes cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on Google Kubernetes Engine'
end end
context 'when user filled form with valid parameters' do context 'when user filled form with valid parameters' do
...@@ -139,7 +139,7 @@ feature 'Gcp Cluster', :js do ...@@ -139,7 +139,7 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add Kubernetes cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on Google Kubernetes Engine'
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
...@@ -159,7 +159,7 @@ feature 'Gcp Cluster', :js do ...@@ -159,7 +159,7 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add Kubernetes cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on Google Kubernetes Engine'
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
...@@ -177,7 +177,7 @@ feature 'Gcp Cluster', :js do ...@@ -177,7 +177,7 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add Kubernetes cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on Google Kubernetes Engine'
end end
it 'user sees a login page' do it 'user sees a login page' do
......
...@@ -83,7 +83,7 @@ feature 'Clusters', :js do ...@@ -83,7 +83,7 @@ feature 'Clusters', :js do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add Kubernetes cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on Google Kubernetes Engine'
end end
it 'user sees a login page' do it 'user sees a login page' do
......
require 'spec_helper'
describe 'Project Jobs Permissions' do
let(:user) { create(:user) }
let(:group) { create(:group, name: 'some group') }
let(:project) { create(:project, :repository, namespace: group) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
let!(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
before do
sign_in(user)
project.enable_ci
end
describe 'jobs pages' do
shared_examples 'recent job page details responds with status' do |status|
before do
visit project_job_path(project, job)
end
it { expect(status_code).to eq(status) }
end
shared_examples 'project jobs page responds with status' do |status|
before do
visit project_jobs_path(project)
end
it { expect(status_code).to eq(status) }
end
context 'when public access for jobs is disabled' do
before do
project.update(public_builds: false)
end
context 'when user is a guest' do
before do
project.add_guest(user)
end
it_behaves_like 'recent job page details responds with status', 404
it_behaves_like 'project jobs page responds with status', 404
end
context 'when project is internal' do
before do
project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'recent job page details responds with status', 404
it_behaves_like 'project jobs page responds with status', 404
end
end
context 'when public access for jobs is enabled' do
before do
project.update(public_builds: true)
end
context 'when project is internal' do
before do
project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'recent job page details responds with status', 200 do
it 'renders job details', :js do
expect(page).to have_content "Job ##{job.id}"
expect(page).to have_css '#build-trace'
end
end
it_behaves_like 'project jobs page responds with status', 200 do
it 'renders job' do
page.within('.build') do
expect(page).to have_content("##{job.id}")
.and have_content(job.sha[0..7])
.and have_content(job.ref)
.and have_content(job.name)
end
end
end
end
end
end
describe 'artifacts page' do
context 'when recent job has artifacts available' do
before do
artifacts = Rails.root.join('spec/fixtures/ci_build_artifacts.zip')
archive = fixture_file_upload(artifacts, 'application/zip')
job.update_attributes(legacy_artifacts_file: archive)
end
context 'when public access for jobs is disabled' do
before do
project.update(public_builds: false)
end
context 'when user with guest role' do
before do
project.add_guest(user)
end
it 'responds with 404 status' do
visit download_project_job_artifacts_path(project, job)
expect(status_code).to eq(404)
end
end
context 'when user with reporter role' do
before do
project.add_reporter(user)
end
it 'starts download artifact' do
visit download_project_job_artifacts_path(project, job)
expect(status_code).to eq(200)
expect(page.response_headers['Content-Type']).to eq 'application/zip'
expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
end
end
end
end
end
end
...@@ -517,7 +517,7 @@ describe 'Pipelines', :js do ...@@ -517,7 +517,7 @@ describe 'Pipelines', :js do
end end
it 'creates a new pipeline' do it 'creates a new pipeline' do
expect { click_on 'Create pipeline' } expect { click_on 'Run pipeline' }
.to change { Ci::Pipeline.count }.by(1) .to change { Ci::Pipeline.count }.by(1)
expect(Ci::Pipeline.last).to be_web expect(Ci::Pipeline.last).to be_web
...@@ -526,7 +526,7 @@ describe 'Pipelines', :js do ...@@ -526,7 +526,7 @@ describe 'Pipelines', :js do
context 'without gitlab-ci.yml' do context 'without gitlab-ci.yml' do
before do before do
click_on 'Create pipeline' click_on 'Run pipeline'
end end
it { expect(page).to have_content('Missing .gitlab-ci.yml file') } it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
...@@ -539,7 +539,7 @@ describe 'Pipelines', :js do ...@@ -539,7 +539,7 @@ describe 'Pipelines', :js do
click_link 'master' click_link 'master'
end end
expect { click_on 'Create pipeline' } expect { click_on 'Run pipeline' }
.to change { Ci::Pipeline.count }.by(1) .to change { Ci::Pipeline.count }.by(1)
end end
end end
...@@ -557,7 +557,7 @@ describe 'Pipelines', :js do ...@@ -557,7 +557,7 @@ describe 'Pipelines', :js do
it 'has field to add a new pipeline' do it 'has field to add a new pipeline' do
expect(page).to have_selector('.js-branch-select') expect(page).to have_selector('.js-branch-select')
expect(find('.js-branch-select')).to have_content project.default_branch expect(find('.js-branch-select')).to have_content project.default_branch
expect(page).to have_content('Create for') expect(page).to have_content('Run on')
end end
end end
......
import $ from 'jquery';
import DeleteModal from '~/branches/branches_delete_modal';
describe('branches delete modal', () => {
describe('setDisableDeleteButton', () => {
let submitSpy;
let $deleteButton;
beforeEach(() => {
setFixtures(`
<div id="modal-delete-branch">
<form>
<button type="submit" class="js-delete-branch">Delete</button>
</form>
</div>
`);
$deleteButton = $('.js-delete-branch');
submitSpy = jasmine.createSpy('submit').and.callFake(event => event.preventDefault());
$('#modal-delete-branch form').on('submit', submitSpy);
// eslint-disable-next-line no-new
new DeleteModal();
});
it('does not submit if button is disabled', () => {
$deleteButton.attr('disabled', true);
$deleteButton.click();
expect(submitSpy).not.toHaveBeenCalled();
});
it('submits if button is not disabled', () => {
$deleteButton.attr('disabled', false);
$deleteButton.click();
expect(submitSpy).toHaveBeenCalled();
});
});
});
...@@ -55,6 +55,16 @@ describe('Multi-file store tree mutations', () => { ...@@ -55,6 +55,16 @@ describe('Multi-file store tree mutations', () => {
expect(tree.tree[1].name).toBe('submodule'); expect(tree.tree[1].name).toBe('submodule');
expect(tree.tree[2].name).toBe('blob'); expect(tree.tree[2].name).toBe('blob');
}); });
it('keeps loading state', () => {
mutations.CREATE_TREE(localState, { treePath: 'project/master' });
mutations.SET_DIRECTORY_DATA(localState, {
data,
treePath: 'project/master',
});
expect(localState.trees['project/master'].loading).toBe(true);
});
}); });
describe('REMOVE_ALL_CHANGES_FILES', () => { describe('REMOVE_ALL_CHANGES_FILES', () => {
......
import Vue from 'vue'; import Vue from 'vue';
import pipelineFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_failed'; import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
import { removeBreakLine } from 'spec/helpers/vue_component_helper';
describe('MRWidgetPipelineFailed', () => { describe('PipelineFailed', () => {
describe('template', () => { describe('template', () => {
const Component = Vue.extend(pipelineFailedComponent); const Component = Vue.extend(PipelineFailed);
const vm = new Component({ const vm = new Component({
el: document.createElement('div'), el: document.createElement('div'),
}); });
it('should have correct elements', () => { it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(vm.$el.innerText).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure'); expect(
removeBreakLine(vm.$el.innerText).trim(),
).toContain('The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure');
}); });
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import commitComp from '~/vue_shared/components/commit.vue'; import commitComp from '~/vue_shared/components/commit.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Commit component', () => { describe('Commit component', () => {
let props; let props;
...@@ -10,25 +11,28 @@ describe('Commit component', () => { ...@@ -10,25 +11,28 @@ describe('Commit component', () => {
CommitComponent = Vue.extend(commitComp); CommitComponent = Vue.extend(commitComp);
}); });
afterEach(() => {
component.$destroy();
});
it('should render a fork icon if it does not represent a tag', () => { it('should render a fork icon if it does not represent a tag', () => {
component = new CommitComponent({ component = mountComponent(CommitComponent, {
propsData: { tag: false,
tag: false, commitRef: {
commitRef: { name: 'master',
name: 'master', ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
},
commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
shortSha: 'b7836edd',
title: 'Commit message',
author: {
avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
path: '/jschatz1',
username: 'jschatz1',
},
}, },
}).$mount(); commitUrl:
'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
shortSha: 'b7836edd',
title: 'Commit message',
author: {
avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
path: '/jschatz1',
username: 'jschatz1',
},
});
expect(component.$el.querySelector('.icon-container').children).toContain('svg'); expect(component.$el.querySelector('.icon-container').children).toContain('svg');
}); });
...@@ -41,7 +45,8 @@ describe('Commit component', () => { ...@@ -41,7 +45,8 @@ describe('Commit component', () => {
name: 'master', name: 'master',
ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
}, },
commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', commitUrl:
'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
shortSha: 'b7836edd', shortSha: 'b7836edd',
title: 'Commit message', title: 'Commit message',
author: { author: {
...@@ -53,9 +58,7 @@ describe('Commit component', () => { ...@@ -53,9 +58,7 @@ describe('Commit component', () => {
commitIconSvg: '<svg></svg>', commitIconSvg: '<svg></svg>',
}; };
component = new CommitComponent({ component = mountComponent(CommitComponent, props);
propsData: props,
}).$mount();
}); });
it('should render a tag icon if it represents a tag', () => { it('should render a tag icon if it represents a tag', () => {
...@@ -63,7 +66,9 @@ describe('Commit component', () => { ...@@ -63,7 +66,9 @@ describe('Commit component', () => {
}); });
it('should render a link to the ref url', () => { it('should render a link to the ref url', () => {
expect(component.$el.querySelector('.ref-name').getAttribute('href')).toEqual(props.commitRef.ref_url); expect(component.$el.querySelector('.ref-name').getAttribute('href')).toEqual(
props.commitRef.ref_url,
);
}); });
it('should render the ref name', () => { it('should render the ref name', () => {
...@@ -71,7 +76,9 @@ describe('Commit component', () => { ...@@ -71,7 +76,9 @@ describe('Commit component', () => {
}); });
it('should render the commit short sha with a link to the commit url', () => { it('should render the commit short sha with a link to the commit url', () => {
expect(component.$el.querySelector('.commit-sha').getAttribute('href')).toEqual(props.commitUrl); expect(component.$el.querySelector('.commit-sha').getAttribute('href')).toEqual(
props.commitUrl,
);
expect(component.$el.querySelector('.commit-sha').textContent).toContain(props.shortSha); expect(component.$el.querySelector('.commit-sha').textContent).toContain(props.shortSha);
}); });
...@@ -88,21 +95,25 @@ describe('Commit component', () => { ...@@ -88,21 +95,25 @@ describe('Commit component', () => {
it('Should render the author avatar with title and alt attributes', () => { it('Should render the author avatar with title and alt attributes', () => {
expect( expect(
component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('data-original-title'), component.$el
.querySelector('.commit-title .avatar-image-container img')
.getAttribute('data-original-title'),
).toContain(props.author.username); ).toContain(props.author.username);
expect( expect(
component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'), component.$el
.querySelector('.commit-title .avatar-image-container img')
.getAttribute('alt'),
).toContain(`${props.author.username}'s avatar`); ).toContain(`${props.author.username}'s avatar`);
}); });
}); });
it('should render the commit title', () => { it('should render the commit title', () => {
expect( expect(component.$el.querySelector('a.commit-row-message').getAttribute('href')).toEqual(
component.$el.querySelector('a.commit-row-message').getAttribute('href'), props.commitUrl,
).toEqual(props.commitUrl); );
expect( expect(component.$el.querySelector('a.commit-row-message').textContent).toContain(
component.$el.querySelector('a.commit-row-message').textContent, props.title,
).toContain(props.title); );
}); });
}); });
...@@ -114,19 +125,18 @@ describe('Commit component', () => { ...@@ -114,19 +125,18 @@ describe('Commit component', () => {
name: 'master', name: 'master',
ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
}, },
commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', commitUrl:
'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
shortSha: 'b7836edd', shortSha: 'b7836edd',
title: null, title: null,
author: {}, author: {},
}; };
component = new CommitComponent({ component = mountComponent(CommitComponent, props);
propsData: props,
}).$mount();
expect( expect(component.$el.querySelector('.commit-title span').textContent).toContain(
component.$el.querySelector('.commit-title span').textContent, "Can't find HEAD commit for this branch",
).toContain('Cant find HEAD commit for this branch'); );
}); });
}); });
}); });
...@@ -79,6 +79,8 @@ describe Gitlab::Diff::Highlight do ...@@ -79,6 +79,8 @@ describe Gitlab::Diff::Highlight do
end end
it 'keeps the original rich line' do it 'keeps the original rich line' do
allow(Gitlab::Sentry).to receive(:track_exception)
code = %q{+ raise RuntimeError, "System commands must be given as an array of strings"} code = %q{+ raise RuntimeError, "System commands must be given as an array of strings"}
expect(subject[5].text).to eq(code) expect(subject[5].text).to eq(code)
...@@ -86,12 +88,9 @@ describe Gitlab::Diff::Highlight do ...@@ -86,12 +88,9 @@ describe Gitlab::Diff::Highlight do
end end
it 'reports to Sentry if configured' do it 'reports to Sentry if configured' do
allow(Gitlab::Sentry).to receive(:enabled?).and_return(true) expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
expect(Gitlab::Sentry).to receive(:context)
expect(Raven).to receive(:capture_exception)
subject expect { subject }. to raise_exception(RangeError)
end end
end end
end end
......
...@@ -470,9 +470,20 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -470,9 +470,20 @@ describe Gitlab::Git::Repository, seed_helper: true do
FileUtils.rm_rf(heads_dir) FileUtils.rm_rf(heads_dir)
FileUtils.mkdir_p(heads_dir) FileUtils.mkdir_p(heads_dir)
repository.expire_has_local_branches_cache
expect(repository.has_local_branches?).to eq(false) expect(repository.has_local_branches?).to eq(false)
end end
end end
context 'memoizes the value' do
it 'returns true' do
expect(repository).to receive(:uncached_has_local_branches?).once.and_call_original
2.times do
expect(repository.has_local_branches?).to eq(true)
end
end
end
end end
context 'with gitaly' do context 'with gitaly' do
......
...@@ -7,7 +7,49 @@ describe Gitlab::Sentry do ...@@ -7,7 +7,49 @@ describe Gitlab::Sentry do
described_class.context(nil) described_class.context(nil)
expect(Raven.tags_context[:locale]).to eq(I18n.locale.to_s) expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s)
end
end
describe '.track_exception' do
let(:exception) { RuntimeError.new('boom') }
before do
allow(described_class).to receive(:enabled?).and_return(true)
end
it 'raises the exception if it should' do
expect(described_class).to receive(:should_raise?).and_return(true)
expect { described_class.track_exception(exception) }
.to raise_error(RuntimeError)
end
context 'when exceptions should not be raised' do
before do
allow(described_class).to receive(:should_raise?).and_return(false)
end
it 'logs the exception with all attributes passed' do
expected_extras = {
some_other_info: 'info',
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
}
expect(Raven).to receive(:capture_exception)
.with(exception, extra: a_hash_including(expected_extras))
described_class.track_exception(
exception,
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1',
extra: { some_other_info: 'info' }
)
end
it 'sets the context' do
expect(described_class).to receive(:context)
described_class.track_exception(exception)
end
end end
end end
end end
...@@ -1469,6 +1469,12 @@ describe Repository do ...@@ -1469,6 +1469,12 @@ describe Repository do
repository.expire_emptiness_caches repository.expire_emptiness_caches
end end
it 'expires the memoized repository cache' do
allow(repository.raw_repository).to receive(:expire_has_local_branches_cache).and_call_original
repository.expire_emptiness_caches
end
end end
describe 'skip_merges option' do describe 'skip_merges option' do
......
...@@ -427,5 +427,20 @@ describe API::Repositories do ...@@ -427,5 +427,20 @@ describe API::Repositories do
let(:request) { get api(route, guest) } let(:request) { get api(route, guest) }
end end
end end
# Regression: https://gitlab.com/gitlab-org/gitlab-ce/issues/45363
describe 'Links header contains working URLs when no `order_by` nor `sort` is given' do
let(:project) { create(:project, :public, :repository) }
let(:current_user) { nil }
it 'returns `Link` header that includes URLs with default value for `order_by` & `sort`' do
get api(route, current_user)
first_link_url = response.headers['Link'].split(';').first
expect(first_link_url).to include('order_by=commits')
expect(first_link_url).to include('sort=asc')
end
end
end end
end end
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