Commit 426924d5 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-07-13

parents 211105b9 6717643c
...@@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => { ...@@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => {
store store
.dispatch('getMergeRequestData', { .dispatch('getMergeRequestData', {
projectId: fullProjectId, projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid, mergeRequestId: to.params.mrid,
}) })
.then(mr => { .then(mr => {
...@@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => { ...@@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => {
.then(() => .then(() =>
store.dispatch('getMergeRequestVersions', { store.dispatch('getMergeRequestVersions', {
projectId: fullProjectId, projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid, mergeRequestId: to.params.mrid,
}), }),
) )
.then(() => .then(() =>
store.dispatch('getMergeRequestChanges', { store.dispatch('getMergeRequestChanges', {
projectId: fullProjectId, projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid, mergeRequestId: to.params.mrid,
}), }),
) )
......
...@@ -4,12 +4,14 @@ import * as types from '../mutation_types'; ...@@ -4,12 +4,14 @@ import * as types from '../mutation_types';
export const getMergeRequestData = ( export const getMergeRequestData = (
{ commit, dispatch, state }, { commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) { if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) {
service service
.getProjectMergeRequestData(projectId, mergeRequestId, { render_html: true }) .getProjectMergeRequestData(targetProjectId || projectId, mergeRequestId, {
render_html: true,
})
.then(({ data }) => { .then(({ data }) => {
commit(types.SET_MERGE_REQUEST, { commit(types.SET_MERGE_REQUEST, {
projectPath: projectId, projectPath: projectId,
...@@ -38,12 +40,12 @@ export const getMergeRequestData = ( ...@@ -38,12 +40,12 @@ export const getMergeRequestData = (
export const getMergeRequestChanges = ( export const getMergeRequestChanges = (
{ commit, dispatch, state }, { commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) { if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) {
service service
.getProjectMergeRequestChanges(projectId, mergeRequestId) .getProjectMergeRequestChanges(targetProjectId || projectId, mergeRequestId)
.then(({ data }) => { .then(({ data }) => {
commit(types.SET_MERGE_REQUEST_CHANGES, { commit(types.SET_MERGE_REQUEST_CHANGES, {
projectPath: projectId, projectPath: projectId,
...@@ -71,12 +73,12 @@ export const getMergeRequestChanges = ( ...@@ -71,12 +73,12 @@ export const getMergeRequestChanges = (
export const getMergeRequestVersions = ( export const getMergeRequestVersions = (
{ commit, dispatch, state }, { commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {}, { projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) => ) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) { if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) {
service service
.getProjectMergeRequestVersions(projectId, mergeRequestId) .getProjectMergeRequestVersions(targetProjectId || projectId, mergeRequestId)
.then(res => res.data) .then(res => res.data)
.then(data => { .then(data => {
commit(types.SET_MERGE_REQUEST_VERSIONS, { commit(types.SET_MERGE_REQUEST_VERSIONS, {
......
<script> <script>
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import { n__ } from '~/locale'; import { n__ } from '~/locale';
import { webIDEUrl } from '~/lib/utils/url_utility'; import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
...@@ -43,7 +43,10 @@ export default { ...@@ -43,7 +43,10 @@ export default {
return this.isBranchTitleLong(this.mr.targetBranch); return this.isBranchTitleLong(this.mr.targetBranch);
}, },
webIdePath() { webIdePath() {
return webIDEUrl(this.mr.statusPath.replace('.json', '')); return mergeUrlParams({
target_project: this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath ?
this.mr.targetProjectFullPath : '',
}, webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`));
}, },
}, },
methods: { methods: {
......
...@@ -16,10 +16,11 @@ export default class MergeRequestStore { ...@@ -16,10 +16,11 @@ export default class MergeRequestStore {
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null; const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
this.squash = data.squash; this.squash = data.squash;
this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath || this.squashBeforeMergeHelpPath =
data.squash_before_merge_help_path; this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true; this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
this.iid = data.iid;
this.title = data.title; this.title = data.title;
this.targetBranch = data.target_branch; this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch; this.sourceBranch = data.source_branch;
...@@ -85,6 +86,8 @@ export default class MergeRequestStore { ...@@ -85,6 +86,8 @@ export default class MergeRequestStore {
this.isMergeAllowed = data.mergeable || false; this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing; this.mergeOngoing = data.merge_ongoing;
this.allowCollaboration = data.allow_collaboration; this.allowCollaboration = data.allow_collaboration;
this.targetProjectFullPath = data.target_project_full_path;
this.sourceProjectFullPath = data.source_project_full_path;
// Cherry-pick and Revert actions related // Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
...@@ -97,7 +100,8 @@ export default class MergeRequestStore { ...@@ -97,7 +100,8 @@ export default class MergeRequestStore {
this.hasCI = data.has_ci; this.hasCI = data.has_ci;
this.ciStatus = data.ci_status; this.ciStatus = data.ci_status;
this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled'; this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled';
this.isPipelinePassing = this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings'; this.isPipelinePassing =
this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings';
this.isPipelineSkipped = this.ciStatus === 'skipped'; this.isPipelineSkipped = this.ciStatus === 'skipped';
this.pipelineDetailedStatus = pipelineStatus; this.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false; this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
......
...@@ -348,11 +348,18 @@ ...@@ -348,11 +348,18 @@
} }
&.activities { &.activities {
display: flex;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
overflow: hidden;
.nav-links { .nav-links {
border-bottom: 0; border-bottom: 0;
} }
@include media-breakpoint-down(xs) {
display: block;
overflow: visible;
}
} }
} }
......
...@@ -657,6 +657,7 @@ module Ci ...@@ -657,6 +657,7 @@ module Ci
variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_NAME', value: name)
variables.append(key: 'CI_JOB_STAGE', value: stage) variables.append(key: 'CI_JOB_STAGE', value: stage)
variables.append(key: 'CI_COMMIT_SHA', value: sha) variables.append(key: 'CI_COMMIT_SHA', value: sha)
variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
variables.append(key: "CI_COMMIT_TAG", value: ref) if tag? variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
......
...@@ -375,8 +375,10 @@ class Project < ActiveRecord::Base ...@@ -375,8 +375,10 @@ class Project < ActiveRecord::Base
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600 chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
validates :build_timeout, allow_nil: true, validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600, numericality: { greater_than_or_equal_to: 10.minutes,
message: 'needs to be at least 10 minutes' } less_than: 1.month,
only_integer: true,
message: 'needs to be beetween 10 minutes and 1 month' }
# Returns a collection of projects that is either public or visible to the # Returns a collection of projects that is either public or visible to the
# logged in user. # logged in user.
......
...@@ -12,9 +12,15 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -12,9 +12,15 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_when_pipeline_succeeds expose :merge_when_pipeline_succeeds
expose :source_branch expose :source_branch
expose :source_project_id expose :source_project_id
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
end
expose :squash expose :squash
expose :target_branch expose :target_branch
expose :target_project_id expose :target_project_id
expose :target_project_full_path do |merge_request|
merge_request.project&.full_path
end
expose :allow_collaboration expose :allow_collaboration
expose :should_be_rebased?, as: :should_be_rebased expose :should_be_rebased?, as: :should_be_rebased
......
.nav-block.activities .nav-block.activities
= render 'shared/event_filter'
.controls .controls
= link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter'
.content_list .content_list
= spinner = spinner
.nav-block.activities .nav-block.activities
= render 'shared/event_filter'
.controls .controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter'
.content_list .content_list
= spinner = spinner
%div{ class: container_class } %div{ class: container_class }
.nav-block.activity-filter-block.activities .nav-block.activity-filter-block.activities
= render 'shared/event_filter'
.controls .controls
= link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do
= icon('rss') = icon('rss')
= render 'shared/event_filter'
.content_list.project-activity{ :"data-href" => activity_project_path(@project) } .content_list.project-activity{ :"data-href" => activity_project_path(@project) }
= spinner = spinner
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.flex-fill
.fade-left= icon('angle-left') .fade-left= icon('angle-left')
.fade-right= icon('angle-right') .fade-right= icon('angle-right')
%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs %ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs
......
---
title: Fix RSS button interaction on Dashboard, Project and Group activities
merge_request: 20549
author:
type: fixed
---
title: Add missing predefined variable and fix docs
merge_request:
author:
type: fixed
---
title: Limit maximum project build timeout setting to 1 month
merge_request: 20591
author:
type: fixed
---
title: Avoid process deadlock in popen by consuming input pipes
merge_request: 20600
author:
type: fixed
...@@ -49,6 +49,7 @@ future GitLab releases.** ...@@ -49,6 +49,7 @@ future GitLab releases.**
| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built | | **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. | | **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built | | **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. |
| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. | | **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
| **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. | | **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. |
| **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message | | **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message |
...@@ -121,6 +122,7 @@ future GitLab releases.** ...@@ -121,6 +122,7 @@ future GitLab releases.**
| `CI_BUILD_ID` | `CI_JOB_ID` | | `CI_BUILD_ID` | `CI_JOB_ID` |
| `CI_BUILD_REF` | `CI_COMMIT_SHA` | | `CI_BUILD_REF` | `CI_COMMIT_SHA` |
| `CI_BUILD_TAG` | `CI_COMMIT_TAG` | | `CI_BUILD_TAG` | `CI_COMMIT_TAG` |
| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` |
| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` | | `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` |
| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` | | `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` |
| `CI_BUILD_NAME` | `CI_JOB_NAME` | | `CI_BUILD_NAME` | `CI_JOB_NAME` |
......
...@@ -6,6 +6,18 @@ module API ...@@ -6,6 +6,18 @@ module API
before { authorize! :download_code, user_project } before { authorize! :download_code, user_project }
helpers do
def user_access
@user_access ||= Gitlab::UserAccess.new(current_user, project: user_project)
end
def authorize_push_to_branch!(branch)
unless user_access.can_push_to_branch?(branch)
forbidden!("You are not allowed to push into this branch")
end
end
end
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
...@@ -67,7 +79,7 @@ module API ...@@ -67,7 +79,7 @@ module API
optional :author_name, type: String, desc: 'Author name for commit' optional :author_name, type: String, desc: 'Author name for commit'
end end
post ':id/repository/commits' do post ':id/repository/commits' do
authorize! :push_code, user_project authorize_push_to_branch!(params[:branch])
attrs = declared_params attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch) attrs[:branch_name] = attrs.delete(:branch)
...@@ -147,7 +159,7 @@ module API ...@@ -147,7 +159,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch' requires :branch, type: String, desc: 'The name of the branch'
end end
post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project authorize_push_to_branch!(params[:branch])
commit = user_project.commit(params[:sha]) commit = user_project.commit(params[:sha])
not_found!('Commit') unless commit not_found!('Commit') unless commit
......
...@@ -21,6 +21,10 @@ module Gitlab ...@@ -21,6 +21,10 @@ module Gitlab
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
stdout.set_encoding(Encoding::ASCII_8BIT) stdout.set_encoding(Encoding::ASCII_8BIT)
# stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
# Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
err_reader = Thread.new { stderr.read }
yield(stdin) if block_given? yield(stdin) if block_given?
stdin.close stdin.close
...@@ -32,7 +36,7 @@ module Gitlab ...@@ -32,7 +36,7 @@ module Gitlab
cmd_output << stdout.read cmd_output << stdout.read
end end
cmd_output << stderr.read cmd_output << err_reader.value
cmd_status = wait_thr.value.exitstatus cmd_status = wait_thr.value.exitstatus
end end
...@@ -55,16 +59,20 @@ module Gitlab ...@@ -55,16 +59,20 @@ module Gitlab
rerr, werr = IO.pipe rerr, werr = IO.pipe
pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true) pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true)
# stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
# Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
out_reader = Thread.new { rout.read }
err_reader = Thread.new { rerr.read }
begin begin
status = process_wait_with_timeout(pid, timeout)
# close write ends so we could read them # close write ends so we could read them
wout.close wout.close
werr.close werr.close
cmd_output = rout.readlines.join status = process_wait_with_timeout(pid, timeout)
cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output
cmd_output = out_reader.value
cmd_output << err_reader.value # Copying the behaviour of `popen` which merges stderr into output
[cmd_output, status.exitstatus] [cmd_output, status.exitstatus]
rescue Timeout::Error => e rescue Timeout::Error => e
......
...@@ -34,11 +34,16 @@ module Gitlab ...@@ -34,11 +34,16 @@ module Gitlab
start = Time.now start = Time.now
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
# stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
# Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
out_reader = Thread.new { stdout.read }
err_reader = Thread.new { stderr.read }
yield(stdin) if block_given? yield(stdin) if block_given?
stdin.close stdin.close
cmd_stdout = stdout.read cmd_stdout = out_reader.value
cmd_stderr = stderr.read cmd_stderr = err_reader.value
cmd_status = wait_thr.value cmd_status = wait_thr.value
end end
......
...@@ -29,8 +29,10 @@ ...@@ -29,8 +29,10 @@
"merge_when_pipeline_succeeds": { "type": "boolean" }, "merge_when_pipeline_succeeds": { "type": "boolean" },
"source_branch": { "type": "string" }, "source_branch": { "type": "string" },
"source_project_id": { "type": "integer" }, "source_project_id": { "type": "integer" },
"source_project_full_path": { "type": ["string", "null"]},
"target_branch": { "type": "string" }, "target_branch": { "type": "string" },
"target_project_id": { "type": "integer" }, "target_project_id": { "type": "integer" },
"target_project_full_path": { "type": ["string", "null"]},
"allow_collaboration": { "type": "boolean"}, "allow_collaboration": { "type": "boolean"},
"metrics": { "metrics": {
"oneOf": [ "oneOf": [
......
...@@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => { ...@@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => {
beforeEach(() => { beforeEach(() => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
mr: { mr: {
iid: 1,
divergedCommitsCount: 12, divergedCommitsCount: 12,
sourceBranch: 'mr-widget-refactor', sourceBranch: 'mr-widget-refactor',
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
...@@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => { ...@@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => {
emailPatchesPath: '/mr/email-patches', emailPatchesPath: '/mr/email-patches',
plainDiffPath: '/mr/plainDiffPath', plainDiffPath: '/mr/plainDiffPath',
statusPath: 'abc', statusPath: 'abc',
sourceProjectFullPath: 'root/gitlab-ce',
targetProjectFullPath: 'gitlab-org/gitlab-ce',
}, },
}); });
}); });
...@@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => { ...@@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => {
const button = vm.$el.querySelector('.js-web-ide'); const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE'); expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual('/-/ide/projectabc'); expect(button.getAttribute('href')).toEqual(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce',
);
}); });
it('renders web ide button with relative URL', () => { it('renders web ide button with blank query string if target & source project branch', done => {
vm.mr.targetProjectFullPath = 'root/gitlab-ce';
vm.$nextTick(() => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=',
);
done();
});
});
it('renders web ide button with relative URL', done => {
gon.relative_url_root = '/gitlab'; gon.relative_url_root = '/gitlab';
vm.mr.iid = 2;
const button = vm.$el.querySelector('.js-web-ide'); vm.$nextTick(() => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE'); expect(button.textContent.trim()).toEqual('Open in Web IDE');
expect(button.getAttribute('href')).toEqual('/-/ide/projectabc'); expect(button.getAttribute('href')).toEqual(
'/gitlab/-/ide/project/root/gitlab-ce/merge_requests/2?target_project=gitlab-org%2Fgitlab-ce',
);
done();
});
}); });
it('renders download dropdown with links', () => { it('renders download dropdown with links', () => {
......
...@@ -29,8 +29,10 @@ export default { ...@@ -29,8 +29,10 @@ export default {
source_branch: 'daaaa', source_branch: 'daaaa',
source_branch_link: 'daaaa', source_branch_link: 'daaaa',
source_project_id: 19, source_project_id: 19,
source_project_full_path: '/group1/project1',
target_branch: 'master', target_branch: 'master',
target_project_id: 19, target_project_id: 19,
target_project_full_path: '/group2/project2',
metrics: { metrics: {
merged_by: { merged_by: {
name: 'Administrator', name: 'Administrator',
......
...@@ -2,6 +2,9 @@ require 'spec_helper' ...@@ -2,6 +2,9 @@ require 'spec_helper'
describe 'Gitlab::Git::Popen' do describe 'Gitlab::Git::Popen' do
let(:path) { Rails.root.join('tmp').to_s } let(:path) { Rails.root.join('tmp').to_s }
let(:test_string) { 'The quick brown fox jumped over the lazy dog' }
# The pipe buffer is typically 64K. This string is about 440K.
let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] }
let(:klass) do let(:klass) do
Class.new(Object) do Class.new(Object) do
...@@ -70,6 +73,15 @@ describe 'Gitlab::Git::Popen' do ...@@ -70,6 +73,15 @@ describe 'Gitlab::Git::Popen' do
end end
end end
end end
context 'with a process that writes a lot of data to stderr' do
it 'returns zero' do
output, status = klass.new.popen(spew_command, path)
expect(output).to include(test_string)
expect(status).to eq(0)
end
end
end end
context 'popen_with_timeout' do context 'popen_with_timeout' do
...@@ -85,6 +97,17 @@ describe 'Gitlab::Git::Popen' do ...@@ -85,6 +97,17 @@ describe 'Gitlab::Git::Popen' do
it { expect(output).to include('tests') } it { expect(output).to include('tests') }
end end
context 'multi-line string' do
let(:test_string) { "this is 1 line\n2nd line\n3rd line\n" }
let(:result) { klass.new.popen_with_timeout(['echo', test_string], timeout, path) }
let(:output) { result.first }
let(:status) { result.last }
it { expect(status).to be_zero }
# echo adds its own line
it { expect(output).to eq(test_string + "\n") }
end
context 'non-zero status' do context 'non-zero status' do
let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) } let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) }
let(:output) { result.first } let(:output) { result.first }
...@@ -110,6 +133,13 @@ describe 'Gitlab::Git::Popen' do ...@@ -110,6 +133,13 @@ describe 'Gitlab::Git::Popen' do
it "handles processes that do not shutdown correctly" do it "handles processes that do not shutdown correctly" do
expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error)
end end
it 'handles process that writes a lot of data to stderr' do
output, status = klass.new.popen_with_timeout(spew_command, timeout, path)
expect(output).to include(test_string)
expect(status).to eq(0)
end
end end
context 'timeout period' do context 'timeout period' do
......
...@@ -55,6 +55,19 @@ describe Gitlab::Popen do ...@@ -55,6 +55,19 @@ describe Gitlab::Popen do
end end
end end
context 'with a process that writes a lot of data to stderr' do
let(:test_string) { 'The quick brown fox jumped over the lazy dog' }
# The pipe buffer is typically 64K. This string is about 440K.
let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] }
it 'returns zero' do
output, status = @klass.new.popen(spew_command, path)
expect(output).to include(test_string)
expect(status).to eq(0)
end
end
context 'without a directory argument' do context 'without a directory argument' do
before do before do
@output, @status = @klass.new.popen(%w(ls)) @output, @status = @klass.new.popen(%w(ls))
......
...@@ -1615,6 +1615,7 @@ describe Ci::Build do ...@@ -1615,6 +1615,7 @@ describe Ci::Build do
{ key: 'CI_JOB_NAME', value: 'test', public: true }, { key: 'CI_JOB_NAME', value: 'test', public: true },
{ key: 'CI_JOB_STAGE', value: 'test', public: true }, { key: 'CI_JOB_STAGE', value: 'test', public: true },
{ key: 'CI_COMMIT_SHA', value: build.sha, public: true }, { key: 'CI_COMMIT_SHA', value: build.sha, public: true },
{ key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true },
{ key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true }, { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
{ key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
{ key: 'CI_BUILD_REF', value: build.sha, public: true }, { key: 'CI_BUILD_REF', value: build.sha, public: true },
......
...@@ -154,23 +154,25 @@ describe Project do ...@@ -154,23 +154,25 @@ describe Project do
it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
it { is_expected.to validate_length_of(:name).is_at_most(255) } it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_length_of(:path).is_at_most(255) } it { is_expected.to validate_length_of(:path).is_at_most(255) }
it { is_expected.to validate_length_of(:description).is_at_most(2000) } it { is_expected.to validate_length_of(:description).is_at_most(2000) }
it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) } it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
it { is_expected.to allow_value('').for(:ci_config_path) } it { is_expected.to allow_value('').for(:ci_config_path) }
it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) } it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) } it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) }
it { is_expected.to validate_presence_of(:creator) } it { is_expected.to validate_presence_of(:creator) }
it { is_expected.to validate_presence_of(:namespace) } it { is_expected.to validate_presence_of(:namespace) }
it { is_expected.to validate_presence_of(:repository_storage) } it { is_expected.to validate_presence_of(:repository_storage) }
it 'validates build timeout constraints' do
is_expected.to validate_numericality_of(:build_timeout)
.only_integer
.is_greater_than_or_equal_to(10.minutes)
.is_less_than(1.month)
.with_message('needs to be beetween 10 minutes and 1 month')
end
it 'does not allow new projects beyond user limits' do it 'does not allow new projects beyond user limits' do
project2 = build(:project) project2 = build(:project)
allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
......
...@@ -522,6 +522,38 @@ describe API::Commits do ...@@ -522,6 +522,38 @@ describe API::Commits do
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
end end
context 'when committing into a fork as a maintainer' do
include_context 'merge request allowing collaboration'
let(:project_id) { forked_project.id }
def push_params(branch_name)
{
branch: branch_name,
commit_message: 'Hello world',
actions: [
{
action: 'create',
file_path: 'foo/bar/baz.txt',
content: 'puts 8'
}
]
}
end
it 'allows pushing to the source branch of the merge request' do
post api(url, user), push_params('feature')
expect(response).to have_gitlab_http_status(:created)
end
it 'denies pushing to another branch' do
post api(url, user), push_params('other-branch')
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end end
describe 'GET /projects/:id/repository/commits/:sha/refs' do describe 'GET /projects/:id/repository/commits/:sha/refs' do
...@@ -1073,11 +1105,29 @@ describe API::Commits do ...@@ -1073,11 +1105,29 @@ describe API::Commits do
it 'returns 400 if you are not allowed to push to the target branch' do it 'returns 400 if you are not allowed to push to the target branch' do
post api(route, current_user), branch: 'feature' post api(route, current_user), branch: 'feature'
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('You are not allowed to push into this branch') expect(json_response['message']).to match(/You are not allowed to push into this branch/)
end end
end end
end end
context 'when cherry picking to a fork as a maintainer' do
include_context 'merge request allowing collaboration'
let(:project_id) { forked_project.id }
it 'allows access from a maintainer that to the source branch' do
post api(route, user), branch: 'feature'
expect(response).to have_gitlab_http_status(:created)
end
it 'denies cherry picking to another branch' do
post api(route, user), branch: 'master'
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end end
describe 'POST /projects/:id/repository/commits/:sha/comments' do describe 'POST /projects/:id/repository/commits/:sha/comments' do
......
...@@ -11,6 +11,21 @@ describe MergeRequestWidgetEntity do ...@@ -11,6 +11,21 @@ describe MergeRequestWidgetEntity do
described_class.new(resource, request: request).as_json described_class.new(resource, request: request).as_json
end end
describe 'source_project_full_path' do
it 'includes the full path of the source project' do
expect(subject[:source_project_full_path]).to be_present
end
context 'when the source project is missing' do
it 'returns `nil` for the source project' do
resource.allow_broken = true
resource.update!(source_project: nil)
expect(subject[:source_project_full_path]).to be_nil
end
end
end
describe 'pipeline' do describe 'pipeline' do
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) } let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) }
......
shared_context 'merge request allowing collaboration' do
include ProjectForksHelper
let(:canonical) { create(:project, :public, :repository) }
let(:forked_project) { fork_project(canonical, nil, repository: true) }
before do
canonical.add_maintainer(user)
create(:merge_request,
target_project: canonical,
source_project: forked_project,
source_branch: 'feature',
allow_collaboration: true)
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