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. Please view this file on the master branch, on stable branches it's out of date.
v 8.2.0 (unreleased) 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 - Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL - 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) 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) - 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 - 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) - 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) - Make diff file view easier to use on mobile screens (Stan Hu)
- Improved performance of finding users by username or Email address - Improved performance of finding users by username or Email address
...@@ -69,6 +81,13 @@ v 8.1.0 (unreleased) ...@@ -69,6 +81,13 @@ v 8.1.0 (unreleased)
- Only render 404 page from /public - Only render 404 page from /public
- Hide passwords from services API (Alex Lossent) - Hide passwords from services API (Alex Lossent)
- Fix: Images cannot show when projects' path was changed - 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 v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......
...@@ -65,3 +65,48 @@ ...@@ -65,3 +65,48 @@
line-height: 42px; 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 @@ ...@@ -10,6 +10,10 @@
border-bottom: 1px solid #E7E9EE; border-bottom: 1px solid #E7E9EE;
margin-bottom: 1em; margin-bottom: 1em;
&.readme-holder {
border-bottom: 0;
}
table { table {
@extend .table; @extend .table;
} }
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
font-family: $monospace_font; font-family: $monospace_font;
white-space: pre; white-space: pre;
word-wrap: normal; word-wrap: normal;
padding: 1px 2px;
} }
kbd { kbd {
......
...@@ -132,6 +132,11 @@ form.edit-issue { ...@@ -132,6 +132,11 @@ form.edit-issue {
} }
} }
.issue-closed-by-widget {
padding: 16px 0;
margin: 0px;
}
.issue-form .select2-container { .issue-form .select2-container {
width: 250px !important; width: 250px !important;
} }
...@@ -30,7 +30,6 @@ ul.notes { ...@@ -30,7 +30,6 @@ ul.notes {
.discussion-header, .discussion-header,
.note-header { .note-header {
@extend .cgray; @extend .cgray;
padding-bottom: 15px;
a:hover { a:hover {
text-decoration: none; text-decoration: none;
...@@ -75,6 +74,10 @@ ul.notes { ...@@ -75,6 +74,10 @@ ul.notes {
} }
} }
.discussion-body {
padding-top: 15px;
}
.discussion { .discussion {
overflow: hidden; overflow: hidden;
display: block; display: block;
......
...@@ -47,3 +47,9 @@ ...@@ -47,3 +47,9 @@
} }
} }
} }
.calendar-hint {
margin-top: -12px;
float: right;
font-size: 12px;
}
...@@ -544,5 +544,5 @@ pre.light-well { ...@@ -544,5 +544,5 @@ pre.light-well {
} }
.project-show-readme .readme-holder { .project-show-readme .readme-holder {
padding: 7px; border-top: 0;
} }
...@@ -4,14 +4,6 @@ ...@@ -4,14 +4,6 @@
margin-right: -$gl-padding; margin-right: -$gl-padding;
} }
.tree_progress {
display: none;
margin: 20px;
&.loading {
display: block;
}
}
.tree-table { .tree-table {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController ...@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user @abuse_report.reporter = current_user
if @abuse_report.save 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." message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message redirect_to root_path, notice: message
else else
......
...@@ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_snippet_visibility, :default_snippet_visibility,
:restricted_signup_domains_raw, :restricted_signup_domains_raw,
:version_check_enabled, :version_check_enabled,
:admin_notification_email,
:user_oauth_applications, :user_oauth_applications,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
......
...@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
BroadcastMessage.find(params[:id]).destroy BroadcastMessage.find(params[:id]).destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_back_or_default(default: { action: 'index' }) }
format.js { render nothing: true } format.js { render nothing: true }
end end
end end
......
...@@ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController
} }
@hook.execute(data, 'system_hooks') @hook.execute(data, 'system_hooks')
redirect_to :back redirect_back_or_default
end end
def hook_params def hook_params
......
...@@ -33,33 +33,33 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -33,33 +33,33 @@ class Admin::UsersController < Admin::ApplicationController
def block def block
if user.block if user.block
redirect_to :back, notice: "Successfully blocked" redirect_back_or_admin_user(notice: "Successfully blocked")
else 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
end end
def unblock def unblock
if user.activate if user.activate
redirect_to :back, notice: "Successfully unblocked" redirect_back_or_admin_user(notice: "Successfully unblocked")
else 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
end end
def unlock def unlock
if user.unlock_access! if user.unlock_access!
redirect_to :back, alert: "Successfully unlocked" redirect_back_or_admin_user(alert: "Successfully unlocked")
else 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
end end
def confirm def confirm
if user.confirm if user.confirm
redirect_to :back, notice: "Successfully confirmed" redirect_back_or_admin_user(notice: "Successfully confirmed")
else 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
end end
...@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController
user.update_secondary_emails! user.update_secondary_emails!
respond_to do |format| 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 } format.js { render nothing: true }
end end
end end
...@@ -157,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -157,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController
:projects_limit, :can_create_group, :admin, :key_id :projects_limit, :can_create_group, :admin, :key_id
) )
end 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 end
...@@ -33,6 +33,10 @@ class ApplicationController < ActionController::Base ...@@ -33,6 +33,10 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
protected protected
# From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example # From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
......
module Ci module Ci
class ProjectsController < Ci::ApplicationController class ProjectsController < Ci::ApplicationController
before_action :project before_action :project, except: [:index]
before_action :authenticate_user!, except: [:build, :badge] before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:badge] before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml] before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
protect_from_forgery 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 # Project status badge
# Image with build status for sha or ref # Image with build status for sha or ref
def badge def badge
......
...@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController ...@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController
def status def status
@repos = client.repos @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 = current_user.created_projects.where(import_type: "github")
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
......
...@@ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController ...@@ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file] dump_file = params[:dump_file]
unless dump_file.respond_to?(:read) 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 end
begin begin
dump = JSON.parse(dump_file.read) dump = JSON.parse(dump_file.read)
rescue 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 end
client = Gitlab::GoogleCodeImport::Client.new(dump) client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid? 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 end
session[:google_code_dump] = dump session[:google_code_dump] = dump
......
...@@ -14,7 +14,7 @@ class InvitesController < ApplicationController ...@@ -14,7 +14,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}." redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
else 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
end end
...@@ -31,7 +31,7 @@ class InvitesController < ApplicationController ...@@ -31,7 +31,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have declined the invitation to join #{label}." redirect_to path, notice: "You have declined the invitation to join #{label}."
else 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
end end
......
...@@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController ...@@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
flash[:alert] = "Failed to save new settings" flash[:alert] = "Failed to save new settings"
end end
redirect_to :back redirect_back_or_default(default: profile_notifications_path)
end end
format.js format.js
......
...@@ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_back_or_default(default: { action: 'show' }) }
end end
end end
......
...@@ -30,7 +30,7 @@ class Projects::CiServicesController < Projects::ApplicationController ...@@ -30,7 +30,7 @@ class Projects::CiServicesController < Projects::ApplicationController
message = { alert: 'We tried to test the service but error occurred' } message = { alert: 'We tried to test the service but error occurred' }
end end
redirect_to :back, message redirect_back_or_default(options: message)
end end
private private
......
...@@ -24,7 +24,7 @@ class Projects::CiWebHooksController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::CiWebHooksController < Projects::ApplicationController
def test def test
Ci::TestHookService.new.execute(hook, current_user) Ci::TestHookService.new.execute(hook, current_user)
redirect_to :back redirect_back_or_default(default: { action: 'index' })
end end
def destroy def destroy
......
...@@ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController
execute(@project, head_ref, @project, base_ref) execute(@project, head_ref, @project, base_ref)
if compare_result if compare_result
@commits = compare_result.commits @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs @diffs = compare_result.diffs
@commit = @commits.last @commit = @commits.last
@first_commit = @commits.first
@line_notes = [] @line_notes = []
end end
end end
......
...@@ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def disable def disable
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
redirect_to :back redirect_back_or_default(default: { action: 'index' })
end end
protected protected
......
...@@ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController
flash[:alert] = 'Hook execution failed. Ensure the project has commits.' flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end end
redirect_to :back redirect_back_or_default(default: { action: 'index' })
end end
def destroy def destroy
......
...@@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update # Allow issues bulk update
before_action :authorize_admin_issues!, only: [: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 respond_to :html
def index def index
...@@ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController
def bulk_update def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute 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 end
def toggle_subscription def toggle_subscription
...@@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController
render nothing: true render nothing: true
end end
def closed_by_merge_requests
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
end
protected protected
def issue def issue
......
...@@ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs def diffs
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit
@comments_allowed = @reply_allowed = true @comments_allowed = @reply_allowed = true
@comments_target = { @comments_target = {
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
...@@ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project @target_project = merge_request.target_project
@source_project = merge_request.source_project @source_project = merge_request.source_project
@commits = @merge_request.compare_commits @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 @diffs = @merge_request.compare_diffs
@note_counts = Note.where(commit_id: @commits.map(&:id)). @note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
...@@ -259,7 +262,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -259,7 +262,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits @commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff @merge_request_diff = @merge_request.merge_request_diff
if @merge_request.locked_long_ago? if @merge_request.locked_long_ago?
@merge_request.unlock_mr @merge_request.unlock_mr
@merge_request.close @merge_request.close
......
...@@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def sort_issues def sort_issues
@issues = @milestone.issues.where(id: params['sortable_issue']) @milestone.sort_issues(params['sortable_issue'].map(&:to_i))
@issues.each do |issue|
issue.position = params['sortable_issue'].index(issue.id.to_s) + 1
issue.save
end
render json: { saved: true } render json: { saved: true }
end end
......
...@@ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.json { render_note_json(@note) } format.json { render_note_json(@note) }
format.html { redirect_to :back } format.html { redirect_back_or_default }
end end
end end
...@@ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.json { render_note_json(@note) } format.json { render_note_json(@note) }
format.html { redirect_to :back } format.html { redirect_back_or_default }
end end
end end
......
...@@ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def leave def leave
if @project.namespace == current_user.namespace 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 end
@project.project_members.find_by(user_id: current_user).destroy @project.project_members.find_by(user_id: current_user).destroy
......
...@@ -12,7 +12,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -12,7 +12,7 @@ class Projects::ServicesController < Projects::ApplicationController
# Parameters to ignore if no value is specified # Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password] FILTER_BLANK_PARAMS = [:password]
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test] before_action :service, only: [:edit, :update, :test]
...@@ -52,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -52,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController
message = { alert: error_message } message = { alert: error_message }
end end
redirect_to :back, message redirect_back_or_default(options: message)
end end
private private
......
class ProjectsController < ApplicationController class ProjectsController < ApplicationController
include ExtractsPath
prepend_before_filter :render_go_import, only: [:show] prepend_before_filter :render_go_import, only: [:show]
skip_before_action :authenticate_user!, only: [:show, :activity] skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize # 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] before_action :event_filter, only: [:show, :activity]
layout :determine_layout layout :determine_layout
...@@ -56,6 +59,8 @@ class ProjectsController < ApplicationController ...@@ -56,6 +59,8 @@ class ProjectsController < ApplicationController
end end
def transfer def transfer
return access_denied! unless can?(current_user, :change_namespace, @project)
namespace = Namespace.find_by(id: params[:new_namespace_id]) namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(project, current_user).execute(namespace) ::Projects::TransferService.new(project, current_user).execute(namespace)
...@@ -64,6 +69,15 @@ class ProjectsController < ApplicationController ...@@ -64,6 +69,15 @@ class ProjectsController < ApplicationController
end end
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 def activity
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController ...@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController
render 'projects/empty' render 'projects/empty'
else else
if current_user if current_user
@membership = @project.project_member_by_id(current_user.id) @membership = @project.team.find_member(current_user.id)
end end
render :show render :show
...@@ -139,6 +153,7 @@ class ProjectsController < ApplicationController ...@@ -139,6 +153,7 @@ class ProjectsController < ApplicationController
def archive def archive
return access_denied! unless can?(current_user, :archive_project, @project) return access_denied! unless can?(current_user, :archive_project, @project)
@project.archive! @project.archive!
respond_to do |format| respond_to do |format|
...@@ -148,6 +163,7 @@ class ProjectsController < ApplicationController ...@@ -148,6 +163,7 @@ class ProjectsController < ApplicationController
def unarchive def unarchive
return access_denied! unless can?(current_user, :archive_project, @project) return access_denied! unless can?(current_user, :archive_project, @project)
@project.unarchive! @project.unarchive!
respond_to do |format| respond_to do |format|
...@@ -225,4 +241,14 @@ class ProjectsController < ApplicationController ...@@ -225,4 +241,14 @@ class ProjectsController < ApplicationController
render "go_import", layout: false render "go_import", layout: false
end 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 end
...@@ -53,15 +53,36 @@ class IssuableFinder ...@@ -53,15 +53,36 @@ class IssuableFinder
end end
end end
def project?
params[:project_id].present?
end
def project def project
return @project if defined?(@project) return @project if defined?(@project)
@project = if project?
if params[:project_id].present? @project = Project.find(params[:project_id])
Project.find(params[:project_id])
else unless Ability.abilities.allowed?(current_user, :read_project, @project)
nil @project = nil
end end
else
@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 end
def search def search
...@@ -72,7 +93,7 @@ class IssuableFinder ...@@ -72,7 +93,7 @@ class IssuableFinder
params[:milestone_title].present? params[:milestone_title].present?
end end
def no_milestones? def filter_by_no_milestone?
milestones? && params[:milestone_title] == Milestone::None.title milestones? && params[:milestone_title] == Milestone::None.title
end end
...@@ -81,12 +102,22 @@ class IssuableFinder ...@@ -81,12 +102,22 @@ class IssuableFinder
@milestones = @milestones =
if milestones? if milestones?
Milestone.where(title: params[:milestone_title]) scope = Milestone.where(project_id: projects)
scope.where(title: params[:milestone_title])
else else
nil nil
end end
end end
def labels?
params[:label_name].present?
end
def filter_by_no_label?
labels? && params[:label_name] == Label::None.title
end
def assignee? def assignee?
params[:assignee_id].present? params[:assignee_id].present?
end end
...@@ -120,19 +151,7 @@ class IssuableFinder ...@@ -120,19 +151,7 @@ class IssuableFinder
private private
def init_collection def init_collection
table_name = klass.table_name klass.all
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
end end
def by_scope(items) def by_scope(items)
...@@ -170,7 +189,12 @@ class IssuableFinder ...@@ -170,7 +189,12 @@ class IssuableFinder
end end
def by_project(items) 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 items
end end
...@@ -185,18 +209,6 @@ class IssuableFinder ...@@ -185,18 +209,6 @@ class IssuableFinder
items.sort(params[:sort]) items.sort(params[:sort])
end 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) def by_assignee(items)
if assignee? if assignee?
items = items.where(assignee_id: assignee.try(:id)) items = items.where(assignee_id: assignee.try(:id))
...@@ -213,20 +225,36 @@ class IssuableFinder ...@@ -213,20 +225,36 @@ class IssuableFinder
items items
end end
def by_label(items) def by_milestone(items)
if params[:label_name].present? if milestones?
if params[:label_name] == Label::None.title if filter_by_no_milestone?
item_ids = LabelLink.where(target_type: klass.name).pluck(:target_id) 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 else
label_names = params[:label_name].split(",") label_names = params[:label_name].split(",")
item_ids = LabelLink.joins(:label). items = items.joins(:labels).where(labels: { title: label_names })
where('labels.title in (?)', label_names).
where(target_type: klass.name).pluck(:target_id)
items = items.where(id: item_ids) if projects
items = items.where(labels: { project_id: projects })
end
end end
end end
......
...@@ -170,7 +170,8 @@ module DiffHelper ...@@ -170,7 +170,8 @@ module DiffHelper
def commit_for_diff(diff) def commit_for_diff(diff)
if diff.deleted_file if diff.deleted_file
@merge_request ? @merge_request.commits.last : @commit.parents.first first_commit = @first_commit || @commit
first_commit.parent
else else
@commit @commit
end end
......
...@@ -19,7 +19,8 @@ module GitlabMarkdownHelper ...@@ -19,7 +19,8 @@ module GitlabMarkdownHelper
escape_once(body) escape_once(body)
end 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) fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a' if fragment.children.size == 1 && fragment.children[0].name == 'a'
...@@ -45,29 +46,39 @@ module GitlabMarkdownHelper ...@@ -45,29 +46,39 @@ module GitlabMarkdownHelper
end end
def markdown(text, context = {}) def markdown(text, context = {})
return "" unless text.present?
context.reverse_merge!( context.reverse_merge!(
current_user: current_user,
path: @path, path: @path,
pipeline: :default,
project: @project, project: @project,
project_wiki: @project_wiki, project_wiki: @project_wiki,
ref: @ref 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 end
# TODO (rspeicher): Remove all usages of this helper and just call `markdown` # TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered # with a custom pipeline depending on the content being rendered
def gfm(text, options = {}) def gfm(text, options = {})
return "" unless text.present?
options.reverse_merge!( options.reverse_merge!(
current_user: current_user,
path: @path, path: @path,
pipeline: :default,
project: @project, project: @project,
project_wiki: @project_wiki, project_wiki: @project_wiki,
ref: @ref 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 end
def asciidoc(text) def asciidoc(text)
......
...@@ -83,6 +83,10 @@ module IssuesHelper ...@@ -83,6 +83,10 @@ module IssuesHelper
end end
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 # Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue module_function :url_for_issue
end end
...@@ -34,7 +34,8 @@ module PreferencesHelper ...@@ -34,7 +34,8 @@ module PreferencesHelper
def project_view_choices def project_view_choices
[ [
['Readme (default)', :readme], ['Readme (default)', :readme],
['Activity view', :activity] ['Activity view', :activity],
['Files view', :files]
] ]
end end
...@@ -46,8 +47,7 @@ module PreferencesHelper ...@@ -46,8 +47,7 @@ module PreferencesHelper
Gitlab::ColorSchemes.for_user(current_user).css_class Gitlab::ColorSchemes.for_user(current_user).css_class
end end
def prefer_readme? def default_project_view
!current_user || current_user ? current_user.project_view : 'readme'
current_user.project_view == 'readme'
end end
end end
...@@ -70,6 +70,10 @@ module ProjectsHelper ...@@ -70,6 +70,10 @@ module ProjectsHelper
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?" "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end 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 def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user) @nav_tabs ||= get_project_nav_tabs(@project, current_user)
end end
...@@ -113,7 +117,7 @@ module ProjectsHelper ...@@ -113,7 +117,7 @@ module ProjectsHelper
nav_tabs << :merge_requests nav_tabs << :merge_requests
end end
if can?(current_user, :read_build, project) if project.gitlab_ci? && can?(current_user, :read_build, project)
nav_tabs << :builds nav_tabs << :builds
end 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 ...@@ -189,7 +189,8 @@ class Ability
:change_visibility_level, :change_visibility_level,
:rename_project, :rename_project,
:remove_project, :remove_project,
:archive_project :archive_project,
:remove_fork_project
] ]
end end
......
...@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true, allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } 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| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
......
...@@ -2,13 +2,13 @@ class Commit ...@@ -2,13 +2,13 @@ class Commit
extend ActiveModel::Naming extend ActiveModel::Naming
include ActiveModel::Conversion include ActiveModel::Conversion
include Mentionable
include Participable include Participable
include Mentionable
include Referable include Referable
include StaticModel include StaticModel
attr_mentionable :safe_message attr_mentionable :safe_message
participant :author, :committer, :notes, :mentioned_users participant :author, :committer, :notes
attr_accessor :project attr_accessor :project
...@@ -164,6 +164,14 @@ class Commit ...@@ -164,6 +164,14 @@ class Commit
@committer ||= User.find_by_any_email(committer_email) @committer ||= User.find_by_any_email(committer_email)
end 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 def notes
project.notes.for_commit_id(self.id) project.notes.for_commit_id(self.id)
end end
...@@ -181,10 +189,6 @@ class Commit ...@@ -181,10 +189,6 @@ class Commit
@raw.short_id(7) @raw.short_id(7)
end end
def parents
@parents ||= Commit.decorate(super, project)
end
def ci_commit def ci_commit
project.ci_commit(sha) project.ci_commit(sha)
end end
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
# #
module Issuable module Issuable
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Mentionable
include Participable include Participable
include Mentionable
included do included do
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
...@@ -47,8 +47,7 @@ module Issuable ...@@ -47,8 +47,7 @@ module Issuable
prefix: true prefix: true
attr_mentionable :title, :description attr_mentionable :title, :description
participant :author, :assignee, :notes_with_associations
participant :author, :assignee, :notes_with_associations, :mentioned_users
end end
module ClassMethods module ClassMethods
...@@ -86,6 +85,10 @@ module Issuable ...@@ -86,6 +85,10 @@ module Issuable
assignee_id_changed? assignee_id_changed?
end end
def open?
opened? || reopened?
end
# #
# Votes # Votes
# #
......
...@@ -20,6 +20,12 @@ module Mentionable ...@@ -20,6 +20,12 @@ module Mentionable
end end
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 # 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 # By default this will be the class name and the result of calling
...@@ -41,22 +47,22 @@ module Mentionable ...@@ -41,22 +47,22 @@ module Mentionable
self self
end end
def all_references(current_user = self.author, text = self.mentionable_text) def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user) ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
ext.analyze(text) ext.analyze(text)
ext ext
end end
def mentioned_users(current_user = nil) def mentioned_users(current_user = nil, load_lazy_references: true)
all_references(current_user).users.uniq all_references(current_user, load_lazy_references: load_lazy_references).users
end end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. # 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? return [] if text.blank?
refs = all_references(current_user, text) refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference] (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
# #
# # ... # # ...
# #
# participant :author, :assignee, :mentioned_users, :notes # participant :author, :assignee, :notes, ->(current_user) { mentioned_users(current_user) }
# end # end
# #
# issue = Issue.last # issue = Issue.last
...@@ -27,7 +27,7 @@ module Participable ...@@ -27,7 +27,7 @@ module Participable
module ClassMethods module ClassMethods
def participant(*attrs) def participant(*attrs)
participant_attrs.concat(attrs.map(&:to_s)) participant_attrs.concat(attrs)
end end
def participant_attrs def participant_attrs
...@@ -37,33 +37,39 @@ module Participable ...@@ -37,33 +37,39 @@ module Participable
# Be aware that this method makes a lot of sql queries. # 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 # Save result into variable if you are going to reuse it inside same request
def participants(current_user = self.author) def participants(current_user = self.author, load_lazy_references: true)
self.class.participant_attrs.flat_map do |attr| participants = self.class.participant_attrs.flat_map do |attr|
meth = method(attr)
value = value =
if meth.arity == 1 || meth.arity == -1 if attr.respond_to?(:call)
meth.call(current_user) instance_exec(current_user, &attr)
else else
meth.call send(attr)
end end
participants_for(value, current_user) participants_for(value, current_user)
end.compact.uniq.select do |user| end.compact.uniq
user.can?(:read_project, self.project)
if load_lazy_references
participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
participants.select! do |user|
user.can?(:read_project, project)
end
end end
participants
end end
private private
def participants_for(value, current_user = nil) def participants_for(value, current_user = nil)
case value case value
when User when User, Gitlab::Markdown::ReferenceFilter::LazyReference
[value] [value]
when Enumerable, ActiveRecord::Relation when Enumerable, ActiveRecord::Relation
value.flat_map { |v| participants_for(v, current_user) } value.flat_map { |v| participants_for(v, current_user) }
when Participable when Participable
value.participants(current_user) value.participants(current_user, load_lazy_references: false)
end end
end end
end end
...@@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base ...@@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base
def source_project def source_project
project project
end 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 end
...@@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff after_create :create_merge_request_diff
after_update :update_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 # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base ...@@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base
reference reference
end 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 def validate_branches
if target_project == source_project && target_branch == source_branch if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target" errors.add :branch_conflict, "You can not use same project/branch for source and target"
...@@ -222,10 +234,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -222,10 +234,6 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end end
def open?
opened? || reopened?
end
def work_in_progress? def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i) !!(title =~ /\A\[?WIP\]?:? /i)
end end
...@@ -294,6 +302,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -294,6 +302,10 @@ class MergeRequest < ActiveRecord::Base
target_project target_project
end 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. # Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author) def closes_issues(current_user = self.author)
if target_branch == project.default_branch if target_branch == project.default_branch
......
...@@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base
commits.first commits.first
end end
def first_commit
commits.last
end
def last_commit_short_sha def last_commit_short_sha
@last_commit_short_sha ||= last_commit.short_id @last_commit_short_sha ||= last_commit.short_id
end end
......
...@@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base ...@@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base
def author_id def author_id
nil nil
end 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 end
...@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord' ...@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator' require 'file_size_validator'
class Note < ActiveRecord::Base class Note < ActiveRecord::Base
include Mentionable
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Participable include Participable
include Mentionable
default_value_for :system, false default_value_for :system, false
attr_mentionable :note attr_mentionable :note
participant :author, :mentioned_users participant :author
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true
......
...@@ -8,6 +8,14 @@ class Repository ...@@ -8,6 +8,14 @@ class Repository
attr_accessor :raw_repository, :path_with_namespace, :project 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) def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace @path_with_namespace = path_with_namespace
@project = project @project = project
...@@ -269,14 +277,6 @@ class Repository ...@@ -269,14 +277,6 @@ class Repository
end end
# Remove archives older than 2 hours # 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) def branches_sorted_by(value)
case value case value
when 'recently_updated' when 'recently_updated'
...@@ -312,13 +312,7 @@ class Repository ...@@ -312,13 +312,7 @@ class Repository
end end
def blob_for_diff(commit, diff) def blob_for_diff(commit, diff)
file = blob_at(commit.id, diff.new_path) blob_at(commit.id, diff.file_path)
unless file
file = prev_blob_for_diff(commit, diff)
end
file
end end
def prev_blob_for_diff(commit, diff) def prev_blob_for_diff(commit, diff)
......
...@@ -183,7 +183,7 @@ class User < ActiveRecord::Base ...@@ -183,7 +183,7 @@ class User < ActiveRecord::Base
# User's Project preference # User's Project preference
# Note: When adding an option, it MUST go on the end of the array. # 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 alias_attribute :private_token, :authentication_token
...@@ -706,12 +706,15 @@ class User < ActiveRecord::Base ...@@ -706,12 +706,15 @@ class User < ActiveRecord::Base
end end
def toggle_star(project) def toggle_star(project)
user_star_project = users_star_projects. UsersStarProject.transaction do
where(project: project, user: self).take user_star_project = users_star_projects.
if user_star_project where(project: project, user: self).lock(true).first
user_star_project.destroy
else if user_star_project
UsersStarProject.create!(project: project, user: self) user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end end
end end
......
...@@ -7,7 +7,7 @@ class ArchiveRepositoryService ...@@ -7,7 +7,7 @@ class ArchiveRepositoryService
end end
def execute(options = {}) def execute(options = {})
project.repository.clean_old_archives RepositoryArchiveCacheWorker.perform_async
metadata = project.repository.archive_metadata(ref, storage_path, format) metadata = project.repository.archive_metadata(ref, storage_path, format)
raise "Repository or ref not found" if metadata.empty? raise "Repository or ref not found" if metadata.empty?
......
...@@ -79,7 +79,7 @@ class GitPushService ...@@ -79,7 +79,7 @@ class GitPushService
authors = Hash.new do |hash, commit| authors = Hash.new do |hash, commit|
email = commit.author_email 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) hash[email] = commit_user(commit)
end end
......
...@@ -6,6 +6,7 @@ module MergeRequests ...@@ -6,6 +6,7 @@ module MergeRequests
# #
class PostMergeService < MergeRequests::BaseService class PostMergeService < MergeRequests::BaseService
def execute(merge_request) def execute(merge_request)
close_issues(merge_request)
merge_request.mark_as_merged merge_request.mark_as_merged
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
...@@ -15,6 +16,15 @@ module MergeRequests ...@@ -15,6 +16,15 @@ module MergeRequests
private 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) def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user) EventCreateService.new.merge_mr(merge_request, current_user)
end 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 @@ ...@@ -47,6 +47,12 @@
= f.label :version_check_enabled do = f.label :version_check_enabled do
= f.check_box :version_check_enabled = f.check_box :version_check_enabled
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 %fieldset
%legend Account and Limit Settings %legend Account and Limit Settings
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
%strong %strong
= link_to user_path(@user) do = link_to user_path(@user) do
= @user.username = @user.username
= render 'users/profile', user: @user = render 'admin/users/profile', user: @user
.panel.panel-default .panel.panel-default
.panel-heading .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"} %div{xmlns: "http://www.w3.org/1999/xhtml"}
- if issue.description.present? = markdown(issue.description, pipeline: :atom, project: issue.project)
= markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"} %div{xmlns: "http://www.w3.org/1999/xhtml"}
- if merge_request.description.present? = markdown(merge_request.description, pipeline: :atom, project: merge_request.project)
= markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"} %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 @@ ...@@ -6,7 +6,7 @@
%i %i
at at
= commit[:timestamp].to_time.to_s(:short) = 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 - if event.commits_count > 15
%p %p
%i %i
......
...@@ -41,4 +41,8 @@ ...@@ -41,4 +41,8 @@
#{link_to "view it on GitLab", @target_url}. #{link_to "view it on GitLab", @target_url}.
- else - else
#{link_to "View it on GitLab", @target_url} #{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 = email_action @target_url
%div %div
= markdown(@note.note, reference_only_path: false) = markdown(@note.note, pipeline: :email)
-if @issue.description -if @issue.description
= markdown(@issue.description, reference_only_path: false) = markdown(@issue.description, pipeline: :email)
- if @issue.assignee_id.present? - if @issue.assignee_id.present?
%p %p
......
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-if @merge_request.description -if @merge_request.description
= markdown(@merge_request.description, reference_only_path: false) = markdown(@merge_request.description, pipeline: :email)
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
.col-sm-10 .col-sm-10
= f.select :layout, layout_choices, {}, class: 'form-control' = f.select :layout, layout_choices, {}, class: 'form-control'
.help-block .help-block
Choose between fixed (max. 1200px) and fluid (100%) application layout Choose between fixed (max. 1200px) and fluid (100%) application layout.
.form-group .form-group
= f.label :dashboard, class: 'control-label' do = f.label :dashboard, class: 'control-label' do
Default Dashboard Default Dashboard
...@@ -52,6 +52,6 @@ ...@@ -52,6 +52,6 @@
.col-sm-10 .col-sm-10
= f.select :project_view, project_view_choices, {}, class: 'form-control' = f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block .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 .panel-footer
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
= render 'projects/last_push'
.gray-content-block.activity-filter-block .gray-content-block.activity-filter-block
- if current_user - if current_user
.pull-right .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 - if readme = @repository.readme
%article.readme-holder#README %article.file-holder.readme-holder
.clearfix .file-title
.pull-right = blob_icon readme.mode, readme.name
&nbsp; = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
- if can?(current_user, :push_code, @project) %strong
= link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do = readme.name
%i.fa-align.fa.fa-pencil .file-content.wiki
.wiki
= cache(readme_cache_key) do = cache(readme_cache_key) do
= render_readme(readme) = render_readme(readme)
- else - else
......
- page_title "Activity" - page_title "Activity"
- header_title project_title(@project, "Activity", activity_project_path(@project)) - header_title project_title(@project, "Activity", activity_project_path(@project))
= render 'projects/last_push'
= render 'projects/activity' = render 'projects/activity'
%ul.breadcrumb.repo-breadcrumb .gray-content-block.top-block
%li .tree-ref-holder
%i.fa.fa-angle-right = render 'shared/ref_switcher', destination: 'blob', path: @path
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path %ul.breadcrumb.repo-breadcrumb
- tree_breadcrumbs(@tree, 6) do |title, path|
%li %li
- if path = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
- if path.end_with?(@path) = @project.path
= link_to namespace_project_blob_path(@project.namespace, @project, path) do - tree_breadcrumbs(@tree, 6) do |title, path|
%strong %li
= truncate(title, length: 40) - if path
- if path.end_with?(@path)
= link_to namespace_project_blob_path(@project.namespace, @project, path) do
%strong
= truncate(title, length: 40)
- else
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else - else
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path) = link_to title, '#'
- else
= link_to title, '#'
%ul.blob-commit-info.hidden-xs %ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path) - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
......
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
= render 'projects/last_push' = render 'projects/last_push'
%div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
%div#tree-holder.tree-holder %div#tree-holder.tree-holder
= render 'blob', blob: @blob = render 'blob', blob: @blob
......
- return unless @membership - 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
%span.dropdown
%a.dropdown-new.btn.btn-new#notifications-button{href: '#', "data-toggle" => "dropdown"}
= icon('bell')
= notification_label(@membership)
= icon('angle-down')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- Notification.project_notification_levels.each do |level|
= notification_list_item(level, @membership)
= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do - when GroupMember
= hidden_field_tag :notification_type, 'project' .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."}
= hidden_field_tag :notification_id, @membership.id = icon('bell')
= hidden_field_tag :notification_level = notification_label(@membership)
%span.dropdown = icon('angle-down')
%a.dropdown-new.btn.btn-new#notifications-button{href: '#', "data-toggle" => "dropdown"}
= icon('bell')
= notification_label(@membership)
= icon('angle-down')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- Notification.project_notification_levels.each do |level|
= notification_list_item(level, @membership)
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%tr %tr
%th %th
%th Service %th Service
%th Desription %th Description
%th Last edit %th Last edit
- @services.sort_by(&:title).each do |service| - @services.sort_by(&:title).each do |service|
%tr %tr
......
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
.files .files
- diff_files.each_with_index do |diff_file, index| - diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file.diff) - diff_commit = commit_for_diff(diff_file)
- blob = project.repository.blob_for_diff(diff_commit, diff_file.diff) - blob = project.repository.blob_for_diff(diff_commit, diff_file)
- next unless blob - next unless blob
= render 'projects/diffs/file', i: index, project: project, = render 'projects/diffs/file', i: index, project: project,
......
.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)} .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? - if diff_file.diff.submodule?
%span %span
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
- else - else
= render "projects/diffs/text_file", diff_file: diff_file, index: i = render "projects/diffs/text_file", diff_file: diff_file, index: i
- elsif blob.image? - 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 = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
- else - else
.nothing-here-block No preview for this file type .nothing-here-block No preview for this file type
......
...@@ -189,6 +189,21 @@ ...@@ -189,6 +189,21 @@
- else - else
.nothing-here-block Only the project owner can transfer a project .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) - if can?(current_user, :remove_project, @project)
.panel.panel-default.panel.panel-danger .panel.panel-default.panel.panel-danger
.panel-heading Remove project .panel-heading Remove project
...@@ -201,7 +216,8 @@ ...@@ -201,7 +216,8 @@
= button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) } = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else - 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 .save-project-loader.hide
.center .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 @@ ...@@ -46,6 +46,7 @@
= markdown(@issue.description) = markdown(@issue.description)
%textarea.hidden.js-task-list-field %textarea.hidden.js-task-list-field
= @issue.description = @issue.description
- if @closed_by_merge_requests.present?
= render 'projects/issues/closed_by_box'
.issue-discussion .issue-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
- if @merge_request.has_ci? - ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
- ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha) - if ci_commit
- if ci_commit - status = ci_commit.status
- status = ci_commit.status .mr-widget-heading
.mr-widget-heading .ci_widget{class: "ci-#{status}"}
.ci_widget{class: "ci-#{status}"} = ci_status_icon(ci_commit)
= ci_status_icon(ci_commit) %span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
%span.ci-coverage
= link_to "View build details", ci_status_path(ci_commit)
- 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
- [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
- if status == :success
- status = "passed"
= icon("check-circle")
- else
= icon("circle")
%span CI build #{status} %span CI build #{status}
for #{@merge_request.last_commit_short_sha}. for #{@merge_request.last_commit_short_sha}.
%span.ci-coverage %span.ci-coverage
= link_to "View build details", ci_status_path(ci_commit) - if ci_build_details_path(@merge_request)
= link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
- else
- # 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
- [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
- if status == :success
- status = "passed"
= icon("check-circle")
- else
= icon("circle")
%span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
%span.ci-coverage
- if ci_build_details_path(@merge_request)
= link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget .ci_widget
= icon("spinner spin") = icon("spinner spin")
Checking CI status for #{@merge_request.last_commit_short_sha}&hellip; Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
.ci_widget.ci-not_found{style: "display:none"} .ci_widget.ci-not_found{style: "display:none"}
= icon("times-circle") = icon("times-circle")
Could not find CI status for #{@merge_request.last_commit_short_sha}. Could not find CI status for #{@merge_request.last_commit_short_sha}.
.ci_widget.ci-error{style: "display:none"} .ci_widget.ci-error{style: "display:none"}
= icon("times-circle") = icon("times-circle")
Could not connect to the CI server. Please check your settings and try again. Could not connect to the CI server. Please check your settings and try again.
:coffeescript :coffeescript
$ -> $ ->
merge_request_widget.getCiStatus() merge_request_widget.getCiStatus()
:plain
location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
...@@ -7,8 +7,7 @@ ...@@ -7,8 +7,7 @@
= render 'shared/no_ssh' = render 'shared/no_ssh'
= render 'shared/no_password' = render 'shared/no_password'
- if prefer_readme? = render 'projects/last_push'
= render 'projects/last_push'
= render "home_panel" = render "home_panel"
...@@ -28,7 +27,7 @@ ...@@ -28,7 +27,7 @@
= link_to project_files_path(@project) do = link_to project_files_path(@project) do
= repository_size = repository_size
- if !prefer_readme? && @repository.readme - if default_project_view != 'readme' && @repository.readme
%li %li
= link_to 'Readme', readme_path(@project) = link_to 'Readme', readme_path(@project)
...@@ -68,14 +67,8 @@ ...@@ -68,14 +67,8 @@
.content-block.second-block.white .content-block.second-block.white
= render 'projects/last_commit', commit: @repository.commit, project: @project = render 'projects/last_commit', commit: @repository.commit, project: @project
%section %div{class: "project-show-#{default_project_view}"}
- if prefer_readme? = render default_project_view
.project-show-readme
= render 'projects/readme'
- else
.project-show-activity
= render 'projects/activity'
- if current_user - if current_user
- access = user_max_access_in_project(current_user, @project) - access = user_max_access_in_project(current_user, @project)
......
...@@ -4,5 +4,5 @@ ...@@ -4,5 +4,5 @@
%span.str-truncated %span.str-truncated
= link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)) = 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 %td.tree_time_ago.cgray
= render 'spinner' = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit %td.hidden-xs.tree_commit
%article.file-holder.readme-holder#README %article.file-holder.readme-holder
.file-title .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 %strong
%i.fa.fa-file
= readme.name = readme.name
.file-content.wiki .file-content.wiki
= render_readme(readme) = render_readme(readme)
.gray-content-block %div.tree-content-holder
%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
.tree-table-holder .tree-table-holder
%table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" } %table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
%thead %thead
...@@ -60,8 +29,6 @@ ...@@ -60,8 +29,6 @@
- if tree.readme - if tree.readme
= render "projects/tree/readme", readme: tree.readme = render "projects/tree/readme", readme: tree.readme
%div.tree_progress
- if allowed_tree_edit? - 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/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' = 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 @@ ...@@ -5,5 +5,5 @@
- path = flatten_tree(tree_item) - path = flatten_tree(tree_item)
= link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)) = link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
%td.tree_time_ago.cgray %td.tree_time_ago.cgray
= render 'spinner' = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit %td.hidden-xs.tree_commit
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
= render 'projects/last_push' = render 'projects/last_push'
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
- if can? current_user, :download_code, @project - if can? current_user, :download_code, @project
.tree-download-holder .tree-download-holder
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true = 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 #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 @@ ...@@ -6,7 +6,7 @@
type: 'button', | type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", | 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-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-html" => "true",
:"data-container" => "body"} :"data-container" => "body"}
SSH SSH
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
type: 'button', | type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", | 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-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-html" => "true",
:"data-container" => "body"} :"data-container" => "body"}
= gitlab_config.protocol.upcase = gitlab_config.protocol.upcase
......
%h4
Contributions calendar
.pull-right
%small Issues, merge requests and push events
#cal-heatmap.calendar #cal-heatmap.calendar
:javascript :javascript
new Calendar( new Calendar(
...@@ -10,3 +6,5 @@ ...@@ -10,3 +6,5 @@
#{@starting_month}, #{@starting_month},
'#{user_calendar_activities_path}' '#{user_calendar_activities_path}'
); );
.calendar-hint Summary of issues, merge requests and push events
...@@ -6,47 +6,72 @@ ...@@ -6,47 +6,72 @@
= render 'shared/show_aside' = render 'shared/show_aside'
.row .cover-block
%section.col-md-7 .avatar-holder
.header-with-avatar = link_to avatar_icon(@user, 400), target: '_blank' do
= link_to avatar_icon(@user, 400), target: '_blank' do = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
= image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: '' .cover-title
%h3 = @user.name
= @user.name
- if @user == current_user .cover-desc
.pull-right.hidden-xs %span
= link_to profile_path, class: 'btn btn-sm' do @#{@user.username}.
= icon('user') - if @user.bio.present?
Profile settings %span
- elsif current_user #{@user.bio}.
.report_abuse.pull-right %span
- if @user.abuse_report Member since #{@user.created_at.stamp("Aug 21, 2011")}
%span#report_abuse_btn.light.btn.btn-sm.btn-close{title: 'Already reported for abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
= icon('exclamation-circle') .cover-desc
- else - unless @user.public_email.blank?
%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 @user.public_email, "mailto:#{@user.public_email}"
= icon('exclamation-circle') - 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
.username
@#{@user.username}
.description
- if @user.bio.present?
= @user.bio
.clearfix .cover-controls
- if @user == current_user
= link_to profile_path, class: 'btn btn-gray' do
= icon('pencil')
- elsif current_user
.report-abuse
- if @user.abuse_report
%button.btn.btn-danger{ title: 'Already reported for abuse',
data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
= icon('exclamation-circle')
- else
= link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
= icon('exclamation-circle')
.gray-content-block.second-block
.user-calendar
%h4.center.light
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
.row.prepend-top-20
%section.col-md-7
- if @groups.any? - if @groups.any?
.prepend-top-20 .prepend-top-20
%h4 Groups %h4 Groups
= render 'groups', groups: @groups = render 'groups', groups: @groups
%hr %hr
.hidden-xs
.user-calendar
%h4.center.light
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
%hr
%h4 %h4
User Activity User Activity
...@@ -59,7 +84,6 @@ ...@@ -59,7 +84,6 @@
.content_list .content_list
= spinner = spinner
%aside.col-md-5 %aside.col-md-5
= render 'profile', user: @user
= render 'projects', projects: @projects, contributed_projects: @contributed_projects = render 'projects', projects: @projects, contributed_projects: @contributed_projects
:coffeescript :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 ...@@ -378,6 +378,7 @@ Gitlab::Application.routes.draw do
[:new, :create, :index], path: "/") do [:new, :create, :index], path: "/") do
member do member do
put :transfer put :transfer
delete :remove_fork
post :archive post :archive
post :unarchive post :unarchive
post :toggle_star post :toggle_star
......
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
(2..20).each do |i| 20.times do |i|
begin begin
User.create!( User.create!(
username: FFaker::Internet.user_name, username: FFaker::Internet.user_name,
...@@ -15,7 +15,7 @@ Gitlab::Seeder.quiet do ...@@ -15,7 +15,7 @@ Gitlab::Seeder.quiet do
end end
end end
(1..5).each do |i| 5.times do |i|
begin begin
User.create!( User.create!(
username: "user#{i}", username: "user#{i}",
......
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
Project.all.each do |project| Project.all.each do |project|
(1..5).each do |i| 5.times do |i|
milestone_params = { milestone_params = {
title: "v#{i}.0", title: "v#{i}.0",
description: FFaker::Lorem.sentence, description: FFaker::Lorem.sentence,
......
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
Project.all.each do |project| Project.all.each do |project|
(1..10).each do |i| 10.times do
issue_params = { issue_params = {
title: FFaker::Lorem.sentence(6), title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence, description: FFaker::Lorem.sentence,
......
...@@ -22,7 +22,7 @@ class Member < ActiveRecord::Base ...@@ -22,7 +22,7 @@ class Member < ActiveRecord::Base
end end
eos eos
(1..50).each do |i| 50.times do |i|
user = User.all.sample user = User.all.sample
PersonalSnippet.seed(:id, [{ 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 ...@@ -6,9 +6,5 @@ class LimitsToMysql < ActiveRecord::Migration
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647 change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
change_column :snippets, :content, :text, limit: 2147483647 change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :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
end end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151016195706) do ActiveRecord::Schema.define(version: 20151020173906) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -46,6 +46,7 @@ ActiveRecord::Schema.define(version: 20151016195706) do ...@@ -46,6 +46,7 @@ ActiveRecord::Schema.define(version: 20151016195706) do
t.integer "session_expire_delay", default: 10080, null: false t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources" t.text "import_sources"
t.text "help_page_text" t.text "help_page_text"
t.string "admin_notification_email"
end end
create_table "audit_events", force: true do |t| create_table "audit_events", force: true do |t|
...@@ -109,6 +110,7 @@ ActiveRecord::Schema.define(version: 20151016195706) do ...@@ -109,6 +110,7 @@ ActiveRecord::Schema.define(version: 20151016195706) do
end 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", "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", "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", "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 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 ...@@ -70,7 +70,16 @@ sudo -u git -H git fetch
sudo -u git -H git checkout v2.6.5 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 ```bash
cd /home/git/gitlab cd /home/git/gitlab
...@@ -91,7 +100,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ...@@ -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 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` #### New configuration options for `gitlab.yml`
...@@ -101,12 +110,33 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y ...@@ -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 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 gitlab start
sudo service nginx restart sudo service nginx restart
### 8. Check application status ### 9. Check application status
Check if GitLab and its environment are configured correctly: Check if GitLab and its environment are configured correctly:
......
...@@ -49,9 +49,7 @@ sudo -u git -H bundle install --without development test mysql --deployment ...@@ -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 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 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:clean assets:precompile cache:clear 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
``` ```
### 5. Start application ### 5. Start application
......
...@@ -23,7 +23,7 @@ class Spinach::Features::AbuseReports < Spinach::FeatureSteps ...@@ -23,7 +23,7 @@ class Spinach::Features::AbuseReports < Spinach::FeatureSteps
end end
step 'I should see a red "Report abuse" button' do 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 end
def user_mike def user_mike
......
...@@ -86,13 +86,13 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -86,13 +86,13 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end end
step 'I should see project "Forum" README' do 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' expect(page).to have_content 'Sample repo for testing gitlab features'
end end
end end
step 'I should see project "Shop" README' do step 'I should see project "Shop" README' do
page.within('#README') do page.within('.readme-holder') do
expect(page).to have_content 'testme' expect(page).to have_content 'testme'
end end
end end
......
...@@ -246,8 +246,8 @@ module API ...@@ -246,8 +246,8 @@ module API
# Example Request: # Example Request:
# DELETE /projects/:id/fork # DELETE /projects/:id/fork
delete ":id/fork" do delete ":id/fork" do
authenticated_as_admin! authorize! :remove_fork_project, user_project
unless user_project.forked_project_link.nil? if user_project.forked?
user_project.forked_project_link.destroy user_project.forked_project_link.destroy
end end
end end
......
...@@ -7,6 +7,14 @@ module Gitlab ...@@ -7,6 +7,14 @@ module Gitlab
module Markdown module Markdown
# Convert a Markdown String into an HTML-safe String of HTML # 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 # markdown - Markdown String
# context - Hash of context options passed to our HTML Pipeline # context - Hash of context options passed to our HTML Pipeline
# #
...@@ -31,6 +39,33 @@ module Gitlab ...@@ -31,6 +39,33 @@ module Gitlab
renderer.render(markdown) renderer.render(markdown)
end 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 # Provide autoload paths for filters to prevent a circular dependency error
autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter' autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter' autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
...@@ -41,6 +76,7 @@ module Gitlab ...@@ -41,6 +76,7 @@ module Gitlab
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter' autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter' autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter' autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter' autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter' autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
...@@ -50,26 +86,20 @@ module Gitlab ...@@ -50,26 +86,20 @@ module Gitlab
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_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
#
# 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
# #
# text - the source text # Returns an HTML-safe String
# options - A Hash of options used to customize output (default: {}): def self.gfm(html, options = {})
# :xhtml - output XHTML instead of HTML return '' unless html.present?
# :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]
)
@pipeline ||= HTML::Pipeline.new(filters) @pipeline ||= HTML::Pipeline.new(filters)
...@@ -78,41 +108,36 @@ module Gitlab ...@@ -78,41 +108,36 @@ module Gitlab
pipeline: options[:pipeline], pipeline: options[:pipeline],
# EmojiFilter # EmojiFilter
asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host, asset_host: Gitlab::Application.config.asset_host,
asset_root: Gitlab.config.gitlab.base_url,
# TableOfContentsFilter
no_header_anchors: options[:no_header_anchors],
# ReferenceFilter # ReferenceFilter
current_user: options[:current_user], only_path: only_path_pipeline?(options[:pipeline]),
only_path: options[:reference_only_path], project: options[:project],
project: options[:project],
# RelativeLinkFilter # RelativeLinkFilter
project_wiki: options[:project_wiki],
ref: options[:ref], ref: options[:ref],
requested_path: options[:path], requested_path: options[:path],
project_wiki: options[:project_wiki]
}
result = @pipeline.call(text, context)
save_options = 0 # TableOfContentsFilter
if options[:xhtml] no_header_anchors: options[:no_header_anchors]
save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML }
end
text = result[:output].to_html(save_with: save_options)
text.html_safe @pipeline.to_html(html, context).html_safe
end end
private private
def self.renderer # Check if a pipeline enables the `only_path` context option
@markdown ||= begin #
renderer = Redcarpet::Render::HTML.new # Returns Boolean
Redcarpet::Markdown.new(renderer, redcarpet_options) def self.only_path_pipeline?(pipeline)
case pipeline
when :atom, :email
false
else
true
end end
end end
...@@ -130,6 +155,17 @@ module Gitlab ...@@ -130,6 +155,17 @@ module Gitlab
}.freeze }.freeze
end 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 # Filters used in our pipeline
# #
# SanitizationFilter should come first so that all generated reference HTML # SanitizationFilter should come first so that all generated reference HTML
......
...@@ -26,6 +26,18 @@ module Gitlab ...@@ -26,6 +26,18 @@ module Gitlab
end end
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) def initialize(*args)
super super
...@@ -53,13 +65,11 @@ module Gitlab ...@@ -53,13 +65,11 @@ module Gitlab
range = CommitRange.new(id, project) range = CommitRange.new(id, project)
if range.valid_commits? if range.valid_commits?
push_result(:commit_range, range)
url = url_for_commit_range(project, range) url = url_for_commit_range(project, range)
title = range.reference_title title = range.reference_title
klass = reference_class(:commit_range) klass = reference_class(:commit_range)
data = data_attribute(project.id) data = data_attribute(project: project.id, commit_range: id)
project_ref += '@' if project_ref project_ref += '@' if project_ref
......
...@@ -26,6 +26,18 @@ module Gitlab ...@@ -26,6 +26,18 @@ module Gitlab
end end
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 def call
replace_text_nodes_matching(Commit.reference_pattern) do |content| replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content) commit_link_filter(content)
...@@ -39,17 +51,15 @@ module Gitlab ...@@ -39,17 +51,15 @@ module Gitlab
# Returns a String with commit references replaced with links. All links # Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling. # have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text) 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) project = self.project_from_ref(project_ref)
if commit = commit_from_ref(project, commit_ref) if commit = self.class.commit_from_ref(project, id)
push_result(:commit, commit)
url = url_for_commit(project, commit) url = url_for_commit(project, commit)
title = escape_once(commit.link_title) title = escape_once(commit.link_title)
klass = reference_class(:commit) klass = reference_class(:commit)
data = data_attribute(project.id) data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref project_ref += '@' if project_ref
...@@ -62,9 +72,9 @@ module Gitlab ...@@ -62,9 +72,9 @@ module Gitlab
end end
end end
def commit_from_ref(project, commit_ref) def self.commit_from_ref(project, id)
if project && project.valid_repo? if project && project.valid_repo?
project.commit(commit_ref) project.commit(id)
end end
end end
......
...@@ -13,18 +13,11 @@ module Gitlab ...@@ -13,18 +13,11 @@ module Gitlab
# #
# ref - String reference. # 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) def project_from_ref(ref)
return context[:project] unless ref return context[:project] unless ref
other = Project.find_with_namespace(ref) 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)
end end
end end
end end
......
...@@ -47,8 +47,9 @@ module Gitlab ...@@ -47,8 +47,9 @@ module Gitlab
title = escape_once("Issue in #{project.external_issue_tracker.title}") title = escape_once("Issue in #{project.external_issue_tracker.title}")
klass = reference_class(:issue) klass = reference_class(:issue)
data = data_attribute(project: project.id)
%(<a href="#{url}" %(<a href="#{url}" #{data}
title="#{title}" title="#{title}"
class="#{klass}">#{match}</a>) class="#{klass}">#{match}</a>)
end end
......
...@@ -27,6 +27,10 @@ module Gitlab ...@@ -27,6 +27,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ issue: LazyReference.new(Issue, node.attr("data-issue")) }
end
def call def call
replace_text_nodes_matching(Issue.reference_pattern) do |content| replace_text_nodes_matching(Issue.reference_pattern) do |content|
issue_link_filter(content) issue_link_filter(content)
...@@ -45,13 +49,11 @@ module Gitlab ...@@ -45,13 +49,11 @@ module Gitlab
project = self.project_from_ref(project_ref) project = self.project_from_ref(project_ref)
if project && issue = project.get_issue(id) if project && issue = project.get_issue(id)
push_result(:issue, issue)
url = url_for_issue(id, project, only_path: context[:only_path]) url = url_for_issue(id, project, only_path: context[:only_path])
title = escape_once("Issue: #{issue.title}") title = escape_once("Issue: #{issue.title}")
klass = reference_class(:issue) klass = reference_class(:issue)
data = data_attribute(project.id) data = data_attribute(project: project.id, issue: issue.id)
%(<a href="#{url}" #{data} %(<a href="#{url}" #{data}
title="#{title}" title="#{title}"
......
...@@ -22,6 +22,10 @@ module Gitlab ...@@ -22,6 +22,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ label: LazyReference.new(Label, node.attr("data-label")) }
end
def call def call
replace_text_nodes_matching(Label.reference_pattern) do |content| replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content) label_link_filter(content)
...@@ -41,11 +45,9 @@ module Gitlab ...@@ -41,11 +45,9 @@ module Gitlab
params = label_params(id, name) params = label_params(id, name)
if label = project.labels.find_by(params) if label = project.labels.find_by(params)
push_result(:label, label)
url = url_for_label(project, label) url = url_for_label(project, label)
klass = reference_class(:label) klass = reference_class(:label)
data = data_attribute(project.id) data = data_attribute(project: project.id, label: label.id)
%(<a href="#{url}" #{data} %(<a href="#{url}" #{data}
class="#{klass}">#{render_colored_label(label)}</a>) class="#{klass}">#{render_colored_label(label)}</a>)
......
...@@ -27,6 +27,10 @@ module Gitlab ...@@ -27,6 +27,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ merge_request: LazyReference.new(MergeRequest, node.attr("data-merge-request")) }
end
def call def call
replace_text_nodes_matching(MergeRequest.reference_pattern) do |content| replace_text_nodes_matching(MergeRequest.reference_pattern) do |content|
merge_request_link_filter(content) merge_request_link_filter(content)
...@@ -45,11 +49,9 @@ module Gitlab ...@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref) project = self.project_from_ref(project_ref)
if project && merge_request = project.merge_requests.find_by(iid: id) 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}") title = escape_once("Merge Request: #{merge_request.title}")
klass = reference_class(:merge_request) 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) 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 ...@@ -11,30 +11,57 @@ module Gitlab
# Context options: # Context options:
# :project (required) - Current project, ignored if reference is cross-project. # :project (required) - Current project, ignored if reference is cross-project.
# :only_path - Generate path-only links. # :only_path - Generate path-only links.
#
# Results:
# :references - A Hash of references that were found and replaced.
class ReferenceFilter < HTML::Pipeline::Filter class ReferenceFilter < HTML::Pipeline::Filter
def initialize(*args) LazyReference = Struct.new(:klass, :ids) do
super 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
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
result[:references] = Hash.new { |hash, type| hash[type] = [] } def self.referenced_by(node)
raise NotImplementedError, "#{self} does not implement #{__method__}"
end end
# Returns a data attribute String to attach to a reference link # Returns a data attribute String to attach to a reference link
# #
# id - Object ID # attributes - Hash, where the key becomes the data attribute name and the
# type - Object type (default: :project) # value is the data attribute value
# #
# Examples: # Examples:
# #
# data_attribute(1) # => "data-project-id=\"1\"" # data_attribute(project: 1, issue: 2)
# data_attribute(2, :user) # => "data-user-id=\"2\"" # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
# data_attribute(3, :group) # => "data-group-id=\"3\"" #
# data_attribute(project: 3, merge_request: 4)
# # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
# #
# Returns a String # Returns a String
def data_attribute(id, type = :project) def data_attribute(attributes = {})
%Q(data-#{type}-id="#{id}") attributes[:reference_filter] = self.class.name
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
end end
def escape_once(html) def escape_once(html)
...@@ -59,16 +86,6 @@ module Gitlab ...@@ -59,16 +86,6 @@ module Gitlab
context[:project] context[:project]
end 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) def reference_class(type)
"gfm gfm-#{type}" "gfm gfm-#{type}"
end end
...@@ -85,15 +102,15 @@ module Gitlab ...@@ -85,15 +102,15 @@ module Gitlab
# Yields the current node's String contents. The result of the block will # Yields the current node's String contents. The result of the block will
# replace the node's existing content and update the current document. # 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) def replace_text_nodes_matching(pattern)
return doc if project.nil? return doc if project.nil?
search_text_nodes(doc).each do |node| search_text_nodes(doc).each do |node|
content = node.to_html
next unless content.match(pattern)
next if ignored_ancestry?(node) next if ignored_ancestry?(node)
next unless node.text =~ pattern
content = node.to_html
html = yield content 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 ...@@ -27,6 +27,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ snippet: LazyReference.new(Snippet, node.attr("data-snippet")) }
end
def call def call
replace_text_nodes_matching(Snippet.reference_pattern) do |content| replace_text_nodes_matching(Snippet.reference_pattern) do |content|
snippet_link_filter(content) snippet_link_filter(content)
...@@ -45,11 +49,9 @@ module Gitlab ...@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref) project = self.project_from_ref(project_ref)
if project && snippet = project.snippets.find_by(id: id) if project && snippet = project.snippets.find_by(id: id)
push_result(:snippet, snippet)
title = escape_once("Snippet: #{snippet.title}") title = escape_once("Snippet: #{snippet.title}")
klass = reference_class(:snippet) klass = reference_class(:snippet)
data = data_attribute(project.id) data = data_attribute(project: project.id, snippet: snippet.id)
url = url_for_snippet(snippet, project) url = url_for_snippet(snippet, project)
......
...@@ -23,6 +23,31 @@ module Gitlab ...@@ -23,6 +23,31 @@ module Gitlab
end end
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 def call
replace_text_nodes_matching(User.reference_pattern) do |content| replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content) user_link_filter(content)
...@@ -61,14 +86,12 @@ module Gitlab ...@@ -61,14 +86,12 @@ module Gitlab
def link_to_all def link_to_all
project = context[:project] project = context[:project]
# FIXME (rspeicher): Law of Demeter
push_result(:user, *project.team.members.flatten)
url = urls.namespace_project_url(project.namespace, project, url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path]) only_path: context[:only_path])
data = data_attribute(project: project.id)
text = User.reference_prefix + 'all' text = User.reference_prefix + 'all'
%(<a href="#{url}" class="#{link_class}">#{text}</a>) %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end end
def link_to_namespace(namespace) def link_to_namespace(namespace)
...@@ -80,30 +103,20 @@ module Gitlab ...@@ -80,30 +103,20 @@ module Gitlab
end end
def link_to_group(group, namespace) 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]) 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 text = Group.reference_prefix + group
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>) %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end end
def link_to_user(user, namespace) def link_to_user(user, namespace)
push_result(:user, namespace.owner)
url = urls.user_url(user, only_path: context[:only_path]) 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 text = User.reference_prefix + user
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>) %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end end
def user_can_reference_group?(group)
Ability.abilities.allowed?(context[:current_user], :read_group, group)
end
end end
end end
end end
...@@ -3,11 +3,12 @@ require 'gitlab/markdown' ...@@ -3,11 +3,12 @@ require 'gitlab/markdown'
module Gitlab module Gitlab
# Extract possible GFM references from an arbitrary String for further processing. # Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor 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 @project = project
@current_user = current_user @current_user = current_user
@load_lazy_references = load_lazy_references
end end
def analyze(text) def analyze(text)
...@@ -26,9 +27,9 @@ module Gitlab ...@@ -26,9 +27,9 @@ module Gitlab
def references def references
@references ||= Hash.new do |references, type| @references ||= Hash.new do |references, type|
type = type.to_sym 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
end end
...@@ -41,21 +42,32 @@ module Gitlab ...@@ -41,21 +42,32 @@ module Gitlab
def pipeline_result(filter_type) def pipeline_result(filter_type)
return [] if @text.blank? return [] if @text.blank?
klass = filter_type.to_s.camelize + 'ReferenceFilter' klass = "#{filter_type.to_s.camelize}ReferenceFilter"
filter = Gitlab::Markdown.const_get(klass) filter = Gitlab::Markdown.const_get(klass)
context = { context = {
project: project, project: project,
current_user: current_user, current_user: current_user,
# We don't actually care about the links generated # We don't actually care about the links generated
only_path: true, 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 = 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 end
end end
...@@ -335,7 +335,7 @@ namespace :gitlab do ...@@ -335,7 +335,7 @@ namespace :gitlab do
print "Redis version >= #{min_redis_version}? ... " print "Redis version >= #{min_redis_version}? ... "
redis_version = run(%W(redis-cli --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 && if redis_version &&
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version)) (Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
puts "yes".green 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 ...@@ -37,6 +37,32 @@ describe Admin::UsersController do
end end
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 describe 'PUT unlock/:id' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -41,7 +41,7 @@ describe Import::GithubController do ...@@ -41,7 +41,7 @@ describe Import::GithubController do
it "assigns variables" do it "assigns variables" do
@project = create(:project, import_type: 'github', creator_id: user.id) @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 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,26 +10,43 @@ describe Projects::ServicesController do ...@@ -10,26 +10,43 @@ describe Projects::ServicesController do
project.team << [user, :master] project.team << [user, :master]
controller.instance_variable_set(:@project, project) controller.instance_variable_set(:@project, project)
controller.instance_variable_set(:@service, service) controller.instance_variable_set(:@service, service)
request.env["HTTP_REFERER"] = "/"
end end
describe "#test" do shared_examples_for 'services controller' do |referrer|
context 'success' do before do
it "should redirect and show success message" do request.env["HTTP_REFERER"] = referrer
expect(service).to receive(:test).and_return({ success: true, result: 'done' })
get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
expect(response.status).to redirect_to('/')
expect(flash[:notice]).to eq('We sent a request to the provided URL')
end
end end
context 'failure' do describe "#test" do
it "should redirect and show failure message" do context 'success' do
expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' }) it "should redirect and show success message" do
get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html expect(service).to receive(:test).and_return({ success: true, result: 'done' })
expect(response.status).to redirect_to('/') get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test') expect(response.status).to redirect_to('/')
expect(flash[:notice]).to eq('We sent a request to the provided URL')
end
end
context 'failure' do
it "should redirect and show failure message" do
expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' })
get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
expect(response.status).to redirect_to('/')
expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
end
end end
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 end
...@@ -22,6 +22,34 @@ describe ProjectsController do ...@@ -22,6 +22,34 @@ describe ProjectsController do
end end
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 context "when requested with case sensitive namespace and project path" do
it "redirects to the normalized path for case mismatch" do it "redirects to the normalized path for case mismatch" do
get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
...@@ -62,4 +90,50 @@ describe ProjectsController do ...@@ -62,4 +90,50 @@ describe ProjectsController do
expect(user.starred?(public_project)).to be_falsey expect(user.starred?(public_project)).to be_falsey
end end
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 end
...@@ -220,7 +220,7 @@ describe 'GitLab Markdown', feature: true do ...@@ -220,7 +220,7 @@ describe 'GitLab Markdown', feature: true do
end end
end end
# `markdown` calls these two methods # Fake a `current_user` helper
def current_user def current_user
@feat.user @feat.user
end end
......
...@@ -34,6 +34,27 @@ feature 'Project', feature: true do ...@@ -34,6 +34,27 @@ feature 'Project', feature: true do
end end
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 describe 'removal', js: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
...@@ -45,13 +66,13 @@ feature 'Project', feature: true do ...@@ -45,13 +66,13 @@ feature 'Project', feature: true do
end end
it 'should remove project' do 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
end end
def remove_project def remove_with_confirm(button_text, confirm_with)
click_button "Remove project" click_button button_text
fill_in 'confirm_name_input', with: project.path fill_in 'confirm_name_input', with: confirm_with
click_button 'Confirm' click_button 'Confirm'
end end
end end
...@@ -11,12 +11,15 @@ describe GitlabMarkdownHelper do ...@@ -11,12 +11,15 @@ describe GitlabMarkdownHelper do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) } let(:snippet) { create(:project_snippet, project: project) }
# Helper expects a current_user method.
let(:current_user) { user }
before do before do
# Ensure the generated reference links aren't redacted
project.team << [user, :master]
# Helper expects a @project instance variable # 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 end
describe "#markdown" do describe "#markdown" do
...@@ -25,23 +28,23 @@ describe GitlabMarkdownHelper do ...@@ -25,23 +28,23 @@ describe GitlabMarkdownHelper do
it "should link to the merge request" do it "should link to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request) 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 end
it "should link to the commit" do it "should link to the commit" do
expected = namespace_project_commit_path(project.namespace, project, commit) expected = namespace_project_commit_path(project.namespace, project, commit)
expect(markdown(actual)).to match(expected) expect(helper.markdown(actual)).to match(expected)
end end
it "should link to the issue" do it "should link to the issue" do
expected = namespace_project_issue_path(project.namespace, project, issue) expected = namespace_project_issue_path(project.namespace, project, issue)
expect(markdown(actual)).to match(expected) expect(helper.markdown(actual)).to match(expected)
end end
end end
describe "override default project" do describe "override default project" do
let(:actual) { issue.to_reference } let(:actual) { issue.to_reference }
let(:second_project) { create(:project) } let(:second_project) { create(:project, :public) }
let(:second_issue) { create(:issue, project: second_project) } let(:second_issue) { create(:issue, project: second_project) }
it 'should link to the issue' do it 'should link to the issue' do
...@@ -56,7 +59,7 @@ describe GitlabMarkdownHelper do ...@@ -56,7 +59,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) } let(:issues) { create_list(:issue, 2, project: project) }
it 'should handle references nested in links with all the text' do 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) doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup # Make sure we didn't create invalid markup
...@@ -86,7 +89,7 @@ describe GitlabMarkdownHelper do ...@@ -86,7 +89,7 @@ describe GitlabMarkdownHelper do
end end
it 'should forward HTML options' do 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) doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v| expect(doc.css('a')).to satisfy do |v|
...@@ -97,13 +100,13 @@ describe GitlabMarkdownHelper do ...@@ -97,13 +100,13 @@ describe GitlabMarkdownHelper do
it "escapes HTML passed in as the body" do it "escapes HTML passed in as the body" do
actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}" 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;') to match('&lt;h1&gt;test&lt;/h1&gt;')
end end
it 'ignores reference links when they are the entire body' do it 'ignores reference links when they are the entire body' do
text = issues[0].to_reference 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>) expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
end end
......
...@@ -117,4 +117,14 @@ describe IssuesHelper do ...@@ -117,4 +117,14 @@ describe IssuesHelper do
end end
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 end
...@@ -140,28 +140,28 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -140,28 +140,28 @@ describe Gitlab::ClosingIssueExtractor do
message = "Closes #{reference} and fix #{reference2}" message = "Closes #{reference} and fix #{reference2}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue]) to match_array([issue, other_issue])
end end
it 'fetches comma-separated issues references in single line message' do it 'fetches comma-separated issues references in single line message' do
message = "Closes #{reference}, closes #{reference2}" message = "Closes #{reference}, closes #{reference2}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue]) to match_array([issue, other_issue])
end end
it 'fetches comma-separated issues numbers in single line message' do it 'fetches comma-separated issues numbers in single line message' do
message = "Closes #{reference}, #{reference2} and #{reference3}" message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue]) to match_array([issue, other_issue, third_issue])
end end
it 'fetches issues in multi-line message' do it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}" message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue]) to match_array([issue, other_issue])
end end
it 'fetches issues in hybrid message' do it 'fetches issues in hybrid message' do
...@@ -169,7 +169,7 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -169,7 +169,7 @@ describe Gitlab::ClosingIssueExtractor do
"Also fixing issues #{reference2}, #{reference3} and #4" "Also fixing issues #{reference2}, #{reference3} and #4"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue]) to match_array([issue, other_issue, third_issue])
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitRangeReferenceFilter do describe CommitRangeReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:commit1) { project.commit } let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") } let(:commit2) { project.commit("HEAD~2") }
...@@ -75,12 +75,20 @@ module Gitlab::Markdown ...@@ -75,12 +75,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("See #{reference}") doc = filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s 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 end
it 'supports an :only_path option' do it 'supports an :only_path option' do
...@@ -92,59 +100,45 @@ module Gitlab::Markdown ...@@ -92,59 +100,45 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do 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 expect(result[:references][:commit_range]).not_to be_empty
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } 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) } let(:reference) { range.to_reference(project) }
before do before do
range.project = project2 range.project = project2
end end
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do expect(doc.css('a').first.attr('href')).
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
expect(filter(act).to_html).to eq exp end
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" it 'links with adjacent text' do
expect(filter(act).to_html).to eq exp doc = filter("Fixed (#{reference}.)")
end
it 'adds to the results hash' do exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
result = pipeline_result("See #{reference}") expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
expect(result[:references][:commit_range]).not_to be_empty
end
end end
context 'when user cannot access reference' do it 'ignores invalid commit IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
expect(filter(act).to_html).to eq exp
it 'ignores valid references' do exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
exp = act = "See #{reference}" expect(filter(act).to_html).to eq exp
end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitReferenceFilter do describe CommitReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:commit) { project.commit } let(:commit) { project.commit }
it 'requires project context' do it 'requires project context' do
...@@ -71,12 +71,20 @@ module Gitlab::Markdown ...@@ -71,12 +71,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("See #{reference}") doc = filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s 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 end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -88,53 +96,39 @@ module Gitlab::Markdown ...@@ -88,53 +96,39 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do 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 expect(result[:references][:commit]).not_to be_empty
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } 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(:commit) { project2.commit }
let(:reference) { commit.to_reference(project) } let(:reference) { commit.to_reference(project) }
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
exp = Regexp.escape(project2.to_reference) expect(doc.css('a').first.attr('href')).
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end end
it 'ignores invalid commit IDs on the referenced project' do it 'links with adjacent text' do
exp = act = "Committed #{invalidate_reference(reference)}" doc = filter("Fixed (#{reference}.)")
expect(filter(act).to_html).to eq exp
end
it 'adds to the results hash' do exp = Regexp.escape(project2.to_reference)
result = pipeline_result("See #{reference}") expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
expect(result[:references][:commit]).not_to be_empty
end
end end
context 'when user cannot access reference' do it 'ignores invalid commit IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Committed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
it 'ignores valid references' do end
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end end
end end
end end
......
...@@ -2,20 +2,16 @@ require 'spec_helper' ...@@ -2,20 +2,16 @@ require 'spec_helper'
module Gitlab::Markdown module Gitlab::Markdown
describe CrossProjectReference do 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 include described_class
describe '#project_from_ref' do describe '#project_from_ref' do
context 'when no project was referenced' do context 'when no project was referenced' do
it 'returns the project from context' 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
end end
...@@ -26,29 +22,13 @@ module Gitlab::Markdown ...@@ -26,29 +22,13 @@ module Gitlab::Markdown
end end
context 'when referenced project exists' do 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). expect(Project).to receive(:find_with_namespace).
with('cross/reference').and_return(project2) 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 expect(project_from_ref('cross/reference')).to eq project2
end
end end
end end
end end
......
...@@ -8,7 +8,7 @@ module Gitlab::Markdown ...@@ -8,7 +8,7 @@ module Gitlab::Markdown
IssuesHelper IssuesHelper
end end
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
it 'requires project context' do it 'requires project context' do
...@@ -68,12 +68,20 @@ module Gitlab::Markdown ...@@ -68,12 +68,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Issue #{reference}") doc = filter("Issue #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s 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 end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -85,60 +93,46 @@ module Gitlab::Markdown ...@@ -85,60 +93,46 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do 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] expect(result[:references][:issue]).to eq [issue]
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } 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(:issue) { create(:issue, project: project2) }
let(:reference) { issue.to_reference(project) } let(:reference) { issue.to_reference(project) }
context 'when user can access reference' do it 'ignores valid references when cross-reference project uses external tracker' do
before { allow_cross_reference! } expect_any_instance_of(Project).to receive(:get_issue).
with(issue.iid).and_return(nil)
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)
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
end
it 'links to a valid reference' do exp = act = "Issue #{reference}"
doc = filter("See #{reference}") expect(filter(act).to_html).to eq exp
end
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid issue IDs on the referenced project' do it 'links to a valid reference' do
exp = act = "Fixed #{invalidate_reference(reference)}" doc = filter("See #{reference}")
expect(filter(act).to_html).to eq exp expect(doc.css('a').first.attr('href')).
end to eq helper.url_for_issue(issue.iid, project2)
end
it 'adds to the results hash' do it 'links with adjacent text' do
result = pipeline_result("Fixed #{reference}") doc = filter("Fixed (#{reference}.)")
expect(result[:references][:issue]).to eq [issue] expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
end end
context 'when user cannot access reference' do it 'ignores invalid issue IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Fixed #{invalidate_reference(reference)}"
it 'ignores valid references' do expect(filter(act).to_html).to eq exp
exp = act = "See #{reference}" end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end end
end end
end end
......
...@@ -5,7 +5,7 @@ module Gitlab::Markdown ...@@ -5,7 +5,7 @@ module Gitlab::Markdown
describe LabelReferenceFilter do describe LabelReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:label) { create(:label, project: project) } let(:label) { create(:label, project: project) }
let(:reference) { label.to_reference } let(:reference) { label.to_reference }
...@@ -25,12 +25,20 @@ module Gitlab::Markdown ...@@ -25,12 +25,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Label #{reference}") doc = filter("Label #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s 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 end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -42,7 +50,7 @@ module Gitlab::Markdown ...@@ -42,7 +50,7 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do 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] expect(result[:references][:label]).to eq [label]
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe MergeRequestReferenceFilter do describe MergeRequestReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:merge) { create(:merge_request, source_project: project) } let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do it 'requires project context' do
...@@ -56,12 +56,20 @@ module Gitlab::Markdown ...@@ -56,12 +56,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Merge #{reference}") doc = filter("Merge #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s 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 end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -73,53 +81,39 @@ module Gitlab::Markdown ...@@ -73,53 +81,39 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do 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] expect(result[:references][:merge_request]).to eq [merge]
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } 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(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { merge.to_reference(project) } let(:reference) { merge.to_reference(project) }
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp expect(doc.css('a').first.attr('href')).
end to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge)
end
it 'adds to the results hash' do it 'links with adjacent text' do
result = pipeline_result("Merge #{reference}") doc = filter("Merge (#{reference}.)")
expect(result[:references][:merge_request]).to eq [merge] expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
end end
context 'when user cannot access reference' do it 'ignores invalid merge IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Merge #{invalidate_reference(reference)}"
it 'ignores valid references' do expect(filter(act).to_html).to eq exp
exp = act = "See #{reference}" end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end 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 ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe SnippetReferenceFilter do describe SnippetReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:snippet) { create(:project_snippet, project: project) } let(:snippet) { create(:project_snippet, project: project) }
let(:reference) { snippet.to_reference } let(:reference) { snippet.to_reference }
...@@ -55,12 +55,20 @@ module Gitlab::Markdown ...@@ -55,12 +55,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}") doc = filter("Snippet #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s 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 end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -72,52 +80,38 @@ module Gitlab::Markdown ...@@ -72,52 +80,38 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do 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] expect(result[:references][:snippet]).to eq [snippet]
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } 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(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { snippet.to_reference(project) } let(:reference) { snippet.to_reference(project) }
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp expect(doc.css('a').first.attr('href')).
end to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'adds to the results hash' do it 'links with adjacent text' do
result = pipeline_result("Snippet #{reference}") doc = filter("See (#{reference}.)")
expect(result[:references][:snippet]).to eq [snippet] expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
end end
context 'when user cannot access reference' do it 'ignores invalid snippet IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "See #{invalidate_reference(reference)}"
it 'ignores valid references' do expect(filter(act).to_html).to eq exp
exp = act = "See #{reference}" end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe UserReferenceFilter do describe UserReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:reference) { user.to_reference } let(:reference) { user.to_reference }
...@@ -39,7 +39,7 @@ module Gitlab::Markdown ...@@ -39,7 +39,7 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do 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] expect(result[:references][:user]).to eq [project.creator]
end end
end end
...@@ -64,59 +64,40 @@ module Gitlab::Markdown ...@@ -64,59 +64,40 @@ module Gitlab::Markdown
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
end end
it 'includes a data-user-id attribute' do it 'includes a data-user attribute' do
doc = filter("Hey #{reference}") doc = filter("Hey #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-user-id') expect(link).to have_attribute('data-user')
expect(link.attr('data-user-id')).to eq user.namespace.owner_id.to_s expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end end
it 'adds to the results hash' do 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] expect(result[:references][:user]).to eq [user]
end end
end end
context 'mentioning a group' do context 'mentioning a group' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:user) { create(:user) }
let(:reference) { group.to_reference } let(:reference) { group.to_reference }
context 'that the current user can read' do it 'links to the Group' do
before do doc = filter("Hey #{reference}")
group.add_developer(user) expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end end
it 'links to the Group' do
doc = filter("Hey #{reference}", current_user: user)
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)
link = doc.css('a').first
expect(link).to have_attribute('data-group-id') it 'includes a data-group attribute' do
expect(link.attr('data-group-id')).to eq group.id.to_s doc = filter("Hey #{reference}")
end link = doc.css('a').first
it 'adds to the results hash' do expect(link).to have_attribute('data-group')
result = pipeline_result("Hey #{reference}", current_user: user) expect(link.attr('data-group')).to eq group.id.to_s
expect(result[:references][:user]).to eq group.users
end
end end
context 'that the current user cannot read' do it 'adds to the results hash' do
it 'ignores references to the Group' do result = reference_pipeline_result("Hey #{reference}")
doc = filter("Hey #{reference}", current_user: user) expect(result[:references][:user]).to eq group.users
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
end end
......
...@@ -13,7 +13,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -13,7 +13,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest] project.team << [@u_bar, :guest]
subject.analyze('@foo, @baduser, @bar, and @offteam') 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 end
it 'ignores user mentions inside specific elements' do it 'ignores user mentions inside specific elements' do
...@@ -37,7 +37,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -37,7 +37,7 @@ describe Gitlab::ReferenceExtractor do
> @offteam > @offteam
}) })
expect(subject.users).to eq([]) expect(subject.users).to match_array([])
end end
it 'accesses valid issue objects' do it 'accesses valid issue objects' do
...@@ -45,7 +45,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -45,7 +45,7 @@ describe Gitlab::ReferenceExtractor do
@i1 = create(:issue, project: project) @i1 = create(:issue, project: project)
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.") 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 end
it 'accesses valid merge requests' do it 'accesses valid merge requests' do
...@@ -53,7 +53,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -53,7 +53,7 @@ describe Gitlab::ReferenceExtractor do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict') @m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.") 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 end
it 'accesses valid labels' do it 'accesses valid labels' do
...@@ -62,7 +62,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -62,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
@l2 = create(:label) @l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}") 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 end
it 'accesses valid snippets' do it 'accesses valid snippets' do
...@@ -71,7 +71,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -71,7 +71,7 @@ describe Gitlab::ReferenceExtractor do
@s2 = create(:project_snippet) @s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}") 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 end
it 'accesses valid commits' do it 'accesses valid commits' do
...@@ -109,7 +109,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -109,7 +109,7 @@ describe Gitlab::ReferenceExtractor do
subject.analyze("this refers issue #{issue.to_reference(project)}") subject.analyze("this refers issue #{issue.to_reference(project)}")
extracted = subject.issues extracted = subject.issues
expect(extracted.size).to eq(1) expect(extracted.size).to eq(1)
expect(extracted).to eq([issue]) expect(extracted).to match_array([issue])
end end
end end
end end
...@@ -68,7 +68,6 @@ describe Issue, "Issuable" do ...@@ -68,7 +68,6 @@ describe Issue, "Issuable" do
end end
end end
describe "#to_hook_data" do describe "#to_hook_data" do
let(:hook_data) { issue.to_hook_data(user) } let(:hook_data) { issue.to_hook_data(user) }
......
...@@ -68,6 +68,43 @@ describe Issue do ...@@ -68,6 +68,43 @@ describe Issue do
end end
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 it_behaves_like 'an editable mentionable' do
subject { create(:issue) } subject { create(:issue) }
......
...@@ -140,4 +140,32 @@ describe Milestone do ...@@ -140,4 +140,32 @@ describe Milestone do
end end
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 end
...@@ -606,28 +606,42 @@ describe API::API, api: true do ...@@ -606,28 +606,42 @@ describe API::API, api: true do
describe 'DELETE /projects/:id/fork' 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) delete api("/projects/#{project_fork_target.id}/fork", user)
expect(response.status).to eq(403) expect(response.status).to eq(404)
end end
it 'should make forked project unforked' do context 'when users belong to project group' do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) let(:project_fork_target) { create(:project, group: create(:group)) }
project_fork_target.reload
expect(project_fork_target.forked_from_project).not_to be_nil
expect(project_fork_target.forked?).to be_truthy
delete api("/projects/#{project_fork_target.id}/fork", admin)
expect(response.status).to eq(200)
project_fork_target.reload
expect(project_fork_target.forked_from_project).to be_nil
expect(project_fork_target.forked?).not_to be_truthy
end
it 'should be idempotent if not forked' do before do
expect(project_fork_target.forked_from_project).to be_nil project_fork_target.group.add_owner user
delete api("/projects/#{project_fork_target.id}/fork", admin) project_fork_target.group.add_developer user2
expect(response.status).to eq(200) end
expect(project_fork_target.reload.forked_from_project).to be_nil
it 'should be forbidden to non-owner users' do
delete api("/projects/#{project_fork_target.id}/fork", user2)
expect(response.status).to eq(403)
end
it 'should make forked project unforked' do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
project_fork_target.reload
expect(project_fork_target.forked_from_project).not_to be_nil
expect(project_fork_target.forked?).to be_truthy
delete api("/projects/#{project_fork_target.id}/fork", admin)
expect(response.status).to eq(200)
project_fork_target.reload
expect(project_fork_target.forked_from_project).to be_nil
expect(project_fork_target.forked?).not_to be_truthy
end
it 'should be idempotent if not forked' do
expect(project_fork_target.forked_from_project).to be_nil
delete api("/projects/#{project_fork_target.id}/fork", admin)
expect(response.status).to eq(200)
expect(project_fork_target.reload.forked_from_project).to be_nil
end
end end
end end
end end
......
...@@ -6,7 +6,7 @@ describe ArchiveRepositoryService do ...@@ -6,7 +6,7 @@ describe ArchiveRepositoryService do
describe "#execute" do describe "#execute" do
it "cleans old archives" 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) subject.execute(timeout: 0.0)
end end
......
...@@ -29,12 +29,19 @@ module FilterSpecHelper ...@@ -29,12 +29,19 @@ module FilterSpecHelper
# #
# Returns the Hash # Returns the Hash
def pipeline_result(body, contexts = {}) 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 = HTML::Pipeline.new([described_class], contexts)
pipeline.call(body) pipeline.call(body)
end 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 # Modify a String reference to make it invalid
# #
# Commit SHAs get reversed, IDs get incremented by 1, all other Strings get # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
...@@ -55,20 +62,6 @@ module FilterSpecHelper ...@@ -55,20 +62,6 @@ module FilterSpecHelper
end end
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 # Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module # module
def urls def urls
......
...@@ -15,18 +15,17 @@ class MarkdownFeature ...@@ -15,18 +15,17 @@ class MarkdownFeature
end end
def group def group
unless @group @group ||= create(:group).tap do |group|
@group = create(:group) group.add_developer(user)
@group.add_developer(user)
end end
@group
end end
# Direct references ---------------------------------------------------------- # Direct references ----------------------------------------------------------
def project def project
@project ||= create(:project) @project ||= create(:project).tap do |project|
project.team << [user, :master]
end
end end
def issue def issue
...@@ -46,12 +45,10 @@ class MarkdownFeature ...@@ -46,12 +45,10 @@ class MarkdownFeature
end end
def commit_range def commit_range
unless @commit_range @commit_range ||= begin
commit2 = project.commit('HEAD~3') commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project) CommitRange.new("#{commit.id}...#{commit2.id}", project)
end end
@commit_range
end end
def simple_label def simple_label
...@@ -65,13 +62,12 @@ class MarkdownFeature ...@@ -65,13 +62,12 @@ class MarkdownFeature
# Cross-references ----------------------------------------------------------- # Cross-references -----------------------------------------------------------
def xproject def xproject
unless @xproject @xproject ||= begin
namespace = create(:namespace, name: 'cross-reference') namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace) create(:project, namespace: namespace) do |project|
@xproject.team << [user, :developer] project.team << [user, :developer]
end
end end
@xproject
end end
def xissue def xissue
...@@ -91,12 +87,10 @@ class MarkdownFeature ...@@ -91,12 +87,10 @@ class MarkdownFeature
end end
def xcommit_range def xcommit_range
unless @xcommit_range @xcommit_range ||= begin
xcommit2 = xproject.commit('HEAD~2') xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject) CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end end
@xcommit_range
end end
def raw_markdown def raw_markdown
......
...@@ -50,6 +50,8 @@ def common_mentionable_setup ...@@ -50,6 +50,8 @@ def common_mentionable_setup
} }
extra_commits.each { |c| commitmap[c.short_id] = c } 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] } allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string) 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