Commit 341541d3 authored by Stan Hu's avatar Stan Hu

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

parents 0051f58f 7dd97cff
...@@ -3,25 +3,30 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -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) v 8.12.0 (unreleased)
- Make push events have equal vertical spacing. - Make push events have equal vertical spacing.
- Add two-factor recovery endpoint to internal API !5510 - 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) - Add font color contrast to external label in admin area (ClemMakesApps)
- Change logo animation to CSS (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps)
- Change merge_error column from string to text type - Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps) - Reduce contributions calendar data payload (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - 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) - 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) - Add hover color to emoji icon (ClemMakesApps)
- Fix branches page dropdown sort alignment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps)
- Add white background for no readme container (ClemMakesApps) - Add white background for no readme container (ClemMakesApps)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import - Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps)
- Add Sentry logging to API calls - Add Sentry logging to API calls
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Remove unused mixins (ClemMakesApps) - Remove unused mixins (ClemMakesApps)
- Fix groups sort dropdown alignment (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Fix markdown help references (ClemMakesApps) - Fix markdown help references (ClemMakesApps)
- Add last commit time to repo view (ClemMakesApps)
- Added tests for diff notes - 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) - Add delimiter to project stars and forks count (ClemMakesApps)
- Fix badge count alignment (ClemMakesApps) - Fix badge count alignment (ClemMakesApps)
- Fix branch title trailing space on hover (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps)
...@@ -41,17 +46,24 @@ v 8.12.0 (unreleased) ...@@ -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) - 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 - Adds response mime type to transaction metric action when it's not HTML
- Fix hover leading space bug in pipeline graph !5980 - 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) v 8.11.4 (unreleased)
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - 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) v 8.11.4 (unreleased)
- Fix issue boards leak private label names and descriptions - Fix issue boards leak private label names and descriptions
v 8.11.3 (unreleased) 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 - Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label - Label list shows all issues (opened or closed) with that label
- Don't show resolve conflicts link before MR status is updated - 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 - Fix IE11 fork button bug !598
- Don't prevent viewing the MR when git refs for conflicts can't be found on disk - 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 - Fix external issue tracker "Issues" link leading to 404s
......
...@@ -195,6 +195,12 @@ ...@@ -195,6 +195,12 @@
.separator + .dropdown-header { .separator + .dropdown-header {
padding-top: 2px; padding-top: 2px;
} }
.unclickable {
cursor: not-allowed;
padding: 5px 8px;
color: $dropdown-header-color;
}
} }
.dropdown-menu-large { .dropdown-menu-large {
......
...@@ -8,10 +8,7 @@ ...@@ -8,10 +8,7 @@
height: 30px; height: 30px;
transition-duration: .3s; transition-duration: .3s;
-webkit-transform: translateZ(0); -webkit-transform: translateZ(0);
background: -webkit-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));
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%);
&.scrolling { &.scrolling {
visibility: visible; visibility: visible;
...@@ -211,12 +208,6 @@ ...@@ -211,12 +208,6 @@
} }
} }
.project-filter-form {
input {
background-color: $background-color;
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
padding-bottom: 0; padding-bottom: 0;
width: 100%; width: 100%;
......
...@@ -43,6 +43,15 @@ ...@@ -43,6 +43,15 @@
border-color: $blue-normal; border-color: $blue-normal;
} }
&.ci-created {
color: $table-text-gray;
border-color: $table-text-gray;
svg {
fill: $table-text-gray;
}
}
svg { svg {
height: 13px; height: 13px;
width: 13px; width: 13px;
......
...@@ -11,6 +11,16 @@ ...@@ -11,6 +11,16 @@
} }
} }
.last-commit {
max-width: 506px;
.last-commit-content {
@include str-truncated;
width: calc(100% - 140px);
margin-left: 3px;
}
}
.tree-table { .tree-table {
margin-bottom: 0; margin-bottom: 0;
......
class Projects::ArtifactsController < Projects::ApplicationController class Projects::ArtifactsController < Projects::ApplicationController
include ExtractsPath
layout 'project' layout 'project'
before_action :authorize_read_build! before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep] before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path
before_action :validate_artifacts! before_action :validate_artifacts!
def download def download
unless artifacts_file.file_storage? if artifacts_file.file_storage?
return redirect_to artifacts_file.url
end
send_file artifacts_file.path, disposition: 'attachment' send_file artifacts_file.path, disposition: 'attachment'
else
redirect_to artifacts_file.url
end
end end
def browse def browse
directory = params[:path] ? "#{params[:path]}/" : '' directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory) @entry = build.artifacts_metadata_entry(directory)
return render_404 unless @entry.exists? render_404 unless @entry.exists?
end end
def file def file
...@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -34,14 +37,41 @@ class Projects::ArtifactsController < Projects::ApplicationController
redirect_to namespace_project_build_path(project.namespace, project, build) redirect_to namespace_project_build_path(project.namespace, project, build)
end 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 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! def validate_artifacts!
render_404 unless build.artifacts? render_404 unless build && build.artifacts?
end end
def build 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 end
def artifacts_file def artifacts_file
......
...@@ -25,6 +25,11 @@ module CiStatusHelper ...@@ -25,6 +25,11 @@ module CiStatusHelper
end end
end end
def ci_status_for_statuseable(subject)
status = subject.try(:status) || 'not found'
status.humanize
end
def ci_icon_for_status(status) def ci_icon_for_status(status)
icon_name = icon_name =
case status case status
...@@ -41,7 +46,7 @@ module CiStatusHelper ...@@ -41,7 +46,7 @@ module CiStatusHelper
when 'play' when 'play'
'icon_play' 'icon_play'
when 'created' when 'created'
'icon_status_pending' 'icon_status_created'
else else
'icon_status_cancel' 'icon_status_cancel'
end end
......
...@@ -149,4 +149,20 @@ module GitlabRoutingHelper ...@@ -149,4 +149,20 @@ module GitlabRoutingHelper
def resend_invite_group_member_path(group_member, *args) def resend_invite_group_member_path(group_member, *args)
resend_invite_group_group_member_path(group_member.source, group_member) resend_invite_group_group_member_path(group_member.source, group_member)
end 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 end
...@@ -98,6 +98,6 @@ module MergeRequestsHelper ...@@ -98,6 +98,6 @@ module MergeRequestsHelper
end end
def merge_request_button_visibility(merge_request, closed) 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
end end
...@@ -355,7 +355,7 @@ class Ability ...@@ -355,7 +355,7 @@ class Ability
rules += named_abilities('project_snippet') rules += named_abilities('project_snippet')
end end
unless project.wiki_enabled unless project.has_wiki?
rules += named_abilities('wiki') rules += named_abilities('wiki')
end end
......
...@@ -65,8 +65,8 @@ module Ci ...@@ -65,8 +65,8 @@ module Ci
end end
# ref can't be HEAD or SHA, can only be branch/tag name # ref can't be HEAD or SHA, can only be branch/tag name
scope :latest_successful_for, ->(ref = default_branch) do def self.latest_successful_for(ref)
where(ref: ref).success.order(id: :desc).limit(1) where(ref: ref).order(id: :desc).success.first
end end
def self.truncate_sha(sha) def self.truncate_sha(sha)
......
...@@ -28,4 +28,8 @@ module NoteOnDiff ...@@ -28,4 +28,8 @@ module NoteOnDiff
def can_be_award_emoji? def can_be_award_emoji?
false false
end end
def to_discussion
Discussion.new([self])
end
end end
...@@ -52,11 +52,11 @@ module Taskable ...@@ -52,11 +52,11 @@ module Taskable
end end
# Return a string that describes the current state of this Taskable's task # 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 def task_status
return '' if description.blank? return '' if description.blank?
sum = tasks.summary 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
end end
...@@ -107,10 +107,6 @@ class DiffNote < Note ...@@ -107,10 +107,6 @@ class DiffNote < Note
self.noteable.find_diff_discussion(self.discussion_id) self.noteable.find_diff_discussion(self.discussion_id)
end end
def to_discussion
Discussion.new([self])
end
private private
def supported? def supported?
......
...@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base
end end
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 :source_branch, presence: true
validates :target_project, presence: true validates :target_project, presence: true
validates :target_branch, presence: true validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds? validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches, unless: [:allow_broken, :importing?] validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_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 :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) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
...@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base ...@@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base
def validate_fork def validate_fork
return true unless target_project && source_project 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, errors.add :validate_fork,
'Source project is not a fork of target project' 'Source project is not a fork of the target project'
end end
def closed_without_fork?
closed? && forked_source_project_missing?
end end
def forked_source_project_missing?
return false unless for_fork?
return true unless source_project
!source_project.forked_from?(target_project)
end end
def ensure_merge_request_diff def ensure_merge_request_diff
...@@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -726,7 +729,9 @@ class MergeRequest < ActiveRecord::Base
end end
def pipeline 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 end
def all_pipelines def all_pipelines
......
...@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base ...@@ -436,7 +436,7 @@ class Project < ActiveRecord::Base
# ref can't be HEAD, can only be branch/tag name or SHA # ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch) 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 if latest_pipeline
latest_pipeline.builds.latest.with_artifacts latest_pipeline.builds.latest.with_artifacts
...@@ -680,6 +680,10 @@ class Project < ActiveRecord::Base ...@@ -680,6 +680,10 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end end
def has_wiki?
wiki_enabled? || has_external_wiki?
end
def external_wiki def external_wiki
if has_external_wiki.nil? if has_external_wiki.nil?
cache_has_external_wiki # Populate cache_has_external_wiki # Populate
...@@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base ...@@ -1096,12 +1100,17 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock !namespace.share_with_group_lock
end 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) pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end end
def ensure_pipeline(sha, ref, current_user = nil) def ensure_pipeline(ref, sha, current_user = nil)
pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user) pipeline_for(ref, sha) ||
pipelines.create(sha: sha, ref: ref, user: current_user)
end end
def enable_ci def enable_ci
......
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
...@@ -45,6 +45,7 @@ class IssuableBaseService < BaseService ...@@ -45,6 +45,7 @@ class IssuableBaseService < BaseService
unless can?(current_user, ability, project) unless can?(current_user, ability, project)
params.delete(:milestone_id) params.delete(:milestone_id)
params.delete(:labels)
params.delete(:add_label_ids) params.delete(:add_label_ids)
params.delete(:remove_label_ids) params.delete(:remove_label_ids)
params.delete(:label_ids) params.delete(:label_ids)
...@@ -72,6 +73,7 @@ class IssuableBaseService < BaseService ...@@ -72,6 +73,7 @@ class IssuableBaseService < BaseService
filter_labels_in_param(:add_label_ids) filter_labels_in_param(:add_label_ids)
filter_labels_in_param(:remove_label_ids) filter_labels_in_param(:remove_label_ids)
filter_labels_in_param(:label_ids) filter_labels_in_param(:label_ids)
find_or_create_label_ids
end end
def filter_labels_in_param(key) def filter_labels_in_param(key)
...@@ -80,6 +82,17 @@ class IssuableBaseService < BaseService ...@@ -80,6 +82,17 @@ class IssuableBaseService < BaseService
params[key] = project.labels.where(id: params[key]).pluck(:id) params[key] = project.labels.where(id: params[key]).pluck(:id)
end 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) def process_label_ids(attributes, existing_label_ids: nil)
label_ids = attributes.delete(:label_ids) label_ids = attributes.delete(:label_ids)
add_label_ids = attributes.delete(:add_label_ids) add_label_ids = attributes.delete(:add_label_ids)
...@@ -162,7 +175,12 @@ class IssuableBaseService < BaseService ...@@ -162,7 +175,12 @@ class IssuableBaseService < BaseService
if params.present? && update_issuable(issuable, params) if params.present? && update_issuable(issuable, params)
issuable.reset_events_cache issuable.reset_events_cache
# 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) handle_common_system_notes(issuable, old_labels: old_labels)
end
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users) handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
issuable.create_new_cross_references!(current_user) issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update') execute_hooks(issuable, 'update')
......
module MergeRequests module MergeRequests
class ResolveService < MergeRequests::BaseService class ResolveService < MergeRequests::BaseService
attr_accessor :conflicts, :rugged, :merge_index attr_accessor :conflicts, :rugged, :merge_index, :merge_request
def execute(merge_request) def execute(merge_request)
@conflicts = merge_request.conflicts @conflicts = merge_request.conflicts
@rugged = project.repository.rugged @rugged = project.repository.rugged
@merge_index = conflicts.merge_index @merge_index = conflicts.merge_index
@merge_request = merge_request
fetch_their_commit!
conflicts.files.each do |file| conflicts.files.each do |file|
write_resolved_file_to_index(file, params[:sections]) write_resolved_file_to_index(file, params[:sections])
...@@ -27,5 +30,21 @@ module MergeRequests ...@@ -27,5 +30,21 @@ module MergeRequests
merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode)
merge_index.conflict_remove(our_path) merge_index.conflict_remove(our_path)
end 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
end end
...@@ -11,6 +11,10 @@ module MergeRequests ...@@ -11,6 +11,10 @@ module MergeRequests
params.except!(:target_project_id) params.except!(:target_project_id)
params.except!(:source_branch) 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) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
update(merge_request) update(merge_request)
......
...@@ -27,6 +27,8 @@ ...@@ -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 = 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 Compare
= render 'projects/buttons/download', project: @project, ref: branch.name
- if can_remove_branch?(@project, 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 = 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") = icon("trash-o")
......
- unless @project.empty_repo? - if !project.empty_repo? && can?(current_user, :download_code, project)
- if can? current_user, :download_code, @project %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'}
= 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 .dropdown.inline
%button.btn{ 'data-toggle' => 'dropdown' }
= icon('download') = 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}'
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- @related_branches.each do |branch| - @related_branches.each do |branch|
%li %li
- target = @project.repository.find_branch(branch).target - 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 - if pipeline
%span.related-branch-ci-status %span.related-branch-ci-status
= render_pipeline_status(pipeline) = render_pipeline_status(pipeline)
......
- if @merge_request.closed_without_fork?
.alert.alert-danger
%p The source project of this merge request has been removed.
.clearfix.detail-page-header .clearfix.detail-page-header
.issuable-header .issuable-header
.issuable-status-box.status-box{ class: status_box_class(@merge_request) } .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
......
- 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
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
= render "projects/buttons/koding" = render "projects/buttons/koding"
.btn-group.project-repo-btn-group .btn-group.project-repo-btn-group
= render "projects/buttons/download" = render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown' = render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting = render 'shared/notifications/button', notification_setting: @notification_setting
......
%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
...@@ -11,8 +11,7 @@ ...@@ -11,8 +11,7 @@
= strip_gpg_signature(tag.message) = strip_gpg_signature(tag.message)
.controls .controls
- if can?(current_user, :download_code, @project) = render 'projects/buttons/download', project: @project, ref: tag.name
= render 'projects/tags/download', ref: tag.name, project: @project
- if can?(current_user, :push_code, @project) - 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 = 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
......
...@@ -12,8 +12,7 @@ ...@@ -12,8 +12,7 @@
= icon('files-o') = icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history') = icon('history')
- if can? current_user, :download_code, @project = render 'projects/buttons/download', project: @project, ref: @tag.name
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
.pull-right .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 = 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
......
...@@ -5,16 +5,17 @@ ...@@ -5,16 +5,17 @@
%tr %tr
%th Name %th Name
%th Last Update %th Last Update
%th.hidden-xs %th.hidden-xs.last-commit
.pull-left Last Commit Last Commit
.last-commit.hidden-sm.pull-left .last-commit-content.hidden-sm
&nbsp;
%i.fa.fa-angle-right %i.fa.fa-angle-right
&nbsp; &nbsp;
%small.light %small.light
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
&ndash; &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' = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
- if @path.present? - if @path.present?
......
...@@ -10,8 +10,7 @@ ...@@ -10,8 +10,7 @@
%div{ class: container_class } %div{ class: container_class }
.tree-controls .tree-controls
= render 'projects/find_file_link' = render 'projects/find_file_link'
- if can? current_user, :download_code, @project = render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
#tree-holder.tree-holder.clearfix #tree-holder.tree-holder.clearfix
.nav-block .nav-block
......
<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>
...@@ -134,7 +134,7 @@ ...@@ -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.' } 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') = icon('question-circle')
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork?
%hr %hr
- if @merge_request.new_record? - if @merge_request.new_record?
.form-group .form-group
...@@ -175,7 +175,7 @@ ...@@ -175,7 +175,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else - else
.pull-right .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?" }, = 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' 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' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
......
...@@ -782,6 +782,14 @@ Rails.application.routes.draw do ...@@ -782,6 +782,14 @@ Rails.application.routes.draw do
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do collection do
post :cancel_all post :cancel_all
resources :artifacts, only: [] do
collection do
get :latest_succeeded,
path: '*ref_name_and_path',
format: false
end
end
end end
member do member do
......
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
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
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -295,16 +295,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do ...@@ -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", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", 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| create_table "ci_sessions", force: :cascade do |t|
t.string "session_id", null: false t.string "session_id", null: false
t.text "data" t.text "data"
...@@ -360,13 +350,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do ...@@ -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 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| create_table "deploy_keys_projects", force: :cascade do |t|
t.integer "deploy_key_id", null: false t.integer "deploy_key_id", null: false
t.integer "project_id", null: false t.integer "project_id", null: false
......
...@@ -998,6 +998,8 @@ is available before it is returned in the JSON response or an empty response is ...@@ -998,6 +998,8 @@ is available before it is returned in the JSON response or an empty response is
## Branches ## Branches
For more information please consult the [Branches](branches.md) documentation.
### List branches ### List branches
Lists all branches of a project. Lists all branches of a project.
...@@ -1016,56 +1018,46 @@ Parameters: ...@@ -1016,56 +1018,46 @@ Parameters:
"name": "async", "name": "async",
"commit": { "commit": {
"id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca", "id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
"parents": [ "parent_ids": [
{ "3f94fc7c85061973edc9906ae170cc269b07ca55"
"id": "3f94fc7c85061973edc9906ae170cc269b07ca55"
}
], ],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
"message": "give Caolan credit where it's due (up top)", "message": "give Caolan credit where it's due (up top)",
"author": { "author_name": "Jeremy Ashkenas",
"name": "Jeremy Ashkenas", "author_email": "jashkenas@example.com",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2010-12-08T21:28:50+00:00", "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" "committed_date": "2010-12-08T21:28:50+00:00"
}, },
"protected": false "protected": false,
"developers_can_push": false,
"developers_can_merge": false
}, },
{ {
"name": "gh-pages", "name": "gh-pages",
"commit": { "commit": {
"id": "101c10a60019fe870d21868835f65c25d64968fc", "id": "101c10a60019fe870d21868835f65c25d64968fc",
"parents": [ "parent_ids": [
{ "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
"id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
}
], ],
"tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a",
"message": "Underscore.js 1.5.2", "message": "Underscore.js 1.5.2",
"author": { "author_name": "Jeremy Ashkenas",
"name": "Jeremy Ashkenas", "author_email": "jashkenas@example.com",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2013-09-07T12:58:21+00:00", "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" "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 GET /projects/:id/repository/branches/:branch
...@@ -1075,6 +1067,8 @@ Parameters: ...@@ -1075,6 +1067,8 @@ Parameters:
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
- `branch` (required) - The name of the branch. - `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 ### Protect single branch
......
...@@ -39,7 +39,7 @@ If you want a quick introduction to GitLab CI, follow our ...@@ -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) - [before_script and after_script](#before_script-and-after_script)
- [Git Strategy](#git-strategy) - [Git Strategy](#git-strategy)
- [Shallow cloning](#shallow-cloning) - [Shallow cloning](#shallow-cloning)
- [Hidden jobs](#hidden-jobs) - [Hidden keys](#hidden-keys)
- [Special YAML features](#special-yaml-features) - [Special YAML features](#special-yaml-features)
- [Anchors](#anchors) - [Anchors](#anchors)
- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml) - [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
...@@ -934,24 +934,27 @@ variables: ...@@ -934,24 +934,27 @@ variables:
GIT_DEPTH: "3" GIT_DEPTH: "3"
``` ```
## Hidden jobs ## Hidden keys
>**Note:** >**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1. 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 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. into templates.
In the following example, `.job_name` will be ignored: In the following example, `.key_name` will be ignored:
```yaml ```yaml
.job_name: .key_name:
script: script:
- rake spec - 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 ## Special YAML features
It's possible to use special YAML features like anchors (`&`), aliases (`*`) 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. ...@@ -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 YAML also has a handy feature called 'anchors', which let you easily duplicate
content across your document. Anchors can be used to duplicate/inherit 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. to provide templates for your jobs.
The following example uses anchors and map merging. It will create two 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, ...@@ -975,7 +978,7 @@ The following example uses anchors and map merging. It will create two jobs,
having their own custom `script` defined: having their own custom `script` defined:
```yaml ```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 image: ruby:2.1
services: services:
- postgres - postgres
...@@ -1081,7 +1084,7 @@ test:mysql: ...@@ -1081,7 +1084,7 @@ test:mysql:
- ruby - 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 ## Validate the .gitlab-ci.yml
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
This style guide recommends best practices for newlines in Ruby code. 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 ```ruby
# bad # bad
......
# 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. ## Overview
Bitbucket will generate an application ID and secret key for you to use.
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. To enable the Bitbucket OmniAuth provider you must register your application
- Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. with Bitbucket.org. Bitbucket will generate an application ID and secret key for
- Application description: Fill this in if you wish. you to use.
- URL: The URL to your GitLab installation. 'https://gitlab.company.com'
1. Select "Save".
1. You should now see a Key and Secret in the list of OAuth customers. 1. Sign in to [Bitbucket.org](https://bitbucket.org).
Keep this page open as you continue configuration. 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
``` ```
For installations from source: >**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.
```sh ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
cd /home/git/gitlab
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:
sudo -u git -H editor config/gitlab.yml
``` ```
# For Omnibus packages
sudo editor /etc/gitlab/gitlab.rb
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. # For installations from source
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
```
1. Add the provider configuration: 1. Follow the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. Add the Bitbucket provider configuration:
For omnibus package: For Omnibus packages:
```ruby ```ruby
gitlab_rails['omniauth_providers'] = [ gitlab_rails['omniauth_providers'] = [
{ {
"name" => "bitbucket", "name" => "bitbucket",
"app_id" => "YOUR_KEY", "app_id" => "BITBUCKET_APP_KEY",
"app_secret" => "YOUR_APP_SECRET", "app_secret" => "BITBUCKET_APP_SECRET",
"url" => "https://bitbucket.org/" "url" => "https://bitbucket.org/"
} }
] ]
``` ```
For installation from source: For installations from source:
``` ```yaml
- { name: 'bitbucket', app_id: 'YOUR_KEY', - { name: 'bitbucket',
app_secret: 'YOUR_APP_SECRET' } app_id: 'BITBUCKET_APP_KEY',
app_secret: 'BITBUCKET_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. 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```). 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
1. Restart GitLab for the changes to take effect. 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 ## 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 ```sh
sudo -u git -H ssh-keygen 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`. When asked to 'Enter file in which to save the key' enter:
Make sure to use an **empty passphrase**. `/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 ```
# For Omnibus packages
sudo editor /var/opt/gitlab/.ssh/config
# For installations from source
sudo editor /home/git/.ssh/config sudo editor /home/git/.ssh/config
``` ```
Add a host configuration for `bitbucket.org`. 1. Add a host configuration for `bitbucket.org`:
```sh ```sh
Host bitbucket.org Host bitbucket.org
...@@ -113,28 +166,46 @@ If you have that file in place, you're all set and should see the "Import projec ...@@ -113,28 +166,46 @@ If you have that file in place, you're all set and should see the "Import projec
User git User git
``` ```
### Step 2: Known hosts 1. Save the file and exit.
1. Manually connect to `bitbucket.org` over SSH, while logged in as the `git`
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: user that GitLab will use:
1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
```sh ```sh
sudo -u git -H ssh bitbucket.org 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 ```sh
The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. The authenticity of host 'bitbucket.org (104.192.143.1)' can't be established.
RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. RSA key fingerprint is SHA256:zzXQOXSRBEiUtuE8AikJYKwbHaxvSc0ojez9YXaGp1A.
Are you sure you want to continue connecting (yes/no)? 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. 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.
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.
1. Your GitLab server is now able to connect to Bitbucket over SSH. ## Acknowledgemts
1. Restart GitLab to allow it to find the new public key. Special thanks to the writer behind the following article:
- http://stratus3d.com/blog/2015/09/06/migrating-from-bitbucket-to-local-gitlab-server/
You should now see the "Import projects from Bitbucket" option on the New Project page enabled. [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
...@@ -102,8 +102,8 @@ To change these settings: ...@@ -102,8 +102,8 @@ To change these settings:
block_auto_created_users: true block_auto_created_users: true
``` ```
Now we can choose one or more of the Supported Providers listed above to continue Now we can choose one or more of the [Supported Providers](#supported-providers)
the configuration process. listed above to continue the configuration process.
## Enable OmniAuth for an Existing User ## Enable OmniAuth for an Existing User
......
...@@ -82,7 +82,7 @@ GitLab 8.1. ...@@ -82,7 +82,7 @@ GitLab 8.1.
```bash ```bash
cd /home/git/gitlab-workhorse cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all 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 sudo -u git -H make
``` ```
......
...@@ -64,7 +64,7 @@ module API ...@@ -64,7 +64,7 @@ module API
ref = branches.first ref = branches.first
end end
pipeline = @project.ensure_pipeline(commit.sha, ref, current_user) pipeline = @project.ensure_pipeline(ref, commit.sha, current_user)
name = params[:name] || params[:context] name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
......
...@@ -154,21 +154,15 @@ module API ...@@ -154,21 +154,15 @@ module API
render_api_error!({ labels: errors }, 400) render_api_error!({ labels: errors }, 400)
end 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? if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400) render_api_error!({ error: 'Spam detected' }, 400)
end end
if issue.valid? 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 present issue, with: Entities::Issue, current_user: current_user
else else
render_validation_error!(issue) render_validation_error!(issue)
...@@ -202,17 +196,11 @@ module API ...@@ -202,17 +196,11 @@ module API
render_api_error!({ labels: errors }, 400) render_api_error!({ labels: errors }, 400)
end end
attrs[:labels] = params[:labels] if params[:labels]
issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
if issue.valid? 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 present issue, with: Entities::Issue, current_user: current_user
else else
render_validation_error!(issue) render_validation_error!(issue)
......
...@@ -12,9 +12,7 @@ module Gitlab ...@@ -12,9 +12,7 @@ module Gitlab
@ref = ref @ref = ref
@job = job @job = job
@pipeline = @project.pipelines @pipeline = @project.pipelines.latest_successful_for(@ref)
.latest_successful_for(@ref)
.first
end end
def entity def entity
......
...@@ -5,11 +5,10 @@ module Gitlab ...@@ -5,11 +5,10 @@ module Gitlab
## ##
# Entry that represents a hidden CI/CD job. # Entry that represents a hidden CI/CD job.
# #
class HiddenJob < Entry class Hidden < Entry
include Validatable include Validatable
validations do validations do
validates :config, type: Hash
validates :config, presence: true validates :config, presence: true
end end
......
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
def compose! def compose!
@config.each do |name, config| @config.each do |name, config|
node = hidden?(name) ? Node::HiddenJob : Node::Job node = hidden?(name) ? Node::Hidden : Node::Job
factory = Node::Factory.new(node) factory = Node::Factory.new(node)
.value(config || {}) .value(config || {})
......
...@@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do ...@@ -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(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 expect(merge_request.reload.closed?).to be_truthy
end 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
end end
......
...@@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do ...@@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do
end end
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) def should_allow_commenting(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side) line = get_line_components(line_holder, diff_side)
line[:content].hover line[:content].hover
......
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
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
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
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
...@@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do ...@@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do
MARKDOWN MARKDOWN
end 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 before do
Warden.test_mode! Warden.test_mode!
...@@ -34,6 +50,7 @@ feature 'Task Lists', feature: true do ...@@ -34,6 +50,7 @@ feature 'Task Lists', feature: true do
end end
describe 'for Issues' do describe 'for Issues' do
describe 'multiple tasks' do
let!(:issue) { create(:issue, description: markdown, author: user, project: project) } let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
it 'renders' do it 'renders' do
...@@ -69,12 +86,48 @@ feature 'Task Lists', feature: true do ...@@ -69,12 +86,48 @@ feature 'Task Lists', feature: true do
it 'provides a summary on Issues#index' do it 'provides a summary on Issues#index' do
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_content("6 tasks (2 completed, 4 remaining)") expect(page).to have_content("2 of 6 tasks completed")
end
end
describe 'single incomplete task' do
let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
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: 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
describe 'single complete task' do
let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) }
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: 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("1 of 1 task completed")
end
end end
end end
describe 'for Notes' do describe 'for Notes' do
let!(:issue) { create(:issue, author: user, project: project) } let!(:issue) { create(:issue, author: user, project: project) }
describe 'multiple tasks' do
let!(:note) do let!(:note) do
create(:note, note: markdown, noteable: issue, create(:note, note: markdown, noteable: issue,
project: project, author: user) project: project, author: user)
...@@ -108,11 +161,43 @@ feature 'Task Lists', feature: true do ...@@ -108,11 +161,43 @@ feature 'Task Lists', feature: true do
end end
end end
describe 'single incomplete task' do
let!(:note) do
create(:note, note: singleIncompleteMarkdown, 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: 1)
expect(page).to have_selector('.note ul input[checked]', count: 0)
end
end
describe 'single complete task' do
let!(:note) do
create(:note, note: singleCompleteMarkdown, 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: 1)
expect(page).to have_selector('.note ul input[checked]', count: 1)
end
end
end
describe 'for Merge Requests' do describe 'for Merge Requests' do
def visit_merge_request(project, merge) def visit_merge_request(project, merge)
visit namespace_project_merge_request_path(project.namespace, project, merge) visit namespace_project_merge_request_path(project.namespace, project, merge)
end end
describe 'multiple tasks' do
let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) } let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
it 'renders for description' do it 'renders for description' do
...@@ -148,7 +233,42 @@ feature 'Task Lists', feature: true do ...@@ -148,7 +233,42 @@ feature 'Task Lists', feature: true do
it 'provides a summary on MergeRequests#index' do it 'provides a summary on MergeRequests#index' do
visit namespace_project_merge_requests_path(project.namespace, project) visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_content("6 tasks (2 completed, 4 remaining)") 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
describe 'single complete task' do
let!(:merge) { create(:merge_request, :simple, description: singleCompleteMarkdown, 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: 1)
end
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 end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Config::Node::HiddenJob do describe Gitlab::Ci::Config::Node::Hidden do
let(:entry) { described_class.new(config) } let(:entry) { described_class.new(config) }
describe 'validations' do describe 'validations' do
context 'when entry config value is correct' do context 'when entry config value is correct' do
let(:config) { { image: 'ruby:2.2' } } let(:config) { [:some, :array] }
describe '#value' do describe '#value' do
it 'returns key 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
end end
...@@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do ...@@ -21,17 +21,6 @@ describe Gitlab::Ci::Config::Node::HiddenJob do
end end
context 'when entry value is not correct' do 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 context 'when config is empty' do
let(:config) { {} } let(:config) { {} }
......
...@@ -74,7 +74,7 @@ describe Gitlab::Ci::Config::Node::Jobs do ...@@ -74,7 +74,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
expect(entry.descendants.first(2)) expect(entry.descendants.first(2))
.to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job)) .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
expect(entry.descendants.last) 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
end end
......
...@@ -282,4 +282,17 @@ describe Ability, lib: true do ...@@ -282,4 +282,17 @@ describe Ability, lib: true do
end end
end 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 end
...@@ -948,15 +948,17 @@ describe Ci::Build, models: true do ...@@ -948,15 +948,17 @@ describe Ci::Build, models: true do
before { build.run! } before { build.run! }
it 'returns false' do it 'returns false' do
expect(build.retryable?).to be false expect(build).not_to be_retryable
end end
end end
context 'when build is finished' do context 'when build is finished' do
before { build.success! } before do
build.success!
end
it 'returns true' do it 'returns true' do
expect(build.retryable?).to be true expect(build).to be_retryable
end end
end end
end end
......
...@@ -195,6 +195,36 @@ describe Ci::Pipeline, models: true do ...@@ -195,6 +195,36 @@ describe Ci::Pipeline, models: true do
end end
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 describe '#manual_actions' do
subject { pipeline.manual_actions } subject { pipeline.manual_actions }
......
...@@ -477,8 +477,8 @@ describe MergeRequest, models: true do ...@@ -477,8 +477,8 @@ describe MergeRequest, models: true do
allow(subject).to receive(:diff_head_sha).and_return('123abc') allow(subject).to receive(:diff_head_sha).and_return('123abc')
expect(subject.source_project).to receive(:pipeline). expect(subject.source_project).to receive(:pipeline_for).
with('123abc', 'master'). with('master', '123abc').
and_return(pipeline) and_return(pipeline)
expect(subject.pipeline).to eq(pipeline) expect(subject.pipeline).to eq(pipeline)
...@@ -962,4 +962,80 @@ describe MergeRequest, models: true do ...@@ -962,4 +962,80 @@ describe MergeRequest, models: true do
expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy
end end
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 end
...@@ -506,6 +506,18 @@ describe Project, models: true do ...@@ -506,6 +506,18 @@ describe Project, models: true do
end end
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 describe '#external_wiki' do
let(:project) { create(:project) } let(:project) { create(:project) }
...@@ -685,23 +697,37 @@ describe Project, models: true do ...@@ -685,23 +697,37 @@ describe Project, models: true do
end end
end end
describe '#pipeline' do describe '#pipeline_for' do
let(:project) { create :project } let(:project) { create(:project) }
let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' } 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 context 'return latest' do
let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' } let!(:pipeline2) { create_pipeline }
before do it { is_expected.to eq(pipeline2) }
pipeline end
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
end end
......
...@@ -15,7 +15,9 @@ describe API::API, api: true do ...@@ -15,7 +15,9 @@ describe API::API, api: true do
describe 'GET /projects/:id/builds ' do describe 'GET /projects/:id/builds ' do
let(:query) { '' } 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 context 'authorized user' do
it 'returns project builds' do it 'returns project builds' do
...@@ -122,7 +124,9 @@ describe API::API, api: true do ...@@ -122,7 +124,9 @@ describe API::API, api: true do
end end
describe 'GET /projects/:id/builds/:build_id' do 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 context 'authorized user' do
it 'returns specific build data' do it 'returns specific build data' do
...@@ -141,7 +145,9 @@ describe API::API, api: true do ...@@ -141,7 +145,9 @@ describe API::API, api: true do
end end
describe 'GET /projects/:id/builds/:build_id/artifacts' do 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 context 'build with artifacts' do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
...@@ -292,7 +298,9 @@ describe API::API, api: true do ...@@ -292,7 +298,9 @@ describe API::API, api: true do
end end
describe 'POST /projects/:id/builds/:build_id/cancel' do 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 'authorized user' do
context 'user with :update_build persmission' do context 'user with :update_build persmission' do
...@@ -323,7 +331,9 @@ describe API::API, api: true do ...@@ -323,7 +331,9 @@ describe API::API, api: true do
describe 'POST /projects/:id/builds/:build_id/retry' do describe 'POST /projects/:id/builds/:build_id/retry' do
let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } 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 'authorized user' do
context 'user with :update_build permission' do context 'user with :update_build permission' do
......
...@@ -95,7 +95,7 @@ describe API::API, api: true do ...@@ -95,7 +95,7 @@ describe API::API, api: true do
end end
it "returns status for CI" do 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') pipeline.update(status: 'success')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
...@@ -105,7 +105,7 @@ describe API::API, api: true do ...@@ -105,7 +105,7 @@ describe API::API, api: true do
end end
it "returns status for CI when pipeline is created" do 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) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
......
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
...@@ -478,6 +479,18 @@ describe API::API, api: true do ...@@ -478,6 +479,18 @@ describe API::API, api: true do
expect(json_response['labels']).to eq(['label', 'label2']) expect(json_response['labels']).to eq(['label', 'label2'])
end 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 it "returns a 400 bad request if title not given" do
post api("/projects/#{project.id}/issues", user), labels: 'label, label2' post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
...@@ -633,6 +646,18 @@ describe API::API, api: true do ...@@ -633,6 +646,18 @@ describe API::API, api: true do
expect(json_response['labels']).to eq([label.title]) expect(json_response['labels']).to eq([label.title])
end 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 it 'removes all labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: '' labels: ''
......
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
...@@ -5,7 +5,7 @@ module Ci ...@@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new } let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) } let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' } 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) } let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) }
describe '#execute' do describe '#execute' do
......
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
...@@ -27,8 +27,8 @@ RSpec.configure do |config| ...@@ -27,8 +27,8 @@ RSpec.configure do |config|
config.display_try_failure_messages = true config.display_try_failure_messages = true
config.include Devise::TestHelpers, type: :controller config.include Devise::TestHelpers, type: :controller
config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :feature config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include StubConfiguration config.include StubConfiguration
config.include EmailHelpers config.include EmailHelpers
config.include TestEnv config.include TestEnv
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# Requires a context containing: # Requires a context containing:
# subject { Issue or MergeRequest } # subject { Issue or MergeRequest }
shared_examples 'a Taskable' do shared_examples 'a Taskable' do
describe 'with multiple tasks' do
before do before do
subject.description = <<-EOT.strip_heredoc subject.description = <<-EOT.strip_heredoc
* [ ] Task 1 * [ ] Task 1
...@@ -14,9 +15,8 @@ shared_examples 'a Taskable' do ...@@ -14,9 +15,8 @@ shared_examples 'a Taskable' do
end end
it 'returns the correct task status' do it 'returns the correct task status' do
expect(subject.task_status).to match('5 tasks') expect(subject.task_status).to match('2 of')
expect(subject.task_status).to match('2 completed') expect(subject.task_status).to match('5 tasks completed')
expect(subject.task_status).to match('3 remaining')
end end
describe '#tasks?' do describe '#tasks?' do
...@@ -29,4 +29,31 @@ shared_examples 'a Taskable' do ...@@ -29,4 +29,31 @@ shared_examples 'a Taskable' do
expect(subject.tasks?).to eq false expect(subject.tasks?).to eq false
end end
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 end
...@@ -6,7 +6,7 @@ module TestEnv ...@@ -6,7 +6,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify. # When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = { BRANCH_SHA = {
'empty-branch' => '7efb185', 'empty-branch' => '7efb185',
'ends-with.json' => '98b0d8b3', 'ends-with.json' => '98b0d8b',
'flatten-dir' => 'e56497b', 'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a', 'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f', 'feature_conflict' => 'bb5206f',
...@@ -37,9 +37,10 @@ module TestEnv ...@@ -37,9 +37,10 @@ module TestEnv
# need to keep all the branches in sync. # need to keep all the branches in sync.
# We currently only need a subset of the branches # We currently only need a subset of the branches
FORKED_BRANCH_SHA = { FORKED_BRANCH_SHA = {
'add-submodule-version-bump' => '3f547c08', 'add-submodule-version-bump' => '3f547c0',
'master' => '5937ac0', 'master' => '5937ac0',
'remove-submodule' => '2a33e0c0' 'remove-submodule' => '2a33e0c',
'conflict-resolvable-fork' => '404fa3f'
} }
# Test environment # Test environment
...@@ -117,22 +118,7 @@ module TestEnv ...@@ -117,22 +118,7 @@ module TestEnv
system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path})) system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path}))
end end
Dir.chdir(repo_path) do set_repo_refs(repo_path, branch_sha)
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
# We must copy bare repositories because we will push to them. # 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})) system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
...@@ -144,6 +130,7 @@ module TestEnv ...@@ -144,6 +130,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path) FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, BRANCH_SHA)
end end
def repos_path def repos_path
...@@ -160,6 +147,7 @@ module TestEnv ...@@ -160,6 +147,7 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path) FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path FileUtils.chmod_R 0755, target_repo_path
set_repo_refs(target_repo_path, FORKED_BRANCH_SHA)
end end
# When no cached assets exist, manually hit the root path to create them # When no cached assets exist, manually hit the root path to create them
...@@ -209,4 +197,23 @@ module TestEnv ...@@ -209,4 +197,23 @@ module TestEnv
def git_env def git_env
{ 'GIT_TEMPLATE_DIR' => '' } { 'GIT_TEMPLATE_DIR' => '' }
end 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 end
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
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
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