Commit 81c8c98e authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-support-bitbucket-server-import

parents 0d9f74d2 fabf6a56
......@@ -359,7 +359,7 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
grape-path-helpers (1.0.5)
grape-path-helpers (1.0.6)
activesupport (>= 4, < 5.1)
grape (~> 1.0)
rake (~> 12)
......
......@@ -5,6 +5,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import timeAgoMixin from '~/vue_shared/mixins/timeago';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import { rightSidebarViews } from '../constants';
export default {
components: {
......@@ -49,6 +50,7 @@ export default {
this.stopPipelinePolling();
},
methods: {
...mapActions(['setRightPane']),
...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']),
startTimer() {
this.intervalId = setInterval(() => {
......@@ -69,24 +71,31 @@ export default {
return `${this.currentProject.web_url}/commit/${shortSha}`;
},
},
rightSidebarViews,
};
</script>
<template>
<footer class="ide-status-bar">
<div
v-if="lastCommit && lastCommitFormatedAge"
v-if="lastCommit"
class="ide-status-branch"
>
<span
v-if="latestPipeline && latestPipeline.details"
class="ide-status-pipeline"
>
<button
type="button"
class="p-0 border-0 h-50"
@click="setRightPane($options.rightSidebarViews.pipelines)"
>
<ci-icon
v-tooltip
:status="latestPipeline.details.status"
:title="latestPipeline.details.status.text"
/>
</button>
Pipeline
<a
:href="latestPipeline.details.status.details_path"
......
......@@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => {
store
.dispatch('getMergeRequestData', {
projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid,
})
.then(mr => {
......@@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => {
.then(() =>
store.dispatch('getMergeRequestVersions', {
projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid,
}),
)
.then(() =>
store.dispatch('getMergeRequestChanges', {
projectId: fullProjectId,
targetProjectId: to.query.target_project,
mergeRequestId: to.params.mrid,
}),
)
......
......@@ -4,12 +4,14 @@ import * as types from '../mutation_types';
export const getMergeRequestData = (
{ commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {},
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) =>
new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) {
service
.getProjectMergeRequestData(projectId, mergeRequestId, { render_html: true })
.getProjectMergeRequestData(targetProjectId || projectId, mergeRequestId, {
render_html: true,
})
.then(({ data }) => {
commit(types.SET_MERGE_REQUEST, {
projectPath: projectId,
......@@ -38,12 +40,12 @@ export const getMergeRequestData = (
export const getMergeRequestChanges = (
{ commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {},
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) =>
new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) {
service
.getProjectMergeRequestChanges(projectId, mergeRequestId)
.getProjectMergeRequestChanges(targetProjectId || projectId, mergeRequestId)
.then(({ data }) => {
commit(types.SET_MERGE_REQUEST_CHANGES, {
projectPath: projectId,
......@@ -71,12 +73,12 @@ export const getMergeRequestChanges = (
export const getMergeRequestVersions = (
{ commit, dispatch, state },
{ projectId, mergeRequestId, force = false } = {},
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {},
) =>
new Promise((resolve, reject) => {
if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) {
service
.getProjectMergeRequestVersions(projectId, mergeRequestId)
.getProjectMergeRequestVersions(targetProjectId || projectId, mergeRequestId)
.then(res => res.data)
.then(data => {
commit(types.SET_MERGE_REQUEST_VERSIONS, {
......
<script>
import $ from 'jquery';
import PerformanceBarService from '../services/performance_bar_service';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue';
import Flash from '../../flash';
export default {
components: {
detailedMetric,
......@@ -69,37 +66,13 @@ export default {
},
},
mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
this.loadRequestDetails(this.requestId, window.location.href);
this.currentRequest = this.requestId;
if (this.lineProfileModal.length) {
this.lineProfileModal.modal('toggle');
}
},
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
},
methods: {
loadRequestDetails(requestId, requestUrl) {
if (!this.store.canTrackRequest(requestUrl)) {
return;
}
this.store.addRequest(requestId, requestUrl);
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
this.store.addRequestDetails(requestId, res.data.data);
})
.catch(() =>
Flash(`Error getting performance bar results for ${requestId}`),
);
},
changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId;
},
......
import Vue from 'vue';
import performanceBarApp from './components/performance_bar_app.vue';
import Flash from '../flash';
import PerformanceBarService from './services/performance_bar_service';
import PerformanceBarStore from './stores/performance_bar_store';
export default ({ container }) =>
new Vue({
el: container,
components: {
performanceBarApp,
performanceBarApp: () => import('./components/performance_bar_app.vue'),
},
data() {
const performanceBarData = document.querySelector(this.$options.el)
......@@ -21,6 +22,34 @@ export default ({ container }) =>
profileUrl: performanceBarData.profileUrl,
};
},
mounted() {
this.interceptor = PerformanceBarService.registerInterceptor(
this.peekUrl,
this.loadRequestDetails,
);
this.loadRequestDetails(this.requestId, window.location.href);
},
beforeDestroy() {
PerformanceBarService.removeInterceptor(this.interceptor);
},
methods: {
loadRequestDetails(requestId, requestUrl) {
if (!this.store.canTrackRequest(requestUrl)) {
return;
}
this.store.addRequest(requestId, requestUrl);
PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId)
.then(res => {
this.store.addRequestDetails(requestId, res.data.data);
})
.catch(() =>
Flash(`Error getting performance bar results for ${requestId}`),
);
},
},
render(createElement) {
return createElement('performance-bar-app', {
props: {
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
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 clipboardButton from '~/vue_shared/components/clipboard_button.vue';
......@@ -43,7 +43,10 @@ export default {
return this.isBranchTitleLong(this.mr.targetBranch);
},
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: {
......
......@@ -16,10 +16,11 @@ export default class MergeRequestStore {
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
this.squash = data.squash;
this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath ||
data.squash_before_merge_help_path;
this.squashBeforeMergeHelpPath =
this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
this.iid = data.iid;
this.title = data.title;
this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch;
......@@ -85,6 +86,8 @@ export default class MergeRequestStore {
this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing;
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
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
......@@ -97,7 +100,8 @@ export default class MergeRequestStore {
this.hasCI = data.has_ci;
this.ciStatus = data.ci_status;
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.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
......
......@@ -321,11 +321,18 @@
}
&.activities {
display: flex;
border-bottom: 1px solid $border-color;
overflow: hidden;
.nav-links {
border-bottom: 0;
}
@include media-breakpoint-down(xs) {
display: block;
overflow: visible;
}
}
}
......
......@@ -653,6 +653,7 @@ module Ci
variables.append(key: 'CI_JOB_NAME', value: name)
variables.append(key: 'CI_JOB_STAGE', value: stage)
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_SLUG', value: ref_slug)
variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
......
......@@ -368,8 +368,10 @@ class Project < ActiveRecord::Base
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
validates :build_timeout, allow_nil: true,
numericality: { greater_than_or_equal_to: 600,
message: 'needs to be at least 10 minutes' }
numericality: { greater_than_or_equal_to: 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
# logged in user.
......
......@@ -174,8 +174,8 @@ class Repository
CommitCollection.new(project, commits, ref)
end
def find_branch(name, fresh_repo: true)
raw_repository.find_branch(name, fresh_repo)
def find_branch(name)
raw_repository.find_branch(name)
end
def find_tag(name)
......
......@@ -10,9 +10,15 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_when_pipeline_succeeds
expose :source_branch
expose :source_project_id
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
end
expose :squash
expose :target_branch
expose :target_project_id
expose :target_project_full_path do |merge_request|
merge_request.project&.full_path
end
expose :allow_collaboration
expose :should_be_rebased?, as: :should_be_rebased
......
.nav-block.activities
= render 'shared/event_filter'
.controls
= link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
.content_list
= spinner
.nav-block.activities
= render 'shared/event_filter'
.controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
.content_list
= spinner
%div{ class: container_class }
.nav-block.activity-filter-block.activities
= render 'shared/event_filter'
.controls
= link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do
= icon('rss')
= render 'shared/event_filter'
.content_list.project-activity{ :"data-href" => activity_project_path(@project) }
= 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-right= icon('angle-right')
%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: Clicking CI icon in Web IDE now opens up pipelines panel
merge_request:
author:
type: added
---
title: Avoid process deadlock in popen by consuming input pipes
merge_request: 20600
author:
type: fixed
---
title: Upgrade grape-path-helpers to 1.0.6
merge_request: 20601
author:
type: other
......@@ -47,6 +47,7 @@ future GitLab releases.**
| **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_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_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 |
......@@ -118,6 +119,7 @@ future GitLab releases.**
| `CI_BUILD_ID` | `CI_JOB_ID` |
| `CI_BUILD_REF` | `CI_COMMIT_SHA` |
| `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_SLUG` | `CI_COMMIT_REF_SLUG` |
| `CI_BUILD_NAME` | `CI_JOB_NAME` |
......
......@@ -364,12 +364,12 @@ When dragging issues between lists, different behavior occurs depending on the s
Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table:
| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable Project Issue Boards | Configurable Group Issue Boards | Assignee Lists
| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable Issue Boards | Assignee Lists
| --- | --- | --- | --- | --- | --- |
| Core | 1 | 1 | No | No | No |
| Starter | Multiple | 1 | Yes | No | No |
| Premium | Multiple | Multiple | Yes | Yes | Yes |
| Ultimate | Multiple | Multiple | Yes | Yes | Yes |
| Core | 1 | 1 | No | No |
| Starter | Multiple | 1 | Yes | No |
| Premium | Multiple | Multiple | Yes | Yes |
| Ultimate | Multiple | Multiple | Yes | Yes |
## Tips
......
......@@ -6,6 +6,18 @@ module API
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
requires :id, type: String, desc: 'The ID of a project'
end
......@@ -67,7 +79,7 @@ module API
optional :author_name, type: String, desc: 'Author name for commit'
end
post ':id/repository/commits' do
authorize! :push_code, user_project
authorize_push_to_branch!(params[:branch])
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
......@@ -142,7 +154,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch'
end
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])
not_found!('Commit') unless commit
......
......@@ -21,6 +21,10 @@ module Gitlab
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
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?
stdin.close
......@@ -32,7 +36,7 @@ module Gitlab
cmd_output << stdout.read
end
cmd_output << stderr.read
cmd_output << err_reader.value
cmd_status = wait_thr.value.exitstatus
end
......@@ -55,16 +59,20 @@ module Gitlab
rerr, werr = IO.pipe
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
status = process_wait_with_timeout(pid, timeout)
# close write ends so we could read them
wout.close
werr.close
cmd_output = rout.readlines.join
cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output
status = process_wait_with_timeout(pid, timeout)
cmd_output = out_reader.value
cmd_output << err_reader.value # Copying the behaviour of `popen` which merges stderr into output
[cmd_output, status.exitstatus]
rescue Timeout::Error => e
......
......@@ -167,24 +167,9 @@ module Gitlab
# Directly find a branch with a simple name (e.g. master)
#
# force_reload causes a new Rugged repository to be instantiated
#
# This is to work around a bug in libgit2 that causes in-memory refs to
# be stale/invalid when packed-refs is changed.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333
def find_branch(name, force_reload = false)
gitaly_migrate(:find_branch) do |is_enabled|
if is_enabled
def find_branch(name)
wrapped_gitaly_errors do
gitaly_ref_client.find_branch(name)
else
reload_rugged if force_reload
rugged_ref = rugged.branches[name]
if rugged_ref
target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
end
end
end
end
......@@ -196,20 +181,8 @@ module Gitlab
# Returns the number of valid branches
def branch_count
gitaly_migrate(:branch_names) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_client.count_branch_names
else
rugged.branches.each(:local).count do |ref|
begin
ref.name && ref.target # ensures the branch is valid
true
rescue Rugged::ReferenceError
false
end
end
end
end
end
......@@ -232,12 +205,8 @@ module Gitlab
# Returns the number of valid tags
def tag_count
gitaly_migrate(:tag_names) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_client.count_tag_names
else
rugged.tags.count
end
end
end
......@@ -260,13 +229,8 @@ module Gitlab
#
# Ref names must start with `refs/`.
def ref_exists?(ref_name)
gitaly_migrate(:ref_exists,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_exists?(ref_name)
else
rugged_ref_exists?(ref_name)
end
end
end
......@@ -274,12 +238,8 @@ module Gitlab
#
# name - The name of the tag as a String.
def tag_exists?(name)
gitaly_migrate(:ref_exists_tags, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_exists?("refs/tags/#{name}")
else
rugged_tag_exists?(name)
end
end
end
......@@ -287,12 +247,8 @@ module Gitlab
#
# name - The name of the branch as a String.
def branch_exists?(name)
gitaly_migrate(:ref_exists_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_exists?("refs/heads/#{name}")
else
rugged_branch_exists?(name)
end
end
end
......@@ -310,12 +266,8 @@ module Gitlab
end
def delete_all_refs_except(prefixes)
gitaly_migrate(:ref_delete_refs) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_client.delete_refs(except_with_prefixes: prefixes)
else
delete_refs(*all_ref_names_except(prefixes))
end
end
end
......@@ -714,25 +666,16 @@ module Gitlab
# Delete the specified branch from the repository
def delete_branch(branch_name)
gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_client.delete_branch(branch_name)
else
rugged.branches.delete(branch_name)
end
end
rescue Rugged::ReferenceError, CommandError => e
rescue CommandError => e
raise DeleteBranchError, e
end
def delete_refs(*ref_names)
gitaly_migrate(:delete_refs,
status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_delete_refs(*ref_names)
else
git_delete_refs(*ref_names)
end
end
end
......@@ -742,12 +685,8 @@ module Gitlab
# create_branch("feature")
# create_branch("other-feature", "master")
def create_branch(ref, start_point = "HEAD")
gitaly_migrate(:create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
wrapped_gitaly_errors do
gitaly_ref_client.create_branch(ref, start_point)
else
rugged_create_branch(ref, start_point)
end
end
end
......@@ -1175,7 +1114,7 @@ module Gitlab
end
def can_be_merged?(source_sha, target_branch)
if target_sha = find_branch(target_branch, true)&.target
if target_sha = find_branch(target_branch)&.target
!gitaly_conflicts_client(source_sha, target_sha).conflicts?
else
false
......@@ -1546,17 +1485,6 @@ module Gitlab
end
end
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
def rugged_ref_exists?(ref_name)
raise ArgumentError, 'invalid refname' unless ref_name.start_with?('refs/')
rugged.references.exist?(ref_name)
rescue Rugged::ReferenceError
false
end
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
......@@ -1564,37 +1492,6 @@ module Gitlab
gitaly_ref_client.ref_exists?(ref_name)
end
# Returns true if the given tag exists
#
# name - The name of the tag as a String.
def rugged_tag_exists?(name)
!!rugged.tags[name]
end
# Returns true if the given branch exists
#
# name - The name of the branch as a String.
def rugged_branch_exists?(name)
rugged.branches.exists?(name)
# If the branch name is invalid (e.g. ".foo") Rugged will raise an error.
# Whatever code calls this method shouldn't have to deal with that so
# instead we just return `false` (which is true since a branch doesn't
# exist when it has an invalid name).
rescue Rugged::ReferenceError
false
end
def rugged_create_branch(ref, start_point)
rugged_ref = rugged.branches.create(ref, start_point)
target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
rescue Rugged::ReferenceError => e
raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ %r{'refs/heads/#{ref}'}
raise InvalidRef.new("Invalid reference #{start_point}")
end
def gitaly_copy_gitattributes(revision)
gitaly_repository_client.apply_gitattributes(revision)
end
......@@ -1687,20 +1584,6 @@ module Gitlab
remote_update(remote_name, url: url)
end
def git_delete_refs(*ref_names)
instructions = ref_names.map do |ref|
"delete #{ref}\x00\x00"
end
message, status = run_git(%w[update-ref --stdin -z]) do |stdin|
stdin.write(instructions.join)
end
unless status.zero?
raise GitError.new("Could not delete refs #{ref_names}: #{message}")
end
end
def gitaly_delete_refs(*ref_names)
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
end
......
......@@ -4,10 +4,18 @@ module Gitlab
file_name_noext + '.log'
end
def self.debug(message)
build.debug(message)
end
def self.error(message)
build.error(message)
end
def self.warn(message)
build.warn(message)
end
def self.info(message)
build.info(message)
end
......
......@@ -34,11 +34,16 @@ module Gitlab
start = Time.now
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?
stdin.close
cmd_stdout = stdout.read
cmd_stderr = stderr.read
cmd_stdout = out_reader.value
cmd_stderr = err_reader.value
cmd_status = wait_thr.value
end
......
......@@ -22,7 +22,7 @@
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-loader": "^7.1.5",
"babel-plugin-transform-define": "^1.3.0",
"babel-preset-latest": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
......@@ -36,7 +36,7 @@
"compression-webpack-plugin": "^1.1.11",
"core-js": "^2.4.1",
"cropper": "^2.3.0",
"css-loader": "^0.28.11",
"css-loader": "^1.0.0",
"d3-array": "^1.2.1",
"d3-axis": "^1.0.8",
"d3-brush": "^1.0.4",
......@@ -90,15 +90,15 @@
"url-loader": "^1.0.1",
"visibilityjs": "^1.2.4",
"vue": "^2.5.16",
"vue-loader": "^15.2.0",
"vue-loader": "^15.2.4",
"vue-resource": "^1.5.0",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.16",
"vue-virtual-scroll-list": "^1.2.5",
"vuex": "^3.0.1",
"webpack": "^4.11.1",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-cli": "^3.0.2",
"webpack": "^4.16.0",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.0.8",
"webpack-stats-plugin": "^0.2.1",
"worker-loader": "^2.0.0"
},
......@@ -123,15 +123,16 @@
"ignore": "^3.3.7",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",
"karma": "^2.0.2",
"karma": "^2.0.4",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^1.4.2",
"karma-jasmine": "^1.1.1",
"karma-jasmine": "^1.1.2",
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "3.0.0",
"nodemon": "^1.17.3",
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.2",
"prettier": "1.12.1",
"webpack-dev-server": "^3.1.4"
}
......
......@@ -29,8 +29,10 @@
"merge_when_pipeline_succeeds": { "type": "boolean" },
"source_branch": { "type": "string" },
"source_project_id": { "type": "integer" },
"source_project_full_path": { "type": ["string", "null"]},
"target_branch": { "type": "string" },
"target_project_id": { "type": "integer" },
"target_project_full_path": { "type": ["string", "null"]},
"allow_collaboration": { "type": "boolean"},
"metrics": {
"oneOf": [
......
......@@ -13,6 +13,7 @@ describe('ideStatusBar', () => {
store.state.currentProjectId = 'abcproject';
store.state.projects.abcproject = projectData;
store.state.currentBranchId = 'master';
vm = createComponentWithStore(Component, store).$mount();
});
......@@ -60,4 +61,29 @@ describe('ideStatusBar', () => {
expect(vm.getCommitPath('abc123de')).toBe('/commit/abc123de');
});
});
describe('pipeline status', () => {
it('opens right sidebar on clicking icon', done => {
spyOn(vm, 'setRightPane');
Vue.set(vm.$store.state.pipelines, 'latestPipeline', {
details: {
status: {
text: 'success',
details_path: 'test',
icon: 'success',
},
},
});
vm
.$nextTick()
.then(() => {
vm.$el.querySelector('.ide-status-pipeline button').click();
expect(vm.setRightPane).toHaveBeenCalledWith('pipelines-list');
})
.then(done)
.catch(done.fail);
});
});
});
......@@ -9,6 +9,9 @@ export const projectData = {
master: {
treeId: 'abcproject/master',
can_push: true,
commit: {
id: '123',
},
},
},
mergeRequests: {},
......
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import MockAdapter from 'axios-mock-adapter';
describe('performance bar', () => {
let mock;
describe('performance bar app', () => {
let vm;
beforeEach(() => {
const store = new PerformanceBarStore();
mock = new MockAdapter(axios);
mock.onGet('/-/peek/results').reply(
200,
{
data: {
gc: {
invokes: 0,
invoke_time: '0.00',
use_size: 0,
total_size: 0,
total_object: 0,
gc_time: '0.00',
},
host: { hostname: 'web-01' },
},
},
{},
);
vm = mountComponent(Vue.extend(performanceBarApp), {
store,
env: 'development',
......@@ -45,44 +21,9 @@ describe('performance bar', () => {
afterEach(() => {
vm.$destroy();
mock.restore();
});
it('sets the class to match the environment', () => {
expect(vm.$el.getAttribute('class')).toContain('development');
});
describe('loadRequestDetails', () => {
beforeEach(() => {
spyOn(vm.store, 'addRequest').and.callThrough();
});
it('does nothing if the request cannot be tracked', () => {
spyOn(vm.store, 'canTrackRequest').and.callFake(() => false);
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).not.toHaveBeenCalled();
});
it('adds the request immediately', () => {
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).toHaveBeenCalledWith(
'123',
'https://gitlab.com/',
);
});
it('makes an HTTP request for the request details', () => {
spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough();
vm.loadRequestDetails('456', 'https://gitlab.com/');
expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith(
'/-/peek/results',
'456',
);
});
});
});
import axios from '~/lib/utils/axios_utils';
import performanceBar from '~/performance_bar';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
import MockAdapter from 'axios-mock-adapter';
describe('performance bar wrapper', () => {
let mock;
let vm;
beforeEach(() => {
const peekWrapper = document.createElement('div');
peekWrapper.setAttribute('id', 'js-peek');
peekWrapper.setAttribute('data-env', 'development');
peekWrapper.setAttribute('data-request-id', '123');
peekWrapper.setAttribute('data-peek-url', '/-/peek/results');
peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true');
document.body.appendChild(peekWrapper);
mock = new MockAdapter(axios);
mock.onGet('/-/peek/results').reply(
200,
{
data: {
gc: {
invokes: 0,
invoke_time: '0.00',
use_size: 0,
total_size: 0,
total_object: 0,
gc_time: '0.00',
},
host: { hostname: 'web-01' },
},
},
{},
);
vm = performanceBar({ container: '#js-peek' });
});
afterEach(() => {
vm.$destroy();
mock.restore();
});
describe('loadRequestDetails', () => {
beforeEach(() => {
spyOn(vm.store, 'addRequest').and.callThrough();
});
it('does nothing if the request cannot be tracked', () => {
spyOn(vm.store, 'canTrackRequest').and.callFake(() => false);
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).not.toHaveBeenCalled();
});
it('adds the request immediately', () => {
vm.loadRequestDetails('123', 'https://gitlab.com/');
expect(vm.store.addRequest).toHaveBeenCalledWith(
'123',
'https://gitlab.com/',
);
});
it('makes an HTTP request for the request details', () => {
spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough();
vm.loadRequestDetails('456', 'https://gitlab.com/');
expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith(
'/-/peek/results',
'456',
);
});
});
});
......@@ -6,6 +6,7 @@ import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
import jasmineDiff from 'jasmine-diff';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
......@@ -35,7 +36,15 @@ Vue.use(Translate);
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
beforeAll(() => jasmine.addMatchers(customMatchers));
beforeAll(() => {
jasmine.addMatchers(
jasmineDiff(jasmine, {
colors: true,
inline: true,
}),
);
jasmine.addMatchers(customMatchers);
});
// globalize common libraries
window.$ = $;
......
......@@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => {
beforeEach(() => {
vm = mountComponent(Component, {
mr: {
iid: 1,
divergedCommitsCount: 12,
sourceBranch: 'mr-widget-refactor',
sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
......@@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => {
emailPatchesPath: '/mr/email-patches',
plainDiffPath: '/mr/plainDiffPath',
statusPath: 'abc',
sourceProjectFullPath: 'root/gitlab-ce',
targetProjectFullPath: 'gitlab-org/gitlab-ce',
},
});
});
......@@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => {
const button = vm.$el.querySelector('.js-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';
vm.mr.iid = 2;
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/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', () => {
......
......@@ -29,8 +29,10 @@ export default {
source_branch: 'daaaa',
source_branch_link: 'daaaa',
source_project_id: 19,
source_project_full_path: '/group1/project1',
target_branch: 'master',
target_project_id: 19,
target_project_full_path: '/group2/project2',
metrics: {
merged_by: {
name: 'Administrator',
......
......@@ -2,6 +2,9 @@ require 'spec_helper'
describe 'Gitlab::Git::Popen' do
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
Class.new(Object) do
......@@ -70,6 +73,15 @@ describe 'Gitlab::Git::Popen' do
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
context 'popen_with_timeout' do
......@@ -85,6 +97,17 @@ describe 'Gitlab::Git::Popen' do
it { expect(output).to include('tests') }
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
let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) }
let(:output) { result.first }
......@@ -110,6 +133,13 @@ describe 'Gitlab::Git::Popen' 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)
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
context 'timeout period' do
......
......@@ -1187,7 +1187,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#find_branch' do
shared_examples 'finding a branch' do
it 'should return a Branch for master' do
branch = repository.find_branch('master')
......@@ -1202,38 +1201,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
context 'when Gitaly find_branch feature is enabled' do
it_behaves_like 'finding a branch'
end
context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do
it_behaves_like 'finding a branch'
context 'force_reload is true' do
it 'should reload Rugged::Repository' do
expect(Rugged::Repository).to receive(:new).twice.and_call_original
repository.find_branch('master')
branch = repository.find_branch('master', force_reload: true)
expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
expect(branch.name).to eq('master')
end
end
context 'force_reload is false' do
it 'should not reload Rugged::Repository' do
expect(Rugged::Repository).to receive(:new).once.and_call_original
branch = repository.find_branch('master', force_reload: false)
expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
expect(branch.name).to eq('master')
end
end
end
end
describe '#ref_name_for_sha' do
let(:ref_path) { 'refs/heads' }
let(:sha) { repository.find_branch('master').dereferenced_target.id }
......
......@@ -55,6 +55,19 @@ describe Gitlab::Popen do
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
before do
@output, @status = @klass.new.popen(%w(ls))
......
......@@ -1613,6 +1613,7 @@ describe Ci::Build do
{ key: 'CI_JOB_NAME', value: 'test', public: true },
{ key: 'CI_JOB_STAGE', value: 'test', 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_SLUG', value: build.ref_slug, public: true },
{ key: 'CI_BUILD_REF', value: build.sha, public: true },
......
......@@ -149,23 +149,25 @@ describe Project do
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_length_of(:name).is_at_most(255) }
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(:description).is_at_most(2000) }
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.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(:namespace) }
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
project2 = build(:project)
allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
......
......@@ -1022,24 +1022,6 @@ describe Repository do
end
end
describe '#find_branch' do
context 'fresh_repo is true' do
it 'delegates the call to raw_repository' do
expect(repository.raw_repository).to receive(:find_branch).with('master', true)
repository.find_branch('master', fresh_repo: true)
end
end
context 'fresh_repo is false' do
it 'delegates the call to raw_repository' do
expect(repository.raw_repository).to receive(:find_branch).with('master', false)
repository.find_branch('master', fresh_repo: false)
end
end
end
describe '#update_branch_with_hooks' do
let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
......
......@@ -514,6 +514,38 @@ describe API::Commits do
expect(response).to have_gitlab_http_status(400)
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
describe 'GET /projects/:id/repository/commits/:sha/refs' do
......@@ -1065,9 +1097,27 @@ describe API::Commits do
it 'returns 400 if you are not allowed to push to the target branch' do
post api(route, current_user), branch: 'feature'
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('You are not allowed to push into this branch')
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to match(/You are not allowed to push into this branch/)
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
......
......@@ -11,6 +11,21 @@ describe MergeRequestWidgetEntity do
described_class.new(resource, request: request).as_json
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
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
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment