Merge branch 'master' into grzesiek/gitlab-ce-fix/non-member-notification-button
[ci skip]
This commit is contained in:
commit
f9cd0639c1
60 changed files with 579 additions and 182 deletions
|
@ -3,12 +3,17 @@ Please view this file on the master branch, on stable branches it's out of date.
|
|||
v 8.2.0 (unreleased)
|
||||
- Show last project commit to default branch on project home page
|
||||
- Highlight comment based on anchor in URL
|
||||
- Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw)
|
||||
- Improved performance of sorting milestone issues
|
||||
- Allow users to select the Files view as default project view (Cristian Bica)
|
||||
|
||||
v 8.1.0 (unreleased)
|
||||
- Show notifications button when user is member of group rather than project (Grzegorz Bizon)
|
||||
- Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge.
|
||||
- Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu)
|
||||
- Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu)
|
||||
- Speed up load times of issue detail pages by roughly 1.5x
|
||||
- If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg)
|
||||
- Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu)
|
||||
- Make diff file view easier to use on mobile screens (Stan Hu)
|
||||
- Improved performance of finding users by username or Email address
|
||||
|
|
|
@ -65,3 +65,48 @@
|
|||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
.cover-block {
|
||||
text-align: center;
|
||||
background: #f7f8fa;
|
||||
margin: -$gl-padding;
|
||||
margin-bottom: 0;
|
||||
padding: 44px $gl-padding;
|
||||
border-bottom: 1px solid $border-color;
|
||||
position: relative;
|
||||
|
||||
.avatar-holder {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.avatar, .identicon {
|
||||
margin: 0 auto;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.identicon {
|
||||
@include border-radius(50%);
|
||||
}
|
||||
}
|
||||
|
||||
.cover-title {
|
||||
color: $gl-header-color;
|
||||
margin: 0;
|
||||
font-size: 23px;
|
||||
font-weight: normal;
|
||||
margin: 16px 0 5px 0;
|
||||
color: #4c4e54;
|
||||
font-size: 23px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.cover-desc {
|
||||
padding: 0 $gl-padding;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
.cover-controls {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
border-bottom: 1px solid #E7E9EE;
|
||||
margin-bottom: 1em;
|
||||
|
||||
&.readme-holder {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
@extend .table;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
font-family: $monospace_font;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
kbd {
|
||||
|
|
|
@ -132,6 +132,11 @@ form.edit-issue {
|
|||
}
|
||||
}
|
||||
|
||||
.issue-closed-by-widget {
|
||||
padding: 16px 0;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.issue-form .select2-container {
|
||||
width: 250px !important;
|
||||
}
|
||||
|
|
|
@ -47,3 +47,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-hint {
|
||||
margin-top: -12px;
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
|
@ -544,5 +544,5 @@ pre.light-well {
|
|||
}
|
||||
|
||||
.project-show-readme .readme-holder {
|
||||
padding: 7px;
|
||||
border-top: 0;
|
||||
}
|
||||
|
|
|
@ -4,14 +4,6 @@
|
|||
margin-right: -$gl-padding;
|
||||
}
|
||||
|
||||
.tree_progress {
|
||||
display: none;
|
||||
margin: 20px;
|
||||
&.loading {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-table {
|
||||
margin-bottom: 0;
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
# Allow issues bulk update
|
||||
before_action :authorize_admin_issues!, only: [:bulk_update]
|
||||
|
||||
# Cross-reference merge requests
|
||||
before_action :closed_by_merge_requests, only: [:show]
|
||||
|
||||
respond_to :html
|
||||
|
||||
def index
|
||||
|
@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
render nothing: true
|
||||
end
|
||||
|
||||
def closed_by_merge_requests
|
||||
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def issue
|
||||
|
|
|
@ -259,7 +259,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@commits = @merge_request.commits
|
||||
|
||||
@merge_request_diff = @merge_request.merge_request_diff
|
||||
|
||||
|
||||
if @merge_request.locked_long_ago?
|
||||
@merge_request.unlock_mr
|
||||
@merge_request.close
|
||||
|
|
|
@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def sort_issues
|
||||
@issues = @milestone.issues.where(id: params['sortable_issue'])
|
||||
@issues.each do |issue|
|
||||
issue.position = params['sortable_issue'].index(issue.id.to_s) + 1
|
||||
issue.save
|
||||
end
|
||||
@milestone.sort_issues(params['sortable_issue'].map(&:to_i))
|
||||
|
||||
render json: { saved: true }
|
||||
end
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
class ProjectsController < ApplicationController
|
||||
include ExtractsPath
|
||||
|
||||
prepend_before_filter :render_go_import, only: [:show]
|
||||
skip_before_action :authenticate_user!, only: [:show, :activity]
|
||||
before_action :project, except: [:new, :create]
|
||||
before_action :repository, except: [:new, :create]
|
||||
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
|
||||
before_action :authorize_admin_project!, only: [:edit, :update]
|
||||
before_action :event_filter, only: [:show, :activity]
|
||||
|
||||
layout :determine_layout
|
||||
|
@ -56,6 +59,8 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def transfer
|
||||
return access_denied! unless can?(current_user, :change_namespace, @project)
|
||||
|
||||
namespace = Namespace.find_by(id: params[:new_namespace_id])
|
||||
::Projects::TransferService.new(project, current_user).execute(namespace)
|
||||
|
||||
|
@ -64,6 +69,15 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def remove_fork
|
||||
return access_denied! unless can?(current_user, :remove_fork_project, @project)
|
||||
|
||||
if @project.forked?
|
||||
@project.forked_project_link.destroy
|
||||
flash[:notice] = 'The fork relationship has been removed.'
|
||||
end
|
||||
end
|
||||
|
||||
def activity
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
@ -139,6 +153,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def archive
|
||||
return access_denied! unless can?(current_user, :archive_project, @project)
|
||||
|
||||
@project.archive!
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -148,6 +163,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def unarchive
|
||||
return access_denied! unless can?(current_user, :archive_project, @project)
|
||||
|
||||
@project.unarchive!
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -225,4 +241,14 @@ class ProjectsController < ApplicationController
|
|||
|
||||
render "go_import", layout: false
|
||||
end
|
||||
|
||||
def repo_exists?
|
||||
project.repository_exists? && !project.empty_repo?
|
||||
end
|
||||
|
||||
# Override get_id from ExtractsPath, which returns the branch and file path
|
||||
# for the blob/tree, which in this case is just the root of the default branch.
|
||||
def get_id
|
||||
project.repository.root_ref
|
||||
end
|
||||
end
|
||||
|
|
|
@ -83,6 +83,10 @@ module IssuesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def merge_requests_sentence(merge_requests)
|
||||
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
|
||||
end
|
||||
|
||||
# Required for Gitlab::Markdown::IssueReferenceFilter
|
||||
module_function :url_for_issue
|
||||
end
|
||||
|
|
|
@ -34,7 +34,8 @@ module PreferencesHelper
|
|||
def project_view_choices
|
||||
[
|
||||
['Readme (default)', :readme],
|
||||
['Activity view', :activity]
|
||||
['Activity view', :activity],
|
||||
['Files view', :files]
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -46,8 +47,7 @@ module PreferencesHelper
|
|||
Gitlab::ColorSchemes.for_user(current_user).css_class
|
||||
end
|
||||
|
||||
def prefer_readme?
|
||||
!current_user ||
|
||||
current_user.project_view == 'readme'
|
||||
def default_project_view
|
||||
current_user ? current_user.project_view : 'readme'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,6 +70,10 @@ module ProjectsHelper
|
|||
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
|
||||
end
|
||||
|
||||
def remove_fork_project_message(project)
|
||||
"You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?"
|
||||
end
|
||||
|
||||
def project_nav_tabs
|
||||
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
|
||||
end
|
||||
|
|
|
@ -189,7 +189,8 @@ class Ability
|
|||
:change_visibility_level,
|
||||
:rename_project,
|
||||
:remove_project,
|
||||
:archive_project
|
||||
:archive_project,
|
||||
:remove_fork_project
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -85,6 +85,10 @@ module Issuable
|
|||
assignee_id_changed?
|
||||
end
|
||||
|
||||
def open?
|
||||
opened? || reopened?
|
||||
end
|
||||
|
||||
#
|
||||
# Votes
|
||||
#
|
||||
|
|
|
@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base
|
|||
def source_project
|
||||
project
|
||||
end
|
||||
|
||||
# From all notes on this issue, we'll select the system notes about linked
|
||||
# merge requests. Of those, the MRs closing `self` are returned.
|
||||
def closed_by_merge_requests(current_user = nil)
|
||||
return [] unless open?
|
||||
|
||||
notes.system.flat_map do |note|
|
||||
note.all_references(current_user).merge_requests
|
||||
end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -222,10 +222,6 @@ class MergeRequest < ActiveRecord::Base
|
|||
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
|
||||
end
|
||||
|
||||
def open?
|
||||
opened? || reopened?
|
||||
end
|
||||
|
||||
def work_in_progress?
|
||||
!!(title =~ /\A\[?WIP\]?:? /i)
|
||||
end
|
||||
|
@ -294,6 +290,10 @@ class MergeRequest < ActiveRecord::Base
|
|||
target_project
|
||||
end
|
||||
|
||||
def closes_issue?(issue)
|
||||
closes_issues.include?(issue)
|
||||
end
|
||||
|
||||
# Return the set of issues that will be closed if this merge request is accepted.
|
||||
def closes_issues(current_user = self.author)
|
||||
if target_branch == project.default_branch
|
||||
|
|
|
@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base
|
|||
def author_id
|
||||
nil
|
||||
end
|
||||
|
||||
# Sorts the issues for the given IDs.
|
||||
#
|
||||
# This method runs a single SQL query using a CASE statement to update the
|
||||
# position of all issues in the current milestone (scoped to the list of IDs).
|
||||
#
|
||||
# Given the ids [10, 20, 30] this method produces a SQL query something like
|
||||
# the following:
|
||||
#
|
||||
# UPDATE issues
|
||||
# SET position = CASE
|
||||
# WHEN id = 10 THEN 1
|
||||
# WHEN id = 20 THEN 2
|
||||
# WHEN id = 30 THEN 3
|
||||
# ELSE position
|
||||
# END
|
||||
# WHERE id IN (10, 20, 30);
|
||||
#
|
||||
# This method expects that the IDs given in `ids` are already Fixnums.
|
||||
def sort_issues(ids)
|
||||
pairs = []
|
||||
|
||||
ids.each_with_index do |id, index|
|
||||
pairs << id
|
||||
pairs << index + 1
|
||||
end
|
||||
|
||||
conditions = 'WHEN id = ? THEN ? ' * ids.length
|
||||
|
||||
issues.where(id: ids).
|
||||
update_all(["position = CASE #{conditions} ELSE position END", *pairs])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -183,7 +183,7 @@ class User < ActiveRecord::Base
|
|||
|
||||
# User's Project preference
|
||||
# Note: When adding an option, it MUST go on the end of the array.
|
||||
enum project_view: [:readme, :activity]
|
||||
enum project_view: [:readme, :activity, :files]
|
||||
|
||||
alias_attribute :private_token, :authentication_token
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ class GitPushService
|
|||
|
||||
authors = Hash.new do |hash, commit|
|
||||
email = commit.author_email
|
||||
return hash[email] if hash.has_key?(email)
|
||||
next hash[email] if hash.has_key?(email)
|
||||
|
||||
hash[email] = commit_user(commit)
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ module MergeRequests
|
|||
#
|
||||
class PostMergeService < MergeRequests::BaseService
|
||||
def execute(merge_request)
|
||||
close_issues(merge_request)
|
||||
merge_request.mark_as_merged
|
||||
create_merge_event(merge_request, current_user)
|
||||
create_note(merge_request)
|
||||
|
@ -15,6 +16,15 @@ module MergeRequests
|
|||
|
||||
private
|
||||
|
||||
def close_issues(merge_request)
|
||||
return unless merge_request.target_branch == project.default_branch
|
||||
|
||||
closed_issues = merge_request.closes_issues(current_user)
|
||||
closed_issues.each do |issue|
|
||||
Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
|
||||
end
|
||||
end
|
||||
|
||||
def create_merge_event(merge_request, current_user)
|
||||
EventCreateService.new.merge_mr(merge_request, current_user)
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
%strong
|
||||
= link_to user_path(@user) do
|
||||
= @user.username
|
||||
= render 'users/profile', user: @user
|
||||
= render 'admin/users/profile', user: @user
|
||||
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
|
|
|
@ -41,4 +41,8 @@
|
|||
#{link_to "view it on GitLab", @target_url}.
|
||||
- else
|
||||
#{link_to "View it on GitLab", @target_url}
|
||||
%br
|
||||
You're receiving this email because of your account on #{link_to Gitlab.config.gitlab.host, root_url}.
|
||||
If you'd like to receive fewer emails, you can adjust your notification settings.
|
||||
|
||||
= email_action @target_url
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
.col-sm-10
|
||||
= f.select :layout, layout_choices, {}, class: 'form-control'
|
||||
.help-block
|
||||
Choose between fixed (max. 1200px) and fluid (100%) application layout
|
||||
Choose between fixed (max. 1200px) and fluid (100%) application layout.
|
||||
.form-group
|
||||
= f.label :dashboard, class: 'control-label' do
|
||||
Default Dashboard
|
||||
|
@ -52,6 +52,6 @@
|
|||
.col-sm-10
|
||||
= f.select :project_view, project_view_choices, {}, class: 'form-control'
|
||||
.help-block
|
||||
Choose what content you want to see when visit project page
|
||||
Choose what content you want to see on a project's home page.
|
||||
.panel-footer
|
||||
= f.submit 'Save', class: 'btn btn-save'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
= render 'projects/last_push'
|
||||
.gray-content-block.activity-filter-block
|
||||
- if current_user
|
||||
.pull-right
|
||||
|
|
6
app/views/projects/_files.html.haml
Normal file
6
app/views/projects/_files.html.haml
Normal file
|
@ -0,0 +1,6 @@
|
|||
#tree-holder.tree-holder.clearfix
|
||||
.gray-content-block.second-block
|
||||
= render 'projects/tree/tree_header', tree: @tree
|
||||
|
||||
= render 'projects/tree/tree_content', tree: @tree
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
- if readme = @repository.readme
|
||||
%article.readme-holder#README
|
||||
.clearfix
|
||||
.pull-right
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
= link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
|
||||
%i.fa-align.fa.fa-pencil
|
||||
.wiki
|
||||
%article.file-holder.readme-holder
|
||||
.file-title
|
||||
= blob_icon readme.mode, readme.name
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
|
||||
%strong
|
||||
= readme.name
|
||||
.file-content.wiki
|
||||
= cache(readme_cache_key) do
|
||||
= render_readme(readme)
|
||||
- else
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
- page_title "Activity"
|
||||
- header_title project_title(@project, "Activity", activity_project_path(@project))
|
||||
|
||||
= render 'projects/last_push'
|
||||
|
||||
= render 'projects/activity'
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
%i.fa.fa-angle-right
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(@tree, 6) do |title, path|
|
||||
.gray-content-block.top-block
|
||||
.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'blob', path: @path
|
||||
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
- if path
|
||||
- if path.end_with?(@path)
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, path) do
|
||||
%strong
|
||||
= truncate(title, length: 40)
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(@tree, 6) do |title, path|
|
||||
%li
|
||||
- if path
|
||||
- if path.end_with?(@path)
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, path) do
|
||||
%strong
|
||||
= truncate(title, length: 40)
|
||||
- else
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to title, '#'
|
||||
= link_to title, '#'
|
||||
|
||||
%ul.blob-commit-info.hidden-xs
|
||||
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
= render 'projects/last_push'
|
||||
|
||||
%div.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'blob', path: @path
|
||||
|
||||
%div#tree-holder.tree-holder
|
||||
= render 'blob', blob: @blob
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
%tr
|
||||
%th
|
||||
%th Service
|
||||
%th Desription
|
||||
%th Description
|
||||
%th Last edit
|
||||
- @services.sort_by(&:title).each do |service|
|
||||
%tr
|
||||
|
|
|
@ -189,6 +189,21 @@
|
|||
- else
|
||||
.nothing-here-block Only the project owner can transfer a project
|
||||
|
||||
- if @project.forked?
|
||||
- if can?(current_user, :remove_fork_project, @project)
|
||||
= form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
|
||||
.panel.panel-default.panel.panel-danger
|
||||
.panel-heading Remove fork relationship
|
||||
.panel-body
|
||||
%p
|
||||
This will remove the fork relationship to source project
|
||||
#{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
|
||||
%br
|
||||
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
|
||||
= button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
|
||||
- else
|
||||
.nothing-here-block Only the project owner can remove the fork relationship.
|
||||
|
||||
- if can?(current_user, :remove_project, @project)
|
||||
.panel.panel-default.panel.panel-danger
|
||||
.panel-heading Remove project
|
||||
|
@ -201,7 +216,8 @@
|
|||
|
||||
= button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
|
||||
- else
|
||||
.nothing-here-block Only project owner can remove a project
|
||||
.nothing-here-block Only the project owner can remove a project.
|
||||
|
||||
|
||||
.save-project-loader.hide
|
||||
.center
|
||||
|
|
3
app/views/projects/issues/_closed_by_box.html.haml
Normal file
3
app/views/projects/issues/_closed_by_box.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
.issue-closed-by-widget
|
||||
= icon('check')
|
||||
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
|
|
@ -46,6 +46,7 @@
|
|||
= markdown(@issue.description)
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @issue.description
|
||||
|
||||
- if @closed_by_merge_requests.present?
|
||||
= render 'projects/issues/closed_by_box'
|
||||
.issue-discussion
|
||||
= render 'projects/issues/discussion'
|
||||
|
|
2
app/views/projects/remove_fork.js.haml
Normal file
2
app/views/projects/remove_fork.js.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
:plain
|
||||
location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
|
|
@ -7,8 +7,7 @@
|
|||
= render 'shared/no_ssh'
|
||||
= render 'shared/no_password'
|
||||
|
||||
- if prefer_readme?
|
||||
= render 'projects/last_push'
|
||||
= render 'projects/last_push'
|
||||
|
||||
= render "home_panel"
|
||||
|
||||
|
@ -28,7 +27,7 @@
|
|||
= link_to project_files_path(@project) do
|
||||
= repository_size
|
||||
|
||||
- if !prefer_readme? && @repository.readme
|
||||
- if default_project_view != 'readme' && @repository.readme
|
||||
%li
|
||||
= link_to 'Readme', readme_path(@project)
|
||||
|
||||
|
@ -68,14 +67,8 @@
|
|||
.content-block.second-block.white
|
||||
= render 'projects/last_commit', commit: @repository.commit, project: @project
|
||||
|
||||
%section
|
||||
- if prefer_readme?
|
||||
.project-show-readme
|
||||
= render 'projects/readme'
|
||||
- else
|
||||
.project-show-activity
|
||||
= render 'projects/activity'
|
||||
|
||||
%div{class: "project-show-#{default_project_view}"}
|
||||
= render default_project_view
|
||||
|
||||
- if current_user
|
||||
- access = user_max_access_in_project(current_user, @project)
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
%span.str-truncated
|
||||
= link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name))
|
||||
%td.tree_time_ago.cgray
|
||||
= render 'spinner'
|
||||
= render 'projects/tree/spinner'
|
||||
%td.hidden-xs.tree_commit
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
%article.file-holder.readme-holder#README
|
||||
%article.file-holder.readme-holder
|
||||
.file-title
|
||||
= link_to '#README' do
|
||||
= blob_icon readme.mode, readme.name
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
|
||||
%strong
|
||||
%i.fa.fa-file
|
||||
= readme.name
|
||||
.file-content.wiki
|
||||
= render_readme(readme)
|
||||
|
|
|
@ -1,35 +1,4 @@
|
|||
.gray-content-block
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(tree, 6) do |title, path|
|
||||
%li
|
||||
- if path
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to title, '#'
|
||||
- if allowed_tree_edit?
|
||||
%li
|
||||
%span.dropdown
|
||||
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
|
||||
= icon('plus')
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
|
||||
= icon('pencil fw')
|
||||
Create file
|
||||
%li
|
||||
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
|
||||
= icon('file fw')
|
||||
Upload file
|
||||
%li.divider
|
||||
%li
|
||||
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
|
||||
= icon('folder fw')
|
||||
New directory
|
||||
|
||||
%div#tree-content-holder.tree-content-holder
|
||||
%div.tree-content-holder
|
||||
.tree-table-holder
|
||||
%table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
|
||||
%thead
|
||||
|
@ -60,8 +29,6 @@
|
|||
- if tree.readme
|
||||
= render "projects/tree/readme", readme: tree.readme
|
||||
|
||||
%div.tree_progress
|
||||
|
||||
- if allowed_tree_edit?
|
||||
= render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
|
||||
= render 'projects/blob/new_dir'
|
32
app/views/projects/tree/_tree_header.html.haml
Normal file
32
app/views/projects/tree/_tree_header.html.haml
Normal file
|
@ -0,0 +1,32 @@
|
|||
.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'tree', path: @path
|
||||
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(tree, 6) do |title, path|
|
||||
%li
|
||||
- if path
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to title, '#'
|
||||
- if allowed_tree_edit?
|
||||
%li
|
||||
%span.dropdown
|
||||
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
|
||||
= icon('plus')
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
|
||||
= icon('pencil fw')
|
||||
Create file
|
||||
%li
|
||||
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
|
||||
= icon('file fw')
|
||||
Upload file
|
||||
%li.divider
|
||||
%li
|
||||
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
|
||||
= icon('folder fw')
|
||||
New directory
|
|
@ -5,5 +5,5 @@
|
|||
- path = flatten_tree(tree_item)
|
||||
= link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
|
||||
%td.tree_time_ago.cgray
|
||||
= render 'spinner'
|
||||
= render 'projects/tree/spinner'
|
||||
%td.hidden-xs.tree_commit
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
= render 'projects/last_push'
|
||||
|
||||
.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'tree', path: @path
|
||||
|
||||
- if can? current_user, :download_code, @project
|
||||
.tree-download-holder
|
||||
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
|
||||
|
||||
#tree-holder.tree-holder.clearfix
|
||||
= render "tree", tree: @tree
|
||||
.gray-content-block.top-block
|
||||
= render 'projects/tree/tree_header', tree: @tree
|
||||
|
||||
= render 'projects/tree/tree_content', tree: @tree
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
%h4
|
||||
Contributions calendar
|
||||
.pull-right
|
||||
%small Issues, merge requests and push events
|
||||
#cal-heatmap.calendar
|
||||
:javascript
|
||||
new Calendar(
|
||||
|
@ -10,3 +6,5 @@
|
|||
#{@starting_month},
|
||||
'#{user_calendar_activities_path}'
|
||||
);
|
||||
|
||||
.calendar-hint Summary of issues, merge requests and push events
|
||||
|
|
|
@ -6,47 +6,72 @@
|
|||
|
||||
= render 'shared/show_aside'
|
||||
|
||||
.row
|
||||
.cover-block
|
||||
.avatar-holder
|
||||
= link_to avatar_icon(@user, 400), target: '_blank' do
|
||||
= image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
|
||||
.cover-title
|
||||
= @user.name
|
||||
|
||||
.cover-desc
|
||||
%span
|
||||
@#{@user.username}.
|
||||
- if @user.bio.present?
|
||||
%span
|
||||
#{@user.bio}.
|
||||
%span
|
||||
Member since #{@user.created_at.stamp("Aug 21, 2011")}
|
||||
|
||||
.cover-desc
|
||||
- unless @user.public_email.blank?
|
||||
= link_to @user.public_email, "mailto:#{@user.public_email}"
|
||||
- unless @user.skype.blank?
|
||||
·
|
||||
= link_to "Skype", "skype:#{@user.skype}"
|
||||
- unless @user.linkedin.blank?
|
||||
·
|
||||
= link_to "LinkedIn", "http://www.linkedin.com/in/#{@user.linkedin}"
|
||||
- unless @user.twitter.blank?
|
||||
·
|
||||
= link_to "Twitter", "http://www.twitter.com/#{@user.twitter}"
|
||||
- unless @user.website_url.blank?
|
||||
·
|
||||
= link_to @user.short_website_url, @user.full_website_url
|
||||
- unless @user.location.blank?
|
||||
·
|
||||
= @user.location
|
||||
|
||||
|
||||
.cover-controls
|
||||
- if @user == current_user
|
||||
= link_to profile_path, class: 'btn btn-gray' do
|
||||
= icon('pencil')
|
||||
- elsif current_user
|
||||
.report-abuse
|
||||
- if @user.abuse_report
|
||||
%button.btn.btn-danger{ title: 'Already reported for abuse',
|
||||
data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
|
||||
= icon('exclamation-circle')
|
||||
- else
|
||||
= link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
|
||||
title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
|
||||
= icon('exclamation-circle')
|
||||
|
||||
.gray-content-block.second-block
|
||||
.user-calendar
|
||||
%h4.center.light
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
.user-calendar-activities
|
||||
|
||||
|
||||
.row.prepend-top-20
|
||||
%section.col-md-7
|
||||
.header-with-avatar
|
||||
= link_to avatar_icon(@user, 400), target: '_blank' do
|
||||
= image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: ''
|
||||
%h3
|
||||
= @user.name
|
||||
- if @user == current_user
|
||||
.pull-right.hidden-xs
|
||||
= link_to profile_path, class: 'btn btn-sm' do
|
||||
= icon('user')
|
||||
Profile settings
|
||||
- elsif current_user
|
||||
.report_abuse.pull-right
|
||||
- if @user.abuse_report
|
||||
%span#report_abuse_btn.light.btn.btn-sm.btn-close{title: 'Already reported for abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
|
||||
= icon('exclamation-circle')
|
||||
- else
|
||||
%a.light.btn.btn-sm{href: new_abuse_report_path(user_id: @user.id), title: 'Report abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
|
||||
= icon('exclamation-circle')
|
||||
|
||||
.username
|
||||
@#{@user.username}
|
||||
.description
|
||||
- if @user.bio.present?
|
||||
= @user.bio
|
||||
|
||||
.clearfix
|
||||
|
||||
- if @groups.any?
|
||||
.prepend-top-20
|
||||
%h4 Groups
|
||||
= render 'groups', groups: @groups
|
||||
%hr
|
||||
|
||||
.hidden-xs
|
||||
.user-calendar
|
||||
%h4.center.light
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
.user-calendar-activities
|
||||
%hr
|
||||
%h4
|
||||
User Activity
|
||||
|
||||
|
@ -59,7 +84,6 @@
|
|||
.content_list
|
||||
= spinner
|
||||
%aside.col-md-5
|
||||
= render 'profile', user: @user
|
||||
= render 'projects', projects: @projects, contributed_projects: @contributed_projects
|
||||
|
||||
:coffeescript
|
||||
|
|
|
@ -378,6 +378,7 @@ Gitlab::Application.routes.draw do
|
|||
[:new, :create, :index], path: "/") do
|
||||
member do
|
||||
put :transfer
|
||||
delete :remove_fork
|
||||
post :archive
|
||||
post :unarchive
|
||||
post :toggle_star
|
||||
|
|
|
@ -23,7 +23,7 @@ class Spinach::Features::AbuseReports < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I should see a red "Report abuse" button' do
|
||||
expect(find(:css, '.report_abuse')).to have_selector(:css, 'span.btn-close')
|
||||
expect(page).to have_button("Already reported for abuse")
|
||||
end
|
||||
|
||||
def user_mike
|
||||
|
|
|
@ -86,13 +86,13 @@ class Spinach::Features::Project < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I should see project "Forum" README' do
|
||||
page.within('#README') do
|
||||
page.within('.readme-holder') do
|
||||
expect(page).to have_content 'Sample repo for testing gitlab features'
|
||||
end
|
||||
end
|
||||
|
||||
step 'I should see project "Shop" README' do
|
||||
page.within('#README') do
|
||||
page.within('.readme-holder') do
|
||||
expect(page).to have_content 'testme'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -246,8 +246,8 @@ module API
|
|||
# Example Request:
|
||||
# DELETE /projects/:id/fork
|
||||
delete ":id/fork" do
|
||||
authenticated_as_admin!
|
||||
unless user_project.forked_project_link.nil?
|
||||
authorize! :remove_fork_project, user_project
|
||||
if user_project.forked?
|
||||
user_project.forked_project_link.destroy
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ module Gitlab
|
|||
def references
|
||||
@references ||= Hash.new do |references, type|
|
||||
type = type.to_sym
|
||||
return references[type] if references.has_key?(type)
|
||||
next references[type] if references.has_key?(type)
|
||||
|
||||
references[type] = pipeline_result(type)
|
||||
end
|
||||
|
|
17
spec/benchmarks/models/milestone_spec.rb
Normal file
17
spec/benchmarks/models/milestone_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Milestone, benchmark: true do
|
||||
describe '#sort_issues' do
|
||||
let(:milestone) { create(:milestone) }
|
||||
|
||||
let(:issue1) { create(:issue, milestone: milestone) }
|
||||
let(:issue2) { create(:issue, milestone: milestone) }
|
||||
let(:issue3) { create(:issue, milestone: milestone) }
|
||||
|
||||
let(:issue_ids) { [issue3.id, issue2.id, issue1.id] }
|
||||
|
||||
benchmark_subject { milestone.sort_issues(issue_ids) }
|
||||
|
||||
it { is_expected.to iterate_per_second(500) }
|
||||
end
|
||||
end
|
|
@ -22,6 +22,34 @@ describe ProjectsController do
|
|||
end
|
||||
end
|
||||
|
||||
context "rendering default project view" do
|
||||
render_views
|
||||
|
||||
it "renders the activity view" do
|
||||
allow(controller).to receive(:current_user).and_return(user)
|
||||
allow(user).to receive(:project_view).and_return('activity')
|
||||
|
||||
get :show, namespace_id: public_project.namespace.path, id: public_project.path
|
||||
expect(response).to render_template('_activity')
|
||||
end
|
||||
|
||||
it "renders the readme view" do
|
||||
allow(controller).to receive(:current_user).and_return(user)
|
||||
allow(user).to receive(:project_view).and_return('readme')
|
||||
|
||||
get :show, namespace_id: public_project.namespace.path, id: public_project.path
|
||||
expect(response).to render_template('_readme')
|
||||
end
|
||||
|
||||
it "renders the files view" do
|
||||
allow(controller).to receive(:current_user).and_return(user)
|
||||
allow(user).to receive(:project_view).and_return('files')
|
||||
|
||||
get :show, namespace_id: public_project.namespace.path, id: public_project.path
|
||||
expect(response).to render_template('_files')
|
||||
end
|
||||
end
|
||||
|
||||
context "when requested with case sensitive namespace and project path" do
|
||||
it "redirects to the normalized path for case mismatch" do
|
||||
get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
|
||||
|
@ -62,4 +90,50 @@ describe ProjectsController do
|
|||
expect(user.starred?(public_project)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE remove_fork" do
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'with forked project' do
|
||||
let(:project_fork) { create(:project, namespace: user.namespace) }
|
||||
|
||||
before do
|
||||
create(:forked_project_link, forked_to_project: project_fork)
|
||||
end
|
||||
|
||||
it 'should remove fork from project' do
|
||||
delete(:remove_fork,
|
||||
namespace_id: project_fork.namespace.to_param,
|
||||
id: project_fork.to_param, format: :js)
|
||||
|
||||
expect(project_fork.forked?).to be_falsey
|
||||
expect(flash[:notice]).to eq('The fork relationship has been removed.')
|
||||
expect(response).to render_template(:remove_fork)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project not forked' do
|
||||
let(:unforked_project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
it 'should do nothing if project was not forked' do
|
||||
delete(:remove_fork,
|
||||
namespace_id: unforked_project.namespace.to_param,
|
||||
id: unforked_project.to_param, format: :js)
|
||||
|
||||
expect(flash[:notice]).to be_nil
|
||||
expect(response).to render_template(:remove_fork)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "does nothing if user is not signed in" do
|
||||
delete(:remove_fork,
|
||||
namespace_id: project.namespace.to_param,
|
||||
id: project.to_param, format: :js)
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,6 +34,27 @@ feature 'Project', feature: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'remove forked relationship', js: true do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
before do
|
||||
login_with user
|
||||
create(:forked_project_link, forked_to_project: project)
|
||||
visit edit_namespace_project_path(project.namespace, project)
|
||||
end
|
||||
|
||||
it 'should remove fork' do
|
||||
expect(page).to have_content 'Remove fork relationship'
|
||||
|
||||
remove_with_confirm('Remove fork relationship', project.path)
|
||||
|
||||
expect(page).to have_content 'The fork relationship has been removed.'
|
||||
expect(project.forked?).to be_falsey
|
||||
expect(page).not_to have_content 'Remove fork relationship'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'removal', js: true do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, namespace: user.namespace) }
|
||||
|
@ -45,13 +66,13 @@ feature 'Project', feature: true do
|
|||
end
|
||||
|
||||
it 'should remove project' do
|
||||
expect { remove_project }.to change {Project.count}.by(-1)
|
||||
expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_project
|
||||
click_button "Remove project"
|
||||
fill_in 'confirm_name_input', with: project.path
|
||||
def remove_with_confirm(button_text, confirm_with)
|
||||
click_button button_text
|
||||
fill_in 'confirm_name_input', with: confirm_with
|
||||
click_button 'Confirm'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -117,4 +117,14 @@ describe IssuesHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#merge_requests_sentence" do
|
||||
subject { merge_requests_sentence(merge_requests)}
|
||||
let(:merge_requests) do
|
||||
[ build(:merge_request, iid: 1), build(:merge_request, iid: 2),
|
||||
build(:merge_request, iid: 3)]
|
||||
end
|
||||
|
||||
it { is_expected.to eq("!1, !2, or !3") }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -68,7 +68,6 @@ describe Issue, "Issuable" do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
describe "#to_hook_data" do
|
||||
let(:hook_data) { issue.to_hook_data(user) }
|
||||
|
||||
|
|
|
@ -68,6 +68,43 @@ describe Issue do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#closed_by_merge_requests' do
|
||||
let(:project) { create(:project) }
|
||||
let(:issue) { create(:issue, project: project, state: "opened")}
|
||||
let(:closed_issue) { build(:issue, project: project, state: "closed")}
|
||||
|
||||
let(:mr) do
|
||||
opts = {
|
||||
title: 'Awesome merge_request',
|
||||
description: "Fixes #{issue.to_reference}",
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master'
|
||||
}
|
||||
MergeRequests::CreateService.new(project, project.owner, opts).execute
|
||||
end
|
||||
|
||||
let(:closed_mr) do
|
||||
opts = {
|
||||
title: 'Awesome merge_request 2',
|
||||
description: "Fixes #{issue.to_reference}",
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master',
|
||||
state: 'closed'
|
||||
}
|
||||
MergeRequests::CreateService.new(project, project.owner, opts).execute
|
||||
end
|
||||
|
||||
it 'returns the merge request to close this issue' do
|
||||
allow(mr).to receive(:closes_issue?).with(issue).and_return(true)
|
||||
|
||||
expect(issue.closed_by_merge_requests).to eq([mr])
|
||||
end
|
||||
|
||||
it "returns an empty array when the current issue is closed already" do
|
||||
expect(closed_issue.closed_by_merge_requests).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'an editable mentionable' do
|
||||
subject { create(:issue) }
|
||||
|
||||
|
|
|
@ -140,4 +140,32 @@ describe Milestone do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#sort_issues' do
|
||||
let(:milestone) { create(:milestone) }
|
||||
|
||||
let(:issue1) { create(:issue, milestone: milestone, position: 1) }
|
||||
let(:issue2) { create(:issue, milestone: milestone, position: 2) }
|
||||
let(:issue3) { create(:issue, milestone: milestone, position: 3) }
|
||||
let(:issue4) { create(:issue, position: 42) }
|
||||
|
||||
it 'sorts the given issues' do
|
||||
milestone.sort_issues([issue3.id, issue2.id, issue1.id])
|
||||
|
||||
issue1.reload
|
||||
issue2.reload
|
||||
issue3.reload
|
||||
|
||||
expect(issue1.position).to eq(3)
|
||||
expect(issue2.position).to eq(2)
|
||||
expect(issue3.position).to eq(1)
|
||||
end
|
||||
|
||||
it 'ignores issues not part of the milestone' do
|
||||
milestone.sort_issues([issue3.id, issue2.id, issue1.id, issue4.id])
|
||||
|
||||
issue4.reload
|
||||
|
||||
expect(issue4.position).to eq(42)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -606,28 +606,42 @@ describe API::API, api: true do
|
|||
|
||||
describe 'DELETE /projects/:id/fork' do
|
||||
|
||||
it "shouldn't available for non admin users" do
|
||||
it "shouldn't be visible to users outside group" do
|
||||
delete api("/projects/#{project_fork_target.id}/fork", user)
|
||||
expect(response.status).to eq(403)
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it 'should make forked project unforked' do
|
||||
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
|
||||
project_fork_target.reload
|
||||
expect(project_fork_target.forked_from_project).not_to be_nil
|
||||
expect(project_fork_target.forked?).to be_truthy
|
||||
delete api("/projects/#{project_fork_target.id}/fork", admin)
|
||||
expect(response.status).to eq(200)
|
||||
project_fork_target.reload
|
||||
expect(project_fork_target.forked_from_project).to be_nil
|
||||
expect(project_fork_target.forked?).not_to be_truthy
|
||||
end
|
||||
context 'when users belong to project group' do
|
||||
let(:project_fork_target) { create(:project, group: create(:group)) }
|
||||
|
||||
it 'should be idempotent if not forked' do
|
||||
expect(project_fork_target.forked_from_project).to be_nil
|
||||
delete api("/projects/#{project_fork_target.id}/fork", admin)
|
||||
expect(response.status).to eq(200)
|
||||
expect(project_fork_target.reload.forked_from_project).to be_nil
|
||||
before do
|
||||
project_fork_target.group.add_owner user
|
||||
project_fork_target.group.add_developer user2
|
||||
end
|
||||
|
||||
it 'should be forbidden to non-owner users' do
|
||||
delete api("/projects/#{project_fork_target.id}/fork", user2)
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
|
||||
it 'should make forked project unforked' do
|
||||
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
|
||||
project_fork_target.reload
|
||||
expect(project_fork_target.forked_from_project).not_to be_nil
|
||||
expect(project_fork_target.forked?).to be_truthy
|
||||
delete api("/projects/#{project_fork_target.id}/fork", admin)
|
||||
expect(response.status).to eq(200)
|
||||
project_fork_target.reload
|
||||
expect(project_fork_target.forked_from_project).to be_nil
|
||||
expect(project_fork_target.forked?).not_to be_truthy
|
||||
end
|
||||
|
||||
it 'should be idempotent if not forked' do
|
||||
expect(project_fork_target.forked_from_project).to be_nil
|
||||
delete api("/projects/#{project_fork_target.id}/fork", admin)
|
||||
expect(response.status).to eq(200)
|
||||
expect(project_fork_target.reload.forked_from_project).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue