Commit 90c338a4 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Move helpers back to original directory

parent cc4ef4da
require 'gon'
class ApplicationController < ActionController::Base
def self.railtie_helpers_paths
"app/helpers/gitlab"
end
include Gitlab::CurrentSettings
include Gitlab::GitlabRoutingHelper
include Gitlab::PageLayoutHelper
include GitlabRoutingHelper
include PageLayoutHelper
PER_PAGE = 20
......
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings
include Gitlab::PageLayoutHelper
include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
......
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
include Gitlab::PageLayoutHelper
include PageLayoutHelper
layout 'profile'
......
class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath
include Gitlab::ApplicationHelper
include ApplicationHelper
before_action :require_non_empty_project
before_action :assign_ref_vars
......
class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
include Gitlab::TreeHelper
include TreeHelper
before_action :require_non_empty_project
before_action :assign_ref_vars
......
......@@ -5,7 +5,7 @@ class Projects::WikisController < Projects::ApplicationController
before_action :authorize_create_wiki!, only: [:edit, :create, :history]
before_action :authorize_admin_wiki!, only: :destroy
before_action :load_project_wiki
include Gitlab::WikiHelper
#include WikiHelper
def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE)
......
class SearchController < ApplicationController
include Gitlab::SearchHelper
include SearchHelper
layout 'search'
......
module AppearancesHelper
def brand_item
nil
end
def brand_title
'GitLab Community Edition'
end
def brand_image
nil
end
def brand_text
nil
end
def brand_header_logo
image_tag 'logo.svg'
end
end
require 'digest/md5'
require 'uri'
module ApplicationHelper
# Check if a particular controller is the current one
#
# args - One or more controller names to check
#
# Examples
#
# # On TreeController
# current_controller?(:tree) # => true
# current_controller?(:commits) # => false
# current_controller?(:commits, :tree) # => true
def current_controller?(*args)
args.any? { |v| v.to_s.downcase == controller.controller_name }
end
# Check if a particular action is the current one
#
# args - One or more action names to check
#
# Examples
#
# # On Projects#new
# current_action?(:new) # => true
# current_action?(:create) # => false
# current_action?(:new, :create) # => true
def current_action?(*args)
args.any? { |v| v.to_s.downcase == action_name }
end
def project_icon(project_id, options = {})
project =
if project_id.is_a?(Project)
project = project_id
else
Project.find_with_namespace(project_id)
end
if project.avatar_url
image_tag project.avatar_url, options
else # generated icon
project_identicon(project, options)
end
end
def project_identicon(project, options = {})
allowed_colors = {
red: 'FFEBEE',
purple: 'F3E5F5',
indigo: 'E8EAF6',
blue: 'E3F2FD',
teal: 'E0F2F1',
orange: 'FBE9E7',
gray: 'EEEEEE'
}
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555"
content_tag(:div, class: options[:class], style: style) do
project.name[0, 1].upcase
end
end
def avatar_icon(user_email = '', size = nil)
user = User.find_by(email: user_email)
if user
user.avatar_url(size) || default_avatar
else
gravatar_icon(user_email, size)
end
end
def gravatar_icon(user_email = '', size = nil)
GravatarService.new.execute(user_email, size) ||
default_avatar
end
def default_avatar
image_path('no_avatar.png')
end
def last_commit(project)
if project.repo_exists?
time_ago_with_tooltip(project.repository.commit.committed_date)
else
'Never'
end
rescue
'Never'
end
def grouped_options_refs
repository = @project.repository
options = [
['Branches', repository.branch_names],
['Tags', VersionSorter.rsort(repository.tag_names)]
]
# If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /\A[0-9a-zA-Z]{6,52}\z/)
options << ['Commit', [@ref]]
end
grouped_options_for_select(options, @ref || @project.default_branch)
end
def emoji_autocomplete_source
# should be an array of strings
# so to_s can be called, because it is sufficient and to_json is too slow
Emoji.names.to_s
end
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
# Skip if event is not about added or modified non-master branch
return false unless event && event.last_push_to_non_root? && !event.rm_ref?
project = event.project
# Skip if project repo is empty or MR disabled
return false unless project && !project.empty_repo? && project.merge_requests_enabled
# Skip if user already created appropriate MR
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
# Skip if user removed branch right after that
return false unless project.repository.branch_names.include?(event.branch_name)
true
end
def hexdigest(string)
Digest::SHA1.hexdigest string
end
def simple_sanitize(str)
sanitize(str, tags: %w(a span))
end
def body_data_page
path = controller.controller_path.split('/')
namespace = path.first if path.second
[namespace, controller.controller_name, controller.action_name].compact.join(':')
end
# shortcut for gitlab config
def gitlab_config
Gitlab.config.gitlab
end
# shortcut for gitlab extra config
def extra_config
Gitlab.config.extra
end
def search_placeholder
if @project && @project.persisted?
'Search in this project'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
'Search in this group'
else
'Search'
end
end
def broadcast_message
BroadcastMessage.current
end
# Render a `time` element with Javascript-based relative date and tooltip
#
# time - Time object
# placement - Tooltip placement String (default: "top")
# html_class - Custom class for `time` element (default: "time_ago")
# skip_js - When true, exclude the `script` tag (default: false)
#
# By default also includes a `script` element with Javascript necessary to
# initialize the `timeago` jQuery extension. If this method is called many
# times, for example rendering hundreds of commits, it's advisable to disable
# this behavior using the `skip_js` argument and re-initializing `timeago`
# manually once all of the elements have been rendered.
#
# A `js-timeago` class is always added to the element, even when a custom
# `html_class` argument is provided.
#
# Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago",
datetime: time.getutc.iso8601,
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement }
element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
element
end
def render_markup(file_name, file_content)
if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
elsif plain?(file_name)
content_tag :pre, class: 'plain-readme' do
file_content
end
else
GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
end
rescue RuntimeError
simple_format(file_content)
end
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
end
def markup?(filename)
Gitlab::MarkupHelper.markup?(filename)
end
def gitlab_markdown?(filename)
Gitlab::MarkupHelper.gitlab_markdown?(filename)
end
def asciidoc?(filename)
Gitlab::MarkupHelper.asciidoc?(filename)
end
def promo_host
'about.gitlab.com'
end
def promo_url
'https://' + promo_host
end
def page_filter_path(options = {})
without = options.delete(:without)
exist_opts = {
state: params[:state],
scope: params[:scope],
label_name: params[:label_name],
milestone_id: params[:milestone_id],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
sort: params[:sort],
}
options = exist_opts.merge(options)
if without.present?
without.each do |key|
options.delete(key)
end
end
path = request.path
path << "?#{options.to_param}"
path
end
def outdated_browser?
browser.ie? && browser.version.to_i < 10
end
def path_to_key(key, admin = false)
if admin
admin_user_key_path(@user, key)
else
profile_key_path(key)
end
end
def state_filters_text_for(entity, project)
titles = {
opened: "Open"
}
entity_title = titles[entity] || entity.to_s.humanize
count =
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
html = content_tag :span, entity_title
if count.present?
html += " "
html += content_tag :span, number_with_delimiter(count), class: 'badge'
end
html.html_safe
end
end
module ApplicationSettingsHelper
def gravatar_enabled?
current_application_settings.gravatar_enabled?
end
def twitter_sharing_enabled?
current_application_settings.twitter_sharing_enabled?
end
def signup_enabled?
current_application_settings.signup_enabled?
end
def signin_enabled?
current_application_settings.signin_enabled?
end
def extra_sign_in_text
current_application_settings.sign_in_text
end
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level)
css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[restricted_visibility_levels][]'
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, level, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id) + name
end
end
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def import_sources_checkboxes(help_block_id)
Gitlab::ImportSources.options.map do |name, source|
checked = current_application_settings.import_sources.include?(source)
css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[import_sources][]'
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id) + name
end
end
end
end
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos', 'crowd'].freeze
def ldap_enabled?
Gitlab.config.ldap.enabled
end
def provider_has_icon?(name)
PROVIDERS_WITH_ICONS.include?(name.to_s)
end
def auth_providers
Gitlab::OAuth::Provider.providers
end
def label_for_provider(name)
Gitlab::OAuth::Provider.label_for(name)
end
def form_based_provider?(name)
FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s }
end
def form_based_providers
auth_providers.select { |provider| form_based_provider?(provider) }
end
def crowd_enabled?
auth_providers.include? :crowd
end
def button_based_providers
auth_providers.reject { |provider| form_based_provider?(provider) }
end
def provider_image_tag(provider, size = 64)
label = label_for_provider(provider)
if provider_has_icon?(provider)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
image_tag(image_path("auth_buttons/#{file_name}"), alt: label, title: "Sign in with #{label}")
else
label
end
end
def auth_active?(provider)
current_user.identities.exists?(provider: provider.to_s)
end
extend self
end
module BlobHelper
def highlight(blob_name, blob_content, nowrap: false, continue: false)
@formatter ||= Rouge::Formatters::HTMLGitlab.new(
nowrap: nowrap,
cssclass: 'code highlight',
lineanchors: true,
lineanchorsid: 'LC'
)
begin
@lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new
result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe
rescue
@lexer = Rouge::Lexers::PlainText
result = @formatter.format(@lexer.lex(blob_content)).html_safe
end
result
end
def no_highlight_files
%w(credits changelog news copying copyright license authors)
end
def edit_blob_link(project, ref, path, options = {})
blob =
begin
project.repository.blob_at(ref, path)
rescue
nil
end
if blob && blob.text?
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
if allowed_tree_edit?(project, ref)
link_to(text,
namespace_project_edit_blob_path(project.namespace, project,
tree_join(ref, path),
link_opts),
class: cls
)
else
content_tag :span, text, class: cls + ' disabled'
end + after.html_safe
else
''
end
end
def leave_edit_message
"Leave edit mode?\nAll unsaved changes will be lost."
end
def editing_preview_title(filename)
if Gitlab::MarkupHelper.previewable?(filename)
'Preview'
else
'Preview changes'
end
end
# Return an image icon depending on the file mode and extension
#
# mode - File unix mode
# mode - File name
def blob_icon(mode, name)
icon("#{file_type_icon_class('file', mode, name)} fw")
end
end
module BranchesHelper
def can_remove_branch?(project, branch_name)
if project.protected_branch? branch_name
false
elsif branch_name == project.repository.root_ref
false
else
can?(current_user, :push_code, project)
end
end
def can_push_branch?(project, branch_name)
return false unless project.repository.branch_names.include?(branch_name)
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end
end
module BroadcastMessagesHelper
def broadcast_styling(broadcast_message)
styling = ''
if broadcast_message.color.present?
styling << "background-color: #{broadcast_message.color}"
styling << '; ' if broadcast_message.font.present?
end
if broadcast_message.font.present?
styling << "color: #{broadcast_message.font}"
end
styling
end
end
# encoding: utf-8
module CommitsHelper
# Returns a link to the commit author. If the author has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the author email as specified in the commit.
#
# options:
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_author_link(commit, options = {})
commit_person_link(commit, options.merge(source: :author))
end
# Just like #author_link but for the committer.
def commit_committer_link(commit, options = {})
commit_person_link(commit, options.merge(source: :committer))
end
def image_diff_class(diff)
if diff.deleted_file
"deleted"
elsif diff.new_file
"added"
else
nil
end
end
def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit"
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end
# Breadcrumb links for a Project and, if applicable, a tree path
def commits_breadcrumbs
return unless @project && @ref
# Add the root project link and the arrow icon
crumbs = content_tag(:li) do
link_to(
@project.path,
namespace_project_commits_path(@project.namespace, @project, @ref)
)
end
if @path
parts = @path.split('/')
parts.each_with_index do |part, i|
crumbs << content_tag(:li) do
# The text is just the individual part, but the link needs all the parts before it
link_to(
part,
namespace_project_commits_path(
@project.namespace,
@project,
tree_join(@ref, parts[0..i].join('/'))
)
)
end
end
end
crumbs.html_safe
end
# Return Project default branch, if it present in array
# Else - first branch in array (mb last actual branch)
def commit_default_branch(project, branches)
branches.include?(project.default_branch) ? branches.delete(project.default_branch) : branches.pop
end
# Returns the sorted alphabetically links to branches, separated by a comma
def commit_branches_links(project, branches)
branches.sort.map do |branch|
link_to(
namespace_project_tree_path(project.namespace, project, branch)
) do
content_tag :span, class: 'label label-gray' do
icon('code-fork') + ' ' + branch
end
end
end.join(" ").html_safe
end
# Returns the sorted links to tags, separated by a comma
def commit_tags_links(project, tags)
sorted = VersionSorter.rsort(tags)
sorted.map do |tag|
link_to(
namespace_project_commits_path(project.namespace, project,
project.repository.find_tag(tag).name)
) do
content_tag :span, class: 'label label-gray' do
icon('tag') + ' ' + tag
end
end
end.join(" ").html_safe
end
def link_to_browse_code(project, commit)
if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path)
return link_to(
"Browse File »",
namespace_project_blob_path(project.namespace, project,
tree_join(commit.id, @path)),
class: "pull-right"
)
elsif @path.present?
return link_to(
"Browse Dir »",
namespace_project_tree_path(project.namespace, project,
tree_join(commit.id, @path)),
class: "pull-right"
)
end
end
link_to(
"Browse Code »",
namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right"
)
end
protected
# Private: Returns a link to a person. If the person has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the person email as specified in the commit.
#
# options:
# source: one of :author or :committer
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
user = commit.send(options[:source])
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
person_name = user.try(:name) || source_name
person_email = user.try(:email) || source_email
text =
if options[:avatar]
avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>}
else
person_name
end
options = {
class: "commit-#{options[:source]}-link has_tooltip",
data: { :'original-title' => sanitize(source_email) }
}
if user.nil?
mail_to(source_email, text.html_safe, options)
else
link_to(text.html_safe, user_path(user), options)
end
end
def view_file_btn(commit_sha, diff, project)
link_to(
namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
end
end
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
def clean(string)
Sanitize.clean(string, remove_contents: true)
end
end
module CompareHelper
def create_mr_button?(from = params[:from], to = params[:to], project = @project)
from.present? &&
to.present? &&
from != to &&
project.merge_requests_enabled &&
project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to)
end
def create_mr_path(from = params[:from], to = params[:to], project = @project)
new_namespace_project_merge_request_path(
project.namespace,
project,
merge_request: {
source_branch: to,
target_branch: from
}
)
end
end
module DashboardHelper
def assigned_issues_dashboard_path
issues_dashboard_path(assignee_id: current_user.id)
end
def assigned_mrs_dashboard_path
merge_requests_dashboard_path(assignee_id: current_user.id)
end
end
module DiffHelper
def allowed_diff_size
if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_FILES
else
Commit::DIFF_SAFE_FILES
end
end
def allowed_diff_lines
if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_LINES
else
Commit::DIFF_SAFE_LINES
end
end
def safe_diff_files(diffs)
lines = 0
safe_files = []
diffs.first(allowed_diff_size).each do |diff|
lines += diff.diff.lines.count
break if lines > allowed_diff_lines
safe_files << Gitlab::Diff::File.new(diff)
end
safe_files
end
def diff_hard_limit_enabled?
# Enabling hard limit allows user to see more diff information
if params[:force_show_diff].present?
true
else
false
end
end
def generate_line_code(file_path, line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def parallel_diff(diff_file, index)
lines = []
skip_next = false
# Building array of lines
#
# [
# left_type, left_line_number, left_line_content, left_line_code,
# right_line_type, right_line_number, right_line_content, right_line_code
# ]
#
diff_file.diff_lines.each do |line|
full_line = line.text
type = line.type
line_code = generate_line_code(diff_file.file_path, line)
line_new = line.new_pos
line_old = line.old_pos
next_line = diff_file.next_line(line.index)
if next_line
next_line_code = generate_line_code(diff_file.file_path, next_line)
next_type = next_line.type
next_line = next_line.text
end
if type == 'match' || type.nil?
# line in the right panel is the same as in the left one
line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code]
lines.push(line)
elsif type == 'old'
if next_type == 'new'
# Left side has text removed, right side has text added
line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code]
lines.push(line)
skip_next = true
elsif next_type == 'old' || next_type.nil?
# Left side has text removed, right side doesn't have any change
# No next line code, no new line number, no new line text
line = [type, line_old, full_line, line_code, next_type, nil, "&nbsp;", nil]
lines.push(line)
end
elsif type == 'new'
if skip_next
# Change has been already included in previous line so no need to do it again
skip_next = false
next
else
# Change is only on the right side, left side has no change
line = [nil, nil, "&nbsp;", line_code, type, line_new, full_line, line_code]
lines.push(line)
end
end
end
lines
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def unfold_class(unfold)
(unfold) ? 'unfold js-unfold' : ''
end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
def line_comments
@line_comments ||= @line_notes.select(&:active?).group_by(&:line_code)
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
comments_left = comments_right = nil
unless type_left.nil? && type_right == 'new'
comments_left = line_comments[line_code_left]
end
unless type_left.nil? && type_right.nil?
comments_right = line_comments[line_code_right]
end
[comments_left, comments_right]
end
def inline_diff_btn
params_copy = params.dup
params_copy[:view] = 'inline'
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do
'Inline'
end
end
def parallel_diff_btn
params_copy = params.dup
params_copy[:view] = 'parallel'
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do
'Side-by-side'
end
end
def submodule_link(blob, ref, repository = @repository)
tree, commit = submodule_links(blob, ref, repository)
commit_id = if commit.nil?
blob.id[0..10]
else
link_to "#{blob.id[0..10]}", commit
end
[
content_tag(:span, link_to(truncate(blob.name, length: 40), tree)),
'@',
content_tag(:span, commit_id, class: 'monospace'),
].join(' ').html_safe
end
end
module EmailsHelper
# Google Actions
# https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url)
name = action_title(url)
if name
data = {
"@context" => "http://schema.org",
"@type" => "EmailMessage",
"action" => {
"@type" => "ViewAction",
"name" => name,
"url" => url,
}
}
content_tag :script, type: 'application/ld+json' do
data.to_json.html_safe
end
end
end
def action_title(url)
return unless url
["merge_requests", "issues", "commit"].each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
end
end
end
def color_email_diff(diffcontent)
formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github')
lexer = Rouge::Lexers::Diff
raw formatter.format(lexer.lex(diffcontent))
end
def password_reset_token_valid_time
valid_hours = Devise.reset_password_within / 60 / 60
if valid_hours >= 24
unit = 'day'
valid_length = (valid_hours / 24).floor
else
unit = 'hour'
valid_length = valid_hours.floor
end
pluralize(valid_length, unit)
end
def reset_token_expire_message
link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email))
msg = "This link is valid for #{password_reset_token_valid_time}. "
msg << "After it expires, you can #{link_tag}."
end
end
module EventsHelper
def link_to_author(event)
author = event.author
if author
link_to author.name, user_path(author.username)
else
event.author_name
end
end
def event_action_name(event)
target = if event.target_type
if event.note?
event.note_target_type
else
event.target_type.titleize.downcase
end
else
'project'
end
[event.action_name, target].join(" ")
end
def event_filter_link(key, tooltip)
key = key.to_s
active = 'active' if @event_filter.active?(key)
link_opts = {
class: "event-filter-link btn btn-default #{active}",
id: "#{key}_event_filter",
title: "Filter by #{tooltip.downcase}",
}
link_to request.path, link_opts do
content_tag(:span, ' ' + tooltip)
end
end
def icon_for_event
{
EventFilter.push => 'upload',
EventFilter.merged => 'check-square-o',
EventFilter.comments => 'comments',
EventFilter.team => 'user',
}
end
def event_feed_title(event)
words = []
words << event.author_name
words << event_action_name(event)
if event.push?
words << event.ref_type
words << event.ref_name
words << "at"
elsif event.commented?
if event.note_commit?
words << event.note_short_commit_id
else
words << "##{truncate event.note_target_iid}"
end
words << "at"
elsif event.target
words << "##{event.target_iid}:"
words << event.target.title if event.target.respond_to?(:title)
words << "at"
end
words << event.project_name
words.join(" ")
end
def event_feed_url(event)
if event.issue?
namespace_project_issue_url(event.project.namespace, event.project,
event.issue)
elsif event.merge_request?
namespace_project_merge_request_url(event.project.namespace,
event.project, event.merge_request)
elsif event.note? && event.note_commit?
namespace_project_commit_url(event.project.namespace, event.project,
event.note_target)
elsif event.note?
if event.note_target
if event.note_commit?
namespace_project_commit_path(event.project.namespace, event.project,
event.note_commit_id,
anchor: dom_id(event.target))
elsif event.note_project_snippet?
namespace_project_snippet_path(event.project.namespace,
event.project, event.note_target)
else
event_note_target_path(event)
end
end
elsif event.push?
if event.push_with_commits? && event.md_ref?
if event.commits_count > 1
namespace_project_compare_url(event.project.namespace, event.project,
from: event.commit_from, to:
event.commit_to)
else
namespace_project_commit_url(event.project.namespace, event.project,
id: event.commit_to)
end
else
namespace_project_commits_url(event.project.namespace, event.project,
event.ref_name)
end
end
end
def event_feed_summary(event)
if event.issue?
render "events/event_issue", issue: event.issue
elsif event.push?
render "events/event_push", event: event
elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request
elsif event.note?
render "events/event_note", note: event.note
end
end
def event_note_target_path(event)
if event.note? && event.note_commit?
namespace_project_commit_path(event.project.namespace, event.project,
event.note_target)
else
polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
anchor: dom_id(event.target))
end
end
def event_note_title_html(event)
if event.note_target
if event.note_commit?
link_to(
namespace_project_commit_path(event.project.namespace, event.project,
event.note_commit_id,
anchor: dom_id(event.target)),
class: "commit_short_id"
) do
"#{event.note_target_type} #{event.note_short_commit_id}"
end
elsif event.note_project_snippet?
link_to(namespace_project_snippet_path(event.project.namespace,
event.project,
event.note_target)) do
"#{event.note_target_type} ##{truncate event.note_target_id}"
end
else
link_to event_note_target_path(event) do
"#{event.note_target_type} ##{truncate event.note_target_iid}"
end
end
else
content_tag :strong do
"(deleted)"
end
end
end
def event_note(text, options = {})
text = first_line_in_markdown(text, 150, options)
sanitize(text, tags: %w(a img b pre code p span))
end
def event_commit_title(message)
escape_once(truncate(message.split("\n").first, length: 70))
rescue
"--broken encoding"
end
def event_to_atom(xml, event)
if event.proper?
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
event_summary = event_feed_summary(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link href: event_link
xml.title truncate(event_title, length: 80)
xml.updated event.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
end
end
end
end
module ExploreHelper
def explore_projects_filter_path(options={})
exist_opts = {
sort: params[:sort],
scope: params[:scope],
group: params[:group],
tag: params[:tag],
visibility_level: params[:visibility_level],
}
options = exist_opts.merge(options)
path = explore_projects_path
path << "?#{options.to_param}"
path
end
end
module ExternalWikiHelper
def get_project_wiki_path(project)
external_wiki_service = project.services.
select { |service| service.to_param == 'external_wiki' }.first
if external_wiki_service.present? && external_wiki_service.active?
external_wiki_service.properties['external_wiki_url']
else
namespace_project_wiki_path(project.namespace, project, :home)
end
end
end
module GitHelper
def strip_gpg_signature(text)
text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
end
end
module Gitlab
module AppearancesHelper
def brand_item
nil
end
def brand_title
'GitLab Community Edition'
end
def brand_image
nil
end
def brand_text
nil
end
def brand_header_logo
image_tag 'logo.svg'
end
end
end
require 'digest/md5'
require 'uri'
module Gitlab
module ApplicationHelper
# Check if a particular controller is the current one
#
# args - One or more controller names to check
#
# Examples
#
# # On TreeController
# current_controller?(:tree) # => true
# current_controller?(:commits) # => false
# current_controller?(:commits, :tree) # => true
def current_controller?(*args)
args.any? { |v| v.to_s.downcase == controller.controller_name }
end
# Check if a particular action is the current one
#
# args - One or more action names to check
#
# Examples
#
# # On Projects#new
# current_action?(:new) # => true
# current_action?(:create) # => false
# current_action?(:new, :create) # => true
def current_action?(*args)
args.any? { |v| v.to_s.downcase == action_name }
end
def project_icon(project_id, options = {})
project =
if project_id.is_a?(Project)
project = project_id
else
Project.find_with_namespace(project_id)
end
if project.avatar_url
image_tag project.avatar_url, options
else # generated icon
project_identicon(project, options)
end
end
def project_identicon(project, options = {})
allowed_colors = {
red: 'FFEBEE',
purple: 'F3E5F5',
indigo: 'E8EAF6',
blue: 'E3F2FD',
teal: 'E0F2F1',
orange: 'FBE9E7',
gray: 'EEEEEE'
}
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555"
content_tag(:div, class: options[:class], style: style) do
project.name[0, 1].upcase
end
end
def avatar_icon(user_email = '', size = nil)
user = User.find_by(email: user_email)
if user
user.avatar_url(size) || default_avatar
else
gravatar_icon(user_email, size)
end
end
def gravatar_icon(user_email = '', size = nil)
GravatarService.new.execute(user_email, size) ||
default_avatar
end
def default_avatar
image_path('no_avatar.png')
end
def last_commit(project)
if project.repo_exists?
time_ago_with_tooltip(project.repository.commit.committed_date)
else
'Never'
end
rescue
'Never'
end
def grouped_options_refs
repository = @project.repository
options = [
['Branches', repository.branch_names],
['Tags', VersionSorter.rsort(repository.tag_names)]
]
# If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /\A[0-9a-zA-Z]{6,52}\z/)
options << ['Commit', [@ref]]
end
grouped_options_for_select(options, @ref || @project.default_branch)
end
def emoji_autocomplete_source
# should be an array of strings
# so to_s can be called, because it is sufficient and to_json is too slow
Emoji.names.to_s
end
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
# Skip if event is not about added or modified non-master branch
return false unless event && event.last_push_to_non_root? && !event.rm_ref?
project = event.project
# Skip if project repo is empty or MR disabled
return false unless project && !project.empty_repo? && project.merge_requests_enabled
# Skip if user already created appropriate MR
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
# Skip if user removed branch right after that
return false unless project.repository.branch_names.include?(event.branch_name)
true
end
def hexdigest(string)
Digest::SHA1.hexdigest string
end
def simple_sanitize(str)
sanitize(str, tags: %w(a span))
end
def body_data_page
path = controller.controller_path.split('/')
namespace = path.first if path.second
[namespace, controller.controller_name, controller.action_name].compact.join(':')
end
# shortcut for gitlab config
def gitlab_config
Gitlab.config.gitlab
end
# shortcut for gitlab extra config
def extra_config
Gitlab.config.extra
end
def search_placeholder
if @project && @project.persisted?
'Search in this project'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
'Search in this group'
else
'Search'
end
end
def broadcast_message
BroadcastMessage.current
end
# Render a `time` element with Javascript-based relative date and tooltip
#
# time - Time object
# placement - Tooltip placement String (default: "top")
# html_class - Custom class for `time` element (default: "time_ago")
# skip_js - When true, exclude the `script` tag (default: false)
#
# By default also includes a `script` element with Javascript necessary to
# initialize the `timeago` jQuery extension. If this method is called many
# times, for example rendering hundreds of commits, it's advisable to disable
# this behavior using the `skip_js` argument and re-initializing `timeago`
# manually once all of the elements have been rendered.
#
# A `js-timeago` class is always added to the element, even when a custom
# `html_class` argument is provided.
#
# Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago",
datetime: time.getutc.iso8601,
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement }
element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
element
end
def render_markup(file_name, file_content)
if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
elsif plain?(file_name)
content_tag :pre, class: 'plain-readme' do
file_content
end
else
GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
end
rescue RuntimeError
simple_format(file_content)
end
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
end
def markup?(filename)
Gitlab::MarkupHelper.markup?(filename)
end
def gitlab_markdown?(filename)
Gitlab::MarkupHelper.gitlab_markdown?(filename)
end
def asciidoc?(filename)
Gitlab::MarkupHelper.asciidoc?(filename)
end
def promo_host
'about.gitlab.com'
end
def promo_url
'https://' + promo_host
end
def page_filter_path(options = {})
without = options.delete(:without)
exist_opts = {
state: params[:state],
scope: params[:scope],
label_name: params[:label_name],
milestone_id: params[:milestone_id],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
sort: params[:sort],
}
options = exist_opts.merge(options)
if without.present?
without.each do |key|
options.delete(key)
end
end
path = request.path
path << "?#{options.to_param}"
path
end
def outdated_browser?
browser.ie? && browser.version.to_i < 10
end
def path_to_key(key, admin = false)
if admin
admin_user_key_path(@user, key)
else
profile_key_path(key)
end
end
def state_filters_text_for(entity, project)
titles = {
opened: "Open"
}
entity_title = titles[entity] || entity.to_s.humanize
count =
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
html = content_tag :span, entity_title
if count.present?
html += " "
html += content_tag :span, number_with_delimiter(count), class: 'badge'
end
html.html_safe
end
end
end
module Gitlab
module ApplicationSettingsHelper
def gravatar_enabled?
current_application_settings.gravatar_enabled?
end
def twitter_sharing_enabled?
current_application_settings.twitter_sharing_enabled?
end
def signup_enabled?
current_application_settings.signup_enabled?
end
def signin_enabled?
current_application_settings.signin_enabled?
end
def extra_sign_in_text
current_application_settings.sign_in_text
end
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level)
css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[restricted_visibility_levels][]'
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, level, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id) + name
end
end
end
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def import_sources_checkboxes(help_block_id)
Gitlab::ImportSources.options.map do |name, source|
checked = current_application_settings.import_sources.include?(source)
css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[import_sources][]'
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id) + name
end
end
end
end
end
module Gitlab
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos'].freeze
def ldap_enabled?
Gitlab.config.ldap.enabled
end
def provider_has_icon?(name)
PROVIDERS_WITH_ICONS.include?(name.to_s)
end
def auth_providers
Gitlab::OAuth::Provider.providers
end
def label_for_provider(name)
Gitlab::OAuth::Provider.label_for(name)
end
def form_based_provider?(name)
FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s }
end
def form_based_providers
auth_providers.select { |provider| form_based_provider?(provider) }
end
def button_based_providers
auth_providers.reject { |provider| form_based_provider?(provider) }
end
def provider_image_tag(provider, size = 64)
label = label_for_provider(provider)
if provider_has_icon?(provider)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
image_tag("auth_buttons/#{file_name}", alt: label, title: "Sign in with #{label}")
else
label
end
end
def auth_active?(provider)
current_user.identities.exists?(provider: provider.to_s)
end
extend self
end
end
module Gitlab
module BlobHelper
def highlight(blob_name, blob_content, nowrap: false, continue: false)
@formatter ||= Rouge::Formatters::HTMLGitlab.new(
nowrap: nowrap,
cssclass: 'code highlight',
lineanchors: true,
lineanchorsid: 'LC'
)
begin
@lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new
result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe
rescue
@lexer = Rouge::Lexers::PlainText
result = @formatter.format(@lexer.lex(blob_content)).html_safe
end
result
end
def no_highlight_files
%w(credits changelog news copying copyright license authors)
end
def edit_blob_link(project, ref, path, options = {})
blob =
begin
project.repository.blob_at(ref, path)
rescue
nil
end
if blob && blob.text?
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
if allowed_tree_edit?(project, ref)
link_to(text,
namespace_project_edit_blob_path(project.namespace, project,
tree_join(ref, path),
link_opts),
class: cls
)
else
content_tag :span, text, class: cls + ' disabled'
end + after.html_safe
else
''
end
end
def leave_edit_message
"Leave edit mode?\nAll unsaved changes will be lost."
end
def editing_preview_title(filename)
if Gitlab::MarkupHelper.previewable?(filename)
'Preview'
else
'Preview changes'
end
end
# Return an image icon depending on the file mode and extension
#
# mode - File unix mode
# mode - File name
def blob_icon(mode, name)
icon("#{file_type_icon_class('file', mode, name)} fw")
end
end
end
module Gitlab
module BranchesHelper
def can_remove_branch?(project, branch_name)
if project.protected_branch? branch_name
false
elsif branch_name == project.repository.root_ref
false
else
can?(current_user, :push_code, project)
end
end
def can_push_branch?(project, branch_name)
return false unless project.repository.branch_names.include?(branch_name)
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end
end
end
module Gitlab
module BroadcastMessagesHelper
def broadcast_styling(broadcast_message)
styling = ''
if broadcast_message.color.present?
styling << "background-color: #{broadcast_message.color}"
styling << '; ' if broadcast_message.font.present?
end
if broadcast_message.font.present?
styling << "color: #{broadcast_message.font}"
end
styling
end
end
end
# encoding: utf-8
module Gitlab
module CommitsHelper
# Returns a link to the commit author. If the author has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the author email as specified in the commit.
#
# options:
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_author_link(commit, options = {})
commit_person_link(commit, options.merge(source: :author))
end
# Just like #author_link but for the committer.
def commit_committer_link(commit, options = {})
commit_person_link(commit, options.merge(source: :committer))
end
def image_diff_class(diff)
if diff.deleted_file
"deleted"
elsif diff.new_file
"added"
else
nil
end
end
def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit"
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
end
# Breadcrumb links for a Project and, if applicable, a tree path
def commits_breadcrumbs
return unless @project && @ref
# Add the root project link and the arrow icon
crumbs = content_tag(:li) do
link_to(
@project.path,
namespace_project_commits_path(@project.namespace, @project, @ref)
)
end
if @path
parts = @path.split('/')
parts.each_with_index do |part, i|
crumbs << content_tag(:li) do
# The text is just the individual part, but the link needs all the parts before it
link_to(
part,
namespace_project_commits_path(
@project.namespace,
@project,
tree_join(@ref, parts[0..i].join('/'))
)
)
end
end
end
crumbs.html_safe
end
# Return Project default branch, if it present in array
# Else - first branch in array (mb last actual branch)
def commit_default_branch(project, branches)
branches.include?(project.default_branch) ? branches.delete(project.default_branch) : branches.pop
end
# Returns the sorted alphabetically links to branches, separated by a comma
def commit_branches_links(project, branches)
branches.sort.map do |branch|
link_to(
namespace_project_tree_path(project.namespace, project, branch)
) do
content_tag :span, class: 'label label-gray' do
icon('code-fork') + ' ' + branch
end
end
end.join(" ").html_safe
end
# Returns the sorted links to tags, separated by a comma
def commit_tags_links(project, tags)
sorted = VersionSorter.rsort(tags)
sorted.map do |tag|
link_to(
namespace_project_commits_path(project.namespace, project,
project.repository.find_tag(tag).name)
) do
content_tag :span, class: 'label label-gray' do
icon('tag') + ' ' + tag
end
end
end.join(" ").html_safe
end
def link_to_browse_code(project, commit)
if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path)
return link_to(
"Browse File »",
namespace_project_blob_path(project.namespace, project,
tree_join(commit.id, @path)),
class: "pull-right"
)
elsif @path.present?
return link_to(
"Browse Dir »",
namespace_project_tree_path(project.namespace, project,
tree_join(commit.id, @path)),
class: "pull-right"
)
end
end
link_to(
"Browse Code »",
namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right"
)
end
protected
# Private: Returns a link to a person. If the person has a matching user and
# is a member of the current @project it will link to the team member page.
# Otherwise it will link to the person email as specified in the commit.
#
# options:
# source: one of :author or :committer
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
user = commit.send(options[:source])
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
person_name = user.try(:name) || source_name
person_email = user.try(:email) || source_email
text =
if options[:avatar]
avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>}
else
person_name
end
options = {
class: "commit-#{options[:source]}-link has_tooltip",
data: { :'original-title' => sanitize(source_email) }
}
if user.nil?
mail_to(source_email, text.html_safe, options)
else
link_to(text.html_safe, user_path(user), options)
end
end
def view_file_btn(commit_sha, diff, project)
link_to(
namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
end
end
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
def clean(string)
Sanitize.clean(string, remove_contents: true)
end
end
end
module Gitlab
module CompareHelper
def create_mr_button?(from = params[:from], to = params[:to], project = @project)
from.present? &&
to.present? &&
from != to &&
project.merge_requests_enabled &&
project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to)
end
def create_mr_path(from = params[:from], to = params[:to], project = @project)
new_namespace_project_merge_request_path(
project.namespace,
project,
merge_request: {
source_branch: to,
target_branch: from
}
)
end
end
end
module Gitlab
module DashboardHelper
def assigned_issues_dashboard_path
issues_dashboard_path(assignee_id: current_user.id)
end
def assigned_mrs_dashboard_path
merge_requests_dashboard_path(assignee_id: current_user.id)
end
end
end
module Gitlab
module DiffHelper
def allowed_diff_size
if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_FILES
else
Commit::DIFF_SAFE_FILES
end
end
def allowed_diff_lines
if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_LINES
else
Commit::DIFF_SAFE_LINES
end
end
def safe_diff_files(diffs)
lines = 0
safe_files = []
diffs.first(allowed_diff_size).each do |diff|
lines += diff.diff.lines.count
break if lines > allowed_diff_lines
safe_files << Gitlab::Diff::File.new(diff)
end
safe_files
end
def diff_hard_limit_enabled?
# Enabling hard limit allows user to see more diff information
if params[:force_show_diff].present?
true
else
false
end
end
def generate_line_code(file_path, line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def parallel_diff(diff_file, index)
lines = []
skip_next = false
# Building array of lines
#
# [
# left_type, left_line_number, left_line_content, left_line_code,
# right_line_type, right_line_number, right_line_content, right_line_code
# ]
#
diff_file.diff_lines.each do |line|
full_line = line.text
type = line.type
line_code = generate_line_code(diff_file.file_path, line)
line_new = line.new_pos
line_old = line.old_pos
next_line = diff_file.next_line(line.index)
if next_line
next_line_code = generate_line_code(diff_file.file_path, next_line)
next_type = next_line.type
next_line = next_line.text
end
if type == 'match' || type.nil?
# line in the right panel is the same as in the left one
line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code]
lines.push(line)
elsif type == 'old'
if next_type == 'new'
# Left side has text removed, right side has text added
line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code]
lines.push(line)
skip_next = true
elsif next_type == 'old' || next_type.nil?
# Left side has text removed, right side doesn't have any change
# No next line code, no new line number, no new line text
line = [type, line_old, full_line, line_code, next_type, nil, "&nbsp;", nil]
lines.push(line)
end
elsif type == 'new'
if skip_next
# Change has been already included in previous line so no need to do it again
skip_next = false
next
else
# Change is only on the right side, left side has no change
line = [nil, nil, "&nbsp;", line_code, type, line_new, full_line, line_code]
lines.push(line)
end
end
end
lines
end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def unfold_class(unfold)
(unfold) ? 'unfold js-unfold' : ''
end
def diff_line_content(line)
if line.blank?
" &nbsp;"
else
line
end
end
def line_comments
@line_comments ||= @line_notes.select(&:active?).group_by(&:line_code)
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
comments_left = comments_right = nil
unless type_left.nil? && type_right == 'new'
comments_left = line_comments[line_code_left]
end
unless type_left.nil? && type_right.nil?
comments_right = line_comments[line_code_right]
end
[comments_left, comments_right]
end
def inline_diff_btn
params_copy = params.dup
params_copy[:view] = 'inline'
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do
'Inline'
end
end
def parallel_diff_btn
params_copy = params.dup
params_copy[:view] = 'parallel'
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do
'Side-by-side'
end
end
def submodule_link(blob, ref, repository = @repository)
tree, commit = submodule_links(blob, ref, repository)
commit_id = if commit.nil?
blob.id[0..10]
else
link_to "#{blob.id[0..10]}", commit
end
[
content_tag(:span, link_to(truncate(blob.name, length: 40), tree)),
'@',
content_tag(:span, commit_id, class: 'monospace'),
].join(' ').html_safe
end
end
end
module Gitlab
module EmailsHelper
# Google Actions
# https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url)
name = action_title(url)
if name
data = {
"@context" => "http://schema.org",
"@type" => "EmailMessage",
"action" => {
"@type" => "ViewAction",
"name" => name,
"url" => url,
}
}
content_tag :script, type: 'application/ld+json' do
data.to_json.html_safe
end
end
end
def action_title(url)
return unless url
["merge_requests", "issues", "commit"].each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
end
end
end
def color_email_diff(diffcontent)
formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github')
lexer = Rouge::Lexers::Diff
raw formatter.format(lexer.lex(diffcontent))
end
def password_reset_token_valid_time
valid_hours = Devise.reset_password_within / 60 / 60
if valid_hours >= 24
unit = 'day'
valid_length = (valid_hours / 24).floor
else
unit = 'hour'
valid_length = valid_hours.floor
end
pluralize(valid_length, unit)
end
def reset_token_expire_message
link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email))
msg = "This link is valid for #{password_reset_token_valid_time}. "
msg << "After it expires, you can #{link_tag}."
end
end
end
module Gitlab
module EventsHelper
def link_to_author(event)
author = event.author
if author
link_to author.name, user_path(author.username)
else
event.author_name
end
end
def event_action_name(event)
target = if event.target_type
if event.note?
event.note_target_type
else
event.target_type.titleize.downcase
end
else
'project'
end
[event.action_name, target].join(" ")
end
def event_filter_link(key, tooltip)
key = key.to_s
active = 'active' if @event_filter.active?(key)
link_opts = {
class: 'event_filter_link',
id: "#{key}_event_filter",
title: "Filter by #{tooltip.downcase}",
data: { toggle: 'tooltip', placement: 'top' }
}
content_tag :li, class: "filter_icon #{active}" do
link_to request.path, link_opts do
icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip)
end
end
end
def icon_for_event
{
EventFilter.push => 'upload',
EventFilter.merged => 'check-square-o',
EventFilter.comments => 'comments',
EventFilter.team => 'user',
}
end
def event_feed_title(event)
words = []
words << event.author_name
words << event_action_name(event)
if event.push?
words << event.ref_type
words << event.ref_name
words << "at"
elsif event.commented?
if event.note_commit?
words << event.note_short_commit_id
else
words << "##{truncate event.note_target_iid}"
end
words << "at"
elsif event.target
words << "##{event.target_iid}:"
words << event.target.title if event.target.respond_to?(:title)
words << "at"
end
words << event.project_name
words.join(" ")
end
def event_feed_url(event)
if event.issue?
namespace_project_issue_url(event.project.namespace, event.project,
event.issue)
elsif event.merge_request?
namespace_project_merge_request_url(event.project.namespace,
event.project, event.merge_request)
elsif event.note? && event.note_commit?
namespace_project_commit_url(event.project.namespace, event.project,
event.note_target)
elsif event.note?
if event.note_target
if event.note_commit?
namespace_project_commit_path(event.project.namespace, event.project,
event.note_commit_id,
anchor: dom_id(event.target))
elsif event.note_project_snippet?
namespace_project_snippet_path(event.project.namespace,
event.project, event.note_target)
else
event_note_target_path(event)
end
end
elsif event.push?
if event.push_with_commits? && event.md_ref?
if event.commits_count > 1
namespace_project_compare_url(event.project.namespace, event.project,
from: event.commit_from, to:
event.commit_to)
else
namespace_project_commit_url(event.project.namespace, event.project,
id: event.commit_to)
end
else
namespace_project_commits_url(event.project.namespace, event.project,
event.ref_name)
end
end
end
def event_feed_summary(event)
if event.issue?
render "events/event_issue", issue: event.issue
elsif event.push?
render "events/event_push", event: event
elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request
elsif event.note?
render "events/event_note", note: event.note
end
end
def event_note_target_path(event)
if event.note? && event.note_commit?
namespace_project_commit_path(event.project.namespace, event.project,
event.note_target)
else
polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
anchor: dom_id(event.target))
end
end
def event_note_title_html(event)
if event.note_target
if event.note_commit?
link_to(
namespace_project_commit_path(event.project.namespace, event.project,
event.note_commit_id,
anchor: dom_id(event.target)),
class: "commit_short_id"
) do
"#{event.note_target_type} #{event.note_short_commit_id}"
end
elsif event.note_project_snippet?
link_to(namespace_project_snippet_path(event.project.namespace,
event.project,
event.note_target)) do
"#{event.note_target_type} ##{truncate event.note_target_id}"
end
else
link_to event_note_target_path(event) do
"#{event.note_target_type} ##{truncate event.note_target_iid}"
end
end
else
content_tag :strong do
"(deleted)"
end
end
end
def event_note(text, options = {})
text = first_line_in_markdown(text, 150, options)
sanitize(text, tags: %w(a img b pre code p span))
end
def event_commit_title(message)
escape_once(truncate(message.split("\n").first, length: 70))
rescue
"--broken encoding"
end
def event_to_atom(xml, event)
if event.proper?
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
event_summary = event_feed_summary(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link href: event_link
xml.title truncate(event_title, length: 80)
xml.updated event.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
end
end
end
end
end
module Gitlab
module ExploreHelper
def explore_projects_filter_path(options={})
exist_opts = {
sort: params[:sort],
scope: params[:scope],
group: params[:group],
tag: params[:tag],
visibility_level: params[:visibility_level],
}
options = exist_opts.merge(options)
path = explore_projects_path
path << "?#{options.to_param}"
path
end
end
end
module Gitlab
module ExternalWikiHelper
def get_project_wiki_path(project)
external_wiki_service = project.services.
select { |service| service.to_param == 'external_wiki' }.first
if external_wiki_service.present? && external_wiki_service.active?
external_wiki_service.properties['external_wiki_url']
else
namespace_project_wiki_path(project.namespace, project, :home)
end
end
end
end
module Gitlab
module GitHelper
def strip_gpg_signature(text)
text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
end
end
end
require 'nokogiri'
module Gitlab
module GitlabMarkdownHelper
include Gitlab::Markdown
include PreferencesHelper
# Use this in places where you would normally use link_to(gfm(...), ...).
#
# It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
# not linked any more). link_to_gfm corrects that. It wraps all parts to
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {})
return "" if body.blank?
escaped_body = if body =~ /\A\<img/
body
else
escape_once(body)
end
gfm_body = gfm(escaped_body, {}, html_options)
fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link.
text = fragment.children[0].text
fragment.children[0].replace(link_to(text, url, html_options))
else
# Traverse the fragment's first generation of children looking for pure
# text, wrapping anything found in the requested link
fragment.children.each do |node|
next unless node.text?
node.replace(link_to(node.text, url, html_options))
end
end
fragment.to_html.html_safe
end
MARKDOWN_OPTIONS = {
no_intra_emphasis: true,
tables: true,
fenced_code_blocks: true,
strikethrough: true,
lax_spacing: true,
space_after_headers: true,
superscript: true,
footnotes: true
}.freeze
def markdown(text, options={})
unless @markdown && options == @options
@options = options
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch
rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, options)
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
@markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS)
end
@markdown.render(text).html_safe
end
def asciidoc(text)
Gitlab::Asciidoc.render(text, {
commit: @commit,
project: @project,
project_wiki: @project_wiki,
requested_path: @path,
ref: @ref
})
end
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(text, max_chars = nil, options = {})
md = markdown(text, options).strip
truncate_visible(md, max_chars || md.length) if md.present?
end
def render_wiki_content(wiki_page)
case wiki_page.format
when :markdown
markdown(wiki_page.content)
when :asciidoc
asciidoc(wiki_page.content)
else
wiki_page.formatted_content.html_safe
end
end
MARKDOWN_TIPS = [
"End a line with two or more spaces for a line-break, or soft-return",
"Inline code can be denoted by `surrounding it with backticks`",
"Blocks of code can be denoted by three backticks ``` or four leading spaces",
"Emoji can be added by :emoji_name:, for example :thumbsup:",
"Notify other participants using @user_name",
"Notify a specific group using @group_name",
"Notify the entire team using @all",
"Reference an issue using a hash, for example issue #123",
"Reference a merge request using an exclamation point, for example MR !123",
"Italicize words or phrases using *asterisks* or _underscores_",
"Bold words or phrases using **double asterisks** or __double underscores__",
"Strikethrough words or phrases using ~~two tildes~~",
"Make a bulleted list using + pluses, - minuses, or * asterisks",
"Denote blockquotes using > at the beginning of a line",
"Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___"
].freeze
# Returns a random markdown tip for use as a textarea placeholder
def random_markdown_tip
MARKDOWN_TIPS.sample
end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
# tags.
def truncate_visible(text, max_chars)
doc = Nokogiri::HTML.fragment(text)
content_length = 0
truncated = false
doc.traverse do |node|
if node.text? || node.content.empty?
if truncated
node.remove
next
end
# Handle line breaks within a node
if node.content.strip.lines.length > 1
node.content = "#{node.content.lines.first.chomp}..."
truncated = true
end
num_remaining = max_chars - content_length
if node.content.length > num_remaining
node.content = node.content.truncate(num_remaining)
truncated = true
end
content_length += node.content.length
end
truncated = truncate_if_block(node, truncated)
end
doc.to_html
end
# Used by #truncate_visible. If +node+ is the first block element, and the
# text hasn't already been truncated, then append "..." to the node contents
# and return true. Otherwise return false.
def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated
node.content = "#{node.content}..." if node.next_sibling
true
else
truncated
end
end
# Returns the text necessary to reference `entity` across projects
#
# project - Project to reference
# entity - Object that responds to `to_reference`
#
# Examples:
#
# cross_project_reference(project, project.issues.first)
# # => 'namespace1/project1#123'
#
# cross_project_reference(project, project.merge_requests.first)
# # => 'namespace1/project1!345'
#
# Returns a String
def cross_project_reference(project, entity)
if entity.respond_to?(:to_reference)
"#{project.to_reference}#{entity.to_reference}"
else
''
end
end
end
end
# Shorter routing method for project and project items
# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
# so we use nested routing for project resources which include project and
# project namespace. To avoid writing long methods every time we define shortcuts for
# some of routing.
#
# For example instead of this:
#
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
#
# We can simply use shortcut:
#
# merge_request_path(merge_request)
#
module Gitlab
module GitlabRoutingHelper
def project_path(project, *args)
namespace_project_path(project.namespace, project, *args)
end
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
def edit_project_path(project, *args)
edit_namespace_project_path(project.namespace, project, *args)
end
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
def merge_request_path(entity, *args)
namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
end
def milestone_path(entity, *args)
namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
end
def project_url(project, *args)
namespace_project_url(project.namespace, project, *args)
end
def edit_project_url(project, *args)
edit_namespace_project_url(project.namespace, project, *args)
end
def issue_url(entity, *args)
namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
end
def merge_request_url(entity, *args)
namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
end
def project_snippet_url(entity, *args)
namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
end
def toggle_subscription_path(entity, *args)
if entity.is_a?(Issue)
toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity)
else
toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity)
end
end
end
end
module Gitlab
module GraphHelper
def get_refs(repo, commit)
refs = ""
refs << commit.ref_names(repo).join(' ')
# append note count
refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
refs
end
def parents_zip_spaces(parents, parent_spaces)
ids = parents.map { |p| p.id }
ids.zip(parent_spaces)
end
end
end
module Gitlab
module GroupsHelper
def remove_user_from_group_message(group, member)
if member.user
"Are you sure you want to remove \"#{member.user.name}\" from \"#{group.name}\"?"
else
"Are you sure you want to revoke the invitation for \"#{member.invite_email}\" to join \"#{group.name}\"?"
end
end
def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?"
end
def should_user_see_group_roles?(user, group)
if user
user.is_admin? || group.members.exists?(user_id: user.id)
else
false
end
end
def group_icon(group)
if group.is_a?(String)
group = Group.find_by(path: group)
end
if group && group.avatar.present?
group.avatar.url
else
image_path('no_group_avatar.png')
end
end
end
end
module Gitlab
module IconsHelper
include FontAwesome::Rails::IconHelper
# Creates an icon tag given icon name(s) and possible icon modifiers.
#
# Right now this method simply delegates directly to `fa_icon` from the
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
fa_icon(names, options)
end
def spinner(text = nil, visible = false)
css_class = 'loading'
css_class << ' hide' unless visible
content_tag :div, class: css_class do
icon('spinner spin') + text
end
end
def boolean_to_icon(value)
if value
icon('circle', class: 'cgreen')
else
icon('power-off', class: 'clgray')
end
end
def public_icon
icon('globe fw')
end
def internal_icon
icon('shield fw')
end
def private_icon
icon('lock fw')
end
def file_type_icon_class(type, mode, name)
if type == 'folder'
icon_class = 'folder'
elsif mode == '120000'
icon_class = 'share'
else
# Guess which icon to choose based on file extension.
# If you think a file extension is missing, feel free to add it on PR
case File.extname(name).downcase
when '.pdf'
icon_class = 'file-pdf-o'
when '.jpg', '.jpeg', '.jif', '.jfif',
'.jp2', '.jpx', '.j2k', '.j2c',
'.png', '.gif', '.tif', '.tiff',
'.svg', '.ico', '.bmp'
icon_class = 'file-image-o'
when '.zip', '.zipx', '.tar', '.gz', '.bz', '.bzip',
'.xz', '.rar', '.7z'
icon_class = 'file-archive-o'
when '.mp3', '.wma', '.ogg', '.oga', '.wav', '.flac', '.aac'
icon_class = 'file-audio-o'
when '.mp4', '.m4p', '.m4v',
'.mpg', '.mp2', '.mpeg', '.mpe', '.mpv',
'.mpg', '.mpeg', '.m2v',
'.avi', '.mkv', '.flv', '.ogv', '.mov',
'.3gp', '.3g2'
icon_class = 'file-video-o'
when '.doc', '.dot', '.docx', '.docm', '.dotx', '.dotm', '.docb'
icon_class = 'file-word-o'
when '.xls', '.xlt', '.xlm', '.xlsx', '.xlsm', '.xltx', '.xltm',
'.xlsb', '.xla', '.xlam', '.xll', '.xlw'
icon_class = 'file-excel-o'
when '.ppt', '.pot', '.pps', '.pptx', '.pptm', '.potx', '.potm',
'.ppam', '.ppsx', '.ppsm', '.sldx', '.sldm'
icon_class = 'file-powerpoint-o'
else
icon_class = 'file-text-o'
end
end
icon_class
end
end
end
module Gitlab
module IssuesHelper
def issue_css_classes(issue)
classes = "issue"
classes << " closed" if issue.closed?
classes << " today" if issue.today?
classes
end
# Returns an OpenStruct object suitable for use by <tt>options_from_collection_for_select</tt>
# to allow filtering issues by an unassigned User or Milestone
def unassigned_filter
# Milestone uses :title, Issue uses :name
OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned')
end
def url_for_project_issues(project = @project, options = {})
return '' if project.nil?
if options[:only_path]
project.issues_tracker.project_path
else
project.issues_tracker.project_url
end
end
def url_for_new_issue(project = @project, options = {})
return '' if project.nil?
if options[:only_path]
project.issues_tracker.new_issue_path
else
project.issues_tracker.new_issue_url
end
end
def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil?
if options[:only_path]
project.issues_tracker.issue_path(issue_iid)
else
project.issues_tracker.issue_url(issue_iid)
end
end
def bulk_update_milestone_options
options_for_select([['None (backlog)', -1]]) +
options_from_collection_for_select(project_active_milestones, 'id',
'title', params[:milestone_id])
end
def milestone_options(object)
options_from_collection_for_select(object.project.milestones.active,
'id', 'title', object.milestone_id)
end
def issue_box_class(item)
if item.respond_to?(:expired?) && item.expired?
'issue-box-expired'
elsif item.respond_to?(:merged?) && item.merged?
'issue-box-merged'
elsif item.closed?
'issue-box-closed'
else
'issue-box-open'
end
end
def issue_to_atom(xml, issue)
xml.entry do
xml.id namespace_project_issue_url(issue.project.namespace,
issue.project, issue)
xml.link href: namespace_project_issue_url(issue.project.namespace,
issue.project, issue)
xml.title truncate(issue.title, length: 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
end
# Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue
end
end
module Gitlab
module LabelsHelper
include ActionView::Helpers::TagHelper
# Link to a Label
#
# label - Label object to link to
# project - Project object which will be used as the context for the label's
# link. If omitted, defaults to `@project`, or the label's own
# project.
# block - An optional block that will be passed to `link_to`, forming the
# body of the link element. If omitted, defaults to
# `render_colored_label`.
#
# Examples:
#
# # Allow the generated link to use the label's own project
# link_to_label(label)
#
# # Force the generated link to use @project
# @project = Project.first
# link_to_label(label)
#
# # Force the generated link to use a provided project
# link_to_label(label, project: Project.last)
#
# # Customize link body with a block
# link_to_label(label) { "My Custom Label Text" }
#
# Returns a String
def link_to_label(label, project: nil, &block)
project ||= @project || label.project
link = namespace_project_issues_path(project.namespace, project,
label_name: label.name)
if block_given?
link_to link, &block
else
link_to render_colored_label(label), link
end
end
def project_label_names
@project.labels.pluck(:title)
end
def render_colored_label(label)
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="label color-label") +
%( style="background-color: #{label_color}; color: #{text_color}">) +
escape_once(label.name) + '</span>'
span.html_safe
end
def suggested_colors
[
'#0033CC',
'#428BCA',
'#44AD8E',
'#A8D695',
'#5CB85C',
'#69D100',
'#004E00',
'#34495E',
'#7F8C8D',
'#A295D6',
'#5843AD',
'#8E44AD',
'#FFECDB',
'#AD4363',
'#D10069',
'#CC0033',
'#FF0000',
'#D9534F',
'#D1D100',
'#F0AD4E',
'#AD8D43'
]
end
def text_color_for_bg(bg_color)
r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
if (r + g + b) > 500
'#333333'
else
'#FFFFFF'
end
end
def project_labels_options(project)
options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name])
end
# Required for Gitlab::Markdown::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once
end
end
module Gitlab
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
target_project = event.project.forked_from_project || event.project
new_namespace_project_merge_request_path(
event.project.namespace,
event.project,
new_mr_from_push_event(event, target_project)
)
end
def new_mr_path_for_fork_from_push_event(event)
new_namespace_project_merge_request_path(
event.project.namespace,
event.project,
new_mr_from_push_event(event, event.project.forked_from_project)
)
end
def new_mr_from_push_event(event, target_project)
{
merge_request: {
source_project_id: event.project.id,
target_project_id: target_project.id,
source_branch: event.branch_name,
target_branch: target_project.repository.root_ref
}
}
end
def mr_css_classes(mr)
classes = "merge-request"
classes << " closed" if mr.closed?
classes << " merged" if mr.merged?
classes
end
def ci_build_details_path(merge_request)
merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch)
end
def merge_path_description(merge_request, separator)
if merge_request.for_fork?
"Project:Branches: #{@merge_request.source_project_path}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
else
"Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
end
end
def issues_sentence(issues)
issues.map { |i| "##{i.iid}" }.to_sentence
end
def mr_change_branches_path(merge_request)
new_namespace_project_merge_request_path(
@project.namespace, @project,
merge_request: {
source_project_id: @merge_request.source_project_id,
target_project_id: @merge_request.target_project_id,
source_branch: @merge_request.source_branch,
target_branch: nil
}
)
end
def source_branch_with_namespace(merge_request)
if merge_request.for_fork?
namespace = link_to(merge_request.source_project_namespace,
project_path(merge_request.source_project))
namespace + ":#{merge_request.source_branch}"
else
merge_request.source_branch
end
end
end
end
module Gitlab
module MilestonesHelper
def milestones_filter_path(opts = {})
if @project
namespace_project_milestones_path(@project.namespace, @project, opts)
elsif @group
group_milestones_path(@group, opts)
else
dashboard_milestones_path(opts)
end
end
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
style: "width: #{milestone.percent_complete}%;"
}
content_tag :div, class: 'progress' do
content_tag :div, nil, options
end
end
def projects_milestones_options
milestones =
if @project
@project.milestones
else
Milestone.where(project_id: @projects)
end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute
grouped_milestones.unshift(Milestone::None)
options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title])
end
end
end
module Gitlab
module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default)
groups = current_user.owned_groups + current_user.masters_groups
users = [current_user.namespace]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
options = []
options << group_opts
options << users_opts
if selected == :current_user && current_user.namespace
selected = current_user.namespace.id
end
grouped_options_for_select(options, selected)
end
def namespace_select_tag(id, opts = {})
css_class = "ajax-namespace-select "
css_class << "multiselect " if opts[:multiple]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
hidden_field_tag(id, value, class: css_class)
end
def namespace_icon(namespace, size = 40)
if namespace.kind_of?(Group)
group_icon(namespace)
else
avatar_icon(namespace.owner.email, size)
end
end
end
end
module Gitlab
module NavHelper
def nav_menu_collapsed?
cookies[:collapsed_nav] == 'true'
end
def nav_sidebar_class
if nav_menu_collapsed?
"page-sidebar-collapsed"
else
"page-sidebar-expanded"
end
end
def nav_header_class
if nav_menu_collapsed?
"header-collapsed"
else
"header-expanded"
end
end
end
end
module Gitlab
module NotesHelper
# Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note)
(@noteable.class.name == note.noteable_type && !note.for_diff_line?)
end
def note_target_fields(note)
hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
hidden_field_tag(:target_id, note.noteable.id)
end
def note_editable?(note)
note.editable? && can?(current_user, :admin_note, note)
end
def link_to_commit_diff_line_note(note)
if note.for_commit_diff_line?
link_to(
"#{note.diff_file_name}:L#{note.diff_new_line}",
namespace_project_commit_path(@project.namespace, @project,
note.noteable, anchor: note.line_code)
)
end
end
def noteable_json(noteable)
{
id: noteable.id,
class: noteable.class.name,
resources: noteable.class.table_name,
project_id: noteable.project.id,
}.to_json
end
def link_to_new_diff_note(line_code, line_type = nil)
discussion_id = Note.build_discussion_id(
@comments_target[:noteable_type],
@comments_target[:noteable_id] || @comments_target[:commit_id],
line_code
)
data = {
noteable_type: @comments_target[:noteable_type],
noteable_id: @comments_target[:noteable_id],
commit_id: @comments_target[:commit_id],
line_code: line_code,
discussion_id: discussion_id,
line_type: line_type
}
button_tag(class: 'btn add-diff-note js-add-diff-note-button',
data: data,
title: 'Add a comment to this line') do
icon('comment-o')
end
end
def link_to_reply_diff(note, line_type = nil)
return unless current_user
data = {
noteable_type: note.noteable_type,
noteable_id: note.noteable_id,
commit_id: note.commit_id,
line_code: note.line_code,
discussion_id: note.discussion_id,
line_type: line_type
}
button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
link_text = icon('comment')
link_text << ' Reply'
end
end
end
end
module Gitlab
module NotificationsHelper
include IconsHelper
def notification_icon(notification)
if notification.disabled?
icon('volume-off', class: 'ns-mute')
elsif notification.participating?
icon('volume-down', class: 'ns-part')
elsif notification.watch?
icon('volume-up', class: 'ns-watch')
else
icon('circle-o', class: 'ns-default')
end
end
end
end
module Gitlab
module PageLayoutHelper
def page_title(*titles)
@page_title ||= []
@page_title.push(*titles.compact) if titles.any?
@page_title.join(" | ")
end
def header_title(title = nil, title_url = nil)
if title
@header_title = title
@header_title_url = title_url
else
@header_title_url ? link_to(@header_title, @header_title_url) : @header_title
end
end
def sidebar(name = nil)
if name
@sidebar = name
else
@sidebar
end
end
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = enabled
else
@fluid_layout
end
end
end
end
module Gitlab
# Helper methods for per-User preferences
module PreferencesHelper
COLOR_SCHEMES = {
1 => 'white',
2 => 'dark',
3 => 'solarized-light',
4 => 'solarized-dark',
5 => 'monokai',
}
COLOR_SCHEMES.default = 'white'
# Helper method to access the COLOR_SCHEMES
#
# The keys are the `color_scheme_ids`
# The values are the `name` of the scheme.
#
# The preview images are `name-scheme-preview.png`
# The stylesheets should use the css class `.name`
def color_schemes
COLOR_SCHEMES.freeze
end
# Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = {
projects: 'Your Projects (default)',
stars: 'Starred Projects'
}.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text
def dashboard_choices
defined = User.dashboards
if defined.size != DASHBOARD_CHOICES.size
# Ensure that anyone adding new options updates this method too
raise RuntimeError, "`User` defines #{defined.size} dashboard choices," +
" but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
else
defined.map do |key, _|
# Use `fetch` so `KeyError` gets raised when a key is missing
[DASHBOARD_CHOICES.fetch(key), key]
end
end
end
def project_view_choices
[
['Readme (default)', :readme],
['Activity view', :activity]
]
end
def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
theme.css_class
end
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
def prefer_readme?
!current_user ||
current_user.project_view == 'readme'
end
end
end
module Gitlab
module ProjectsHelper
def remove_from_project_team_message(project, member)
if member.user
"You are going to remove #{member.user.name} from #{project.name} project team. Are you sure?"
else
"You are going to revoke the invitation for #{member.invite_email} to join #{project.name} project team. Are you sure?"
end
end
def link_to_project(project)
link_to [project.namespace.becomes(Namespace), project] do
title = content_tag(:span, project.name, class: 'project-name')
if project.namespace
namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name')
title = namespace + title
end
title
end
end
def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author' }
opts = default_opts.merge(opts)
return "(deleted)" unless author
author_html = ""
# Build avatar image tag
author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
author_html = author_html.html_safe
if opts[:name]
link_to(author_html, user_path(author), class: "author_link").html_safe
else
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe
end
end
def project_title(project)
if project.group
content_tag :span do
link_to(
simple_sanitize(project.group.name), group_path(project.group)
) + ' / ' +
link_to(simple_sanitize(project.name),
project_path(project))
end
else
owner = project.namespace.owner
content_tag :span do
link_to(
simple_sanitize(owner.name), user_path(owner)
) + ' / ' +
link_to(simple_sanitize(project.name),
project_path(project))
end
end
end
def remove_project_message(project)
"You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
end
def transfer_project_message(project)
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end
def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
end
def project_nav_tab?(name)
project_nav_tabs.include? name
end
def project_active_milestones
@project.milestones.active.order("due_date, title ASC")
end
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
@project
else
deploy_key.projects.find { |project| can?(current_user, :read_project, project) }
end
end
def can_change_visibility_level?(project, current_user)
return false unless can?(current_user, :change_visibility_level, project)
if project.forked?
project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE
else
true
end
end
private
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
if !project.empty_repo? && can?(current_user, :download_code, project)
nav_tabs << [:files, :commits, :network, :graphs]
end
if project.repo_exists? && can?(current_user, :read_merge_request, project)
nav_tabs << :merge_requests
end
if can?(current_user, :admin_project, project)
nav_tabs << :settings
end
if can?(current_user, :read_issue, project)
nav_tabs << :issues
end
if can?(current_user, :read_wiki, project)
nav_tabs << :wiki
end
if can?(current_user, :read_project_snippet, project)
nav_tabs << :snippets
end
if can?(current_user, :read_label, project)
nav_tabs << :labels
end
if can?(current_user, :read_milestone, project)
nav_tabs << :milestones
end
nav_tabs.flatten
end
def git_user_name
if current_user
current_user.name
else
"Your name"
end
end
def git_user_email
if current_user
current_user.email
else
"your@email.com"
end
end
def repository_size(project = nil)
"#{(project || @project).repository_size} MB"
rescue
# In order to prevent 500 error
# when application cannot allocate memory
# to calculate repo size - just show 'Unknown'
'unknown'
end
def default_url_to_repo(project = nil)
project = project || @project
current_user ? project.url_to_repo : project.http_url_to_repo
end
def default_clone_protocol
current_user ? "ssh" : "http"
end
def project_last_activity(project)
if project.last_activity_at
time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
else
"Never"
end
end
def add_contribution_guide_path(project)
if project && !project.repository.contribution_guide
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "CONTRIBUTING.md",
commit_message: "Add contribution guide"
)
end
end
def add_changelog_path(project)
if project && !project.repository.changelog
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "CHANGELOG",
commit_message: "Add changelog"
)
end
end
def add_license_path(project)
if project && !project.repository.license
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "LICENSE",
commit_message: "Add license"
)
end
end
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path(
project.namespace,
project,
tree_join(project.default_branch,
contribution_guide.name)
)
end
end
def readme_path(project)
filename_path(project, :readme)
end
def changelog_path(project)
filename_path(project, :changelog)
end
def license_path(project)
filename_path(project, :license)
end
def version_path(project)
filename_path(project, :version)
end
def hidden_pass_url(original_url)
result = URI(original_url)
result.password = '*****' unless result.password.nil?
result
rescue
original_url
end
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
namespace_project_wiki_path(proj.namespace, proj, page, url_params)
end
def project_status_css_class(status)
case status
when "started"
"active"
when "failed"
"danger"
when "finished"
"success"
end
end
def user_max_access_in_project(user, project)
level = project.team.max_member_access(user)
if level
Gitlab::Access.options_with_owner.key(level)
end
end
def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?"
end
def new_readme_path
ref = @repository.root_ref if @repository
ref ||= 'master'
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
end
def last_push_event
if current_user
current_user.recent_push(@project.id)
end
end
def readme_cache_key
sha = @project.commit.try(:sha) || 'nil'
[@project.id, sha, "readme"].join('-')
end
def round_commit_count(project)
count = project.commit_count
if count > 10000
'10000+'
elsif count > 5000
'5000+'
elsif count > 1000
'1000+'
else
count
end
end
private
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
namespace_project_blob_path(
project.namespace,
project,
tree_join(project.default_branch,
blob.name)
)
end
end
end
end
module Gitlab
module SearchHelper
def search_autocomplete_opts(term)
return unless current_user
resources_results = [
groups_autocomplete(term),
projects_autocomplete(term)
].flatten
generic_results = project_autocomplete + default_autocomplete + help_autocomplete
generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") }
[
resources_results,
generic_results
].flatten.uniq do |item|
item[:label]
end
end
private
# Autocomplete results for various settings pages
def default_autocomplete
[
{ label: "Profile settings", url: profile_path },
{ label: "SSH Keys", url: profile_keys_path },
{ label: "Dashboard", url: root_path },
{ label: "Admin Section", url: admin_root_path },
]
end
# Autocomplete results for internal help pages
def help_autocomplete
[
{ label: "help: API Help", url: help_page_path("api", "README") },
{ label: "help: Markdown Help", url: help_page_path("markdown", "markdown") },
{ label: "help: Permissions Help", url: help_page_path("permissions", "permissions") },
{ label: "help: Public Access Help", url: help_page_path("public_access", "public_access") },
{ label: "help: Rake Tasks Help", url: help_page_path("raketasks", "README") },
{ label: "help: SSH Keys Help", url: help_page_path("ssh", "README") },
{ label: "help: System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
{ label: "help: Web Hooks Help", url: help_page_path("web_hooks", "web_hooks") },
{ label: "help: Workflow Help", url: help_page_path("workflow", "README") },
]
end
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
prefix = search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref
[
{ label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
{ label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) },
{ label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
{ label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
{ label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
{ label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) },
{ label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
]
else
[]
end
end
# Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5)
current_user.authorized_groups.search(term).limit(limit).map do |group|
{
label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group)
}
end
end
# Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
url: namespace_project_path(p.namespace, p)
}
end
end
def search_result_sanitize(str)
Sanitize.clean(str)
end
def search_filter_path(options={})
exist_opts = {
search: params[:search],
project_id: params[:project_id],
group_id: params[:group_id],
scope: params[:scope]
}
options = exist_opts.merge(options)
search_path(options)
end
# Sanitize html generated after parsing markdown from issue description or comment
def search_md_sanitize(html)
sanitize(html, tags: %w(a p ol ul li pre code))
end
end
end
module Gitlab
module SelectsHelper
def users_select_tag(id, opts = {})
css_class = "ajax-users-select "
css_class << "multiselect " if opts[:multiple]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
placeholder = opts[:placeholder] || 'Search for a user'
null_user = opts[:null_user] || false
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false
project = opts[:project] || @project
html = {
class: css_class,
'data-placeholder' => placeholder,
'data-null-user' => null_user,
'data-any-user' => any_user,
'data-email-user' => email_user,
'data-first-user' => first_user,
'data-current-user' => current_user
}
unless opts[:scope] == :all
if project
html['data-project-id'] = project.id
elsif @group
html['data-group-id'] = @group.id
end
end
hidden_field_tag(id, value, html)
end
def groups_select_tag(id, opts = {})
css_class = "ajax-groups-select "
css_class << "multiselect " if opts[:multiple]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
hidden_field_tag(id, value, class: css_class)
end
end
end
module Gitlab
module SnippetsHelper
def lifetime_select_options
options = [
['forever', nil],
['1 day', "#{Date.current + 1.day}"],
['1 week', "#{Date.current + 1.week}"],
['1 month', "#{Date.current + 1.month}"]
]
options_for_select(options)
end
def reliable_snippet_path(snippet)
if snippet.project_id?
namespace_project_snippet_path(snippet.project.namespace,
snippet.project, snippet)
else
snippet_path(snippet)
end
end
end
end
module Gitlab
module SortingHelper
def sort_options_hash
{
sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created,
sort_value_milestone_soon => sort_title_milestone_soon,
sort_value_milestone_later => sort_title_milestone_later,
sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
}
end
def sort_title_oldest_updated
'Oldest updated'
end
def sort_title_recently_updated
'Recently updated'
end
def sort_title_oldest_created
'Oldest created'
end
def sort_title_recently_created
'Recently created'
end
def sort_title_milestone_soon
'Milestone due soon'
end
def sort_title_milestone_later
'Milestone due later'
end
def sort_title_name
'Name'
end
def sort_title_largest_repo
'Largest repository'
end
def sort_title_recently_signin
'Recent sign in'
end
def sort_title_oldest_signin
'Oldest sign in'
end
def sort_value_oldest_updated
'updated_asc'
end
def sort_value_recently_updated
'updated_desc'
end
def sort_value_oldest_created
'created_asc'
end
def sort_value_recently_created
'created_desc'
end
def sort_value_milestone_soon
'milestone_due_asc'
end
def sort_value_milestone_later
'milestone_due_desc'
end
def sort_value_name
'name_asc'
end
def sort_value_largest_repo
'repository_size_desc'
end
def sort_value_recently_signin
'recent_sign_in'
end
def sort_value_oldest_signin
'oldest_sign_in'
end
end
end
module Gitlab
module SubmoduleHelper
include Gitlab::ShellAdapter
# links to files listing for submodule if submodule is a project on this server
def submodule_links(submodule_item, ref = nil, repository = @repository)
url = repository.submodule_url_for(ref, submodule_item.path)
return url, nil unless url =~ /([^\/:]+)\/([^\/]+\.git)\Z/
namespace = $1
project = $2
project.chomp!('.git')
if self_url?(url, namespace, project)
return namespace_project_path(namespace, project),
namespace_project_tree_path(namespace, project,
submodule_item.id)
elsif relative_self_url?(url)
relative_self_links(url, submodule_item.id)
elsif github_dot_com_url?(url)
standard_links('github.com', namespace, project, submodule_item.id)
elsif gitlab_dot_com_url?(url)
standard_links('gitlab.com', namespace, project, submodule_item.id)
else
return url, nil
end
end
protected
def github_dot_com_url?(url)
url =~ /github\.com[\/:][^\/]+\/[^\/]+\Z/
end
def gitlab_dot_com_url?(url)
url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/
end
def self_url?(url, namespace, project)
return true if url == [ Gitlab.config.gitlab.url, '/', namespace, '/',
project, '.git' ].join('')
url == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
end
def relative_self_url?(url)
# (./)?(../repo.git) || (./)?(../../project/repo.git) )
url =~ /\A((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*\.git\z/ || url =~ /\A((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*\.git\z/
end
def standard_links(host, namespace, project, commit)
base = [ 'https://', host, '/', namespace, '/', project ].join('')
[base, [ base, '/tree/', commit ].join('')]
end
def relative_self_links(url, commit)
# Map relative links to a namespace and project
# For example:
# ../bar.git -> same namespace, repo bar
# ../foo/bar.git -> namespace foo, repo bar
# ../../foo/bar/baz.git -> namespace bar, repo baz
components = url.split('/')
base = components.pop.gsub(/.git$/, '')
namespace = components.pop.gsub(/^\.\.$/, '')
if namespace.empty?
namespace = @project.namespace.path
end
[
namespace_project_path(namespace, base),
namespace_project_tree_path(namespace, base, commit)
]
end
end
end
module Gitlab
module TabHelper
# Navigation link helper
#
# Returns an `li` element with an 'active' class if the supplied
# controller(s) and/or action(s) are currently active. The content of the
# element is the value passed to the block.
#
# options - The options hash used to determine if the element is "active" (default: {})
# :controller - One or more controller names to check (optional).
# :action - One or more action names to check (optional).
# :path - A shorthand path, such as 'dashboard#index', to check (optional).
# :html_options - Extra options to be passed to the list element (optional).
# block - An optional block that will become the contents of the returned
# `li` element.
#
# When both :controller and :action are specified, BOTH must match in order
# to be marked as active. When only one is given, either can match.
#
# Examples
#
# # Assuming we're on TreeController#show
#
# # Controller matches, but action doesn't
# nav_link(controller: [:tree, :refs], action: :edit) { "Hello" }
# # => '<li>Hello</li>'
#
# # Controller matches
# nav_link(controller: [:tree, :refs]) { "Hello" }
# # => '<li class="active">Hello</li>'
#
# # Several paths
# nav_link(path: ['tree#show', 'profile#show']) { "Hello" }
# # => '<li class="active">Hello</li>'
#
# # Shorthand path
# nav_link(path: 'tree#show') { "Hello" }
# # => '<li class="active">Hello</li>'
#
# # Supplying custom options for the list element
# nav_link(controller: :tree, html_options: {class: 'home'}) { "Hello" }
# # => '<li class="home active">Hello</li>'
#
# Returns a list item element String
def nav_link(options = {}, &block)
klass = active_nav_link?(options) ? 'active' : ''
# Add our custom class into the html_options, which may or may not exist
# and which may or may not already have a :class key
o = options.delete(:html_options) || {}
o[:class] ||= ''
o[:class] += ' ' + klass
o[:class].strip!
if block_given?
content_tag(:li, capture(&block), o)
else
content_tag(:li, nil, o)
end
end
def active_nav_link?(options)
if path = options.delete(:path)
unless path.respond_to?(:each)
path = [path]
end
path.any? do |single_path|
current_path?(single_path)
end
elsif page = options.delete(:page)
unless page.respond_to?(:each)
page = [page]
end
page.any? do |single_page|
current_page?(single_page)
end
else
c = options.delete(:controller)
a = options.delete(:action)
if c && a
# When given both options, make sure BOTH are true
current_controller?(*c) && current_action?(*a)
else
# Otherwise check EITHER option
current_controller?(*c) || current_action?(*a)
end
end
end
def current_path?(path)
c, a, _ = path.split('#')
current_controller?(c) && current_action?(a)
end
def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
"active"
end
end
def branches_tab_class
if current_controller?(:protected_branches) ||
current_controller?(:branches) ||
current_page?(namespace_project_repository_path(@project.namespace,
@project))
'active'
end
end
# Use nav_tab for save controller/action but different params
def nav_tab(key, value, &block)
o = {}
o[:class] = ""
if value.nil?
o[:class] << " active" if params[key].blank?
else
o[:class] << " active" if params[key] == value
end
if block_given?
content_tag(:li, capture(&block), o)
else
content_tag(:li, nil, o)
end
end
end
end
module Gitlab
module TagsHelper
def tag_path(tag)
"/tags/#{tag}"
end
def tag_list(project)
html = ''
project.tag_list.each do |tag|
html << link_to(tag, tag_path(tag))
end
html.html_safe
end
end
end
module Gitlab
module TreeHelper
# Sorts a repository's tree so that folders are before files and renders
# their corresponding partials
#
def render_tree(tree)
# Render Folders before Files/Submodules
folders, files, submodules = tree.trees, tree.blobs, tree.submodules
tree = ""
# Render folders if we have any
tree << render(partial: 'projects/tree/tree_item', collection: folders,
locals: { type: 'folder' }) if folders.present?
# Render files if we have any
tree << render(partial: 'projects/tree/blob_item', collection: files,
locals: { type: 'file' }) if files.present?
# Render submodules if we have any
tree << render(partial: 'projects/tree/submodule_item',
collection: submodules) if submodules.present?
tree.html_safe
end
def render_readme(readme)
render_markup(readme.name, readme.data)
end
# Return an image icon depending on the file type and mode
#
# type - String type of the tree item; either 'folder' or 'file'
# mode - File unix mode
# name - File name
def tree_icon(type, mode, name)
icon("#{file_type_icon_class(type, mode, name)} fw")
end
def tree_hex_class(content)
"file_#{hexdigest(content.name)}"
end
# Simple shortcut to File.join
def tree_join(*args)
File.join(*args)
end
def allowed_tree_edit?(project = nil, ref = nil)
project ||= @project
ref ||= @ref
return false unless project.repository.branch_names.include?(ref)
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
end
def tree_breadcrumbs(tree, max_links = 2)
if @path.present?
part_path = ""
parts = @path.split('/')
yield('..', nil) if parts.count > max_links
parts.each do |part|
part_path = File.join(part_path, part) unless part_path.empty?
part_path = part if part_path.empty?
next unless parts.last(2).include?(part) if parts.count > max_links
yield(part, tree_join(@ref, part_path))
end
end
end
def up_dir_path
file = File.join(@path, "..")
tree_join(@ref, file)
end
# returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(tree)
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir?
return tree_join(tree.name, flatten_tree(subtree.first))
else
return tree.name
end
end
end
end
module Gitlab
module VersionCheckHelper
def version_status_badge
if Rails.env.production?
image_tag VersionCheck.new.url
end
end
end
end
module Gitlab
module VisibilityLevelHelper
def visibility_level_color(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
'vs-private'
when Gitlab::VisibilityLevel::INTERNAL
'vs-internal'
when Gitlab::VisibilityLevel::PUBLIC
'vs-public'
end
end
# Return the description for the +level+ argument.
#
# +level+ One of the Gitlab::VisibilityLevel constants
# +form_model+ Either a model object (Project, Snippet, etc.) or the name of
# a Project or Snippet class.
def visibility_level_description(level, form_model)
case form_model.is_a?(String) ? form_model : form_model.class.name
when 'PersonalSnippet', 'ProjectSnippet', 'Snippet'
snippet_visibility_level_description(level)
when 'Project'
project_visibility_level_description(level)
end
end
def project_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "Project access must be granted explicitly for each user."
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The project can be cloned by"
haml_concat "any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The project can be cloned"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def snippet_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "The snippet is visible only for me."
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The snippet can be accessed"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
private_icon
when Gitlab::VisibilityLevel::INTERNAL
internal_icon
when Gitlab::VisibilityLevel::PUBLIC
public_icon
end
end
def visibility_level_label(level)
Project.visibility_levels.key(level)
end
def restricted_visibility_levels(show_all = false)
return [] if current_user.is_admin? && !show_all
current_application_settings.restricted_visibility_levels || []
end
def default_project_visibility
current_application_settings.default_project_visibility
end
def default_snippet_visibility
current_application_settings.default_snippet_visibility
end
def skip_level?(form_model, level)
form_model.is_a?(Project) &&
form_model.forked? &&
!Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
end
end
end
module Gitlab
module WikiHelper
# Rails v4.1.9+ escapes all model IDs, converting slashes into %2F. The
# only way around this is to implement our own path generators.
def namespace_project_wiki_path(namespace, project, wiki_page, *args)
slug =
case wiki_page
when Symbol
wiki_page
when String
wiki_page
else
wiki_page.slug
end
namespace_project_path(namespace, project) + "/wikis/#{slug}"
end
def edit_namespace_project_wiki_path(namespace, project, wiki_page, *args)
namespace_project_wiki_path(namespace, project, wiki_page) + '/edit'
end
def history_namespace_project_wiki_path(namespace, project, wiki_page, *args)
namespace_project_wiki_path(namespace, project, wiki_page) + '/history'
end
end
end
require 'nokogiri'
module GitlabMarkdownHelper
# Use this in places where you would normally use link_to(gfm(...), ...).
#
# It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
# not linked any more). link_to_gfm corrects that. It wraps all parts to
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {})
return "" if body.blank?
escaped_body = if body =~ /\A\<img/
body
else
escape_once(body)
end
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user)
fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link.
text = fragment.children[0].text
fragment.children[0].replace(link_to(text, url, html_options))
else
# Traverse the fragment's first generation of children looking for pure
# text, wrapping anything found in the requested link
fragment.children.each do |node|
next unless node.text?
node.replace(link_to(node.text, url, html_options))
end
end
# Add any custom CSS classes to the GFM-generated reference links
if html_options[:class]
fragment.css('a.gfm').add_class(html_options[:class])
end
fragment.to_html.html_safe
end
def markdown(text, context = {})
context.merge!(
current_user: current_user,
path: @path,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
Gitlab::Markdown.render(text, context)
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 = {})
options.merge!(
current_user: current_user,
path: @path,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
Gitlab::Markdown.gfm(text, options)
end
def asciidoc(text)
Gitlab::Asciidoc.render(text, {
commit: @commit,
project: @project,
project_wiki: @project_wiki,
requested_path: @path,
ref: @ref
})
end
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(text, max_chars = nil, options = {})
md = markdown(text, options).strip
truncate_visible(md, max_chars || md.length) if md.present?
end
def render_wiki_content(wiki_page)
case wiki_page.format
when :markdown
markdown(wiki_page.content)
when :asciidoc
asciidoc(wiki_page.content)
else
wiki_page.formatted_content.html_safe
end
end
MARKDOWN_TIPS = [
"End a line with two or more spaces for a line-break, or soft-return",
"Inline code can be denoted by `surrounding it with backticks`",
"Blocks of code can be denoted by three backticks ``` or four leading spaces",
"Emoji can be added by :emoji_name:, for example :thumbsup:",
"Notify other participants using @user_name",
"Notify a specific group using @group_name",
"Notify the entire team using @all",
"Reference an issue using a hash, for example issue #123",
"Reference a merge request using an exclamation point, for example MR !123",
"Italicize words or phrases using *asterisks* or _underscores_",
"Bold words or phrases using **double asterisks** or __double underscores__",
"Strikethrough words or phrases using ~~two tildes~~",
"Make a bulleted list using + pluses, - minuses, or * asterisks",
"Denote blockquotes using > at the beginning of a line",
"Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___"
].freeze
# Returns a random markdown tip for use as a textarea placeholder
def random_markdown_tip
MARKDOWN_TIPS.sample
end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
# tags.
def truncate_visible(text, max_chars)
doc = Nokogiri::HTML.fragment(text)
content_length = 0
truncated = false
doc.traverse do |node|
if node.text? || node.content.empty?
if truncated
node.remove
next
end
# Handle line breaks within a node
if node.content.strip.lines.length > 1
node.content = "#{node.content.lines.first.chomp}..."
truncated = true
end
num_remaining = max_chars - content_length
if node.content.length > num_remaining
node.content = node.content.truncate(num_remaining)
truncated = true
end
content_length += node.content.length
end
truncated = truncate_if_block(node, truncated)
end
doc.to_html
end
# Used by #truncate_visible. If +node+ is the first block element, and the
# text hasn't already been truncated, then append "..." to the node contents
# and return true. Otherwise return false.
def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated
node.content = "#{node.content}..." if node.next_sibling
true
else
truncated
end
end
# Returns the text necessary to reference `entity` across projects
#
# project - Project to reference
# entity - Object that responds to `to_reference`
#
# Examples:
#
# cross_project_reference(project, project.issues.first)
# # => 'namespace1/project1#123'
#
# cross_project_reference(project, project.merge_requests.first)
# # => 'namespace1/project1!345'
#
# Returns a String
def cross_project_reference(project, entity)
if entity.respond_to?(:to_reference)
"#{project.to_reference}#{entity.to_reference}"
else
''
end
end
end
# Shorter routing method for project and project items
# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
# so we use nested routing for project resources which include project and
# project namespace. To avoid writing long methods every time we define shortcuts for
# some of routing.
#
# For example instead of this:
#
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
#
# We can simply use shortcut:
#
# merge_request_path(merge_request)
#
module GitlabRoutingHelper
def project_path(project, *args)
namespace_project_path(project.namespace, project, *args)
end
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
def edit_project_path(project, *args)
edit_namespace_project_path(project.namespace, project, *args)
end
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
def merge_request_path(entity, *args)
namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
end
def milestone_path(entity, *args)
namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
end
def project_url(project, *args)
namespace_project_url(project.namespace, project, *args)
end
def edit_project_url(project, *args)
edit_namespace_project_url(project.namespace, project, *args)
end
def issue_url(entity, *args)
namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
end
def merge_request_url(entity, *args)
namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
end
def project_snippet_url(entity, *args)
namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
end
def toggle_subscription_path(entity, *args)
if entity.is_a?(Issue)
toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity)
else
toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity)
end
end
end
module GraphHelper
def get_refs(repo, commit)
refs = ""
refs << commit.ref_names(repo).join(' ')
# append note count
refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
refs
end
def parents_zip_spaces(parents, parent_spaces)
ids = parents.map { |p| p.id }
ids.zip(parent_spaces)
end
end
module GroupsHelper
def remove_user_from_group_message(group, member)
if member.user
"Are you sure you want to remove \"#{member.user.name}\" from \"#{group.name}\"?"
else
"Are you sure you want to revoke the invitation for \"#{member.invite_email}\" to join \"#{group.name}\"?"
end
end
def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?"
end
def should_user_see_group_roles?(user, group)
if user
user.is_admin? || group.members.exists?(user_id: user.id)
else
false
end
end
def group_icon(group)
if group.is_a?(String)
group = Group.find_by(path: group)
end
if group && group.avatar.present?
group.avatar.url
else
image_path('no_group_avatar.png')
end
end
def group_title(group, name, url)
content_tag :span do
link_to(
simple_sanitize(group.name), group_path(group)
) + ' &middot; '.html_safe +
link_to(simple_sanitize(name), url)
end
end
end
module IconsHelper
include FontAwesome::Rails::IconHelper
# Creates an icon tag given icon name(s) and possible icon modifiers.
#
# Right now this method simply delegates directly to `fa_icon` from the
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
fa_icon(names, options)
end
def spinner(text = nil, visible = false)
css_class = 'loading'
css_class << ' hide' unless visible
content_tag :div, class: css_class do
icon('spinner spin') + text
end
end
def boolean_to_icon(value)
if value
icon('circle', class: 'cgreen')
else
icon('power-off', class: 'clgray')
end
end
def public_icon
icon('globe fw')
end
def internal_icon
icon('shield fw')
end
def private_icon
icon('lock fw')
end
def file_type_icon_class(type, mode, name)
if type == 'folder'
icon_class = 'folder'
elsif mode == '120000'
icon_class = 'share'
else
# Guess which icon to choose based on file extension.
# If you think a file extension is missing, feel free to add it on PR
case File.extname(name).downcase
when '.pdf'
icon_class = 'file-pdf-o'
when '.jpg', '.jpeg', '.jif', '.jfif',
'.jp2', '.jpx', '.j2k', '.j2c',
'.png', '.gif', '.tif', '.tiff',
'.svg', '.ico', '.bmp'
icon_class = 'file-image-o'
when '.zip', '.zipx', '.tar', '.gz', '.bz', '.bzip',
'.xz', '.rar', '.7z'
icon_class = 'file-archive-o'
when '.mp3', '.wma', '.ogg', '.oga', '.wav', '.flac', '.aac'
icon_class = 'file-audio-o'
when '.mp4', '.m4p', '.m4v',
'.mpg', '.mp2', '.mpeg', '.mpe', '.mpv',
'.mpg', '.mpeg', '.m2v',
'.avi', '.mkv', '.flv', '.ogv', '.mov',
'.3gp', '.3g2'
icon_class = 'file-video-o'
when '.doc', '.dot', '.docx', '.docm', '.dotx', '.dotm', '.docb'
icon_class = 'file-word-o'
when '.xls', '.xlt', '.xlm', '.xlsx', '.xlsm', '.xltx', '.xltm',
'.xlsb', '.xla', '.xlam', '.xll', '.xlw'
icon_class = 'file-excel-o'
when '.ppt', '.pot', '.pps', '.pptx', '.pptm', '.potx', '.potm',
'.ppam', '.ppsx', '.ppsm', '.sldx', '.sldm'
icon_class = 'file-powerpoint-o'
else
icon_class = 'file-text-o'
end
end
icon_class
end
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.
module NotificationsHelper
include IconsHelper
def notification_icon(notification)
if notification.disabled?
icon('volume-off', class: 'ns-mute')
elsif notification.participating?
icon('volume-down', class: 'ns-part')
elsif notification.watch?
icon('volume-up', class: 'ns-watch')
else
icon('circle-o', class: 'ns-default')
end
end
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.
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