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 send_file artifacts_file.path, disposition: 'attachment'
else
redirect_to artifacts_file.url
end end
send_file artifacts_file.path, disposition: 'attachment'
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 errors.add :validate_fork,
true 'Source project is not a fork of the target project'
else end
# If source and target projects are different
# we should check if source project is actually a fork of target project def closed_without_fork?
if source_project.forked_from?(target_project) closed? && forked_source_project_missing?
true end
else
errors.add :validate_fork, def forked_source_project_missing?
'Source project is not a fork of target project' return false unless for_fork?
end return true unless source_project
end
!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
handle_common_system_notes(issuable, old_labels: old_labels)
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
handle_common_system_notes(issuable, old_labels: old_labels)
end
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users) 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
= icon('download') %button.btn{ 'data-toggle' => 'dropdown' }
= icon('download')
%span.caret
%span.sr-only
Select Archive Format
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li.dropdown-header Source code
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
- pipeline = project.pipelines.latest_successful_for(ref)
- if pipeline
- artifacts = pipeline.builds.latest.with_artifacts
- if artifacts.any?
%li.dropdown-header Artifacts
- unless pipeline.latest?
- latest_pipeline = project.pipeline_for(ref)
%li
.unclickable= ci_status_for_statuseable(latest_pipeline)
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
= link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do
%i.fa.fa-download
%span Download '#{job.name}'
...@@ -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
......
This diff is collapsed.
...@@ -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
This diff is collapsed.
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') }
it { is_expected.to eq(pipeline) } shared_examples 'giving the correct pipeline' do
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
pipeline2
end end
end
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
it { is_expected.to eq(pipeline2) } 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
...@@ -26,9 +26,9 @@ RSpec.configure do |config| ...@@ -26,9 +26,9 @@ RSpec.configure do |config|
config.verbose_retry = true config.verbose_retry = true
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 LoginHelpers, type: :feature config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :request config.include LoginHelpers, type: :feature
config.include StubConfiguration config.include StubConfiguration
config.include EmailHelpers config.include EmailHelpers
config.include TestEnv config.include TestEnv
......
...@@ -3,30 +3,57 @@ ...@@ -3,30 +3,57 @@
# 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
before do describe 'with multiple tasks' do
subject.description = <<-EOT.strip_heredoc before do
* [ ] Task 1 subject.description = <<-EOT.strip_heredoc
* [x] Task 2 * [ ] Task 1
* [x] Task 3 * [x] Task 2
* [ ] Task 4 * [x] Task 3
* [ ] Task 5 * [ ] Task 4
EOT * [ ] Task 5
EOT
end
it 'returns the correct task status' do
expect(subject.task_status).to match('2 of')
expect(subject.task_status).to match('5 tasks completed')
end
describe '#tasks?' do
it 'returns true when object has tasks' do
expect(subject.tasks?).to eq true
end
it 'returns false when object has no tasks' do
subject.description = 'Now I have no tasks'
expect(subject.tasks?).to eq false
end
end
end end
it 'returns the correct task status' do describe 'with an incomplete task' do
expect(subject.task_status).to match('5 tasks') before do
expect(subject.task_status).to match('2 completed') subject.description = <<-EOT.strip_heredoc
expect(subject.task_status).to match('3 remaining') * [ ] 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 end
describe '#tasks?' do describe 'with a complete task' do
it 'returns true when object has tasks' do before do
expect(subject.tasks?).to eq true subject.description = <<-EOT.strip_heredoc
* [x] Task 1
EOT
end end
it 'returns false when object has no tasks' do it 'returns the correct task status' do
subject.description = 'Now I have no tasks' expect(subject.task_status).to match('1 of')
expect(subject.tasks?).to eq false expect(subject.task_status).to match('1 task completed')
end end
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