Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into git-archive-improvements-2
This commit is contained in:
commit
e4008bc415
138 changed files with 1787 additions and 812 deletions
16
CHANGELOG
16
CHANGELOG
|
@ -1,12 +1,21 @@
|
|||
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)
|
||||
|
|
1
GITLAB_GIT_HTTP_SERVER_VERSION
Normal file
1
GITLAB_GIT_HTTP_SERVER_VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
0.3.0
|
|
@ -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: []
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
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
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
class ProjectsController < ApplicationController
|
||||
include ExtractsPath
|
||||
|
||||
prepend_before_filter :render_go_import, only: [:show]
|
||||
skip_before_action :authenticate_user!, only: [:show, :activity]
|
||||
before_action :project, except: [:new, :create]
|
||||
before_action :repository, except: [:new, :create]
|
||||
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
|
||||
before_action :authorize_admin_project!, only: [:edit, :update]
|
||||
before_action :event_filter, only: [:show, :activity]
|
||||
|
||||
layout :determine_layout
|
||||
|
@ -56,6 +59,8 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def transfer
|
||||
return access_denied! unless can?(current_user, :change_namespace, @project)
|
||||
|
||||
namespace = Namespace.find_by(id: params[:new_namespace_id])
|
||||
::Projects::TransferService.new(project, current_user).execute(namespace)
|
||||
|
||||
|
@ -64,6 +69,15 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def remove_fork
|
||||
return access_denied! unless can?(current_user, :remove_fork_project, @project)
|
||||
|
||||
if @project.forked?
|
||||
@project.forked_project_link.destroy
|
||||
flash[:notice] = 'The fork relationship has been removed.'
|
||||
end
|
||||
end
|
||||
|
||||
def activity
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
@ -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] })
|
||||
|
||||
items = items.where('id NOT IN (?)', item_ids)
|
||||
if projects
|
||||
items = items.where(milestones: { project_id: projects })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
items
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
12
app/mailers/abuse_report_mailer.rb
Normal file
12
app/mailers/abuse_report_mailer.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
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
|
||||
|
|
9
app/models/group_label.rb
Normal file
9
app/models/group_label.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class GroupLabel
|
||||
attr_accessor :title, :labels
|
||||
alias_attribute :name, :title
|
||||
|
||||
def initialize(title, labels)
|
||||
@title = title
|
||||
@labels = labels
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
|
|
26
app/services/labels/group_service.rb
Normal file
26
app/services/labels/group_service.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
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
|
||||
|
|
11
app/views/abuse_report_mailer/notify.html.haml
Normal file
11
app/views/abuse_report_mailer/notify.html.haml
Normal file
|
@ -0,0 +1,11 @@
|
|||
%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
|
5
app/views/abuse_report_mailer/notify.text.haml
Normal file
5
app/views/abuse_report_mailer/notify.text.haml
Normal file
|
@ -0,0 +1,5 @@
|
|||
#{@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
|
||||
|
|
20
app/views/ci/projects/index.html.haml
Normal file
20
app/views/ci/projects/index.html.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
.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
|
|
@ -1,3 +1,2 @@
|
|||
%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)
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
%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)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
%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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
%div
|
||||
= markdown(@note.note, reference_only_path: false)
|
||||
= markdown(@note.note, pipeline: :email)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
-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} → #{@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'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
= render 'projects/last_push'
|
||||
.gray-content-block.activity-filter-block
|
||||
- if current_user
|
||||
.pull-right
|
||||
|
|
6
app/views/projects/_files.html.haml
Normal file
6
app/views/projects/_files.html.haml
Normal file
|
@ -0,0 +1,6 @@
|
|||
#tree-holder.tree-holder.clearfix
|
||||
.gray-content-block.second-block
|
||||
= render 'projects/tree/tree_header', tree: @tree
|
||||
|
||||
= render 'projects/tree/tree_content', tree: @tree
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
- if readme = @repository.readme
|
||||
%article.readme-holder#README
|
||||
.clearfix
|
||||
.pull-right
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
= link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
|
||||
%i.fa-align.fa.fa-pencil
|
||||
.wiki
|
||||
%article.file-holder.readme-holder
|
||||
.file-title
|
||||
= blob_icon readme.mode, readme.name
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
|
||||
%strong
|
||||
= readme.name
|
||||
.file-content.wiki
|
||||
= cache(readme_cache_key) do
|
||||
= render_readme(readme)
|
||||
- else
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
- page_title "Activity"
|
||||
- header_title project_title(@project, "Activity", activity_project_path(@project))
|
||||
|
||||
= render 'projects/last_push'
|
||||
|
||||
= render 'projects/activity'
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
%i.fa.fa-angle-right
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(@tree, 6) do |title, path|
|
||||
.gray-content-block.top-block
|
||||
.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'blob', path: @path
|
||||
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
- if path
|
||||
- if path.end_with?(@path)
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, path) do
|
||||
%strong
|
||||
= truncate(title, length: 40)
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(@tree, 6) do |title, path|
|
||||
%li
|
||||
- if path
|
||||
- if path.end_with?(@path)
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, path) do
|
||||
%strong
|
||||
= truncate(title, length: 40)
|
||||
- else
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to title, '#'
|
||||
= link_to title, '#'
|
||||
|
||||
%ul.blob-commit-info.hidden-xs
|
||||
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
= render 'projects/last_push'
|
||||
|
||||
%div.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'blob', path: @path
|
||||
|
||||
%div#tree-holder.tree-holder
|
||||
= render 'blob', blob: @blob
|
||||
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
- 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
|
||||
|
|
3
app/views/projects/issues/_closed_by_box.html.haml
Normal file
3
app/views/projects/issues/_closed_by_box.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
.issue-closed-by-widget
|
||||
= icon('check')
|
||||
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
|
|
@ -46,6 +46,7 @@
|
|||
= markdown(@issue.description)
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @issue.description
|
||||
|
||||
- if @closed_by_merge_requests.present?
|
||||
= render 'projects/issues/closed_by_box'
|
||||
.issue-discussion
|
||||
= render 'projects/issues/discussion'
|
||||
|
|
|
@ -1,44 +1,43 @@
|
|||
- 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)
|
||||
- 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
|
||||
= icon("spinner spin")
|
||||
Checking CI status for #{@merge_request.last_commit_short_sha}…
|
||||
|
||||
.ci_widget
|
||||
= icon("spinner spin")
|
||||
Checking 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-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()
|
||||
|
|
2
app/views/projects/remove_fork.js.haml
Normal file
2
app/views/projects/remove_fork.js.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
:plain
|
||||
location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
|
|
@ -7,8 +7,7 @@
|
|||
= render 'shared/no_ssh'
|
||||
= render 'shared/no_password'
|
||||
|
||||
- if prefer_readme?
|
||||
= render 'projects/last_push'
|
||||
= render 'projects/last_push'
|
||||
|
||||
= render "home_panel"
|
||||
|
||||
|
@ -28,7 +27,7 @@
|
|||
= link_to project_files_path(@project) do
|
||||
= repository_size
|
||||
|
||||
- if !prefer_readme? && @repository.readme
|
||||
- if default_project_view != 'readme' && @repository.readme
|
||||
%li
|
||||
= link_to 'Readme', readme_path(@project)
|
||||
|
||||
|
@ -68,14 +67,8 @@
|
|||
.content-block.second-block.white
|
||||
= render 'projects/last_commit', commit: @repository.commit, project: @project
|
||||
|
||||
%section
|
||||
- if prefer_readme?
|
||||
.project-show-readme
|
||||
= render 'projects/readme'
|
||||
- else
|
||||
.project-show-activity
|
||||
= render 'projects/activity'
|
||||
|
||||
%div{class: "project-show-#{default_project_view}"}
|
||||
= render default_project_view
|
||||
|
||||
- if current_user
|
||||
- access = user_max_access_in_project(current_user, @project)
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
%span.str-truncated
|
||||
= link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name))
|
||||
%td.tree_time_ago.cgray
|
||||
= render 'spinner'
|
||||
= render 'projects/tree/spinner'
|
||||
%td.hidden-xs.tree_commit
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
%article.file-holder.readme-holder#README
|
||||
%article.file-holder.readme-holder
|
||||
.file-title
|
||||
= link_to '#README' do
|
||||
= blob_icon readme.mode, readme.name
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
|
||||
%strong
|
||||
%i.fa.fa-file
|
||||
= readme.name
|
||||
.file-content.wiki
|
||||
= render_readme(readme)
|
||||
|
|
|
@ -1,35 +1,4 @@
|
|||
.gray-content-block
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(tree, 6) do |title, path|
|
||||
%li
|
||||
- if path
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to title, '#'
|
||||
- if allowed_tree_edit?
|
||||
%li
|
||||
%span.dropdown
|
||||
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
|
||||
= icon('plus')
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
|
||||
= icon('pencil fw')
|
||||
Create file
|
||||
%li
|
||||
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
|
||||
= icon('file fw')
|
||||
Upload file
|
||||
%li.divider
|
||||
%li
|
||||
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
|
||||
= icon('folder fw')
|
||||
New directory
|
||||
|
||||
%div#tree-content-holder.tree-content-holder
|
||||
%div.tree-content-holder
|
||||
.tree-table-holder
|
||||
%table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
|
||||
%thead
|
||||
|
@ -60,8 +29,6 @@
|
|||
- if tree.readme
|
||||
= render "projects/tree/readme", readme: tree.readme
|
||||
|
||||
%div.tree_progress
|
||||
|
||||
- if allowed_tree_edit?
|
||||
= render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
|
||||
= render 'projects/blob/new_dir'
|
32
app/views/projects/tree/_tree_header.html.haml
Normal file
32
app/views/projects/tree/_tree_header.html.haml
Normal file
|
@ -0,0 +1,32 @@
|
|||
.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'tree', path: @path
|
||||
|
||||
%ul.breadcrumb.repo-breadcrumb
|
||||
%li
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
|
||||
= @project.path
|
||||
- tree_breadcrumbs(tree, 6) do |title, path|
|
||||
%li
|
||||
- if path
|
||||
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
|
||||
- else
|
||||
= link_to title, '#'
|
||||
- if allowed_tree_edit?
|
||||
%li
|
||||
%span.dropdown
|
||||
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
|
||||
= icon('plus')
|
||||
%ul.dropdown-menu
|
||||
%li
|
||||
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
|
||||
= icon('pencil fw')
|
||||
Create file
|
||||
%li
|
||||
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
|
||||
= icon('file fw')
|
||||
Upload file
|
||||
%li.divider
|
||||
%li
|
||||
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
|
||||
= icon('folder fw')
|
||||
New directory
|
|
@ -5,5 +5,5 @@
|
|||
- path = flatten_tree(tree_item)
|
||||
= link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
|
||||
%td.tree_time_ago.cgray
|
||||
= render 'spinner'
|
||||
= render 'projects/tree/spinner'
|
||||
%td.hidden-xs.tree_commit
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
= render 'projects/last_push'
|
||||
|
||||
.tree-ref-holder
|
||||
= render 'shared/ref_switcher', destination: 'tree', path: @path
|
||||
|
||||
- if can? current_user, :download_code, @project
|
||||
.tree-download-holder
|
||||
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
|
||||
|
||||
#tree-holder.tree-holder.clearfix
|
||||
= render "tree", tree: @tree
|
||||
.gray-content-block.top-block
|
||||
= render 'projects/tree/tree_header', tree: @tree
|
||||
|
||||
= render 'projects/tree/tree_content', tree: @tree
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
%h4
|
||||
Contributions calendar
|
||||
.pull-right
|
||||
%small Issues, merge requests and push events
|
||||
#cal-heatmap.calendar
|
||||
:javascript
|
||||
new Calendar(
|
||||
|
@ -10,3 +6,5 @@
|
|||
#{@starting_month},
|
||||
'#{user_calendar_activities_path}'
|
||||
);
|
||||
|
||||
.calendar-hint Summary of issues, merge requests and push events
|
||||
|
|
|
@ -6,47 +6,72 @@
|
|||
|
||||
= render 'shared/show_aside'
|
||||
|
||||
.row
|
||||
.cover-block
|
||||
.avatar-holder
|
||||
= link_to avatar_icon(@user, 400), target: '_blank' do
|
||||
= image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
|
||||
.cover-title
|
||||
= @user.name
|
||||
|
||||
.cover-desc
|
||||
%span
|
||||
@#{@user.username}.
|
||||
- if @user.bio.present?
|
||||
%span
|
||||
#{@user.bio}.
|
||||
%span
|
||||
Member since #{@user.created_at.stamp("Aug 21, 2011")}
|
||||
|
||||
.cover-desc
|
||||
- unless @user.public_email.blank?
|
||||
= link_to @user.public_email, "mailto:#{@user.public_email}"
|
||||
- unless @user.skype.blank?
|
||||
·
|
||||
= link_to "Skype", "skype:#{@user.skype}"
|
||||
- unless @user.linkedin.blank?
|
||||
·
|
||||
= link_to "LinkedIn", "http://www.linkedin.com/in/#{@user.linkedin}"
|
||||
- unless @user.twitter.blank?
|
||||
·
|
||||
= link_to "Twitter", "http://www.twitter.com/#{@user.twitter}"
|
||||
- unless @user.website_url.blank?
|
||||
·
|
||||
= link_to @user.short_website_url, @user.full_website_url
|
||||
- unless @user.location.blank?
|
||||
·
|
||||
= @user.location
|
||||
|
||||
|
||||
.cover-controls
|
||||
- if @user == current_user
|
||||
= link_to profile_path, class: 'btn btn-gray' do
|
||||
= icon('pencil')
|
||||
- elsif current_user
|
||||
.report-abuse
|
||||
- if @user.abuse_report
|
||||
%button.btn.btn-danger{ title: 'Already reported for abuse',
|
||||
data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
|
||||
= icon('exclamation-circle')
|
||||
- else
|
||||
= link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
|
||||
title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
|
||||
= icon('exclamation-circle')
|
||||
|
||||
.gray-content-block.second-block
|
||||
.user-calendar
|
||||
%h4.center.light
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
.user-calendar-activities
|
||||
|
||||
|
||||
.row.prepend-top-20
|
||||
%section.col-md-7
|
||||
.header-with-avatar
|
||||
= link_to avatar_icon(@user, 400), target: '_blank' do
|
||||
= image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: ''
|
||||
%h3
|
||||
= @user.name
|
||||
- if @user == current_user
|
||||
.pull-right.hidden-xs
|
||||
= link_to profile_path, class: 'btn btn-sm' do
|
||||
= icon('user')
|
||||
Profile settings
|
||||
- elsif current_user
|
||||
.report_abuse.pull-right
|
||||
- if @user.abuse_report
|
||||
%span#report_abuse_btn.light.btn.btn-sm.btn-close{title: 'Already reported for abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
|
||||
= icon('exclamation-circle')
|
||||
- else
|
||||
%a.light.btn.btn-sm{href: new_abuse_report_path(user_id: @user.id), title: 'Report abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
|
||||
= icon('exclamation-circle')
|
||||
|
||||
.username
|
||||
@#{@user.username}
|
||||
.description
|
||||
- if @user.bio.present?
|
||||
= @user.bio
|
||||
|
||||
.clearfix
|
||||
|
||||
- if @groups.any?
|
||||
.prepend-top-20
|
||||
%h4 Groups
|
||||
= render 'groups', groups: @groups
|
||||
%hr
|
||||
|
||||
.hidden-xs
|
||||
.user-calendar
|
||||
%h4.center.light
|
||||
%i.fa.fa-spinner.fa-spin
|
||||
.user-calendar-activities
|
||||
%hr
|
||||
%h4
|
||||
User Activity
|
||||
|
||||
|
@ -59,7 +84,6 @@
|
|||
.content_list
|
||||
= spinner
|
||||
%aside.col-md-5
|
||||
= render 'profile', user: @user
|
||||
= render 'projects', projects: @projects, contributed_projects: @contributed_projects
|
||||
|
||||
:coffeescript
|
||||
|
|
|
@ -378,6 +378,7 @@ Gitlab::Application.routes.draw do
|
|||
[:new, :create, :index], path: "/") do
|
||||
member do
|
||||
put :transfer
|
||||
delete :remove_fork
|
||||
post :archive
|
||||
post :unarchive
|
||||
post :toggle_star
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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}",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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, [{
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddAdminNotificationEmailSetting < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :application_settings, :admin_notification_email, :string
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
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
|
5
db/migrate/20151016195706_add_notes_line_code_index.rb
Normal file
5
db/migrate/20151016195706_add_notes_line_code_index.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
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
|
||||
#
|
||||
# text - the source text
|
||||
# options - A Hash of options used to customize output (default: {}):
|
||||
# :xhtml - output XHTML instead of HTML
|
||||
# :reference_only_path - Use relative path for reference links
|
||||
def self.gfm(text, options = {})
|
||||
return text if text.nil?
|
||||
|
||||
# Duplicate the string so we don't alter the original, then call to_str
|
||||
# to cast it back to a String instead of a SafeBuffer. This is required
|
||||
# for gsub calls to work as we need them to.
|
||||
text = text.dup.to_str
|
||||
|
||||
options.reverse_merge!(
|
||||
xhtml: false,
|
||||
reference_only_path: true,
|
||||
project: options[:project],
|
||||
current_user: options[:current_user]
|
||||
)
|
||||
# html - HTML String
|
||||
# options - A Hash of options used to customize output (default: {})
|
||||
# :no_header_anchors - Disable header anchors in TableOfContentsFilter
|
||||
# :path - Current path String
|
||||
# :pipeline - Symbol pipeline type
|
||||
# :project - Current Project object
|
||||
# :project_wiki - Current ProjectWiki object
|
||||
# :ref - Current ref String
|
||||
#
|
||||
# Returns an HTML-safe String
|
||||
def self.gfm(html, options = {})
|
||||
return '' unless html.present?
|
||||
|
||||
@pipeline ||= HTML::Pipeline.new(filters)
|
||||
|
||||
|
@ -78,41 +108,36 @@ module Gitlab
|
|||
pipeline: options[:pipeline],
|
||||
|
||||
# EmojiFilter
|
||||
asset_root: Gitlab.config.gitlab.base_url,
|
||||
asset_host: Gitlab::Application.config.asset_host,
|
||||
|
||||
# TableOfContentsFilter
|
||||
no_header_anchors: options[:no_header_anchors],
|
||||
asset_root: Gitlab.config.gitlab.base_url,
|
||||
|
||||
# ReferenceFilter
|
||||
current_user: options[:current_user],
|
||||
only_path: options[:reference_only_path],
|
||||
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]
|
||||
|
||||
# TableOfContentsFilter
|
||||
no_header_anchors: options[:no_header_anchors]
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue