Commit d37cf642 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into hanloong/gitlab-ce-add-dates-snippets-show

parents d9944fdb 35618a3d
Please view this file on the master branch, on stable branches it's out of date.
v 8.2.0 (unreleased)
- Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu)
- Improved performance of replacing references in comments
- Fix duplicate repositories in GitHub import page (Stan Hu)
- Redirect to a default path if HTTP_REFERER is not set (Stan Hu)
- 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)
- Send an email to admin email when a user is reported for spam (Jonathan Rochkind)
- 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
......@@ -69,6 +81,13 @@ v 8.1.0 (unreleased)
- Only render 404 page from /public
- Hide passwords from services API (Alex Lossent)
- Fix: Images cannot show when projects' path was changed
- Let gitlab-git-http-server generate and serve 'git archive' downloads
- Optimize query when filtering on issuables (Zeger-Jan van de Weg)
- Fix padding of outdated discussion item.
v 8.0.5
- Correct lookup-by-email for LDAP logins
- Fix loading spinner sometimes not being hidden on Merge Request tab switches
v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......
......@@ -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;
}
......@@ -30,7 +30,6 @@ ul.notes {
.discussion-header,
.note-header {
@extend .cgray;
padding-bottom: 15px;
a:hover {
text-decoration: none;
......@@ -75,6 +74,10 @@ ul.notes {
}
}
.discussion-body {
padding-top: 15px;
}
.discussion {
overflow: hidden;
display: block;
......
......@@ -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;
......
......@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user
if @abuse_report.save
if current_application_settings.admin_notification_email.present?
AbuseReportMailer.delay.notify(@abuse_report.id)
end
message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message
else
......
......@@ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_snippet_visibility,
:restricted_signup_domains_raw,
:version_check_enabled,
:admin_notification_email,
:user_oauth_applications,
restricted_visibility_levels: [],
import_sources: []
......
......@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
BroadcastMessage.find(params[:id]).destroy
respond_to do |format|
format.html { redirect_to :back }
format.html { redirect_back_or_default(default: { action: 'index' }) }
format.js { render nothing: true }
end
end
......
......@@ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController
}
@hook.execute(data, 'system_hooks')
redirect_to :back
redirect_back_or_default
end
def hook_params
......
......@@ -33,33 +33,33 @@ class Admin::UsersController < Admin::ApplicationController
def block
if user.block
redirect_to :back, notice: "Successfully blocked"
redirect_back_or_admin_user(notice: "Successfully blocked")
else
redirect_to :back, alert: "Error occurred. User was not blocked"
redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
end
end
def unblock
if user.activate
redirect_to :back, notice: "Successfully unblocked"
redirect_back_or_admin_user(notice: "Successfully unblocked")
else
redirect_to :back, alert: "Error occurred. User was not unblocked"
redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
end
end
def unlock
if user.unlock_access!
redirect_to :back, alert: "Successfully unlocked"
redirect_back_or_admin_user(alert: "Successfully unlocked")
else
redirect_to :back, alert: "Error occurred. User was not unlocked"
redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
end
end
def confirm
if user.confirm
redirect_to :back, notice: "Successfully confirmed"
redirect_back_or_admin_user(notice: "Successfully confirmed")
else
redirect_to :back, alert: "Error occurred. User was not confirmed"
redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
end
end
......@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController
user.update_secondary_emails!
respond_to do |format|
format.html { redirect_to :back, notice: "Successfully removed email." }
format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
format.js { render nothing: true }
end
end
......@@ -157,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController
:projects_limit, :can_create_group, :admin, :key_id
)
end
def redirect_back_or_admin_user(options = {})
redirect_back_or_default(default: default_route, options: options)
end
def default_route
[:admin, @user]
end
end
......@@ -33,6 +33,10 @@ class ApplicationController < ActionController::Base
render_404
end
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
protected
# From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
......
module Ci
class ProjectsController < Ci::ApplicationController
before_action :project
before_action :authenticate_user!, except: [:build, :badge]
before_action :authorize_access_project!, except: [:badge]
before_action :project, except: [:index]
before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge]
protect_from_forgery
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project)
end
# Project status badge
# Image with build status for sha or ref
def badge
......
......@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController
def status
@repos = client.repos
client.orgs.each do |org|
@repos += client.org_repos(org.login)
end
@already_added_projects = current_user.created_projects.where(import_type: "github")
already_added_projects_names = @already_added_projects.pluck(:import_source)
......
......@@ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
return redirect_to :back, alert: "You need to upload a Google Takeout archive."
return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." })
end
begin
dump = JSON.parse(dump_file.read)
rescue
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end
session[:google_code_dump] = dump
......
......@@ -14,7 +14,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
else
redirect_to :back, alert: "The invitation could not be accepted."
redirect_back_or_default(options: { alert: "The invitation could not be accepted." })
end
end
......@@ -31,7 +31,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have declined the invitation to join #{label}."
else
redirect_to :back, alert: "The invitation could not be declined."
redirect_back_or_default(options: { alert: "The invitation could not be declined." })
end
end
......
......@@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
flash[:alert] = "Failed to save new settings"
end
redirect_to :back
redirect_back_or_default(default: profile_notifications_path)
end
format.js
......
......@@ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController
end
respond_to do |format|
format.html { redirect_to :back }
format.html { redirect_back_or_default(default: { action: 'show' }) }
end
end
......
......@@ -30,7 +30,7 @@ class Projects::CiServicesController < Projects::ApplicationController
message = { alert: 'We tried to test the service but error occurred' }
end
redirect_to :back, message
redirect_back_or_default(options: message)
end
private
......
......@@ -24,7 +24,7 @@ class Projects::CiWebHooksController < Projects::ApplicationController
def test
Ci::TestHookService.new.execute(hook, current_user)
redirect_to :back
redirect_back_or_default(default: { action: 'index' })
end
def destroy
......
......@@ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController
execute(@project, head_ref, @project, base_ref)
if compare_result
@commits = compare_result.commits
@commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
@commit = @commits.last
@first_commit = @commits.first
@line_notes = []
end
end
......
......@@ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def disable
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
redirect_to :back
redirect_back_or_default(default: { action: 'index' })
end
protected
......
......@@ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end
redirect_to :back
redirect_back_or_default(default: { action: 'index' })
end
def destroy
......
......@@ -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
......@@ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_to :back, notice: "#{result[:count]} issues updated"
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
def toggle_subscription
......@@ -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
......
......@@ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs
@commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit
@comments_allowed = @reply_allowed = true
@comments_target = {
noteable_type: 'MergeRequest',
......@@ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits
@commit = @merge_request.compare_commits.last
@commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
......
......@@ -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
......
......@@ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format|
format.json { render_note_json(@note) }
format.html { redirect_to :back }
format.html { redirect_back_or_default }
end
end
......@@ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format|
format.json { render_note_json(@note) }
format.html { redirect_to :back }
format.html { redirect_back_or_default }
end
end
......
......@@ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def leave
if @project.namespace == current_user.namespace
return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.')
message = 'You can not leave your own project. Transfer or delete the project.'
return redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
end
@project.project_members.find_by(user_id: current_user).destroy
......
......@@ -52,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController
message = { alert: error_message }
end
redirect_to :back, message
redirect_back_or_default(options: message)
end
private
......
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
......@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController
render 'projects/empty'
else
if current_user
@membership = @project.project_member_by_id(current_user.id)
@membership = @project.team.find_member(current_user.id)
end
render :show
......@@ -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
......@@ -53,14 +53,35 @@ class IssuableFinder
end
end
def project?
params[:project_id].present?
end
def project
return @project if defined?(@project)
@project =
if params[:project_id].present?
Project.find(params[:project_id])
if project?
@project = Project.find(params[:project_id])
unless Ability.abilities.allowed?(current_user, :read_project, @project)
@project = nil
end
else
nil
@project = nil
end
@project
end
def projects
return @projects if defined?(@projects)
if project?
project
elsif current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects
else
ProjectsFinder.new.execute(current_user)
end
end
......@@ -72,7 +93,7 @@ class IssuableFinder
params[:milestone_title].present?
end
def no_milestones?
def filter_by_no_milestone?
milestones? && params[:milestone_title] == Milestone::None.title
end
......@@ -81,12 +102,22 @@ class IssuableFinder
@milestones =
if milestones?
Milestone.where(title: params[:milestone_title])
scope = Milestone.where(project_id: projects)
scope.where(title: params[:milestone_title])
else
nil
end
end
def labels?
params[:label_name].present?
end
def filter_by_no_label?
labels? && params[:label_name] == Label::None.title
end
def assignee?
params[:assignee_id].present?
end
......@@ -120,19 +151,7 @@ class IssuableFinder
private
def init_collection
table_name = klass.table_name
if project
if Ability.abilities.allowed?(current_user, :read_project, project)
project.send(table_name)
else
[]
end
elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project)
else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
end
klass.all
end
def by_scope(items)
......@@ -170,7 +189,12 @@ class IssuableFinder
end
def by_project(items)
items = items.of_projects(project.id) if project
items =
if projects
items.of_projects(projects).references(:project)
else
items.none
end
items
end
......@@ -185,18 +209,6 @@ class IssuableFinder
items.sort(params[:sort])
end
def by_milestone(items)
if milestones?
if no_milestones?
items = items.where(milestone_id: [-1, nil])
else
items = items.where(milestone_id: milestones.try(:pluck, :id))
end
end
items
end
def by_assignee(items)
if assignee?
items = items.where(assignee_id: assignee.try(:id))
......@@ -213,20 +225,36 @@ class IssuableFinder
items
end
def by_label(items)
if params[:label_name].present?
if params[:label_name] == Label::None.title
item_ids = LabelLink.where(target_type: klass.name).pluck(:target_id)
def by_milestone(items)
if milestones?
if filter_by_no_milestone?
items = items.where(milestone_id: [-1, nil])
else
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
if projects
items = items.where(milestones: { project_id: projects })
end
end
end
items
end
items = items.where('id NOT IN (?)', item_ids)
def by_label(items)
if labels?
if filter_by_no_label?
items = items.
joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
where(label_links: { id: nil })
else
label_names = params[:label_name].split(",")
item_ids = LabelLink.joins(:label).
where('labels.title in (?)', label_names).
where(target_type: klass.name).pluck(:target_id)
items = items.joins(:labels).where(labels: { title: label_names })
items = items.where(id: item_ids)
if projects
items = items.where(labels: { project_id: projects })
end
end
end
......
......@@ -170,7 +170,8 @@ module DiffHelper
def commit_for_diff(diff)
if diff.deleted_file
@merge_request ? @merge_request.commits.last : @commit.parents.first
first_commit = @first_commit || @commit
first_commit.parent
else
@commit
end
......
......@@ -19,7 +19,8 @@ module GitlabMarkdownHelper
escape_once(body)
end
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user)
user = current_user if defined?(current_user)
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
......@@ -45,29 +46,39 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
return "" unless text.present?
context.reverse_merge!(
current_user: current_user,
path: @path,
pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
Gitlab::Markdown.render(text, context)
user = current_user if defined?(current_user)
html = Gitlab::Markdown.render(text, context)
Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user)
end
# TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered
def gfm(text, options = {})
return "" unless text.present?
options.reverse_merge!(
current_user: current_user,
path: @path,
pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
Gitlab::Markdown.gfm(text, options)
user = current_user if defined?(current_user)
html = Gitlab::Markdown.gfm(text, options)
Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
end
def asciidoc(text)
......
......@@ -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
......@@ -113,7 +117,7 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
if can?(current_user, :read_build, project)
if project.gitlab_ci? && can?(current_user, :read_build, project)
nav_tabs << :builds
end
......
class AbuseReportMailer < BaseMailer
include Gitlab::CurrentSettings
def notify(abuse_report_id)
@abuse_report = AbuseReport.find(abuse_report_id)
mail(
to: current_application_settings.admin_notification_email,
subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
)
end
end
......@@ -189,7 +189,8 @@ class Ability
:change_visibility_level,
:rename_project,
:remove_project,
:archive_project
:archive_project,
:remove_fork_project
]
end
......
......@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
validates :admin_notification_email,
allow_blank: true,
email: true
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
......
......@@ -2,13 +2,13 @@ class Commit
extend ActiveModel::Naming
include ActiveModel::Conversion
include Mentionable
include Participable
include Mentionable
include Referable
include StaticModel
attr_mentionable :safe_message
participant :author, :committer, :notes, :mentioned_users
participant :author, :committer, :notes
attr_accessor :project
......@@ -164,6 +164,14 @@ class Commit
@committer ||= User.find_by_any_email(committer_email)
end
def parents
@parents ||= parent_ids.map { |id| project.commit(id) }
end
def parent
@parent ||= project.commit(self.parent_id) if self.parent_id
end
def notes
project.notes.for_commit_id(self.id)
end
......@@ -181,10 +189,6 @@ class Commit
@raw.short_id(7)
end
def parents
@parents ||= Commit.decorate(super, project)
end
def ci_commit
project.ci_commit(sha)
end
......
......@@ -6,8 +6,8 @@
#
module Issuable
extend ActiveSupport::Concern
include Mentionable
include Participable
include Mentionable
included do
belongs_to :author, class_name: "User"
......@@ -47,8 +47,7 @@ module Issuable
prefix: true
attr_mentionable :title, :description
participant :author, :assignee, :notes_with_associations, :mentioned_users
participant :author, :assignee, :notes_with_associations
end
module ClassMethods
......@@ -86,6 +85,10 @@ module Issuable
assignee_id_changed?
end
def open?
opened? || reopened?
end
#
# Votes
#
......
......@@ -20,6 +20,12 @@ module Mentionable
end
end
included do
if self < Participable
participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) }
end
end
# Returns the text used as the body of a Note when this object is referenced
#
# By default this will be the class name and the result of calling
......@@ -41,22 +47,22 @@ module Mentionable
self
end
def all_references(current_user = self.author, text = self.mentionable_text)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
ext.analyze(text)
ext
end
def mentioned_users(current_user = nil)
all_references(current_user).users.uniq
def mentioned_users(current_user = nil, load_lazy_references: true)
all_references(current_user, load_lazy_references: load_lazy_references).users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def referenced_mentionables(current_user = self.author, text = self.mentionable_text)
def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
return [] if text.blank?
refs = all_references(current_user, text)
(refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference]
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
......@@ -12,7 +12,7 @@
#
# # ...
#
# participant :author, :assignee, :mentioned_users, :notes
# participant :author, :assignee, :notes, ->(current_user) { mentioned_users(current_user) }
# end
#
# issue = Issue.last
......@@ -27,7 +27,7 @@ module Participable
module ClassMethods
def participant(*attrs)
participant_attrs.concat(attrs.map(&:to_s))
participant_attrs.concat(attrs)
end
def participant_attrs
......@@ -37,33 +37,39 @@ module Participable
# Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request
def participants(current_user = self.author)
self.class.participant_attrs.flat_map do |attr|
meth = method(attr)
def participants(current_user = self.author, load_lazy_references: true)
participants = self.class.participant_attrs.flat_map do |attr|
value =
if meth.arity == 1 || meth.arity == -1
meth.call(current_user)
if attr.respond_to?(:call)
instance_exec(current_user, &attr)
else
meth.call
send(attr)
end
participants_for(value, current_user)
end.compact.uniq.select do |user|
user.can?(:read_project, self.project)
end.compact.uniq
if load_lazy_references
participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
participants.select! do |user|
user.can?(:read_project, project)
end
end
participants
end
private
def participants_for(value, current_user = nil)
case value
when User
when User, Gitlab::Markdown::ReferenceFilter::LazyReference
[value]
when Enumerable, ActiveRecord::Relation
value.flat_map { |v| participants_for(v, current_user) }
when Participable
value.participants(current_user)
value.participants(current_user, load_lazy_references: false)
end
end
end
......@@ -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
......@@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff
after_update :update_merge_request_diff
delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
delegate :commits, :diffs, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
......@@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base
reference
end
def last_commit
merge_request_diff ? merge_request_diff.last_commit : compare_commits.last
end
def first_commit
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
def last_commit_short_sha
last_commit.short_id
end
def validate_branches
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
......@@ -222,10 +234,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 +302,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
......
......@@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base
commits.first
end
def first_commit
commits.last
end
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.short_id
end
......
......@@ -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
......@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Note < ActiveRecord::Base
include Mentionable
include Gitlab::CurrentSettings
include Participable
include Mentionable
default_value_for :system, false
attr_mentionable :note
participant :author, :mentioned_users
participant :author
belongs_to :project
belongs_to :noteable, polymorphic: true
......
......@@ -8,6 +8,14 @@ class Repository
attr_accessor :raw_repository, :path_with_namespace, :project
def self.clean_old_archives
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
return unless File.directory?(repository_downloads_path)
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace
@project = project
......@@ -269,14 +277,6 @@ class Repository
end
# Remove archives older than 2 hours
def clean_old_archives
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
return unless File.directory?(repository_downloads_path)
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
def branches_sorted_by(value)
case value
when 'recently_updated'
......@@ -312,13 +312,7 @@ class Repository
end
def blob_for_diff(commit, diff)
file = blob_at(commit.id, diff.new_path)
unless file
file = prev_blob_for_diff(commit, diff)
end
file
blob_at(commit.id, diff.file_path)
end
def prev_blob_for_diff(commit, diff)
......
......@@ -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
......@@ -706,14 +706,17 @@ class User < ActiveRecord::Base
end
def toggle_star(project)
UsersStarProject.transaction do
user_star_project = users_star_projects.
where(project: project, user: self).take
where(project: project, user: self).lock(true).first
if user_star_project
user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end
end
def manageable_namespaces
@manageable_namespaces ||= [namespace] + owned_groups + masters_groups
......
......@@ -7,7 +7,7 @@ class ArchiveRepositoryService
end
def execute(options = {})
project.repository.clean_old_archives
RepositoryArchiveCacheWorker.perform_async
metadata = project.repository.archive_metadata(ref, storage_path, format)
raise "Repository or ref not found" if metadata.empty?
......
......@@ -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
......
%p
#{link_to @abuse_report.user.name, user_url(@abuse_report.user)}
(@#{@abuse_report.user.username}) was reported for abuse by
#{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)}
(@#{@abuse_report.reporter.username}).
%blockquote
= @abuse_report.message
%p
= link_to "View details", abuse_reports_url
#{@abuse_report.user.name} (@#{@abuse_report.user.username}) was reported for abuse by #{@abuse_report.reporter.name} (@#{@abuse_report.reporter.username}).
\
> #{@abuse_report.message}
\
View details: #{admin_abuse_reports_url}
......@@ -47,6 +47,12 @@
= f.label :version_check_enabled do
= f.check_box :version_check_enabled
Version check enabled
.form-group
= f.label :admin_notification_email, class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :admin_notification_email, class: 'form-control'
.help-block
Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
%fieldset
%legend Account and Limit Settings
......
......@@ -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
......
.wiki
%h1
GitLab CI is now integrated in GitLab UI
%h2 For existing projects
%p
Check the following pages to find the CI status you're looking for:
%ul
%li Projects page - shows CI status for each project.
%li Project commits page - show CI status for each commit.
%h2 For new projects
%p
If you want to enable CI for a new project it is easy as adding
= link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
file to your repository
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if issue.description.present?
= markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project)
= markdown(issue.description, pipeline: :atom, project: issue.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if merge_request.description.present?
= markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project)
= markdown(merge_request.description, pipeline: :atom, project: merge_request.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"}
= markdown(note.note, xhtml: true, reference_only_path: false, project: note.project)
= markdown(note.note, pipeline: :atom, project: note.project)
......@@ -6,7 +6,7 @@
%i
at
= commit[:timestamp].to_time.to_s(:short)
%blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project)
%blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project)
- if event.commits_count > 15
%p
%i
......
......@@ -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
%div
= markdown(@note.note, reference_only_path: false)
= markdown(@note.note, pipeline: :email)
-if @issue.description
= markdown(@issue.description, reference_only_path: false)
= markdown(@issue.description, pipeline: :email)
- if @issue.assignee_id.present?
%p
......
......@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-if @merge_request.description
= markdown(@merge_request.description, reference_only_path: false)
= markdown(@merge_request.description, pipeline: :email)
......@@ -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'
= render 'projects/last_push'
.gray-content-block.activity-filter-block
- if current_user
.pull-right
......
#tree-holder.tree-holder.clearfix
.gray-content-block.second-block
= render 'projects/tree/tree_header', tree: @tree
= render 'projects/tree/tree_content', tree: @tree
- if readme = @repository.readme
%article.readme-holder#README
.clearfix
.pull-right
&nbsp;
- 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
......
- page_title "Activity"
- header_title project_title(@project, "Activity", activity_project_path(@project))
= render 'projects/last_push'
= render 'projects/activity'
%ul.breadcrumb.repo-breadcrumb
.gray-content-block.top-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
%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|
......
......@@ -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
......
- return unless @membership
= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do
- case @membership
- when ProjectMember
= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do
= hidden_field_tag :notification_type, 'project'
= hidden_field_tag :notification_id, @membership.id
= hidden_field_tag :notification_level
......@@ -12,3 +12,9 @@
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- Notification.project_notification_levels.each do |level|
= notification_list_item(level, @membership)
- when GroupMember
.btn.btn-new.disabled.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
= icon('bell')
= notification_label(@membership)
= icon('angle-down')
......@@ -6,7 +6,7 @@
%tr
%th
%th Service
%th Desription
%th Description
%th Last edit
- @services.sort_by(&:title).each do |service|
%tr
......
......@@ -15,8 +15,8 @@
.files
- diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file.diff)
- blob = project.repository.blob_for_diff(diff_commit, diff_file.diff)
- diff_commit = commit_for_diff(diff_file)
- blob = project.repository.blob_for_diff(diff_commit, diff_file)
- next unless blob
= render 'projects/diffs/file', i: index, project: project,
......
.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
.diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
.diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- if diff_file.diff.submodule?
%span
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
......@@ -38,7 +38,7 @@
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- elsif blob.image?
- old_file = project.repository.prev_blob_for_diff(@commit, diff_file)
- old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
- else
.nothing-here-block No preview for this file type
......
......@@ -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
......
.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'
- if @merge_request.has_ci?
- ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
- if ci_commit
- ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
- if ci_commit
- status = ci_commit.status
.mr-widget-heading
.ci_widget{class: "ci-#{status}"}
......@@ -10,7 +9,7 @@
%span.ci-coverage
= link_to "View build details", ci_status_path(ci_commit)
- else
- elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- # Remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading
......
: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
%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)
.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'
......
.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
......@@ -6,7 +6,7 @@
type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
:"data-clone" => project.ssh_url_to_repo, |
:"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH",
:"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH.",
:"data-html" => "true",
:"data-container" => "body"}
SSH
......@@ -15,7 +15,7 @@
type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
:"data-clone" => project.http_url_to_repo, |
:"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}",
:"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}.",
:"data-html" => "true",
:"data-container" => "body"}
= gitlab_config.protocol.upcase
......
%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
%section.col-md-7
.header-with-avatar
.cover-block
.avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: ''
%h3
= 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?
&middot;
= link_to "Skype", "skype:#{@user.skype}"
- unless @user.linkedin.blank?
&middot;
= link_to "LinkedIn", "http://www.linkedin.com/in/#{@user.linkedin}"
- unless @user.twitter.blank?
&middot;
= link_to "Twitter", "http://www.twitter.com/#{@user.twitter}"
- unless @user.website_url.blank?
&middot;
= link_to @user.short_website_url, @user.full_website_url
- unless @user.location.blank?
&middot;
= @user.location
.cover-controls
- if @user == current_user
.pull-right.hidden-xs
= link_to profile_path, class: 'btn btn-sm' do
= icon('user')
Profile settings
= link_to profile_path, class: 'btn btn-gray' do
= icon('pencil')
- elsif current_user
.report_abuse.pull-right
.report-abuse
- 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'}}
%button.btn.btn-danger{ title: 'Already reported for abuse',
data: { toggle: 'tooltip', placement: 'left', 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'}}
= 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')
.username
@#{@user.username}
.description
- if @user.bio.present?
= @user.bio
.gray-content-block.second-block
.user-calendar
%h4.center.light
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
.clearfix
.row.prepend-top-20
%section.col-md-7
- 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
......
class RepositoryArchiveCacheWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform
Repository.clean_old_archives
end
end
class RepositoryArchiveWorker
include Sidekiq::Worker
sidekiq_options queue: :archive_repo
attr_accessor :project, :ref, :format
def perform(project_id, ref, format)
@project = Project.find(project_id)
@ref, @format = ref, format.downcase
repository = project.repository
repository.clean_old_archives
return unless file_path
return if archived? || archiving?
repository.archive_repo(ref, storage_path, format)
end
private
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
def file_path
@file_path ||= project.repository.archive_file_path(ref, storage_path, format)
end
def pid_file_path
@pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
end
def archived?
File.exist?(file_path)
end
def archiving?
File.exist?(pid_file_path)
end
end
......@@ -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
......
Gitlab::Seeder.quiet do
(2..20).each do |i|
20.times do |i|
begin
User.create!(
username: FFaker::Internet.user_name,
......@@ -15,7 +15,7 @@ Gitlab::Seeder.quiet do
end
end
(1..5).each do |i|
5.times do |i|
begin
User.create!(
username: "user#{i}",
......
Gitlab::Seeder.quiet do
Project.all.each do |project|
(1..5).each do |i|
5.times do |i|
milestone_params = {
title: "v#{i}.0",
description: FFaker::Lorem.sentence,
......
Gitlab::Seeder.quiet do
Project.all.each do |project|
(1..10).each do |i|
10.times do
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
......
......@@ -22,7 +22,7 @@ class Member < ActiveRecord::Base
end
eos
(1..50).each do |i|
50.times do |i|
user = User.all.sample
PersonalSnippet.seed(:id, [{
......
class AddAdminNotificationEmailSetting < ActiveRecord::Migration
def change
add_column :application_settings, :admin_notification_email, :string
end
end
class FixBuildTags < ActiveRecord::Migration
def change
execute("UPDATE taggings SET taggable_type='CommitStatus' WHERE taggable_type='Ci::Build'")
end
end
class FailBuildWithoutNames < ActiveRecord::Migration
def change
execute("UPDATE ci_builds SET status='failed' WHERE name IS NULL AND status='pending'")
end
end
class CiLimitsToMysql < ActiveRecord::Migration
def change
return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
# CI
change_column :ci_builds, :trace, :text, limit: 1073741823
change_column :ci_commits, :push_data, :text, limit: 16777215
end
end
class AddCiBuildsIndexForStatus < ActiveRecord::Migration
def change
add_index :ci_builds, [:commit_id, :status, :type]
end
end
......@@ -6,9 +6,5 @@ class LimitsToMysql < ActiveRecord::Migration
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :text, limit: 2147483647
# CI
change_column :ci_builds, :trace, :text, limit: 1073741823
change_column :ci_commits, :push_data, :text, limit: 16777215
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151016195706) do
ActiveRecord::Schema.define(version: 20151020173906) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -46,6 +46,7 @@ ActiveRecord::Schema.define(version: 20151016195706) do
t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
t.text "help_page_text"
t.string "admin_notification_email"
end
create_table "audit_events", force: true do |t|
......@@ -109,6 +110,7 @@ ActiveRecord::Schema.define(version: 20151016195706) do
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
......
......@@ -70,7 +70,16 @@ sudo -u git -H git fetch
sudo -u git -H git checkout v2.6.5
```
### 5. Install libs, migrations, etc.
### 5. Update gitlab-git-http-server
```bash
cd /home/git/gitlab-git-http-server
sudo -u git -H git fetch origin
sudo -u git -H git checkout 0.3.0
sudo -u git -H make
```
### 6. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
......@@ -91,7 +100,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 6. Update configuration files
### 7. Update configuration files
#### New configuration options for `gitlab.yml`
......@@ -101,12 +110,33 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y
git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example
```
### 7. Start application
#### Nginx configuration
View changes between the previous recommended Nginx configuration and the
current one:
```sh
# For HTTPS configurations
git diff origin/8-0-stable:lib/support/nginx/gitlab-ssl origin/8-1-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-0-stable:lib/support/nginx/gitlab origin/8-1-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-git-http-server listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-1-stable/lib/support/init.d/gitlab.default.example#L34
### 8. Start application
sudo service gitlab start
sudo service nginx restart
### 8. Check application status
### 9. Check application status
Check if GitLab and its environment are configured correctly:
......
......@@ -49,9 +49,7 @@ sudo -u git -H bundle install --without development test mysql --deployment
sudo -u git -H bundle install --without development test postgres --deployment
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 5. Start application
......
......@@ -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
......
......@@ -7,6 +7,14 @@ module Gitlab
module Markdown
# Convert a Markdown String into an HTML-safe String of HTML
#
# Note that while the returned HTML will have been sanitized of dangerous
# HTML, it may post a risk of information leakage if it's not also passed
# through `post_process`.
#
# Also note that the returned String is always HTML, not XHTML. Views
# requiring XHTML, such as Atom feeds, need to call `post_process` on the
# result, providing the appropriate `pipeline` option.
#
# markdown - Markdown String
# context - Hash of context options passed to our HTML Pipeline
#
......@@ -31,6 +39,33 @@ module Gitlab
renderer.render(markdown)
end
# Perform post-processing on an HTML String
#
# This method is used to perform state-dependent changes to a String of
# HTML, such as removing references that the current user doesn't have
# permission to make (`RedactorFilter`).
#
# html - String to process
# options - Hash of options to customize output
# :pipeline - Symbol pipeline type
# :project - Project
# :user - User object
#
# Returns an HTML-safe String
def self.post_process(html, options)
context = {
project: options[:project],
current_user: options[:user]
}
doc = post_processor.to_document(html, context)
if options[:pipeline] == :atom
doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
else
doc.to_html
end.html_safe
end
# Provide autoload paths for filters to prevent a circular dependency error
autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
......@@ -41,6 +76,7 @@ module Gitlab
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
......@@ -50,26 +86,20 @@ module Gitlab
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
# Public: Parse the provided text with GitLab-Flavored Markdown
# Public: Parse the provided HTML with GitLab-Flavored Markdown
#
# text - the source text
# options - A Hash of options used to customize output (default: {}):
# :xhtml - output XHTML instead of HTML
# :reference_only_path - Use relative path for reference links
def self.gfm(text, options = {})
return text if text.nil?
# Duplicate the string so we don't alter the original, then call to_str
# to cast it back to a String instead of a SafeBuffer. This is required
# for gsub calls to work as we need them to.
text = text.dup.to_str
options.reverse_merge!(
xhtml: false,
reference_only_path: true,
project: options[:project],
current_user: options[:current_user]
)
# html - HTML String
# options - A Hash of options used to customize output (default: {})
# :no_header_anchors - Disable header anchors in TableOfContentsFilter
# :path - Current path String
# :pipeline - Symbol pipeline type
# :project - Current Project object
# :project_wiki - Current ProjectWiki object
# :ref - Current ref String
#
# Returns an HTML-safe String
def self.gfm(html, options = {})
return '' unless html.present?
@pipeline ||= HTML::Pipeline.new(filters)
......@@ -78,41 +108,36 @@ module Gitlab
pipeline: options[:pipeline],
# EmojiFilter
asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host,
# TableOfContentsFilter
no_header_anchors: options[:no_header_anchors],
asset_root: Gitlab.config.gitlab.base_url,
# ReferenceFilter
current_user: options[:current_user],
only_path: options[:reference_only_path],
only_path: only_path_pipeline?(options[:pipeline]),
project: options[:project],
# RelativeLinkFilter
project_wiki: options[:project_wiki],
ref: options[:ref],
requested_path: options[:path],
project_wiki: options[:project_wiki]
}
result = @pipeline.call(text, context)
save_options = 0
if options[:xhtml]
save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
end
text = result[:output].to_html(save_with: save_options)
# TableOfContentsFilter
no_header_anchors: options[:no_header_anchors]
}
text.html_safe
@pipeline.to_html(html, context).html_safe
end
private
def self.renderer
@markdown ||= begin
renderer = Redcarpet::Render::HTML.new
Redcarpet::Markdown.new(renderer, redcarpet_options)
# Check if a pipeline enables the `only_path` context option
#
# Returns Boolean
def self.only_path_pipeline?(pipeline)
case pipeline
when :atom, :email
false
else
true
end
end
......@@ -130,6 +155,17 @@ module Gitlab
}.freeze
end
def self.renderer
@markdown ||= begin
renderer = Redcarpet::Render::HTML.new
Redcarpet::Markdown.new(renderer, redcarpet_options)
end
end
def self.post_processor
@post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
end
# Filters used in our pipeline
#
# SanitizationFilter should come first so that all generated reference HTML
......
......@@ -26,6 +26,18 @@ module Gitlab
end
end
def self.referenced_by(node)
project = Project.find(node.attr("data-project")) rescue nil
return unless project
id = node.attr("data-commit-range")
range = CommitRange.new(id, project)
return unless range.valid_commits?
{ commit_range: range }
end
def initialize(*args)
super
......@@ -53,13 +65,11 @@ module Gitlab
range = CommitRange.new(id, project)
if range.valid_commits?
push_result(:commit_range, range)
url = url_for_commit_range(project, range)
title = range.reference_title
klass = reference_class(:commit_range)
data = data_attribute(project.id)
data = data_attribute(project: project.id, commit_range: id)
project_ref += '@' if project_ref
......
......@@ -26,6 +26,18 @@ module Gitlab
end
end
def self.referenced_by(node)
project = Project.find(node.attr("data-project")) rescue nil
return unless project
id = node.attr("data-commit")
commit = commit_from_ref(project, id)
return unless commit
{ commit: commit }
end
def call
replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content)
......@@ -39,17 +51,15 @@ module Gitlab
# Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text)
self.class.references_in(text) do |match, commit_ref, project_ref|
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
if commit = commit_from_ref(project, commit_ref)
push_result(:commit, commit)
if commit = self.class.commit_from_ref(project, id)
url = url_for_commit(project, commit)
title = escape_once(commit.link_title)
klass = reference_class(:commit)
data = data_attribute(project.id)
data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref
......@@ -62,9 +72,9 @@ module Gitlab
end
end
def commit_from_ref(project, commit_ref)
def self.commit_from_ref(project, id)
if project && project.valid_repo?
project.commit(commit_ref)
project.commit(id)
end
end
......
......@@ -13,18 +13,11 @@ module Gitlab
#
# ref - String reference.
#
# Returns a Project, or nil if the reference can't be accessed
# Returns a Project, or nil if the reference can't be found
def project_from_ref(ref)
return context[:project] unless ref
other = Project.find_with_namespace(ref)
return nil unless other && user_can_reference_project?(other)
other
end
def user_can_reference_project?(project, user = context[:current_user])
Ability.abilities.allowed?(user, :read_project, project)
Project.find_with_namespace(ref)
end
end
end
......
......@@ -47,8 +47,9 @@ module Gitlab
title = escape_once("Issue in #{project.external_issue_tracker.title}")
klass = reference_class(:issue)
data = data_attribute(project: project.id)
%(<a href="#{url}"
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{match}</a>)
end
......
......@@ -27,6 +27,10 @@ module Gitlab
end
end
def self.referenced_by(node)
{ issue: LazyReference.new(Issue, node.attr("data-issue")) }
end
def call
replace_text_nodes_matching(Issue.reference_pattern) do |content|
issue_link_filter(content)
......@@ -45,13 +49,11 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && issue = project.get_issue(id)
push_result(:issue, issue)
url = url_for_issue(id, project, only_path: context[:only_path])
title = escape_once("Issue: #{issue.title}")
klass = reference_class(:issue)
data = data_attribute(project.id)
data = data_attribute(project: project.id, issue: issue.id)
%(<a href="#{url}" #{data}
title="#{title}"
......
......@@ -22,6 +22,10 @@ module Gitlab
end
end
def self.referenced_by(node)
{ label: LazyReference.new(Label, node.attr("data-label")) }
end
def call
replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content)
......@@ -41,11 +45,9 @@ module Gitlab
params = label_params(id, name)
if label = project.labels.find_by(params)
push_result(:label, label)
url = url_for_label(project, label)
klass = reference_class(:label)
data = data_attribute(project.id)
data = data_attribute(project: project.id, label: label.id)
%(<a href="#{url}" #{data}
class="#{klass}">#{render_colored_label(label)}</a>)
......
......@@ -27,6 +27,10 @@ module Gitlab
end
end
def self.referenced_by(node)
{ merge_request: LazyReference.new(MergeRequest, node.attr("data-merge-request")) }
end
def call
replace_text_nodes_matching(MergeRequest.reference_pattern) do |content|
merge_request_link_filter(content)
......@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && merge_request = project.merge_requests.find_by(iid: id)
push_result(:merge_request, merge_request)
title = escape_once("Merge Request: #{merge_request.title}")
klass = reference_class(:merge_request)
data = data_attribute(project.id)
data = data_attribute(project: project.id, merge_request: merge_request.id)
url = url_for_merge_request(merge_request, project)
......
require 'gitlab/markdown'
require 'html/pipeline/filter'
module Gitlab
module Markdown
# HTML filter that removes references to records that the current user does
# not have permission to view.
#
# Expected to be run in its own post-processing pipeline.
#
class RedactorFilter < HTML::Pipeline::Filter
def call
doc.css('a.gfm').each do |node|
unless user_can_reference?(node)
node.replace(node.text)
end
end
doc
end
private
def user_can_reference?(node)
if node.has_attribute?('data-reference-filter')
reference_type = node.attr('data-reference-filter')
reference_filter = reference_type.constantize
reference_filter.user_can_reference?(current_user, node, context)
else
true
end
end
def current_user
context[:current_user]
end
end
end
end
......@@ -11,30 +11,57 @@ module Gitlab
# Context options:
# :project (required) - Current project, ignored if reference is cross-project.
# :only_path - Generate path-only links.
#
# Results:
# :references - A Hash of references that were found and replaced.
class ReferenceFilter < HTML::Pipeline::Filter
def initialize(*args)
super
LazyReference = Struct.new(:klass, :ids) do
def self.load(refs)
lazy_references, values = refs.partition { |ref| ref.is_a?(self) }
lazy_values = lazy_references.group_by(&:klass).flat_map do |klass, refs|
ids = refs.flat_map(&:ids)
klass.where(id: ids)
end
result[:references] = Hash.new { |hash, type| hash[type] = [] }
values + lazy_values
end
def load
self.klass.where(id: self.ids)
end
end
def self.user_can_reference?(user, node, context)
if node.has_attribute?('data-project')
project_id = node.attr('data-project').to_i
return true if project_id == context[:project].try(:id)
project = Project.find(project_id) rescue nil
Ability.abilities.allowed?(user, :read_project, project)
else
true
end
end
def self.referenced_by(node)
raise NotImplementedError, "#{self} does not implement #{__method__}"
end
# Returns a data attribute String to attach to a reference link
#
# id - Object ID
# type - Object type (default: :project)
# attributes - Hash, where the key becomes the data attribute name and the
# value is the data attribute value
#
# Examples:
#
# data_attribute(1) # => "data-project-id=\"1\""
# data_attribute(2, :user) # => "data-user-id=\"2\""
# data_attribute(3, :group) # => "data-group-id=\"3\""
# data_attribute(project: 1, issue: 2)
# # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
#
# data_attribute(project: 3, merge_request: 4)
# # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
#
# Returns a String
def data_attribute(id, type = :project)
%Q(data-#{type}-id="#{id}")
def data_attribute(attributes = {})
attributes[:reference_filter] = self.class.name
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
end
def escape_once(html)
......@@ -59,16 +86,6 @@ module Gitlab
context[:project]
end
# Add a reference to the pipeline's result Hash
#
# type - Singular Symbol reference type (e.g., :issue, :user, etc.)
# values - One or more Objects to add
def push_result(type, *values)
return if values.empty?
result[:references][type].push(*values)
end
def reference_class(type)
"gfm gfm-#{type}"
end
......@@ -85,15 +102,15 @@ module Gitlab
# Yields the current node's String contents. The result of the block will
# replace the node's existing content and update the current document.
#
# Returns the updated Nokogiri::XML::Document object.
# Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_text_nodes_matching(pattern)
return doc if project.nil?
search_text_nodes(doc).each do |node|
content = node.to_html
next unless content.match(pattern)
next if ignored_ancestry?(node)
next unless node.text =~ pattern
content = node.to_html
html = yield content
......
require 'gitlab/markdown'
require 'html/pipeline/filter'
module Gitlab
module Markdown
# HTML filter that gathers all referenced records that the current user has
# permission to view.
#
# Expected to be run in its own post-processing pipeline.
#
class ReferenceGathererFilter < HTML::Pipeline::Filter
def initialize(*)
super
result[:references] ||= Hash.new { |hash, type| hash[type] = [] }
end
def call
doc.css('a.gfm').each do |node|
gather_references(node)
end
load_lazy_references unless context[:load_lazy_references] == false
doc
end
private
def gather_references(node)
return unless node.has_attribute?('data-reference-filter')
reference_type = node.attr('data-reference-filter')
reference_filter = reference_type.constantize
return if context[:reference_filter] && reference_filter != context[:reference_filter]
return unless reference_filter.user_can_reference?(current_user, node, context)
references = reference_filter.referenced_by(node)
return unless references
references.each do |type, values|
Array.wrap(values).each do |value|
result[:references][type] << value
end
end
end
# Will load all references of one type using one query.
def load_lazy_references
refs = result[:references]
refs.each do |type, values|
refs[type] = ReferenceFilter::LazyReference.load(values)
end
end
def current_user
context[:current_user]
end
end
end
end
......@@ -27,6 +27,10 @@ module Gitlab
end
end
def self.referenced_by(node)
{ snippet: LazyReference.new(Snippet, node.attr("data-snippet")) }
end
def call
replace_text_nodes_matching(Snippet.reference_pattern) do |content|
snippet_link_filter(content)
......@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && snippet = project.snippets.find_by(id: id)
push_result(:snippet, snippet)
title = escape_once("Snippet: #{snippet.title}")
klass = reference_class(:snippet)
data = data_attribute(project.id)
data = data_attribute(project: project.id, snippet: snippet.id)
url = url_for_snippet(snippet, project)
......
......@@ -23,6 +23,31 @@ module Gitlab
end
end
def self.referenced_by(node)
if node.has_attribute?('data-group')
group = Group.find(node.attr('data-group')) rescue nil
return unless group
{ user: group.users }
elsif node.has_attribute?('data-user')
{ user: LazyReference.new(User, node.attr('data-user')) }
elsif node.has_attribute?('data-project')
project = Project.find(node.attr('data-project')) rescue nil
return unless project
{ user: project.team.members.flatten }
end
end
def self.user_can_reference?(user, node, context)
if node.has_attribute?('data-group')
group = Group.find(node.attr('data-group')) rescue nil
Ability.abilities.allowed?(user, :read_group, group)
else
super
end
end
def call
replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
......@@ -61,14 +86,12 @@ module Gitlab
def link_to_all
project = context[:project]
# FIXME (rspeicher): Law of Demeter
push_result(:user, *project.team.members.flatten)
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
data = data_attribute(project: project.id)
text = User.reference_prefix + 'all'
%(<a href="#{url}" class="#{link_class}">#{text}</a>)
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end
def link_to_namespace(namespace)
......@@ -80,30 +103,20 @@ module Gitlab
end
def link_to_group(group, namespace)
return unless user_can_reference_group?(namespace)
push_result(:user, *namespace.users)
url = urls.group_url(group, only_path: context[:only_path])
data = data_attribute(namespace.id, :group)
data = data_attribute(group: namespace.id)
text = Group.reference_prefix + group
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end
def link_to_user(user, namespace)
push_result(:user, namespace.owner)
url = urls.user_url(user, only_path: context[:only_path])
data = data_attribute(namespace.owner_id, :user)
data = data_attribute(user: namespace.owner_id)
text = User.reference_prefix + user
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end
def user_can_reference_group?(group)
Ability.abilities.allowed?(context[:current_user], :read_group, group)
end
end
end
end
......@@ -3,11 +3,12 @@ require 'gitlab/markdown'
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
attr_accessor :project, :current_user
attr_accessor :project, :current_user, :load_lazy_references
def initialize(project, current_user = nil)
def initialize(project, current_user = nil, load_lazy_references: true)
@project = project
@current_user = current_user
@load_lazy_references = load_lazy_references
end
def analyze(text)
......@@ -26,9 +27,9 @@ 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).uniq
references[type] = pipeline_result(type)
end
end
......@@ -41,21 +42,32 @@ module Gitlab
def pipeline_result(filter_type)
return [] if @text.blank?
klass = filter_type.to_s.camelize + 'ReferenceFilter'
klass = "#{filter_type.to_s.camelize}ReferenceFilter"
filter = Gitlab::Markdown.const_get(klass)
context = {
project: project,
current_user: current_user,
# We don't actually care about the links generated
only_path: true,
ignore_blockquotes: true
ignore_blockquotes: true,
# ReferenceGathererFilter
load_lazy_references: false,
reference_filter: filter
}
pipeline = HTML::Pipeline.new([filter], context)
pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
result = pipeline.call(@text)
result[:references][filter_type]
values = result[:references][filter_type].uniq
if @load_lazy_references
values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq
end
values
end
end
end
......@@ -335,7 +335,7 @@ namespace :gitlab do
print "Redis version >= #{min_redis_version}? ... "
redis_version = run(%W(redis-cli --version))
redis_version = redis_version.try(:match, /redis-cli (.*)/)
redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
if redis_version &&
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
puts "yes".green
......
require 'spec_helper'
describe Gitlab::Markdown::ReferenceFilter, benchmark: true do
let(:input) do
html = <<-EOF
<p>Hello @alice and @bob, how are you doing today?</p>
<p>This is simple @dummy text to see how the @ReferenceFilter class performs
when @processing HTML.</p>
EOF
Nokogiri::HTML.fragment(html)
end
let(:project) { create(:empty_project) }
let(:filter) { described_class.new(input, project: project) }
describe '#replace_text_nodes_matching' do
let(:iterations) { 6000 }
describe 'with identical input and output HTML' do
benchmark_subject do
filter.replace_text_nodes_matching(User.reference_pattern) do |content|
content
end
end
it { is_expected.to iterate_per_second(iterations) }
end
describe 'with different input and output HTML' do
benchmark_subject do
filter.replace_text_nodes_matching(User.reference_pattern) do |content|
'@eve'
end
end
it { is_expected.to iterate_per_second(iterations) }
end
end
end
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
require 'spec_helper'
describe AbuseReportsController do
let(:reporter) { create(:user) }
let(:user) { create(:user) }
let(:message) { "This user is a spammer" }
before do
sign_in(reporter)
end
describe "POST create" do
context "with admin notification email set" do
let(:admin_email) { "admin@example.com"}
before(:each) do
stub_application_setting(admin_notification_email: admin_email)
end
it "sends a notification email" do
post :create,
abuse_report: {
user_id: user.id,
message: message
}
email = ActionMailer::Base.deliveries.last
expect(email.to).to eq([admin_email])
expect(email.subject).to include(user.username)
expect(email.text_part.body).to include(message)
end
it "saves the abuse report" do
expect do
post :create,
abuse_report: {
user_id: user.id,
message: message
}
end.to change { AbuseReport.count }.by(1)
end
end
context "without admin notification email set" do
before(:each) do
stub_application_setting(admin_notification_email: nil)
end
it "does not send a notification email" do
expect do
post :create,
abuse_report: {
user_id: user.id,
message: message
}
end.not_to change { ActionMailer::Base.deliveries.count }
end
it "saves the abuse report" do
expect do
post :create,
abuse_report: {
user_id: user.id,
message: message
}
end.to change { AbuseReport.count }.by(1)
end
end
end
end
......@@ -37,6 +37,32 @@ describe Admin::UsersController do
end
end
describe 'PUT block/:id' do
let(:user) { create(:user) }
it 'blocks user' do
put :block, id: user.username
user.reload
expect(user.blocked?).to be_truthy
expect(flash[:notice]).to eq 'Successfully blocked'
end
end
describe 'PUT unblock/:id' do
let(:user) { create(:user) }
before do
user.block
end
it 'unblocks user' do
put :unblock, id: user.username
user.reload
expect(user.blocked?).to be_falsey
expect(flash[:notice]).to eq 'Successfully unblocked'
end
end
describe 'PUT unlock/:id' do
let(:user) { create(:user) }
......
......@@ -41,7 +41,7 @@ describe Import::GithubController do
it "assigns variables" do
@project = create(:project, import_type: 'github', creator_id: user.id)
stub_client(repos: [@repo], orgs: [@org], org_repos: [@org_repo])
stub_client(repos: [@repo, @org_repo], orgs: [@org], org_repos: [@org_repo])
get :status
......
require 'spec_helper'
describe InvitesController do
let(:token) { '123456' }
let(:user) { create(:user) }
let(:member) { create(:project_member, invite_token: token, invite_email: 'test@abc.com', user: user) }
before do
controller.instance_variable_set(:@member, member)
sign_in(user)
end
describe 'GET #accept' do
it 'accepts user' do
get :accept, id: token
member.reload
expect(response.status).to eq(302)
expect(member.user).to eq(user)
expect(flash[:notice]).to include 'You have been granted'
end
end
describe 'GET #decline' do
it 'declines user' do
get :decline, id: token
expect{member.reload}.to raise_error ActiveRecord::RecordNotFound
expect(response.status).to eq(302)
expect(flash[:notice]).to include 'You have declined the invitation to join'
end
end
end
......@@ -10,7 +10,11 @@ describe Projects::ServicesController do
project.team << [user, :master]
controller.instance_variable_set(:@project, project)
controller.instance_variable_set(:@service, service)
request.env["HTTP_REFERER"] = "/"
end
shared_examples_for 'services controller' do |referrer|
before do
request.env["HTTP_REFERER"] = referrer
end
describe "#test" do
......@@ -32,4 +36,17 @@ describe Projects::ServicesController do
end
end
end
end
describe 'referrer defined' do
it_should_behave_like 'services controller' do
let!(:referrer) { "/" }
end
end
describe 'referrer undefined' do
it_should_behave_like 'services controller' do
let!(:referrer) { nil }
end
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
......@@ -220,7 +220,7 @@ describe 'GitLab Markdown', feature: true do
end
end
# `markdown` calls these two methods
# Fake a `current_user` helper
def current_user
@feat.user
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
......@@ -11,12 +11,15 @@ describe GitlabMarkdownHelper do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) }
# Helper expects a current_user method.
let(:current_user) { user }
before do
# Ensure the generated reference links aren't redacted
project.team << [user, :master]
# Helper expects a @project instance variable
@project = project
helper.instance_variable_set(:@project, project)
# Stub the `current_user` helper
allow(helper).to receive(:current_user).and_return(user)
end
describe "#markdown" do
......@@ -25,23 +28,23 @@ describe GitlabMarkdownHelper do
it "should link to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
expect(markdown(actual)).to match(expected)
expect(helper.markdown(actual)).to match(expected)
end
it "should link to the commit" do
expected = namespace_project_commit_path(project.namespace, project, commit)
expect(markdown(actual)).to match(expected)
expect(helper.markdown(actual)).to match(expected)
end
it "should link to the issue" do
expected = namespace_project_issue_path(project.namespace, project, issue)
expect(markdown(actual)).to match(expected)
expect(helper.markdown(actual)).to match(expected)
end
end
describe "override default project" do
let(:actual) { issue.to_reference }
let(:second_project) { create(:project) }
let(:second_project) { create(:project, :public) }
let(:second_issue) { create(:issue, project: second_project) }
it 'should link to the issue' do
......@@ -56,7 +59,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) }
it 'should handle references nested in links with all the text' do
actual = link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup
......@@ -86,7 +89,7 @@ describe GitlabMarkdownHelper do
end
it 'should forward HTML options' do
actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v|
......@@ -97,13 +100,13 @@ describe GitlabMarkdownHelper do
it "escapes HTML passed in as the body" do
actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
expect(link_to_gfm(actual, commit_path)).
expect(helper.link_to_gfm(actual, commit_path)).
to match('&lt;h1&gt;test&lt;/h1&gt;')
end
it 'ignores reference links when they are the entire body' do
text = issues[0].to_reference
act = link_to_gfm(text, '/foo')
act = helper.link_to_gfm(text, '/foo')
expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
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
......@@ -140,28 +140,28 @@ describe Gitlab::ClosingIssueExtractor do
message = "Closes #{reference} and fix #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
to match_array([issue, other_issue])
end
it 'fetches comma-separated issues references in single line message' do
message = "Closes #{reference}, closes #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
to match_array([issue, other_issue])
end
it 'fetches comma-separated issues numbers in single line message' do
message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue])
to match_array([issue, other_issue, third_issue])
end
it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
to match_array([issue, other_issue])
end
it 'fetches issues in hybrid message' do
......@@ -169,7 +169,7 @@ describe Gitlab::ClosingIssueExtractor do
"Also fixing issues #{reference2}, #{reference3} and #4"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue])
to match_array([issue, other_issue, third_issue])
end
end
end
......
......@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitRangeReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project) }
let(:project) { create(:project, :public) }
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
......@@ -75,12 +75,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end
it 'includes a data-project-id attribute' do
it 'includes a data-project attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project-id')
expect(link.attr('data-project-id')).to eq project.id.to_s
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-commit-range attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit-range')
expect(link.attr('data-commit-range')).to eq range.to_reference
end
it 'supports an :only_path option' do
......@@ -92,23 +100,20 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("See #{reference}")
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:reference) { range.to_reference(project) }
before do
range.project = project2
end
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
......@@ -132,20 +137,9 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("See #{reference}")
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
......@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project) }
let(:project) { create(:project, :public) }
let(:commit) { project.commit }
it 'requires project context' do
......@@ -71,12 +71,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end
it 'includes a data-project-id attribute' do
it 'includes a data-project attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project-id')
expect(link.attr('data-project-id')).to eq project.id.to_s
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-commit attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit')
expect(link.attr('data-commit')).to eq commit.id
end
it 'supports an :only_path context' do
......@@ -88,20 +96,17 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("See #{reference}")
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit }
let(:reference) { commit.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
......@@ -122,20 +127,9 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("See #{reference}")
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
......@@ -2,20 +2,16 @@ require 'spec_helper'
module Gitlab::Markdown
describe CrossProjectReference do
# context in the html-pipeline sense, not in the rspec sense
let(:context) do
{
current_user: double('user'),
project: double('project')
}
end
include described_class
describe '#project_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
expect(project_from_ref(nil)).to eq context[:project]
project = double
allow(self).to receive(:context).and_return({ project: project })
expect(project_from_ref(nil)).to eq project
end
end
......@@ -26,31 +22,15 @@ module Gitlab::Markdown
end
context 'when referenced project exists' do
let(:project2) { double('referenced project') }
it 'returns the referenced project' do
project2 = double('referenced project')
before do
expect(Project).to receive(:find_with_namespace).
with('cross/reference').and_return(project2)
end
context 'and the user has permission to read it' do
it 'returns the referenced project' do
expect(self).to receive(:user_can_reference_project?).
with(project2).and_return(true)
expect(project_from_ref('cross/reference')).to eq project2
end
end
context 'and the user does not have permission to read it' do
it 'returns nil' do
expect(self).to receive(:user_can_reference_project?).
with(project2).and_return(false)
expect(project_from_ref('cross/reference')).to be_nil
end
end
end
end
end
end
......@@ -8,7 +8,7 @@ module Gitlab::Markdown
IssuesHelper
end
let(:project) { create(:empty_project) }
let(:project) { create(:empty_project, :public) }
let(:issue) { create(:issue, project: project) }
it 'requires project context' do
......@@ -68,12 +68,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end
it 'includes a data-project-id attribute' do
it 'includes a data-project attribute' do
doc = filter("Issue #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project-id')
expect(link.attr('data-project-id')).to eq project.id.to_s
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-issue attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-issue')
expect(link.attr('data-issue')).to eq issue.id.to_s
end
it 'supports an :only_path context' do
......@@ -85,20 +93,17 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Fixed #{reference}")
result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { issue.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(Project).to receive(:get_issue).
with(issue.iid).and_return(nil)
......@@ -126,20 +131,9 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Fixed #{reference}")
result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
......@@ -5,7 +5,7 @@ module Gitlab::Markdown
describe LabelReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:project) { create(:empty_project, :public) }
let(:label) { create(:label, project: project) }
let(:reference) { label.to_reference }
......@@ -25,12 +25,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end
it 'includes a data-project-id attribute' do
it 'includes a data-project attribute' do
doc = filter("Label #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project-id')
expect(link.attr('data-project-id')).to eq project.id.to_s
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-label attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-label')
expect(link.attr('data-label')).to eq label.id.to_s
end
it 'supports an :only_path context' do
......@@ -42,7 +50,7 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Label #{reference}")
result = reference_pipeline_result("Label #{reference}")
expect(result[:references][:label]).to eq [label]
end
......
......@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe MergeRequestReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project) }
let(:project) { create(:project, :public) }
let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do
......@@ -56,12 +56,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
it 'includes a data-project-id attribute' do
it 'includes a data-project attribute' do
doc = filter("Merge #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project-id')
expect(link.attr('data-project-id')).to eq project.id.to_s
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-merge-request attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-merge-request')
expect(link.attr('data-merge-request')).to eq merge.id.to_s
end
it 'supports an :only_path context' do
......@@ -73,20 +81,17 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Merge #{reference}")
result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { merge.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
......@@ -107,20 +112,9 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Merge #{reference}")
result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe RedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
it 'ignores non-GFM links' do
html = %(See <a href="https://google.com/">Google</a>)
doc = filter(html, current_user: double)
expect(doc.css('a').length).to eq 1
end
def reference_link(data)
link_to('text', '', class: 'gfm', data: data)
end
context 'with data-project' do
it 'removes unpermitted Project references' do
user = create(:user)
project = create(:empty_project)
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0
end
it 'allows permitted Project references' do
user = create(:user)
project = create(:empty_project)
project.team << [user, :master]
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
end
it 'handles invalid Project references' do
link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
expect { filter(link) }.not_to raise_error
end
end
context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
group = create(:group)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0
end
it 'allows permitted Group references' do
user = create(:user)
group = create(:group)
group.add_developer(user)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
end
it 'handles invalid Group references' do
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
expect { filter(link) }.not_to raise_error
end
end
context 'with data-user' do
it 'allows any User reference' do
user = create(:user)
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
doc = filter(link)
expect(doc.css('a').length).to eq 1
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe ReferenceGathererFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
def reference_link(data)
link_to('text', '', class: 'gfm', data: data)
end
context "for issue references" do
context 'with data-project' do
it 'removes unpermitted Project references' do
user = create(:user)
project = create(:empty_project)
issue = create(:issue, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:issue]).to be_empty
end
it 'allows permitted Project references' do
user = create(:user)
project = create(:empty_project)
issue = create(:issue, project: project)
project.team << [user, :master]
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:issue]).to eq([issue])
end
it 'handles invalid Project references' do
link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
expect { pipeline_result(link) }.not_to raise_error
end
end
end
context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
group = create(:group)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:user]).to be_empty
end
it 'allows permitted Group references' do
user = create(:user)
group = create(:group)
group.add_developer(user)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:user]).to eq([user])
end
it 'handles invalid Group references' do
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
expect { pipeline_result(link) }.not_to raise_error
end
end
context 'with data-user' do
it 'allows any User reference' do
user = create(:user)
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
result = pipeline_result(link)
expect(result[:references][:user]).to eq([user])
end
end
end
end
end
......@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe SnippetReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:project) { create(:empty_project, :public) }
let(:snippet) { create(:project_snippet, project: project) }
let(:reference) { snippet.to_reference }
......@@ -55,12 +55,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end
it 'includes a data-project-id attribute' do
it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project-id')
expect(link.attr('data-project-id')).to eq project.id.to_s
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-snippet attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-snippet')
expect(link.attr('data-snippet')).to eq snippet.id.to_s
end
it 'supports an :only_path context' do
......@@ -72,20 +80,17 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Snippet #{reference}")
result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { snippet.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
......@@ -105,20 +110,9 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Snippet #{reference}")
result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
......@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe UserReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
let(:reference) { user.to_reference }
......@@ -39,7 +39,7 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
result = pipeline_result("Hey #{reference}")
result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [project.creator]
end
end
......@@ -64,62 +64,43 @@ module Gitlab::Markdown
expect(doc.css('a').length).to eq 1
end
it 'includes a data-user-id attribute' do
it 'includes a data-user attribute' do
doc = filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-user-id')
expect(link.attr('data-user-id')).to eq user.namespace.owner_id.to_s
expect(link).to have_attribute('data-user')
expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end
it 'adds to the results hash' do
result = pipeline_result("Hey #{reference}")
result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [user]
end
end
context 'mentioning a group' do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:reference) { group.to_reference }
context 'that the current user can read' do
before do
group.add_developer(user)
end
it 'links to the Group' do
doc = filter("Hey #{reference}", current_user: user)
doc = filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end
it 'includes a data-group-id attribute' do
doc = filter("Hey #{reference}", current_user: user)
it 'includes a data-group attribute' do
doc = filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-group-id')
expect(link.attr('data-group-id')).to eq group.id.to_s
expect(link).to have_attribute('data-group')
expect(link.attr('data-group')).to eq group.id.to_s
end
it 'adds to the results hash' do
result = pipeline_result("Hey #{reference}", current_user: user)
result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq group.users
end
end
context 'that the current user cannot read' do
it 'ignores references to the Group' do
doc = filter("Hey #{reference}", current_user: user)
expect(doc.to_html).to eq "Hey #{reference}"
end
it 'does not add to the results hash' do
result = pipeline_result("Hey #{reference}", current_user: user)
expect(result[:references][:user]).to eq []
end
end
end
it 'links with adjacent text' do
doc = filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
......
......@@ -13,7 +13,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest]
subject.analyze('@foo, @baduser, @bar, and @offteam')
expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam])
expect(subject.users).to match_array([@u_foo, @u_bar, @u_offteam])
end
it 'ignores user mentions inside specific elements' do
......@@ -37,7 +37,7 @@ describe Gitlab::ReferenceExtractor do
> @offteam
})
expect(subject.users).to eq([])
expect(subject.users).to match_array([])
end
it 'accesses valid issue objects' do
......@@ -45,7 +45,7 @@ describe Gitlab::ReferenceExtractor do
@i1 = create(:issue, project: project)
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
expect(subject.issues).to eq([@i0, @i1])
expect(subject.issues).to match_array([@i0, @i1])
end
it 'accesses valid merge requests' do
......@@ -53,7 +53,7 @@ describe Gitlab::ReferenceExtractor do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
expect(subject.merge_requests).to eq([@m1, @m0])
expect(subject.merge_requests).to match_array([@m1, @m0])
end
it 'accesses valid labels' do
......@@ -62,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
@l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
expect(subject.labels).to eq([@l0, @l1])
expect(subject.labels).to match_array([@l0, @l1])
end
it 'accesses valid snippets' do
......@@ -71,7 +71,7 @@ describe Gitlab::ReferenceExtractor do
@s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}")
expect(subject.snippets).to eq([@s0, @s1])
expect(subject.snippets).to match_array([@s0, @s1])
end
it 'accesses valid commits' do
......@@ -109,7 +109,7 @@ describe Gitlab::ReferenceExtractor do
subject.analyze("this refers issue #{issue.to_reference(project)}")
extracted = subject.issues
expect(extracted.size).to eq(1)
expect(extracted).to eq([issue])
expect(extracted).to match_array([issue])
end
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,8 +606,21 @@ 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(404)
end
context 'when users belong to project group' do
let(:project_fork_target) { create(:project, group: create(:group)) }
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
......@@ -631,6 +644,7 @@ describe API::API, api: true do
end
end
end
end
describe 'GET /projects/search/:query' do
let!(:query) { 'query'}
......
......@@ -6,7 +6,7 @@ describe ArchiveRepositoryService do
describe "#execute" do
it "cleans old archives" do
expect(project.repository).to receive(:clean_old_archives)
expect(RepositoryArchiveCacheWorker).to receive(:perform_async)
subject.execute(timeout: 0.0)
end
......
......@@ -29,12 +29,19 @@ module FilterSpecHelper
#
# Returns the Hash
def pipeline_result(body, contexts = {})
contexts.reverse_merge!(project: project)
contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class], contexts)
pipeline.call(body)
end
def reference_pipeline_result(body, contexts = {})
contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
pipeline.call(body)
end
# Modify a String reference to make it invalid
#
# Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
......@@ -55,20 +62,6 @@ module FilterSpecHelper
end
end
# Stub CrossProjectReference#user_can_reference_project? to return true for
# the current test
def allow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(true)
end
# Stub CrossProjectReference#user_can_reference_project? to return false for
# the current test
def disallow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(false)
end
# Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module
def urls
......
......@@ -15,18 +15,17 @@ class MarkdownFeature
end
def group
unless @group
@group = create(:group)
@group.add_developer(user)
@group ||= create(:group).tap do |group|
group.add_developer(user)
end
@group
end
# Direct references ----------------------------------------------------------
def project
@project ||= create(:project)
@project ||= create(:project).tap do |project|
project.team << [user, :master]
end
end
def issue
......@@ -46,12 +45,10 @@ class MarkdownFeature
end
def commit_range
unless @commit_range
@commit_range ||= begin
commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
@commit_range
end
def simple_label
......@@ -65,13 +62,12 @@ class MarkdownFeature
# Cross-references -----------------------------------------------------------
def xproject
unless @xproject
@xproject ||= begin
namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace)
@xproject.team << [user, :developer]
create(:project, namespace: namespace) do |project|
project.team << [user, :developer]
end
end
@xproject
end
def xissue
......@@ -91,12 +87,10 @@ class MarkdownFeature
end
def xcommit_range
unless @xcommit_range
@xcommit_range ||= begin
xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end
@xcommit_range
end
def raw_markdown
......
......@@ -50,6 +50,8 @@ def common_mentionable_setup
}
extra_commits.each { |c| commitmap[c.short_id] = c }
allow(Project).to receive(:find).and_call_original
allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string)
......
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