Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

This commit is contained in:
Stan Hu 2016-08-30 14:48:17 -07:00
commit 341541d3af
76 changed files with 1493 additions and 483 deletions

View File

@ -3,25 +3,30 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased)
- Make push events have equal vertical spacing.
- Add two-factor recovery endpoint to internal API !5510
- Remove vendor prefixes for linear-gradient CSS (ClemMakesApps)
- Add font color contrast to external label in admin area (ClemMakesApps)
- Change logo animation to CSS (ClemMakesApps)
- Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
- Shorten task status phrase (ClemMakesApps)
- Add hover color to emoji icon (ClemMakesApps)
- Fix branches page dropdown sort alignment (ClemMakesApps)
- Add white background for no readme container (ClemMakesApps)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps)
- Add Sentry logging to API calls
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Remove unused mixins (ClemMakesApps)
- Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Fix markdown help references (ClemMakesApps)
- Add last commit time to repo view (ClemMakesApps)
- Added tests for diff notes
- Add a button to download latest successful artifacts for branches and tags !5142
- Add delimiter to project stars and forks count (ClemMakesApps)
- Fix badge count alignment (ClemMakesApps)
- Fix branch title trailing space on hover (ClemMakesApps)
@ -41,17 +46,24 @@ v 8.12.0 (unreleased)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
- Adds response mime type to transaction metric action when it's not HTML
- Fix hover leading space bug in pipeline graph !5980
- User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
v 8.11.4 (unreleased)
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
- Creating an issue through our API now emails label subscribers !5720
- Fix resolving conflicts on forks
- Fix diff commenting on merge requests created prior to 8.10
v 8.11.4 (unreleased)
- Fix issue boards leak private label names and descriptions
v 8.11.3 (unreleased)
v 8.11.3
- Do not enforce using hash with hidden key in CI configuration. !6079
- Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label
- Don't show resolve conflicts link before MR status is updated
- Fix "Wiki" link not appearing in navigation for projects with external wiki
- Fix IE11 fork button bug !598
- Don't prevent viewing the MR when git refs for conflicts can't be found on disk
- Fix external issue tracker "Issues" link leading to 404s

View File

@ -195,6 +195,12 @@
.separator + .dropdown-header {
padding-top: 2px;
}
.unclickable {
cursor: not-allowed;
padding: 5px 8px;
color: $dropdown-header-color;
}
}
.dropdown-menu-large {

View File

@ -8,10 +8,7 @@
height: 30px;
transition-duration: .3s;
-webkit-transform: translateZ(0);
background: -webkit-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: -o-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: -moz-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%);
background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
@ -211,12 +208,6 @@
}
}
.project-filter-form {
input {
background-color: $background-color;
}
}
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
width: 100%;

View File

@ -43,6 +43,15 @@
border-color: $blue-normal;
}
&.ci-created {
color: $table-text-gray;
border-color: $table-text-gray;
svg {
fill: $table-text-gray;
}
}
svg {
height: 13px;
width: 13px;

View File

@ -11,6 +11,16 @@
}
}
.last-commit {
max-width: 506px;
.last-commit-content {
@include str-truncated;
width: calc(100% - 140px);
margin-left: 3px;
}
}
.tree-table {
margin-bottom: 0;

View File

@ -1,22 +1,25 @@
class Projects::ArtifactsController < Projects::ApplicationController
include ExtractsPath
layout 'project'
before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path
before_action :validate_artifacts!
def download
unless artifacts_file.file_storage?
return redirect_to artifacts_file.url
if artifacts_file.file_storage?
send_file artifacts_file.path, disposition: 'attachment'
else
redirect_to artifacts_file.url
end
send_file artifacts_file.path, disposition: 'attachment'
end
def browse
directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory)
return render_404 unless @entry.exists?
render_404 unless @entry.exists?
end
def file
@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController
redirect_to namespace_project_build_path(project.namespace, project, build)
end
def latest_succeeded
target_path = artifacts_action_path(@path, project, build)
if target_path
redirect_to(target_path)
else
render_404
end
end
private
def extract_ref_name_and_path
return unless params[:ref_name_and_path]
@ref_name, @path = extract_ref(params[:ref_name_and_path])
end
def validate_artifacts!
render_404 unless build.artifacts?
render_404 unless build && build.artifacts?
end
def build
@build ||= project.builds.find_by!(id: params[:build_id])
@build ||= build_from_id || build_from_ref
end
def build_from_id
project.builds.find_by(id: params[:build_id]) if params[:build_id]
end
def build_from_ref
return unless @ref_name
builds = project.latest_successful_builds_for(@ref_name)
builds.find_by(name: params[:job])
end
def artifacts_file

View File

@ -25,6 +25,11 @@ module CiStatusHelper
end
end
def ci_status_for_statuseable(subject)
status = subject.try(:status) || 'not found'
status.humanize
end
def ci_icon_for_status(status)
icon_name =
case status
@ -41,7 +46,7 @@ module CiStatusHelper
when 'play'
'icon_play'
when 'created'
'icon_status_pending'
'icon_status_created'
else
'icon_status_cancel'
end

View File

@ -149,4 +149,20 @@ module GitlabRoutingHelper
def resend_invite_group_member_path(group_member, *args)
resend_invite_group_group_member_path(group_member.source, group_member)
end
# Artifacts
def artifacts_action_path(path, project, build)
action, path_params = path.split('/', 2)
args = [project.namespace, project, build, path_params]
case action
when 'download'
download_namespace_project_build_artifacts_path(*args)
when 'browse'
browse_namespace_project_build_artifacts_path(*args)
when 'file'
file_namespace_project_build_artifacts_path(*args)
end
end
end

View File

@ -98,6 +98,6 @@ module MergeRequestsHelper
end
def merge_request_button_visibility(merge_request, closed)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?)
return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork?
end
end

View File

@ -355,7 +355,7 @@ class Ability
rules += named_abilities('project_snippet')
end
unless project.wiki_enabled
unless project.has_wiki?
rules += named_abilities('wiki')
end

View File

@ -65,8 +65,8 @@ module Ci
end
# ref can't be HEAD or SHA, can only be branch/tag name
scope :latest_successful_for, ->(ref = default_branch) do
where(ref: ref).success.order(id: :desc).limit(1)
def self.latest_successful_for(ref)
where(ref: ref).order(id: :desc).success.first
end
def self.truncate_sha(sha)

View File

@ -28,4 +28,8 @@ module NoteOnDiff
def can_be_award_emoji?
false
end
def to_discussion
Discussion.new([self])
end
end

View File

@ -52,11 +52,11 @@ module Taskable
end
# Return a string that describes the current state of this Taskable's task
# list items, e.g. "20 tasks (12 completed, 8 remaining)"
# list items, e.g. "12 of 20 tasks completed"
def task_status
return '' if description.blank?
sum = tasks.summary
"#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)"
"#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed"
end
end

View File

@ -107,10 +107,6 @@ class DiffNote < Note
self.noteable.find_diff_discussion(self.discussion_id)
end
def to_discussion
Discussion.new([self])
end
private
def supported?

View File

@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base
end
end
validates :source_project, presence: true, unless: [:allow_broken, :importing?]
validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches, unless: [:allow_broken, :importing?]
validate :validate_fork
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork, unless: :closed_without_fork?
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base
def validate_fork
return true unless target_project && source_project
return true if target_project == source_project
return true unless forked_source_project_missing?
if target_project == source_project
true
else
# If source and target projects are different
# we should check if source project is actually a fork of target project
if source_project.forked_from?(target_project)
true
else
errors.add :validate_fork,
'Source project is not a fork of target project'
end
end
errors.add :validate_fork,
'Source project is not a fork of the target project'
end
def closed_without_fork?
closed? && forked_source_project_missing?
end
def forked_source_project_missing?
return false unless for_fork?
return true unless source_project
!source_project.forked_from?(target_project)
end
def ensure_merge_request_diff
@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base
end
def pipeline
@pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
return unless diff_head_sha && source_project
@pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
end
def all_pipelines

View File

@ -436,7 +436,7 @@ class Project < ActiveRecord::Base
# ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch)
latest_pipeline = pipelines.latest_successful_for(ref).first
latest_pipeline = pipelines.latest_successful_for(ref)
if latest_pipeline
latest_pipeline.builds.latest.with_artifacts
@ -680,6 +680,10 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end
def has_wiki?
wiki_enabled? || has_external_wiki?
end
def external_wiki
if has_external_wiki.nil?
cache_has_external_wiki # Populate
@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
def pipeline(sha, ref)
def pipeline_for(ref, sha = nil)
sha ||= commit(ref).try(:sha)
return unless sha
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
def ensure_pipeline(sha, ref, current_user = nil)
pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
def ensure_pipeline(ref, sha, current_user = nil)
pipeline_for(ref, sha) ||
pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci

View File

@ -1,35 +0,0 @@
module Ci
class WebHookService
def build_end(build)
execute_hooks(build.project, build_data(build))
end
def execute_hooks(project, data)
project.web_hooks.each do |web_hook|
async_execute_hook(web_hook, data)
end
end
def async_execute_hook(hook, data)
Sidekiq::Client.enqueue(Ci::WebHookWorker, hook.id, data)
end
def build_data(build)
project = build.project
data = {}
data.merge!({
build_id: build.id,
build_name: build.name,
build_status: build.status,
build_started_at: build.started_at,
build_finished_at: build.finished_at,
project_id: project.id,
project_name: project.name,
gitlab_url: project.gitlab_url,
ref: build.ref,
before_sha: build.before_sha,
sha: build.sha,
})
end
end
end

View File

@ -45,6 +45,7 @@ class IssuableBaseService < BaseService
unless can?(current_user, ability, project)
params.delete(:milestone_id)
params.delete(:labels)
params.delete(:add_label_ids)
params.delete(:remove_label_ids)
params.delete(:label_ids)
@ -72,6 +73,7 @@ class IssuableBaseService < BaseService
filter_labels_in_param(:add_label_ids)
filter_labels_in_param(:remove_label_ids)
filter_labels_in_param(:label_ids)
find_or_create_label_ids
end
def filter_labels_in_param(key)
@ -80,6 +82,17 @@ class IssuableBaseService < BaseService
params[key] = project.labels.where(id: params[key]).pluck(:id)
end
def find_or_create_label_ids
labels = params.delete(:labels)
return unless labels
params[:label_ids] = labels.split(",").map do |label_name|
project.labels.create_with(color: Label::DEFAULT_COLOR)
.find_or_create_by(title: label_name.strip)
.id
end
end
def process_label_ids(attributes, existing_label_ids: nil)
label_ids = attributes.delete(:label_ids)
add_label_ids = attributes.delete(:add_label_ids)
@ -162,7 +175,12 @@ class IssuableBaseService < BaseService
if params.present? && update_issuable(issuable, params)
issuable.reset_events_cache
handle_common_system_notes(issuable, old_labels: old_labels)
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
handle_common_system_notes(issuable, old_labels: old_labels)
end
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')

View File

@ -1,11 +1,14 @@
module MergeRequests
class ResolveService < MergeRequests::BaseService
attr_accessor :conflicts, :rugged, :merge_index
attr_accessor :conflicts, :rugged, :merge_index, :merge_request
def execute(merge_request)
@conflicts = merge_request.conflicts
@rugged = project.repository.rugged
@merge_index = conflicts.merge_index
@merge_request = merge_request
fetch_their_commit!
conflicts.files.each do |file|
write_resolved_file_to_index(file, params[:sections])
@ -27,5 +30,21 @@ module MergeRequests
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
merge_index.conflict_remove(our_path)
end
# If their commit (in the target project) doesn't exist in the source project, it
# can't be a parent for the merge commit we're about to create. If that's the case,
# fetch the target branch ref into the source project so the commit exists in both.
#
def fetch_their_commit!
return if rugged.include?(conflicts.their_commit.oid)
random_string = SecureRandom.hex
project.repository.fetch_ref(
merge_request.target_project.repository.path_to_repo,
"refs/heads/#{merge_request.target_branch}",
"refs/tmp/#{random_string}/head"
)
end
end
end

View File

@ -11,6 +11,10 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
if merge_request.closed_without_fork?
params.except!(:target_branch, :force_remove_source_branch)
end
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request)

View File

@ -27,6 +27,8 @@
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do
Compare
= render 'projects/buttons/download', project: @project, ref: branch.name
- if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")

View File

@ -1,4 +1,42 @@
- unless @project.empty_repo?
- if can? current_user, :download_code, @project
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
= icon('download')
- if !project.empty_repo? && can?(current_user, :download_code, project)
%span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'}
.dropdown.inline
%button.btn{ 'data-toggle' => 'dropdown' }
= icon('download')
%span.caret
%span.sr-only
Select Archive Format
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li.dropdown-header Source code
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
- pipeline = project.pipelines.latest_successful_for(ref)
- if pipeline
- artifacts = pipeline.builds.latest.with_artifacts
- if artifacts.any?
%li.dropdown-header Artifacts
- unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref)
%li
.unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do
%i.fa.fa-download
%span Download '#{job.name}'

View File

@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- target = @project.repository.find_branch(branch).target
- pipeline = @project.pipeline(target.sha, branch) if target
- pipeline = @project.pipeline_for(branch, target.sha) if target
- if pipeline
%span.related-branch-ci-status
= render_pipeline_status(pipeline)

View File

@ -1,3 +1,7 @@
- if @merge_request.closed_without_fork?
.alert.alert-danger
%p The source project of this merge request has been removed.
.clearfix.detail-page-header
.issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }

View File

@ -1,37 +0,0 @@
- ref = ref || nil
- btn_class = btn_class || ''
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
%ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
- else
%span.btn-group{class: btn_class}
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
%span zip
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
%span tar.gz

View File

@ -72,7 +72,7 @@
= render "projects/buttons/koding"
.btn-group.project-repo-btn-group
= render "projects/buttons/download"
= render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting

View File

@ -1,14 +0,0 @@
%span.btn-group
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do
%span Source code
%a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%span Download tar.gz

View File

@ -11,8 +11,7 @@
= strip_gpg_signature(tag.message)
.controls
- if can?(current_user, :download_code, @project)
= render 'projects/tags/download', ref: tag.name, project: @project
= render 'projects/buttons/download', project: @project, ref: tag.name
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do

View File

@ -12,8 +12,7 @@
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do

View File

@ -5,16 +5,17 @@
%tr
%th Name
%th Last Update
%th.hidden-xs
.pull-left Last Commit
.last-commit.hidden-sm.pull-left
&nbsp;
%th.hidden-xs.last-commit
Last Commit
.last-commit-content.hidden-sm
%i.fa.fa-angle-right
&nbsp;
%small.light
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
&ndash;
= truncate(@commit.title, length: 50)
= time_ago_with_tooltip(@commit.committed_date)
&ndash;
= @commit.full_title
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
- if @path.present?

View File

@ -10,8 +10,7 @@
%div{ class: container_class }
.tree-controls
= render 'projects/find_file_link'
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
= render 'projects/buttons/download', project: @project, ref: @ref
#tree-holder.tree-holder.clearfix
.nav-block

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" enable-background="new 0 0 14 14"><path d="M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z" /><circle cx="7" cy="7" r="3.25"/></svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@ -134,7 +134,7 @@
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
= icon('question-circle')
- if issuable.is_a?(MergeRequest)
- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
%hr
- if @merge_request.new_record?
.form-group
@ -175,7 +175,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
.pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
- if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'

View File

@ -782,6 +782,14 @@ Rails.application.routes.draw do
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
resources :artifacts, only: [] do
collection do
get :latest_succeeded,
path: '*ref_name_and_path',
format: false
end
end
end
member do

View File

@ -0,0 +1,11 @@
class DropUnusedCiTables < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
drop_table(:ci_services)
drop_table(:ci_web_hooks)
end
end

View File

@ -0,0 +1,16 @@
class EnsureLockVersionHasNoDefault < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
change_column_default :issues, :lock_version, nil
change_column_default :merge_requests, :lock_version, nil
execute('UPDATE issues SET lock_version = 1 WHERE lock_version = 0')
execute('UPDATE merge_requests SET lock_version = 1 WHERE lock_version = 0')
end
def down
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160823081327) do
ActiveRecord::Schema.define(version: 20160827011312) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -295,16 +295,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do
add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
create_table "ci_services", force: :cascade do |t|
t.string "type"
t.string "title"
t.integer "project_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "active", default: false, null: false
t.text "properties"
end
create_table "ci_sessions", force: :cascade do |t|
t.string "session_id", null: false
t.text "data"
@ -360,13 +350,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do
add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree
create_table "ci_web_hooks", force: :cascade do |t|
t.string "url", null: false
t.integer "project_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "deploy_keys_projects", force: :cascade do |t|
t.integer "deploy_key_id", null: false
t.integer "project_id", null: false

View File

@ -998,6 +998,8 @@ is available before it is returned in the JSON response or an empty response is
## Branches
For more information please consult the [Branches](branches.md) documentation.
### List branches
Lists all branches of a project.
@ -1016,56 +1018,46 @@ Parameters:
"name": "async",
"commit": {
"id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
"parents": [
{
"id": "3f94fc7c85061973edc9906ae170cc269b07ca55"
}
"parent_ids": [
"3f94fc7c85061973edc9906ae170cc269b07ca55"
],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
"message": "give Caolan credit where it's due (up top)",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"author_name": "Jeremy Ashkenas",
"author_email": "jashkenas@example.com",
"authored_date": "2010-12-08T21:28:50+00:00",
"committer_name": "Jeremy Ashkenas",
"committer_email": "jashkenas@example.com",
"committed_date": "2010-12-08T21:28:50+00:00"
},
"protected": false
"protected": false,
"developers_can_push": false,
"developers_can_merge": false
},
{
"name": "gh-pages",
"commit": {
"id": "101c10a60019fe870d21868835f65c25d64968fc",
"parents": [
{
"id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
}
"parent_ids": [
"9c15d2e26945a665131af5d7b6d30a06ba338aaa"
],
"tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a",
"message": "Underscore.js 1.5.2",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"author_name": "Jeremy Ashkenas",
"author_email": "jashkenas@example.com",
"authored_date": "2013-09-07T12:58:21+00:00",
"committer_name": "Jeremy Ashkenas",
"committer_email": "jashkenas@example.com",
"committed_date": "2013-09-07T12:58:21+00:00"
},
"protected": false
"protected": false,
"developers_can_push": false,
"developers_can_merge": false
}
]
```
### List single branch
### Single branch
Lists a specific branch of a project.
A specific branch of a project.
```
GET /projects/:id/repository/branches/:branch
@ -1075,6 +1067,8 @@ Parameters:
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
- `branch` (required) - The name of the branch.
- `developers_can_push` - Flag if developers can push to the branch.
- `developers_can_merge` - Flag if developers can merge to the branch.
### Protect single branch

View File

@ -39,7 +39,7 @@ If you want a quick introduction to GitLab CI, follow our
- [before_script and after_script](#before_script-and-after_script)
- [Git Strategy](#git-strategy)
- [Shallow cloning](#shallow-cloning)
- [Hidden jobs](#hidden-jobs)
- [Hidden keys](#hidden-keys)
- [Special YAML features](#special-yaml-features)
- [Anchors](#anchors)
- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
@ -934,24 +934,27 @@ variables:
GIT_DEPTH: "3"
```
## Hidden jobs
## Hidden keys
>**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Jobs that start with a dot (`.`) will be not processed by GitLab CI. You can
Keys that start with a dot (`.`) will be not processed by GitLab CI. You can
use this feature to ignore jobs, or use the
[special YAML features](#special-yaml-features) and transform the hidden jobs
[special YAML features](#special-yaml-features) and transform the hidden keys
into templates.
In the following example, `.job_name` will be ignored:
In the following example, `.key_name` will be ignored:
```yaml
.job_name:
.key_name:
script:
- rake spec
```
Hidden keys can be hashes like normal CI jobs, but you are also allowed to use
different types of structures to leverage special YAML features.
## Special YAML features
It's possible to use special YAML features like anchors (`&`), aliases (`*`)
@ -967,7 +970,7 @@ Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
YAML also has a handy feature called 'anchors', which let you easily duplicate
content across your document. Anchors can be used to duplicate/inherit
properties, and is a perfect example to be used with [hidden jobs](#hidden-jobs)
properties, and is a perfect example to be used with [hidden keys](#hidden-keys)
to provide templates for your jobs.
The following example uses anchors and map merging. It will create two jobs,
@ -975,7 +978,7 @@ The following example uses anchors and map merging. It will create two jobs,
having their own custom `script` defined:
```yaml
.job_template: &job_definition # Hidden job that defines an anchor named 'job_definition'
.job_template: &job_definition # Hidden key that defines an anchor named 'job_definition'
image: ruby:2.1
services:
- postgres
@ -1081,7 +1084,7 @@ test:mysql:
- ruby
```
You can see that the hidden jobs are conveniently used as templates.
You can see that the hidden keys are conveniently used as templates.
## Validate the .gitlab-ci.yml

View File

@ -2,7 +2,7 @@
This style guide recommends best practices for newlines in Ruby code.
## Rule: separate code with newlines only when it makes sense from logic perspectice
## Rule: separate code with newlines only to group together related logic
```ruby
# bad

View File

@ -1,111 +1,164 @@
# Integrate your server with Bitbucket
# Integrate your GitLab server with Bitbucket
Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account.
Import projects from Bitbucket.org and login to your GitLab instance with your
Bitbucket.org account.
To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket.
Bitbucket will generate an application ID and secret key for you to use.
## Overview
1. Sign in to Bitbucket.
You can set up Bitbucket.org as an OAuth provider so that you can use your
credentials to authenticate into GitLab or import your projects from
Bitbucket.org.
1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you.
- To use Bitbucket.org as an OmniAuth provider, follow the [Bitbucket OmniAuth
provider](#bitbucket-omniauth-provider) section.
- To import projects from Bitbucket, follow both the
[Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) and
[Bitbucket project import](#bitbucket-project-import) sections.
1. Select "OAuth" in the left menu.
## Bitbucket OmniAuth provider
1. Select "Add consumer".
> **Note:**
Make sure to first follow the [Initial OmniAuth configuration][init-oauth]
before proceeding with setting up the Bitbucket integration.
1. Provide the required details.
- Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- Application description: Fill this in if you wish.
- URL: The URL to your GitLab installation. 'https://gitlab.company.com'
1. Select "Save".
To enable the Bitbucket OmniAuth provider you must register your application
with Bitbucket.org. Bitbucket will generate an application ID and secret key for
you to use.
1. You should now see a Key and Secret in the list of OAuth customers.
Keep this page open as you continue configuration.
1. Sign in to [Bitbucket.org](https://bitbucket.org).
1. Navigate to your individual user settings (**Bitbucket settings**) or a team's
settings (**Manage team**), depending on how you want the application registered.
It does not matter if the application is registered as an individual or a
team, that is entirely up to you.
1. Select **OAuth** in the left menu under "Access Management".
1. Select **Add consumer**.
1. Provide the required details:
1. On your GitLab server, open the configuration file.
| Item | Description |
| :--- | :---------- |
| **Name** | This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. |
| **Application description** | Fill this in if you wish. |
| **Callback URL** | Leave blank. |
| **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. |
For omnibus package:
And grant at least the following permissions:
```sh
sudo editor /etc/gitlab/gitlab.rb
```
Account: Email
Repositories: Read, Admin
```
>**Note:**
It may seem a little odd to giving GitLab admin permissions to repositories,
but this is needed in order for GitLab to be able to clone the repositories.
![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
1. Select **Save**.
1. Select your newly created OAuth consumer and you should now see a Key and
Secret in the list of OAuth customers. Keep this page open as you continue
the configuration.
![Bitbucket OAuth key](img/bitbucket_oauth_keys.png)
1. On your GitLab server, open the configuration file:
```
# For Omnibus packages
sudo editor /etc/gitlab/gitlab.rb
# For installations from source
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
```
1. Follow the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. Add the Bitbucket provider configuration:
For Omnibus packages:
```ruby
gitlab_rails['omniauth_providers'] = [
{
"name" => "bitbucket",
"app_id" => "BITBUCKET_APP_KEY",
"app_secret" => "BITBUCKET_APP_SECRET",
"url" => "https://bitbucket.org/"
}
]
```
For installations from source:
```sh
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml
```yaml
- { name: 'bitbucket',
app_id: 'BITBUCKET_APP_KEY',
app_secret: 'BITBUCKET_APP_SECRET' }
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
---
1. Add the provider configuration:
For omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
{
"name" => "bitbucket",
"app_id" => "YOUR_KEY",
"app_secret" => "YOUR_APP_SECRET",
"url" => "https://bitbucket.org/"
}
]
```
For installation from source:
```
- { name: 'bitbucket', app_id: 'YOUR_KEY',
app_secret: 'YOUR_APP_SECRET' }
```
1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7.
1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7.
Where `BITBUCKET_APP_KEY` is the Key and `BITBUCKET_APP_SECRET` the Secret
from the Bitbucket application page.
1. Save the configuration file.
1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
installed GitLab via Omnibus or from source respectively.
1. If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```).
1. Restart GitLab for the changes to take effect.
On the sign in page there should now be a Bitbucket icon below the regular sign in form.
Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application.
If everything goes well the user will be returned to GitLab and will be signed in.
On the sign in page there should now be a Bitbucket icon below the regular sign
in form. Click the icon to begin the authentication process. Bitbucket will ask
the user to sign in and authorize the GitLab application. If everything goes
well, the user will be returned to GitLab and will be signed in.
## Bitbucket project import
To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com.
To allow projects to be imported directly into GitLab, Bitbucket requires two
extra setup steps compared to [GitHub](github.md) and [GitLab.com](gitlab.md).
Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key.
Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and
instead requires GitLab to use SSH and identify itself using your GitLab
server's SSH key.
### Step 1: Public key
To be able to access repositories on Bitbucket, GitLab will automatically
register your public key with Bitbucket as a deploy key for the repositories to
be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa` which
translates to `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages and to
`/home/git/.ssh/bitbucket_rsa.pub` for installations from source.
To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
---
If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
Below are the steps that will allow GitLab to be able to import your projects
from Bitbucket.
1. Create a new SSH key:
1. Make sure you [have enabled the Bitbucket OAuth support](#bitbucket-omniauth-provider).
1. Create a new SSH key with an **empty passphrase**:
```sh
sudo -u git -H ssh-keygen
```
When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
Make sure to use an **empty passphrase**.
When asked to 'Enter file in which to save the key' enter:
`/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages or
`/home/git/.ssh/bitbucket_rsa` for installations from source. The name is
important so make sure to get it right.
1. Configure SSH client to use your new key:
> **Warning:**
This key must NOT be associated with ANY existing Bitbucket accounts. If it
is, the import will fail with an `Access denied! Please verify you can add
deploy keys to this repository.` error.
Open the SSH configuration file of the git user.
1. Next, you need to to configure the SSH client to use your new key. Open the
SSH configuration file of the `git` user:
```sh
sudo editor /home/git/.ssh/config
```
# For Omnibus packages
sudo editor /var/opt/gitlab/.ssh/config
# For installations from source
sudo editor /home/git/.ssh/config
```
Add a host configuration for `bitbucket.org`.
1. Add a host configuration for `bitbucket.org`:
```sh
Host bitbucket.org
@ -113,28 +166,46 @@ If you have that file in place, you're all set and should see the "Import projec
User git
```
### Step 2: Known hosts
To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
1. Save the file and exit.
1. Manually connect to `bitbucket.org` over SSH, while logged in as the `git`
user that GitLab will use:
```sh
sudo -u git -H ssh bitbucket.org
```
1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
That step is performed because GitLab needs to connect to Bitbucket over SSH,
in order to add `bitbucket.org` to your GitLab server's known SSH hosts.
1. Verify the RSA key fingerprint you'll see in the response matches the one
in the [Bitbucket documentation][bitbucket-docs] (the specific IP address
doesn't matter):
```sh
The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
The authenticity of host 'bitbucket.org (104.192.143.1)' can't be established.
RSA key fingerprint is SHA256:zzXQOXSRBEiUtuE8AikJYKwbHaxvSc0ojez9YXaGp1A.
Are you sure you want to continue connecting (yes/no)?
```
1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
1. Your GitLab server is now able to connect to Bitbucket over SSH.
1. If the fingerprint matches, type `yes` to continue connecting and have
`bitbucket.org` be added to your known SSH hosts. After confirming you should
see a permission denied message. If you see an authentication successful
message you have done something wrong. The key you are using has already been
added to a Bitbucket account and will cause the import script to fail. Ensure
the key you are using CANNOT authenticate with Bitbucket.
1. Restart GitLab to allow it to find the new public key.
You should now see the "Import projects from Bitbucket" option on the New Project page enabled.
Your GitLab server is now able to connect to Bitbucket over SSH. You should be
able to see the "Import projects from Bitbucket" option on the New Project page
enabled.
## Acknowledgemts
Special thanks to the writer behind the following article:
- http://stratus3d.com/blog/2015/09/06/migrating-from-bitbucket-to-local-gitlab-server/
[init-oauth]: omniauth.md#initial-omniauth-configuration
[bitbucket-docs]: https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints
[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -102,8 +102,8 @@ To change these settings:
block_auto_created_users: true
```
Now we can choose one or more of the Supported Providers listed above to continue
the configuration process.
Now we can choose one or more of the [Supported Providers](#supported-providers)
listed above to continue the configuration process.
## Enable OmniAuth for an Existing User

View File

@ -82,7 +82,7 @@ GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.7.8
sudo -u git -H git checkout v0.7.11
sudo -u git -H make
```

View File

@ -64,7 +64,7 @@ module API
ref = branches.first
end
pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
pipeline = @project.ensure_pipeline(ref, commit.sha, current_user)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])

View File

@ -154,21 +154,15 @@ module API
render_api_error!({ labels: errors }, 400)
end
project = user_project
attrs[:labels] = params[:labels] if params[:labels]
issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute
issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute
if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400)
end
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
# we already checked its name, so there can't be an error here
if params[:labels].present?
issue.add_labels_by_names(params[:labels].split(','))
end
present issue, with: Entities::Issue, current_user: current_user
else
render_validation_error!(issue)
@ -202,17 +196,11 @@ module API
render_api_error!({ labels: errors }, 400)
end
attrs[:labels] = params[:labels] if params[:labels]
issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
# we already checked its name, so there can't be an error here
if params[:labels] && can?(current_user, :admin_issue, user_project)
issue.remove_labels
# Create and add labels to the new created issue
issue.add_labels_by_names(params[:labels].split(','))
end
present issue, with: Entities::Issue, current_user: current_user
else
render_validation_error!(issue)

View File

@ -12,9 +12,7 @@ module Gitlab
@ref = ref
@job = job
@pipeline = @project.pipelines
.latest_successful_for(@ref)
.first
@pipeline = @project.pipelines.latest_successful_for(@ref)
end
def entity

View File

@ -5,11 +5,10 @@ module Gitlab
##
# Entry that represents a hidden CI/CD job.
#
class HiddenJob < Entry
class Hidden < Entry
include Validatable
validations do
validates :config, type: Hash
validates :config, presence: true
end

View File

@ -30,7 +30,7 @@ module Gitlab
def compose!
@config.each do |name, config|
node = hidden?(name) ? Node::HiddenJob : Node::Job
node = hidden?(name) ? Node::Hidden : Node::Job
factory = Node::Factory.new(node)
.value(config || {})

View File

@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.closed?).to be_truthy
end
it 'allows editing of a closed merge request' do
merge_request.close!
put :update,
namespace_id: project.namespace.path,
project_id: project.path,
id: merge_request.iid,
merge_request: {
title: 'New title'
}
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.title).to eq 'New title'
end
it 'does not allow to update target branch closed merge request' do
merge_request.close!
put :update,
namespace_id: project.namespace.path,
project_id: project.path,
id: merge_request.iid,
merge_request: {
target_branch: 'new_branch'
}
expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
end
end
end

View File

@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do
end
end
context 'when the MR only supports legacy diff notes' do
before do
@merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline')
end
context 'with a new line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
end
end
context 'with an old line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
end
end
context 'with an unchanged line' do
it 'should allow commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
end
end
context 'with a match line' do
it 'should not allow commenting' do
should_not_allow_commenting(find('.match', match: :first))
end
end
end
def should_allow_commenting(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
line[:content].hover

View File

@ -0,0 +1,40 @@
require 'spec_helper'
feature 'Download buttons in branches page', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit('binary-encoding').sha,
ref: 'binary-encoding', # make sure the branch is in the 1st page!
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when checking branches' do
context 'with artifacts' do
before do
visit namespace_project_branches_path(project.namespace, project)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end

View File

@ -0,0 +1,41 @@
require 'spec_helper'
feature 'Download buttons in files tree', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit.sha,
ref: project.default_branch,
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when files tree' do
context 'with artifacts' do
before do
visit namespace_project_tree_path(
project.namespace, project, project.default_branch)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end

View File

@ -0,0 +1,40 @@
require 'spec_helper'
feature 'Download buttons in project main page', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit.sha,
ref: project.default_branch,
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when checking project main page' do
context 'with artifacts' do
before do
visit namespace_project_path(project.namespace, project)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end

View File

@ -0,0 +1,41 @@
require 'spec_helper'
feature 'Download buttons in tags page', feature: true do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
given(:tag) { 'v1.0.0' }
given(:project) { create(:project) }
given(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit(tag).sha,
ref: tag,
status: status)
end
given!(:build) do
create(:ci_build, :success, :artifacts,
pipeline: pipeline,
status: pipeline.status,
name: 'build')
end
background do
login_as(user)
project.team << [user, role]
end
describe 'when checking tags' do
context 'with artifacts' do
before do
visit namespace_project_tags_path(project.namespace, project)
end
scenario 'shows download artifacts button' do
expect(page).to have_link "Download '#{build.name}'"
end
end
end
end

View File

@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do
MARKDOWN
end
let(:singleIncompleteMarkdown) do
<<-MARKDOWN.strip_heredoc
This is a task list:
- [ ] Incomplete entry 1
MARKDOWN
end
let(:singleCompleteMarkdown) do
<<-MARKDOWN.strip_heredoc
This is a task list:
- [x] Incomplete entry 1
MARKDOWN
end
before do
Warden.test_mode!
@ -34,77 +50,145 @@ feature 'Task Lists', feature: true do
end
describe 'for Issues' do
let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
describe 'multiple tasks' do
let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
it 'renders' do
visit_issue(project, issue)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 6)
expect(page).to have_selector('ul input[checked]', count: 2)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 6)
expect(page).to have_selector('ul input[checked]', count: 2)
end
it 'contains the required selectors' do
visit_issue(project, issue)
container = '.detail-page-description .description.js-task-list-container'
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
end
it 'is only editable by author' do
visit_issue(project, issue)
expect(page).to have_selector('.js-task-list-container')
logout(:user)
login_as(user2)
visit current_path
expect(page).not_to have_selector('.js-task-list-container')
end
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("2 of 6 tasks completed")
end
end
it 'contains the required selectors' do
visit_issue(project, issue)
describe 'single incomplete task' do
let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
container = '.detail-page-description .description.js-task-list-container'
it 'renders' do
visit_issue(project, issue)
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 0)
end
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("0 of 1 task completed")
end
end
it 'is only editable by author' do
visit_issue(project, issue)
expect(page).to have_selector('.js-task-list-container')
describe 'single complete task' do
let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) }
logout(:user)
it 'renders' do
visit_issue(project, issue)
login_as(user2)
visit current_path
expect(page).not_to have_selector('.js-task-list-container')
end
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 1)
end
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("1 of 1 task completed")
end
end
end
describe 'for Notes' do
let!(:issue) { create(:issue, author: user, project: project) }
let!(:note) do
create(:note, note: markdown, noteable: issue,
project: project, author: user)
describe 'multiple tasks' do
let!(:note) do
create(:note, note: markdown, noteable: issue,
project: project, author: user)
end
it 'renders for note body' do
visit_issue(project, issue)
expect(page).to have_selector('.note ul.task-list', count: 1)
expect(page).to have_selector('.note li.task-list-item', count: 6)
expect(page).to have_selector('.note ul input[checked]', count: 2)
end
it 'contains the required selectors' do
visit_issue(project, issue)
expect(page).to have_selector('.note .js-task-list-container')
expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
end
it 'is only editable by author' do
visit_issue(project, issue)
expect(page).to have_selector('.js-task-list-container')
logout(:user)
login_as(user2)
visit current_path
expect(page).not_to have_selector('.js-task-list-container')
end
end
it 'renders for note body' do
visit_issue(project, issue)
describe 'single incomplete task' do
let!(:note) do
create(:note, note: singleIncompleteMarkdown, noteable: issue,
project: project, author: user)
end
expect(page).to have_selector('.note ul.task-list', count: 1)
expect(page).to have_selector('.note li.task-list-item', count: 6)
expect(page).to have_selector('.note ul input[checked]', count: 2)
it 'renders for note body' do
visit_issue(project, issue)
expect(page).to have_selector('.note ul.task-list', count: 1)
expect(page).to have_selector('.note li.task-list-item', count: 1)
expect(page).to have_selector('.note ul input[checked]', count: 0)
end
end
it 'contains the required selectors' do
visit_issue(project, issue)
describe 'single complete task' do
let!(:note) do
create(:note, note: singleCompleteMarkdown, noteable: issue,
project: project, author: user)
end
expect(page).to have_selector('.note .js-task-list-container')
expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox')
expect(page).to have_selector('.note .js-task-list-container .js-task-list-field')
end
it 'renders for note body' do
visit_issue(project, issue)
it 'is only editable by author' do
visit_issue(project, issue)
expect(page).to have_selector('.js-task-list-container')
logout(:user)
login_as(user2)
visit current_path
expect(page).not_to have_selector('.js-task-list-container')
expect(page).to have_selector('.note ul.task-list', count: 1)
expect(page).to have_selector('.note li.task-list-item', count: 1)
expect(page).to have_selector('.note ul input[checked]', count: 1)
end
end
end
@ -113,42 +197,78 @@ feature 'Task Lists', feature: true do
visit namespace_project_merge_request_path(project.namespace, project, merge)
end
let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
describe 'multiple tasks' do
let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
it 'renders for description' do
visit_merge_request(project, merge)
it 'renders for description' do
visit_merge_request(project, merge)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 6)
expect(page).to have_selector('ul input[checked]', count: 2)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 6)
expect(page).to have_selector('ul input[checked]', count: 2)
end
it 'contains the required selectors' do
visit_merge_request(project, merge)
container = '.detail-page-description .description.js-task-list-container'
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
end
it 'is only editable by author' do
visit_merge_request(project, merge)
expect(page).to have_selector('.js-task-list-container')
logout(:user)
login_as(user2)
visit current_path
expect(page).not_to have_selector('.js-task-list-container')
end
it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("2 of 6 tasks completed")
end
end
describe 'single incomplete task' do
let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) }
it 'renders for description' do
visit_merge_request(project, merge)
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 0)
end
it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("0 of 1 task completed")
end
end
it 'contains the required selectors' do
visit_merge_request(project, merge)
describe 'single complete task' do
let!(:merge) { create(:merge_request, :simple, description: singleCompleteMarkdown, author: user, source_project: project) }
container = '.detail-page-description .description.js-task-list-container'
it 'renders for description' do
visit_merge_request(project, merge)
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
end
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
expect(page).to have_selector('ul input[checked]', count: 1)
end
it 'is only editable by author' do
visit_merge_request(project, merge)
expect(page).to have_selector('.js-task-list-container')
logout(:user)
login_as(user2)
visit current_path
expect(page).not_to have_selector('.js-task-list-container')
end
it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("6 tasks (2 completed, 4 remaining)")
it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("1 of 1 task completed")
end
end
end
end

View File

@ -1,15 +1,15 @@
require 'spec_helper'
describe Gitlab::Ci::Config::Node::HiddenJob do
describe Gitlab::Ci::Config::Node::Hidden do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when entry config value is correct' do
let(:config) { { image: 'ruby:2.2' } }
let(:config) { [:some, :array] }
describe '#value' do
it 'returns key value' do
expect(entry.value).to eq(image: 'ruby:2.2')
expect(entry.value).to eq [:some, :array]
end
end
@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do
end
context 'when entry value is not correct' do
context 'incorrect config value type' do
let(:config) { ['incorrect'] }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'hidden job config should be a hash'
end
end
end
context 'when config is empty' do
let(:config) { {} }

View File

@ -74,7 +74,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
expect(entry.descendants.first(2))
.to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
expect(entry.descendants.last)
.to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob)
.to be_an_instance_of(Gitlab::Ci::Config::Node::Hidden)
end
end

View File

@ -282,4 +282,17 @@ describe Ability, lib: true do
end
end
end
describe '.project_disabled_features_rules' do
let(:project) { build(:project) }
subject { described_class.project_disabled_features_rules(project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
expect(project).to receive(:has_wiki?).and_return(false)
expect(subject).to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
end
end
end
end

View File

@ -948,15 +948,17 @@ describe Ci::Build, models: true do
before { build.run! }
it 'returns false' do
expect(build.retryable?).to be false
expect(build).not_to be_retryable
end
end
context 'when build is finished' do
before { build.success! }
before do
build.success!
end
it 'returns true' do
expect(build.retryable?).to be true
expect(build).to be_retryable
end
end
end

View File

@ -195,6 +195,36 @@ describe Ci::Pipeline, models: true do
end
end
context 'with non-empty project' do
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_pipeline,
project: project,
ref: project.default_branch,
sha: project.commit.sha)
end
describe '#latest?' do
context 'with latest sha' do
it 'returns true' do
expect(pipeline).to be_latest
end
end
context 'with not latest sha' do
before do
pipeline.update(
sha: project.commit("#{project.default_branch}~1").sha)
end
it 'returns false' do
expect(pipeline).not_to be_latest
end
end
end
end
describe '#manual_actions' do
subject { pipeline.manual_actions }

View File

@ -477,8 +477,8 @@ describe MergeRequest, models: true do
allow(subject).to receive(:diff_head_sha).and_return('123abc')
expect(subject.source_project).to receive(:pipeline).
with('123abc', 'master').
expect(subject.source_project).to receive(:pipeline_for).
with('master', '123abc').
and_return(pipeline)
expect(subject.pipeline).to eq(pipeline)
@ -962,4 +962,80 @@ describe MergeRequest, models: true do
expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
end
end
describe "#forked_source_project_missing?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
context "when the fork exists" do
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it { expect(merge_request.forked_source_project_missing?).to be_falsey }
end
context "when the source project is the same as the target project" do
let(:merge_request) { create(:merge_request, source_project: project) }
it { expect(merge_request.forked_source_project_missing?).to be_falsey }
end
context "when the fork does not exist" do
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it "returns true" do
unlink_project.execute
merge_request.reload
expect(merge_request.forked_source_project_missing?).to be_truthy
end
end
end
describe "#closed_without_fork?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
context "when the merge request is closed" do
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project)
end
it "returns false if the fork exist" do
expect(closed_merge_request.closed_without_fork?).to be_falsey
end
it "returns true if the fork does not exist" do
unlink_project.execute
closed_merge_request.reload
expect(closed_merge_request.closed_without_fork?).to be_truthy
end
end
context "when the merge request is open" do
let(:open_merge_request) do
create(:merge_request,
source_project: fork_project,
target_project: project)
end
it "returns false" do
expect(open_merge_request.closed_without_fork?).to be_falsey
end
end
end
end

View File

@ -506,6 +506,18 @@ describe Project, models: true do
end
end
describe '#has_wiki?' do
let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) }
let(:wiki_enabled_project) { build(:project, wiki_enabled: true) }
let(:external_wiki_project) { build(:project, has_external_wiki: true) }
it 'returns true if project is wiki enabled or has external wiki' do
expect(wiki_enabled_project).to have_wiki
expect(external_wiki_project).to have_wiki
expect(no_wiki_project).not_to have_wiki
end
end
describe '#external_wiki' do
let(:project) { create(:project) }
@ -685,23 +697,37 @@ describe Project, models: true do
end
end
describe '#pipeline' do
let(:project) { create :project }
let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' }
describe '#pipeline_for' do
let(:project) { create(:project) }
let!(:pipeline) { create_pipeline }
subject { project.pipeline(pipeline.sha, 'master') }
shared_examples 'giving the correct pipeline' do
it { is_expected.to eq(pipeline) }
it { is_expected.to eq(pipeline) }
context 'return latest' do
let!(:pipeline2) { create_pipeline }
context 'return latest' do
let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' }
before do
pipeline
pipeline2
it { is_expected.to eq(pipeline2) }
end
end
it { is_expected.to eq(pipeline2) }
context 'with explicit sha' do
subject { project.pipeline_for('master', pipeline.sha) }
it_behaves_like 'giving the correct pipeline'
end
context 'with implicit sha' do
subject { project.pipeline_for('master') }
it_behaves_like 'giving the correct pipeline'
end
def create_pipeline
create(:ci_pipeline,
project: project,
ref: 'master',
sha: project.commit('master').sha)
end
end

View File

@ -15,7 +15,9 @@ describe API::API, api: true do
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
before do
get api("/projects/#{project.id}/builds?#{query}", api_user)
end
context 'authorized user' do
it 'returns project builds' do
@ -122,7 +124,9 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/builds/:build_id' do
before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
before do
get api("/projects/#{project.id}/builds/#{build.id}", api_user)
end
context 'authorized user' do
it 'returns specific build data' do
@ -141,7 +145,9 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/builds/:build_id/artifacts' do
before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
before do
get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
context 'build with artifacts' do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
@ -292,7 +298,9 @@ describe API::API, api: true do
end
describe 'POST /projects/:id/builds/:build_id/cancel' do
before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) }
before do
post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user)
end
context 'authorized user' do
context 'user with :update_build persmission' do
@ -323,7 +331,9 @@ describe API::API, api: true do
describe 'POST /projects/:id/builds/:build_id/retry' do
let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
before do
post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user)
end
context 'authorized user' do
context 'user with :update_build permission' do

View File

@ -95,7 +95,7 @@ describe API::API, api: true do
end
it "returns status for CI" do
pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master')
pipeline = project.ensure_pipeline('master', project.repository.commit.sha)
pipeline.update(status: 'success')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
@ -105,7 +105,7 @@ describe API::API, api: true do
end
it "returns status for CI when pipeline is created" do
project.ensure_pipeline(project.repository.commit.sha, 'master')
project.ensure_pipeline('master', project.repository.commit.sha)
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)

View File

@ -2,6 +2,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:non_member) { create(:user) }
@ -478,6 +479,18 @@ describe API::API, api: true do
expect(json_response['labels']).to eq(['label', 'label2'])
end
it "sends notifications for subscribers of newly added labels" do
label = project.labels.first
label.toggle_subscription(user2)
perform_enqueued_jobs do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', labels: label.title
end
should_email(user2)
end
it "returns a 400 bad request if title not given" do
post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
expect(response).to have_http_status(400)
@ -633,6 +646,18 @@ describe API::API, api: true do
expect(json_response['labels']).to eq([label.title])
end
it "sends notifications for subscribers of newly added labels when issue is updated" do
label = create(:label, title: 'foo', color: '#FFAABB', project: project)
label.toggle_subscription(user2)
perform_enqueued_jobs do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title', labels: label.title
end
should_email(user2)
end
it 'removes all labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: ''

View File

@ -0,0 +1,117 @@
require 'spec_helper'
describe Projects::ArtifactsController do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_pipeline,
project: project,
sha: project.commit.sha,
ref: project.default_branch,
status: 'success')
end
let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
describe 'GET /:project/builds/artifacts/:ref_name/browse?job=name' do
before do
project.team << [user, :developer]
login_as(user)
end
def path_from_ref(
ref = pipeline.ref, job = build.name, path = 'browse')
latest_succeeded_namespace_project_artifacts_path(
project.namespace,
project,
[ref, path].join('/'),
job: job)
end
context 'cannot find the build' do
shared_examples 'not found' do
it { expect(response).to have_http_status(:not_found) }
end
context 'has no such ref' do
before do
get path_from_ref('TAIL', build.name)
end
it_behaves_like 'not found'
end
context 'has no such build' do
before do
get path_from_ref(pipeline.ref, 'NOBUILD')
end
it_behaves_like 'not found'
end
context 'has no path' do
before do
get path_from_ref(pipeline.sha, build.name, '')
end
it_behaves_like 'not found'
end
end
context 'found the build and redirect' do
shared_examples 'redirect to the build' do
it 'redirects' do
path = browse_namespace_project_build_artifacts_path(
project.namespace,
project,
build)
expect(response).to redirect_to(path)
end
end
context 'with regular branch' do
before do
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
get path_from_ref('master')
end
it_behaves_like 'redirect to the build'
end
context 'with branch name containing slash' do
before do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
get path_from_ref('improve/awesome')
end
it_behaves_like 'redirect to the build'
end
context 'with branch name and path containing slashes' do
before do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
get path_from_ref('improve/awesome', build.name, 'file/README.md')
end
it 'redirects' do
path = file_namespace_project_build_artifacts_path(
project.namespace,
project,
build,
'README.md')
expect(response).to redirect_to(path)
end
end
end
end
end

View File

@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' }
let(:pipeline) { project.ensure_pipeline(commit_sha, 'master') }
let(:pipeline) { project.ensure_pipeline('master', commit_sha) }
let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) }
describe '#execute' do

View File

@ -0,0 +1,87 @@
require 'spec_helper'
describe MergeRequests::ResolveService do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) do
create(:forked_project_with_submodules) do |fork_project|
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
end
end
let(:merge_request) do
create(:merge_request,
source_branch: 'conflict-resolvable', source_project: project,
target_branch: 'conflict-start')
end
let(:merge_request_from_fork) do
create(:merge_request,
source_branch: 'conflict-resolvable-fork', source_project: fork_project,
target_branch: 'conflict-start', target_project: project)
end
describe '#execute' do
context 'with valid params' do
let(:params) do
{
sections: {
'2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin',
'6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin'
},
commit_message: 'This is a commit message!'
}
end
context 'when the source and target project are the same' do
before do
MergeRequests::ResolveService.new(project, user, params).execute(merge_request)
end
it 'creates a commit with the message' do
expect(merge_request.source_branch_head.message).to eq(params[:commit_message])
end
it 'creates a commit with the correct parents' do
expect(merge_request.source_branch_head.parents.map(&:id)).
to eq(['1450cd639e0bc6721eb02800169e464f212cde06',
'75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b'])
end
end
context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do
project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false)
end
before do
MergeRequests::ResolveService.new(fork_project, user, params).execute(merge_request_from_fork)
end
it 'creates a commit with the message' do
expect(merge_request_from_fork.source_branch_head.message).to eq(params[:commit_message])
end
it 'creates a commit with the correct parents' do
expect(merge_request_from_fork.source_branch_head.parents.map(&:id)).
to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813',
target_head])
end
end
end
context 'when a resolution is missing' do
let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } }
let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) }
it 'raises a MissingResolution error' do
expect { service.execute(merge_request) }.
to raise_error(Gitlab::Conflict::File::MissingResolution)
end
end
end
end

View File

@ -26,9 +26,9 @@ RSpec.configure do |config|
config.verbose_retry = true
config.display_try_failure_messages = true
config.include Devise::TestHelpers, type: :controller
config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include Devise::TestHelpers, type: :controller
config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :feature
config.include StubConfiguration
config.include EmailHelpers
config.include TestEnv

View File

@ -3,30 +3,57 @@
# Requires a context containing:
# subject { Issue or MergeRequest }
shared_examples 'a Taskable' do
before do
subject.description = <<-EOT.strip_heredoc
* [ ] Task 1
* [x] Task 2
* [x] Task 3
* [ ] Task 4
* [ ] Task 5
EOT
end
it 'returns the correct task status' do
expect(subject.task_status).to match('5 tasks')
expect(subject.task_status).to match('2 completed')
expect(subject.task_status).to match('3 remaining')
end
describe '#tasks?' do
it 'returns true when object has tasks' do
expect(subject.tasks?).to eq true
describe 'with multiple tasks' do
before do
subject.description = <<-EOT.strip_heredoc
* [ ] Task 1
* [x] Task 2
* [x] Task 3
* [ ] Task 4
* [ ] Task 5
EOT
end
it 'returns false when object has no tasks' do
subject.description = 'Now I have no tasks'
expect(subject.tasks?).to eq false
it 'returns the correct task status' do
expect(subject.task_status).to match('2 of')
expect(subject.task_status).to match('5 tasks completed')
end
describe '#tasks?' do
it 'returns true when object has tasks' do
expect(subject.tasks?).to eq true
end
it 'returns false when object has no tasks' do
subject.description = 'Now I have no tasks'
expect(subject.tasks?).to eq false
end
end
end
describe 'with an incomplete task' do
before do
subject.description = <<-EOT.strip_heredoc
* [ ] Task 1
EOT
end
it 'returns the correct task status' do
expect(subject.task_status).to match('0 of')
expect(subject.task_status).to match('1 task completed')
end
end
describe 'with a complete task' do
before do
subject.description = <<-EOT.strip_heredoc
* [x] Task 1
EOT
end
it 'returns the correct task status' do
expect(subject.task_status).to match('1 of')
expect(subject.task_status).to match('1 task completed')
end
end
end

View File

@ -6,7 +6,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'empty-branch' => '7efb185',
'ends-with.json' => '98b0d8b3',
'ends-with.json' => '98b0d8b',
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
@ -37,9 +37,10 @@ module TestEnv
# need to keep all the branches in sync.
# We currently only need a subset of the branches
FORKED_BRANCH_SHA = {
'add-submodule-version-bump' => '3f547c08',
'master' => '5937ac0',
'remove-submodule' => '2a33e0c0'
'add-submodule-version-bump' => '3f547c0',
'master' => '5937ac0',
'remove-submodule' => '2a33e0c',
'conflict-resolvable-fork' => '404fa3f'
}
# Test environment
@ -117,22 +118,7 @@ module TestEnv
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end
Dir.chdir(repo_path) do
branch_sha.each do |branch, sha|
# Try to reset without fetching to avoid using the network.
reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
unless system(*reset)
if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
unless system(*reset)
raise 'The fetched test seed '\
'does not contain the required revision.'
end
else
raise 'Could not fetch test seed repository.'
end
end
end
end
set_repo_refs(repo_path, branch_sha)
# We must copy bare repositories because we will push to them.
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
@ -144,6 +130,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, BRANCH_SHA)
end
def repos_path
@ -160,6 +147,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
end
# When no cached assets exist, manually hit the root path to create them
@ -209,4 +197,23 @@ module TestEnv
def git_env
{ 'GIT_TEMPLATE_DIR' => '' }
end
def set_repo_refs(repo_path, branch_sha)
Dir.chdir(repo_path) do
branch_sha.each do |branch, sha|
# Try to reset without fetching to avoid using the network.
reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha})
unless system(*reset)
if system(*%W(#{Gitlab.config.git.bin_path} fetch origin))
unless system(*reset)
raise 'The fetched test seed '\
'does not contain the required revision.'
end
else
raise 'Could not fetch test seed repository.'
end
end
end
end
end
end

View File

@ -0,0 +1,53 @@
require 'spec_helper'
describe 'projects/merge_requests/edit.html.haml' do
include Devise::TestHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project,
author: user)
end
before do
assign(:project, project)
assign(:merge_request, closed_merge_request)
allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:current_user)
.and_return(User.find(closed_merge_request.author_id))
end
context 'when a merge request without fork' do
it "shows editable fields" do
unlink_project.execute
closed_merge_request.reload
render
expect(rendered).to have_field('merge_request[title]')
expect(rendered).to have_field('merge_request[description]')
expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false)
end
end
context 'when a merge request with an existing source project is closed' do
it "shows editable fields" do
render
expect(rendered).to have_field('merge_request[title]')
expect(rendered).to have_field('merge_request[description]')
expect(rendered).to have_selector('#merge_request_assignee_id', visible: false)
expect(rendered).to have_selector('#merge_request_milestone_id', visible: false)
expect(rendered).to have_selector('#merge_request_target_branch', visible: false)
end
end
end

View File

@ -0,0 +1,44 @@
require 'spec_helper'
describe 'projects/merge_requests/show.html.haml' do
include Devise::TestHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
target_project: project,
author: user)
end
before do
assign(:project, project)
assign(:merge_request, closed_merge_request)
assign(:commits_count, 0)
allow(view).to receive(:can?).and_return(true)
end
context 'when the merge request is closed' do
it 'shows the "Reopen" button' do
render
expect(rendered).to have_css('a', visible: true, text: 'Reopen')
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
it 'does not show the "Reopen" button when the source project does not exist' do
unlink_project.execute
closed_merge_request.reload
render
expect(rendered).to have_css('a', visible: false, text: 'Reopen')
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
end
end