Commit e4008bc4 authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into git-archive-improvements-2

parents a321404f befff682
Please view this file on the master branch, on stable branches it's out of date.
v 8.2.0 (unreleased)
- Fix duplicate repositories in GitHub import page (Stan Hu)
- Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL
- Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw)
- Improved performance of sorting milestone issues
- Allow users to select the Files view as default project view (Cristian Bica)
v 8.1.0 (unreleased)
- Send an email to admin email when a user is reported for spam (Jonathan Rochkind)
- Show notifications button when user is member of group rather than project (Grzegorz Bizon)
- Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge.
- Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu)
- Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu)
- Speed up load times of issue detail pages by roughly 1.5x
- If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg)
- Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu)
- Make diff file view easier to use on mobile screens (Stan Hu)
- Improved performance of finding users by username or Email address
......@@ -59,6 +68,7 @@ v 8.1.0 (unreleased)
- Fix position of hamburger in header for smaller screens (Han Loong Liauw)
- Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
- Persist filters when sorting on admin user page (Jerry Lukins)
- Allow dashboard and group issues/MRs to be filtered by label
- Add spellcheck=false to certain input fields
- Invalidate stored service password if the endpoint URL is changed
- Project names are not fully shown if group name is too big, even on group page view
......@@ -68,6 +78,12 @@ v 8.1.0 (unreleased)
- Hide passwords from services API (Alex Lossent)
- Fix: Images cannot show when projects' path was changed
- Let gitlab-git-http-server generate and serve 'git archive' downloads
- Optimize query when filtering on issuables (Zeger-Jan van de Weg)
- Fix padding of outdated discussion item.
v 8.0.5
- Correct lookup-by-email for LDAP logins
- Fix loading spinner sometimes not being hidden on Merge Request tab switches
v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......
......@@ -65,3 +65,48 @@
line-height: 42px;
}
}
.cover-block {
text-align: center;
background: #f7f8fa;
margin: -$gl-padding;
margin-bottom: 0;
padding: 44px $gl-padding;
border-bottom: 1px solid $border-color;
position: relative;
.avatar-holder {
margin-bottom: 16px;
.avatar, .identicon {
margin: 0 auto;
float: none;
}
.identicon {
@include border-radius(50%);
}
}
.cover-title {
color: $gl-header-color;
margin: 0;
font-size: 23px;
font-weight: normal;
margin: 16px 0 5px 0;
color: #4c4e54;
font-size: 23px;
line-height: 1.1;
}
.cover-desc {
padding: 0 $gl-padding;
color: $gl-text-color;
}
.cover-controls {
position: absolute;
top: 10px;
right: 10px;
}
}
......@@ -10,6 +10,10 @@
border-bottom: 1px solid #E7E9EE;
margin-bottom: 1em;
&.readme-holder {
border-bottom: 0;
}
table {
@extend .table;
}
......
......@@ -18,7 +18,6 @@
font-family: $monospace_font;
white-space: pre;
word-wrap: normal;
padding: 1px 2px;
}
kbd {
......
......@@ -132,6 +132,11 @@ form.edit-issue {
}
}
.issue-closed-by-widget {
padding: 16px 0;
margin: 0px;
}
.issue-form .select2-container {
width: 250px !important;
}
......@@ -30,7 +30,6 @@ ul.notes {
.discussion-header,
.note-header {
@extend .cgray;
padding-bottom: 15px;
a:hover {
text-decoration: none;
......@@ -75,6 +74,10 @@ ul.notes {
}
}
.discussion-body {
padding-top: 15px;
}
.discussion {
overflow: hidden;
display: block;
......
......@@ -47,3 +47,9 @@
}
}
}
.calendar-hint {
margin-top: -12px;
float: right;
font-size: 12px;
}
......@@ -544,5 +544,5 @@ pre.light-well {
}
.project-show-readme .readme-holder {
padding: 7px;
border-top: 0;
}
......@@ -4,14 +4,6 @@
margin-right: -$gl-padding;
}
.tree_progress {
display: none;
margin: 20px;
&.loading {
display: block;
}
}
.tree-table {
margin-bottom: 0;
......
......@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user
if @abuse_report.save
if current_application_settings.admin_notification_email.present?
AbuseReportMailer.delay.notify(@abuse_report.id)
end
message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message
else
......
......@@ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_snippet_visibility,
:restricted_signup_domains_raw,
:version_check_enabled,
:admin_notification_email,
:user_oauth_applications,
restricted_visibility_levels: [],
import_sources: []
......
module Ci
class ProjectsController < Ci::ApplicationController
before_action :project
before_action :authenticate_user!, except: [:build, :badge]
before_action :authorize_access_project!, except: [:badge]
before_action :project, except: [:index]
before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge]
protect_from_forgery
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project)
end
# Project status badge
# Image with build status for sha or ref
def badge
......
......@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController
def status
@repos = client.repos
client.orgs.each do |org|
@repos += client.org_repos(org.login)
end
@already_added_projects = current_user.created_projects.where(import_type: "github")
already_added_projects_names = @already_added_projects.pluck(:import_source)
......
......@@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
# Cross-reference merge requests
before_action :closed_by_merge_requests, only: [:show]
respond_to :html
def index
......@@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController
render nothing: true
end
def closed_by_merge_requests
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
end
protected
def issue
......
......@@ -259,7 +259,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
......
......@@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def sort_issues
@issues = @milestone.issues.where(id: params['sortable_issue'])
@issues.each do |issue|
issue.position = params['sortable_issue'].index(issue.id.to_s) + 1
issue.save
end
@milestone.sort_issues(params['sortable_issue'].map(&:to_i))
render json: { saved: true }
end
......
class ProjectsController < ApplicationController
include ExtractsPath
prepend_before_filter :render_go_import, only: [:show]
skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
before_action :authorize_admin_project!, only: [:edit, :update]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
......@@ -56,6 +59,8 @@ class ProjectsController < ApplicationController
end
def transfer
return access_denied! unless can?(current_user, :change_namespace, @project)
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(project, current_user).execute(namespace)
......@@ -64,6 +69,15 @@ class ProjectsController < ApplicationController
end
end
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
if @project.forked?
@project.forked_project_link.destroy
flash[:notice] = 'The fork relationship has been removed.'
end
end
def activity
respond_to do |format|
format.html
......@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController
render 'projects/empty'
else
if current_user
@membership = @project.project_member_by_id(current_user.id)
@membership = @project.team.find_member(current_user.id)
end
render :show
......@@ -139,6 +153,7 @@ class ProjectsController < ApplicationController
def archive
return access_denied! unless can?(current_user, :archive_project, @project)
@project.archive!
respond_to do |format|
......@@ -148,6 +163,7 @@ class ProjectsController < ApplicationController
def unarchive
return access_denied! unless can?(current_user, :archive_project, @project)
@project.unarchive!
respond_to do |format|
......@@ -225,4 +241,14 @@ class ProjectsController < ApplicationController
render "go_import", layout: false
end
def repo_exists?
project.repository_exists? && !project.empty_repo?
end
# Override get_id from ExtractsPath, which returns the branch and file path
# for the blob/tree, which in this case is just the root of the default branch.
def get_id
project.repository.root_ref
end
end
......@@ -53,15 +53,36 @@ class IssuableFinder
end
end
def project?
params[:project_id].present?
end
def project
return @project if defined?(@project)
@project =
if params[:project_id].present?
Project.find(params[:project_id])
else
nil
end
if project?
@project = Project.find(params[:project_id])
unless Ability.abilities.allowed?(current_user, :read_project, @project)
@project = nil
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
def search
......@@ -72,7 +93,7 @@ class IssuableFinder
params[:milestone_title].present?
end
def no_milestones?
def filter_by_no_milestone?
milestones? && params[:milestone_title] == Milestone::None.title
end
......@@ -81,12 +102,22 @@ class IssuableFinder
@milestones =
if milestones?
Milestone.where(title: params[:milestone_title])
scope = Milestone.where(project_id: projects)
scope.where(title: params[:milestone_title])
else
nil
end
end
def labels?
params[:label_name].present?
end
def filter_by_no_label?
labels? && params[:label_name] == Label::None.title
end
def assignee?
params[:assignee_id].present?
end
......@@ -120,19 +151,7 @@ class IssuableFinder
private
def init_collection
table_name = klass.table_name
if project
if Ability.abilities.allowed?(current_user, :read_project, project)
project.send(table_name)
else
[]
end
elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project)
else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
end
klass.all
end
def by_scope(items)
......@@ -170,7 +189,12 @@ class IssuableFinder
end
def by_project(items)
items = items.of_projects(project.id) if project
items =
if projects
items.of_projects(projects).references(:project)
else
items.none
end
items
end
......@@ -185,18 +209,6 @@ class IssuableFinder
items.sort(params[:sort])
end
def by_milestone(items)
if milestones?
if no_milestones?
items = items.where(milestone_id: [-1, nil])
else
items = items.where(milestone_id: milestones.try(:pluck, :id))
end
end
items
end
def by_assignee(items)
if assignee?
items = items.where(assignee_id: assignee.try(:id))
......@@ -213,20 +225,36 @@ class IssuableFinder
items
end
def by_label(items)
if params[:label_name].present?
if params[:label_name] == Label::None.title
item_ids = LabelLink.where(target_type: klass.name).pluck(:target_id)
def by_milestone(items)
if milestones?
if filter_by_no_milestone?
items = items.where(milestone_id: [-1, nil])
else
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
if projects
items = items.where(milestones: { project_id: projects })
end
end
end
items
end
items = items.where('id NOT IN (?)', item_ids)
def by_label(items)
if labels?
if filter_by_no_label?
items = items.
joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
where(label_links: { id: nil })
else
label_names = params[:label_name].split(",")
item_ids = LabelLink.joins(:label).
where('labels.title in (?)', label_names).
where(target_type: klass.name).pluck(:target_id)
items = items.joins(:labels).where(labels: { title: label_names })
items = items.where(id: item_ids)
if projects
items = items.where(labels: { project_id: projects })
end
end
end
......
......@@ -19,7 +19,8 @@ module GitlabMarkdownHelper
escape_once(body)
end
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user)
user = current_user if defined?(current_user)
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
......@@ -45,29 +46,39 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
return "" unless text.present?
context.reverse_merge!(
current_user: current_user,
path: @path,
pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
Gitlab::Markdown.render(text, context)
user = current_user if defined?(current_user)
html = Gitlab::Markdown.render(text, context)
Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user)
end
# TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered
def gfm(text, options = {})
return "" unless text.present?
options.reverse_merge!(
current_user: current_user,
path: @path,
pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
Gitlab::Markdown.gfm(text, options)
user = current_user if defined?(current_user)
html = Gitlab::Markdown.gfm(text, options)
Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
end
def asciidoc(text)
......
......@@ -83,6 +83,10 @@ module IssuesHelper
end
end
def merge_requests_sentence(merge_requests)
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
end
# Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue
end
......@@ -92,11 +92,19 @@ module LabelsHelper
end
end
def project_labels_options(project)
labels = project.labels.to_a
labels.unshift(Label::None)
labels.unshift(Label::Any)
options_from_collection_for_select(labels, 'name', 'title', params[:label_name])
def projects_labels_options
labels =
if @project
@project.labels
else
Label.where(project_id: @projects)
end
grouped_labels = Labels::GroupService.new(labels).execute
grouped_labels.unshift(Label::None)
grouped_labels.unshift(Label::Any)
options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
end
# Required for Gitlab::Markdown::LabelReferenceFilter
......
......@@ -34,7 +34,8 @@ module PreferencesHelper
def project_view_choices
[
['Readme (default)', :readme],
['Activity view', :activity]
['Activity view', :activity],
['Files view', :files]
]
end
......@@ -46,8 +47,7 @@ module PreferencesHelper
Gitlab::ColorSchemes.for_user(current_user).css_class
end
def prefer_readme?
!current_user ||
current_user.project_view == 'readme'
def default_project_view
current_user ? current_user.project_view : 'readme'
end
end
......@@ -70,6 +70,10 @@ module ProjectsHelper
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end
def remove_fork_project_message(project)
"You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?"
end
def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
end
......@@ -113,7 +117,7 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
if can?(current_user, :read_build, project)
if project.gitlab_ci? && can?(current_user, :read_build, project)
nav_tabs << :builds
end
......
class AbuseReportMailer < BaseMailer
include Gitlab::CurrentSettings
def notify(abuse_report_id)
@abuse_report = AbuseReport.find(abuse_report_id)
mail(
to: current_application_settings.admin_notification_email,
subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
)
end
end
......@@ -189,7 +189,8 @@ class Ability
:change_visibility_level,
:rename_project,
:remove_project,
:archive_project
:archive_project,
:remove_fork_project
]
end
......
......@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
validates :admin_notification_email,
allow_blank: true,
email: true
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
......
......@@ -2,13 +2,13 @@ class Commit
extend ActiveModel::Naming
include ActiveModel::Conversion
include Mentionable
include Participable
include Mentionable
include Referable
include StaticModel
attr_mentionable :safe_message
participant :author, :committer, :notes, :mentioned_users
participant :author, :committer, :notes
attr_accessor :project
......
......@@ -6,8 +6,8 @@
#
module Issuable
extend ActiveSupport::Concern
include Mentionable
include Participable
include Mentionable
included do
belongs_to :author, class_name: "User"
......@@ -47,8 +47,7 @@ module Issuable
prefix: true
attr_mentionable :title, :description
participant :author, :assignee, :notes_with_associations, :mentioned_users
participant :author, :assignee, :notes_with_associations
end
module ClassMethods
......@@ -86,6 +85,10 @@ module Issuable
assignee_id_changed?
end
def open?
opened? || reopened?
end
#
# Votes
#
......
......@@ -20,6 +20,12 @@ module Mentionable
end
end
included do
if self < Participable
participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) }
end
end
# Returns the text used as the body of a Note when this object is referenced
#
# By default this will be the class name and the result of calling
......@@ -41,22 +47,22 @@ module Mentionable
self
end
def all_references(current_user = self.author, text = self.mentionable_text)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
ext.analyze(text)
ext
end
def mentioned_users(current_user = nil)
all_references(current_user).users.uniq
def mentioned_users(current_user = nil, load_lazy_references: true)
all_references(current_user, load_lazy_references: load_lazy_references).users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def referenced_mentionables(current_user = self.author, text = self.mentionable_text)
def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
return [] if text.blank?
refs = all_references(current_user, text)
(refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference]
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
......@@ -12,7 +12,7 @@
#
# # ...
#
# participant :author, :assignee, :mentioned_users, :notes
# participant :author, :assignee, :notes, ->(current_user) { mentioned_users(current_user) }
# end
#
# issue = Issue.last
......@@ -27,7 +27,7 @@ module Participable
module ClassMethods
def participant(*attrs)
participant_attrs.concat(attrs.map(&:to_s))
participant_attrs.concat(attrs)
end
def participant_attrs
......@@ -37,33 +37,39 @@ module Participable
# Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request
def participants(current_user = self.author)
self.class.participant_attrs.flat_map do |attr|
meth = method(attr)
def participants(current_user = self.author, load_lazy_references: true)
participants = self.class.participant_attrs.flat_map do |attr|
value =
if meth.arity == 1 || meth.arity == -1
meth.call(current_user)
if attr.respond_to?(:call)
instance_exec(current_user, &attr)
else
meth.call
send(attr)
end
participants_for(value, current_user)
end.compact.uniq.select do |user|
user.can?(:read_project, self.project)
end.compact.uniq
if load_lazy_references
participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
participants.select! do |user|
user.can?(:read_project, project)
end
end
participants
end
private
def participants_for(value, current_user = nil)
case value
when User
when User, Gitlab::Markdown::ReferenceFilter::LazyReference
[value]
when Enumerable, ActiveRecord::Relation
value.flat_map { |v| participants_for(v, current_user) }
when Participable
value.participants(current_user)
value.participants(current_user, load_lazy_references: false)
end
end
end
class GroupLabel
attr_accessor :title, :labels
alias_attribute :name, :title
def initialize(title, labels)
@title = title
@labels = labels
end
end
class GroupMilestone
attr_accessor :title, :milestones
alias_attribute :name, :title
def initialize(title, milestones)
......@@ -7,18 +7,10 @@ class GroupMilestone
@milestones = milestones
end
def title
@title
end
def safe_title
@title.parameterize
end
def milestones
@milestones
end
def projects
milestones.map { |milestone| milestone.project }
end
......
......@@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base
def source_project
project
end
# From all notes on this issue, we'll select the system notes about linked
# merge requests. Of those, the MRs closing `self` are returned.
def closed_by_merge_requests(current_user = nil)
return [] unless open?
notes.system.flat_map do |note|
note.all_references(current_user).merge_requests
end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
end
end
......@@ -222,10 +222,6 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
def open?
opened? || reopened?
end
def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i)
end
......@@ -294,6 +290,10 @@ class MergeRequest < ActiveRecord::Base
target_project
end
def closes_issue?(issue)
closes_issues.include?(issue)
end
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
......
......@@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base
def author_id
nil
end
# Sorts the issues for the given IDs.
#
# This method runs a single SQL query using a CASE statement to update the
# position of all issues in the current milestone (scoped to the list of IDs).
#
# Given the ids [10, 20, 30] this method produces a SQL query something like
# the following:
#
# UPDATE issues
# SET position = CASE
# WHEN id = 10 THEN 1
# WHEN id = 20 THEN 2
# WHEN id = 30 THEN 3
# ELSE position
# END
# WHERE id IN (10, 20, 30);
#
# This method expects that the IDs given in `ids` are already Fixnums.
def sort_issues(ids)
pairs = []
ids.each_with_index do |id, index|
pairs << id
pairs << index + 1
end
conditions = 'WHEN id = ? THEN ? ' * ids.length
issues.where(id: ids).
update_all(["position = CASE #{conditions} ELSE position END", *pairs])
end
end
......@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Note < ActiveRecord::Base
include Mentionable
include Gitlab::CurrentSettings
include Participable
include Mentionable
default_value_for :system, false
attr_mentionable :note
participant :author, :mentioned_users
participant :author
belongs_to :project
belongs_to :noteable, polymorphic: true
......
......@@ -183,7 +183,7 @@ class User < ActiveRecord::Base
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity]
enum project_view: [:readme, :activity, :files]
alias_attribute :private_token, :authentication_token
......@@ -706,12 +706,15 @@ class User < ActiveRecord::Base
end
def toggle_star(project)
user_star_project = users_star_projects.
where(project: project, user: self).take
if user_star_project
user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
UsersStarProject.transaction do
user_star_project = users_star_projects.
where(project: project, user: self).lock(true).first
if user_star_project
user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end
end
......
......@@ -79,7 +79,7 @@ class GitPushService
authors = Hash.new do |hash, commit|
email = commit.author_email
return hash[email] if hash.has_key?(email)
next hash[email] if hash.has_key?(email)
hash[email] = commit_user(commit)
end
......
module Labels
class GroupService < ::BaseService
def initialize(project_labels)
@project_labels = project_labels.group_by(&:title)
end
def execute
build(@project_labels)
end
def label(title)
if title
group_label = @project_labels[title].group_by(&:title)
build(group_label).first
else
nil
end
end
private
def build(label)
label.map { |title, labels| GroupLabel.new(title, labels) }
end
end
end
......@@ -6,6 +6,7 @@ module MergeRequests
#
class PostMergeService < MergeRequests::BaseService
def execute(merge_request)
close_issues(merge_request)
merge_request.mark_as_merged
create_merge_event(merge_request, current_user)
create_note(merge_request)
......@@ -15,6 +16,15 @@ module MergeRequests
private
def close_issues(merge_request)
return unless merge_request.target_branch == project.default_branch
closed_issues = merge_request.closes_issues(current_user)
closed_issues.each do |issue|
Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
end
end
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
......
%p
#{link_to @abuse_report.user.name, user_url(@abuse_report.user)}
(@#{@abuse_report.user.username}) was reported for abuse by
#{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)}
(@#{@abuse_report.reporter.username}).
%blockquote
= @abuse_report.message
%p
= link_to "View details", abuse_reports_url
#{@abuse_report.user.name} (@#{@abuse_report.user.username}) was reported for abuse by #{@abuse_report.reporter.name} (@#{@abuse_report.reporter.username}).
\
> #{@abuse_report.message}
\
View details: #{admin_abuse_reports_url}
......@@ -47,6 +47,12 @@
= f.label :version_check_enabled do
= f.check_box :version_check_enabled
Version check enabled
.form-group
= f.label :admin_notification_email, class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :admin_notification_email, class: 'form-control'
.help-block
Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
%fieldset
%legend Account and Limit Settings
......
......@@ -14,7 +14,7 @@
%strong
= link_to user_path(@user) do
= @user.username
= render 'users/profile', user: @user
= render 'admin/users/profile', user: @user
.panel.panel-default
.panel-heading
......
.wiki
%h1
GitLab CI is now integrated in GitLab UI
%h2 For existing projects
%p
Check the following pages to find the CI status you're looking for:
%ul
%li Projects page - shows CI status for each project.
%li Project commits page - show CI status for each commit.
%h2 For new projects
%p
If you want to enable CI for a new project it is easy as adding
= link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
file to your repository
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if issue.description.present?
= markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project)
= markdown(issue.description, pipeline: :atom, project: issue.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if merge_request.description.present?
= markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project)
= markdown(merge_request.description, pipeline: :atom, project: merge_request.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"}
= markdown(note.note, xhtml: true, reference_only_path: false, project: note.project)
= markdown(note.note, pipeline: :atom, project: note.project)
......@@ -6,7 +6,7 @@
%i
at
= commit[:timestamp].to_time.to_s(:short)
%blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project)
%blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project)
- if event.commits_count > 15
%p
%i
......
......@@ -41,4 +41,8 @@
#{link_to "view it on GitLab", @target_url}.
- else
#{link_to "View it on GitLab", @target_url}
%br
You're receiving this email because of your account on #{link_to Gitlab.config.gitlab.host, root_url}.
If you'd like to receive fewer emails, you can adjust your notification settings.
= email_action @target_url
%div
= markdown(@note.note, reference_only_path: false)
= markdown(@note.note, pipeline: :email)
-if @issue.description
= markdown(@issue.description, reference_only_path: false)
= markdown(@issue.description, pipeline: :email)
- if @issue.assignee_id.present?
%p
......
......@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-if @merge_request.description
= markdown(@merge_request.description, reference_only_path: false)
= markdown(@merge_request.description, pipeline: :email)
......@@ -38,7 +38,7 @@
.col-sm-10
= f.select :layout, layout_choices, {}, class: 'form-control'
.help-block
Choose between fixed (max. 1200px) and fluid (100%) application layout
Choose between fixed (max. 1200px) and fluid (100%) application layout.
.form-group
= f.label :dashboard, class: 'control-label' do
Default Dashboard
......@@ -52,6 +52,6 @@
.col-sm-10
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
Choose what content you want to see when visit project page
Choose what content you want to see on a project's home page.
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
= render 'projects/last_push'
.gray-content-block.activity-filter-block
- if current_user
.pull-right
......
#tree-holder.tree-holder.clearfix
.gray-content-block.second-block
= render 'projects/tree/tree_header', tree: @tree
= render 'projects/tree/tree_content', tree: @tree
- if readme = @repository.readme
%article.readme-holder#README
.clearfix
.pull-right
&nbsp;
- if can?(current_user, :push_code, @project)
= link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
%i.fa-align.fa.fa-pencil
.wiki
%article.file-holder.readme-holder
.file-title
= blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
%strong
= readme.name
.file-content.wiki
= cache(readme_cache_key) do
= render_readme(readme)
- else
......
- page_title "Activity"
- header_title project_title(@project, "Activity", activity_project_path(@project))
= render 'projects/last_push'
= render 'projects/activity'
%ul.breadcrumb.repo-breadcrumb
%li
%i.fa.fa-angle-right
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(@tree, 6) do |title, path|
.gray-content-block.top-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
%ul.breadcrumb.repo-breadcrumb
%li
- if path
- if path.end_with?(@path)
= link_to namespace_project_blob_path(@project.namespace, @project, path) do
%strong
= truncate(title, length: 40)
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(@tree, 6) do |title, path|
%li
- if path
- if path.end_with?(@path)
= link_to namespace_project_blob_path(@project.namespace, @project, path) do
%strong
= truncate(title, length: 40)
- else
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
= link_to title, '#'
%ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
......
......@@ -3,9 +3,6 @@
= render 'projects/last_push'
%div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
%div#tree-holder.tree-holder
= render 'blob', blob: @blob
......
- 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
= 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)
- when GroupMember
.btn.btn-new.disabled.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
= icon('bell')
= notification_label(@membership)
= icon('angle-down')
......@@ -6,7 +6,7 @@
%tr
%th
%th Service
%th Desription
%th Description
%th Last edit
- @services.sort_by(&:title).each do |service|
%tr
......
......@@ -189,6 +189,21 @@
- else
.nothing-here-block Only the project owner can transfer a project
- if @project.forked?
- if can?(current_user, :remove_fork_project, @project)
= form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
.panel.panel-default.panel.panel-danger
.panel-heading Remove fork relationship
.panel-body
%p
This will remove the fork relationship to source project
#{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
%br
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
= button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
- else
.nothing-here-block Only the project owner can remove the fork relationship.
- if can?(current_user, :remove_project, @project)
.panel.panel-default.panel.panel-danger
.panel-heading Remove project
......@@ -201,7 +216,8 @@
= button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else
.nothing-here-block Only project owner can remove a project
.nothing-here-block Only the project owner can remove a project.
.save-project-loader.hide
.center
......
......@@ -17,6 +17,6 @@
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}
.form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4
.issue-closed-by-widget
= icon('check')
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
......@@ -46,6 +46,7 @@
= markdown(@issue.description)
%textarea.hidden.js-task-list-field
= @issue.description
- if @closed_by_merge_requests.present?
= render 'projects/issues/closed_by_box'
.issue-discussion
= render 'projects/issues/discussion'
- if @merge_request.has_ci?
- ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
- if ci_commit
- status = ci_commit.status
.mr-widget-heading
.ci_widget{class: "ci-#{status}"}
= ci_status_icon(ci_commit)
- ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
- if ci_commit
- status = ci_commit.status
.mr-widget-heading
.ci_widget{class: "ci-#{status}"}
= 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}
for #{@merge_request.last_commit_short_sha}.
%span.ci-coverage
= link_to "View build details", ci_status_path(ci_commit)
- 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"
- 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
= icon("spinner spin")
Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
.ci_widget
= icon("spinner spin")
Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
.ci_widget.ci-not_found{style: "display:none"}
= icon("times-circle")
Could not find CI status for #{@merge_request.last_commit_short_sha}.
.ci_widget.ci-not_found{style: "display:none"}
= icon("times-circle")
Could not find CI status for #{@merge_request.last_commit_short_sha}.
.ci_widget.ci-error{style: "display:none"}
= icon("times-circle")
Could not connect to the CI server. Please check your settings and try again.
.ci_widget.ci-error{style: "display:none"}
= icon("times-circle")
Could not connect to the CI server. Please check your settings and try again.
:coffeescript
$ ->
merge_request_widget.getCiStatus()
:coffeescript
$ ->
merge_request_widget.getCiStatus()
:plain
location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
......@@ -7,8 +7,7 @@
= render 'shared/no_ssh'
= render 'shared/no_password'
- if prefer_readme?
= render 'projects/last_push'
= render 'projects/last_push'
= render "home_panel"
......@@ -28,7 +27,7 @@
= link_to project_files_path(@project) do
= repository_size
- if !prefer_readme? && @repository.readme
- if default_project_view != 'readme' && @repository.readme
%li
= link_to 'Readme', readme_path(@project)
......@@ -68,14 +67,8 @@
.content-block.second-block.white
= render 'projects/last_commit', commit: @repository.commit, project: @project
%section
- if prefer_readme?
.project-show-readme
= render 'projects/readme'
- else
.project-show-activity
= render 'projects/activity'
%div{class: "project-show-#{default_project_view}"}
= render default_project_view
- if current_user
- access = user_max_access_in_project(current_user, @project)
......
......@@ -4,5 +4,5 @@
%span.str-truncated
= link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name))
%td.tree_time_ago.cgray
= render 'spinner'
= render 'projects/tree/spinner'
%td.hidden-xs.tree_commit
%article.file-holder.readme-holder#README
%article.file-holder.readme-holder
.file-title
= link_to '#README' do
= blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
%strong
%i.fa.fa-file
= readme.name
.file-content.wiki
= render_readme(readme)
.gray-content-block
%ul.breadcrumb.repo-breadcrumb
%li
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(tree, 6) do |title, path|
%li
- if path
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
- if allowed_tree_edit?
%li
%span.dropdown
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
= icon('pencil fw')
Create file
%li
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
= icon('file fw')
Upload file
%li.divider
%li
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
= icon('folder fw')
New directory
%div#tree-content-holder.tree-content-holder
%div.tree-content-holder
.tree-table-holder
%table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
%thead
......@@ -60,8 +29,6 @@
- if tree.readme
= render "projects/tree/readme", readme: tree.readme
%div.tree_progress
- if allowed_tree_edit?
= render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
= render 'projects/blob/new_dir'
......
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
%ul.breadcrumb.repo-breadcrumb
%li
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(tree, 6) do |title, path|
%li
- if path
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
- if allowed_tree_edit?
%li
%span.dropdown
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
= icon('pencil fw')
Create file
%li
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
= icon('file fw')
Upload file
%li.divider
%li
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
= icon('folder fw')
New directory
......@@ -5,5 +5,5 @@
- path = flatten_tree(tree_item)
= link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
%td.tree_time_ago.cgray
= render 'spinner'
= render 'projects/tree/spinner'
%td.hidden-xs.tree_commit
......@@ -6,12 +6,12 @@
= render 'projects/last_push'
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
- if can? current_user, :download_code, @project
.tree-download-holder
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
#tree-holder.tree-holder.clearfix
= render "tree", tree: @tree
.gray-content-block.top-block
= render 'projects/tree/tree_header', tree: @tree
= render 'projects/tree/tree_content', tree: @tree
......@@ -6,7 +6,7 @@
type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
:"data-clone" => project.ssh_url_to_repo, |
:"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH",
:"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH.",
:"data-html" => "true",
:"data-container" => "body"}
SSH
......@@ -15,7 +15,7 @@
type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
:"data-clone" => project.http_url_to_repo, |
:"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}",
:"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}.",
:"data-html" => "true",
:"data-container" => "body"}
= gitlab_config.protocol.upcase
......
......@@ -42,11 +42,10 @@
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Milestone'})
- if @project
.filter-item.inline.labels-filter
= select_tag('label_name', project_labels_options(@project),
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Label'})
.filter-item.inline.labels-filter
= select_tag('label_name', projects_labels_options,
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Label'})
.pull-right
= render 'shared/sort_dropdown'
......
%h4
Contributions calendar
.pull-right
%small Issues, merge requests and push events
#cal-heatmap.calendar
:javascript
new Calendar(
......@@ -10,3 +6,5 @@
#{@starting_month},
'#{user_calendar_activities_path}'
);
.calendar-hint Summary of issues, merge requests and push events
......@@ -6,47 +6,72 @@
= render 'shared/show_aside'
.row
%section.col-md-7
.header-with-avatar
= link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: ''
%h3
= @user.name
- if @user == current_user
.pull-right.hidden-xs
= link_to profile_path, class: 'btn btn-sm' do
= icon('user')
Profile settings
- elsif current_user
.report_abuse.pull-right
- if @user.abuse_report
%span#report_abuse_btn.light.btn.btn-sm.btn-close{title: 'Already reported for abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
= icon('exclamation-circle')
- else
%a.light.btn.btn-sm{href: new_abuse_report_path(user_id: @user.id), title: 'Report abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
= icon('exclamation-circle')
.cover-block
.avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
.cover-title
= @user.name
.cover-desc
%span
@#{@user.username}.
- if @user.bio.present?
%span
#{@user.bio}.
%span
Member since #{@user.created_at.stamp("Aug 21, 2011")}
.cover-desc
- unless @user.public_email.blank?
= link_to @user.public_email, "mailto:#{@user.public_email}"
- unless @user.skype.blank?
&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?
.prepend-top-20
%h4 Groups
= render 'groups', groups: @groups
%hr
.hidden-xs
.user-calendar
%h4.center.light
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
%hr
%h4
User Activity
......@@ -59,7 +84,6 @@
.content_list
= spinner
%aside.col-md-5
= render 'profile', user: @user
= render 'projects', projects: @projects, contributed_projects: @contributed_projects
:coffeescript
......
......@@ -378,6 +378,7 @@ Gitlab::Application.routes.draw do
[:new, :create, :index], path: "/") do
member do
put :transfer
delete :remove_fork
post :archive
post :unarchive
post :toggle_star
......
Gitlab::Seeder.quiet do
(2..20).each do |i|
20.times do |i|
begin
User.create!(
username: FFaker::Internet.user_name,
......@@ -15,7 +15,7 @@ Gitlab::Seeder.quiet do
end
end
(1..5).each do |i|
5.times do |i|
begin
User.create!(
username: "user#{i}",
......
Gitlab::Seeder.quiet do
Project.all.each do |project|
(1..5).each do |i|
5.times do |i|
milestone_params = {
title: "v#{i}.0",
description: FFaker::Lorem.sentence,
......
Gitlab::Seeder.quiet do
Project.all.each do |project|
(1..10).each do |i|
10.times do
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
......
......@@ -22,7 +22,7 @@ class Member < ActiveRecord::Base
end
eos
(1..50).each do |i|
50.times do |i|
user = User.all.sample
PersonalSnippet.seed(:id, [{
......
class AddAdminNotificationEmailSetting < ActiveRecord::Migration
def change
add_column :application_settings, :admin_notification_email, :string
end
end
class AddCiBuildsAndProjectsIndexes < ActiveRecord::Migration
def change
add_index :ci_projects, :gitlab_id
add_index :ci_projects, :shared_runners_enabled
add_index :ci_builds, :type
add_index :ci_builds, :status
end
end
class AddNotesLineCodeIndex < ActiveRecord::Migration
def change
add_index :notes, :line_code
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151016131433) do
ActiveRecord::Schema.define(version: 20151016195706) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -46,6 +46,7 @@ ActiveRecord::Schema.define(version: 20151016131433) do
t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
t.text "help_page_text"
t.string "admin_notification_email"
end
create_table "audit_events", force: true do |t|
......@@ -115,6 +116,8 @@ ActiveRecord::Schema.define(version: 20151016131433) do
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree
create_table "ci_commits", force: true do |t|
t.integer "project_id"
......@@ -190,6 +193,9 @@ ActiveRecord::Schema.define(version: 20151016131433) do
t.text "generated_yaml_config"
end
add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree
add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
create_table "ci_runner_projects", force: true do |t|
t.integer "runner_id", null: false
t.integer "project_id", null: false
......@@ -530,6 +536,7 @@ ActiveRecord::Schema.define(version: 20151016131433) do
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
......
......@@ -325,6 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server
sudo -u git -H git checkout 0.3.0
sudo -u git -H make
### Initialize Database and Activate Advanced Features
......
......@@ -84,6 +84,7 @@ Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server
sudo -u git -H git checkout 0.2.14
sudo -u git -H make
```
......
......@@ -23,7 +23,7 @@ class Spinach::Features::AbuseReports < Spinach::FeatureSteps
end
step 'I should see a red "Report abuse" button' do
expect(find(:css, '.report_abuse')).to have_selector(:css, 'span.btn-close')
expect(page).to have_button("Already reported for abuse")
end
def user_mike
......
......@@ -86,13 +86,13 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
page.within('#README') do
page.within('.readme-holder') do
expect(page).to have_content 'Sample repo for testing gitlab features'
end
end
step 'I should see project "Shop" README' do
page.within('#README') do
page.within('.readme-holder') do
expect(page).to have_content 'testme'
end
end
......
......@@ -246,8 +246,8 @@ module API
# Example Request:
# DELETE /projects/:id/fork
delete ":id/fork" do
authenticated_as_admin!
unless user_project.forked_project_link.nil?
authorize! :remove_fork_project, user_project
if user_project.forked?
user_project.forked_project_link.destroy
end
end
......
......@@ -7,6 +7,14 @@ module Gitlab
module Markdown
# Convert a Markdown String into an HTML-safe String of HTML
#
# Note that while the returned HTML will have been sanitized of dangerous
# HTML, it may post a risk of information leakage if it's not also passed
# through `post_process`.
#
# Also note that the returned String is always HTML, not XHTML. Views
# requiring XHTML, such as Atom feeds, need to call `post_process` on the
# result, providing the appropriate `pipeline` option.
#
# markdown - Markdown String
# context - Hash of context options passed to our HTML Pipeline
#
......@@ -31,6 +39,33 @@ module Gitlab
renderer.render(markdown)
end
# Perform post-processing on an HTML String
#
# This method is used to perform state-dependent changes to a String of
# HTML, such as removing references that the current user doesn't have
# permission to make (`RedactorFilter`).
#
# html - String to process
# options - Hash of options to customize output
# :pipeline - Symbol pipeline type
# :project - Project
# :user - User object
#
# Returns an HTML-safe String
def self.post_process(html, options)
context = {
project: options[:project],
current_user: options[:user]
}
doc = post_processor.to_document(html, context)
if options[:pipeline] == :atom
doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
else
doc.to_html
end.html_safe
end
# Provide autoload paths for filters to prevent a circular dependency error
autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
......@@ -41,6 +76,7 @@ module Gitlab
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
......@@ -50,26 +86,20 @@ module Gitlab
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
# Public: Parse the provided text with GitLab-Flavored Markdown
# Public: Parse the provided HTML with GitLab-Flavored Markdown
#
# 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
# options - A Hash of options used to customize output (default: {}):
# :xhtml - output XHTML instead of HTML
# :reference_only_path - Use relative path for reference links
def self.gfm(text, options = {})
return text if text.nil?
# Duplicate the string so we don't alter the original, then call to_str
# to cast it back to a String instead of a SafeBuffer. This is required
# for gsub calls to work as we need them to.
text = text.dup.to_str
options.reverse_merge!(
xhtml: false,
reference_only_path: true,
project: options[:project],
current_user: options[:current_user]
)
# Returns an HTML-safe String
def self.gfm(html, options = {})
return '' unless html.present?
@pipeline ||= HTML::Pipeline.new(filters)
......@@ -78,41 +108,36 @@ module Gitlab
pipeline: options[:pipeline],
# EmojiFilter
asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host,
# TableOfContentsFilter
no_header_anchors: options[:no_header_anchors],
asset_root: Gitlab.config.gitlab.base_url,
# ReferenceFilter
current_user: options[:current_user],
only_path: options[:reference_only_path],
project: options[:project],
only_path: only_path_pipeline?(options[:pipeline]),
project: options[:project],
# RelativeLinkFilter
project_wiki: options[:project_wiki],
ref: options[:ref],
requested_path: options[:path],
project_wiki: options[:project_wiki]
}
result = @pipeline.call(text, context)
save_options = 0
if options[:xhtml]
save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
end
text = result[:output].to_html(save_with: save_options)
# TableOfContentsFilter
no_header_anchors: options[:no_header_anchors]
}
text.html_safe
@pipeline.to_html(html, context).html_safe
end
private
def self.renderer
@markdown ||= begin
renderer = Redcarpet::Render::HTML.new
Redcarpet::Markdown.new(renderer, redcarpet_options)
# Check if a pipeline enables the `only_path` context option
#
# Returns Boolean
def self.only_path_pipeline?(pipeline)
case pipeline
when :atom, :email
false
else
true
end
end
......@@ -130,6 +155,17 @@ module Gitlab
}.freeze
end
def self.renderer
@markdown ||= begin
renderer = Redcarpet::Render::HTML.new
Redcarpet::Markdown.new(renderer, redcarpet_options)
end
end
def self.post_processor
@post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
end
# Filters used in our pipeline
#
# SanitizationFilter should come first so that all generated reference HTML
......
......@@ -26,6 +26,18 @@ module Gitlab
end
end
def self.referenced_by(node)
project = Project.find(node.attr("data-project")) rescue nil
return unless project
id = node.attr("data-commit-range")
range = CommitRange.new(id, project)
return unless range.valid_commits?
{ commit_range: range }
end
def initialize(*args)
super
......@@ -53,13 +65,11 @@ module Gitlab
range = CommitRange.new(id, project)
if range.valid_commits?
push_result(:commit_range, range)
url = url_for_commit_range(project, range)
title = range.reference_title
klass = reference_class(:commit_range)
data = data_attribute(project.id)
data = data_attribute(project: project.id, commit_range: id)
project_ref += '@' if project_ref
......
......@@ -26,6 +26,18 @@ module Gitlab
end
end
def self.referenced_by(node)
project = Project.find(node.attr("data-project")) rescue nil
return unless project
id = node.attr("data-commit")
commit = commit_from_ref(project, id)
return unless commit
{ commit: commit }
end
def call
replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content)
......@@ -39,17 +51,15 @@ module Gitlab
# Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text)
self.class.references_in(text) do |match, commit_ref, project_ref|
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
if commit = commit_from_ref(project, commit_ref)
push_result(:commit, commit)
if commit = self.class.commit_from_ref(project, id)
url = url_for_commit(project, commit)
title = escape_once(commit.link_title)
klass = reference_class(:commit)
data = data_attribute(project.id)
data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref
......@@ -62,9 +72,9 @@ module Gitlab
end
end
def commit_from_ref(project, commit_ref)
def self.commit_from_ref(project, id)
if project && project.valid_repo?
project.commit(commit_ref)
project.commit(id)
end
end
......
......@@ -13,18 +13,11 @@ module Gitlab
#
# ref - String reference.
#
# Returns a Project, or nil if the reference can't be accessed
# Returns a Project, or nil if the reference can't be found
def project_from_ref(ref)
return context[:project] unless ref
other = Project.find_with_namespace(ref)
return nil unless other && user_can_reference_project?(other)
other
end
def user_can_reference_project?(project, user = context[:current_user])
Ability.abilities.allowed?(user, :read_project, project)
Project.find_with_namespace(ref)
end
end
end
......
......@@ -47,8 +47,9 @@ module Gitlab
title = escape_once("Issue in #{project.external_issue_tracker.title}")
klass = reference_class(:issue)
data = data_attribute(project: project.id)
%(<a href="#{url}"
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{match}</a>)
end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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