Commit 0bca65b2 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into gsmethells/gitlab-ce-sort-by-due-date

parents dbbd2b86 234f4bf2
......@@ -4,9 +4,17 @@ v 8.3.0 (unreleased)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
- Add ignore whitespace change option to commit view
- Fire update hook from GitLab
- Fix: sort milestones by due date once again (Greg Smethells)
- Don't show project fork event as "imported"
- Add API endpoint to fetch merge request commits list
- Expose events API with comment information and author info
- Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
v 8.2.2
- Fix 404 in redirection after removing a project (Stan Hu)
......@@ -14,6 +22,9 @@ v 8.2.2
- Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
- Fix: Raw private snippets access workflow
- Prevent "413 Request entity too large" errors when pushing large files with LFS
- Fix invalid links within projects dashboard header
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
- Fix: duplicate email notifications on issue comments
v 8.2.1
- Forcefully update builds that didn't want to update with state machine
......
......@@ -3,5 +3,5 @@
# lib/support/init.d, which call scripts in bin/ .
#
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q mailers -q default
worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
......@@ -135,17 +135,25 @@ $ ->
), 1
# Initialize tooltips
$('body').tooltip({
selector: '.has_tooltip, [data-toggle="tooltip"], .page-sidebar-collapsed .nav-sidebar a'
$('body').tooltip(
selector: '.has_tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
if $el.attr('id') == 'js-shortcuts-home'
# Place the logo tooltip on the right when collapsed, bottom when expanded
$el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
else
# Otherwise use the data-placement attribute, or 'bottom' if undefined
$el.data('placement') or 'bottom'
})
$el.data('placement') || 'bottom'
)
$('.header-logo .home').tooltip(
placement: (_, el) ->
$el = $(el)
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
container: 'body'
)
$('.page-with-sidebar').tooltip(
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
placement: 'right'
container: 'body'
)
# Form submitter
$('.trigger-submit').on 'change', ->
......
......@@ -29,7 +29,7 @@
$('#filter_issue_search').val($('#issue_search').val())
initSelects: ->
$("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
......
......@@ -10,17 +10,20 @@ class @MergeRequestWidget
constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false)
mergeInProgress: ->
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
type: 'GET'
url: $('.merge-request').data('url')
success: (data) =>
if data.state == "merged"
location.reload()
urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
window.location.href = window.location.href + urlSuffix
else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else
setTimeout(merge_request_widget.mergeInProgress, 2000)
callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
setTimeout(callback, 2000)
dataType: 'json'
getMergeStatus: ->
......
......@@ -5,6 +5,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
)
......@@ -2,3 +2,9 @@ class @User
constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList()
$('.hide-project-limit-message').on 'click', (e) ->
path = '/'
$.cookie('hide_project_limit_message', 'false', { path: path })
$(@).parents('.project-limit-message').remove()
e.preventDefault()
......@@ -32,17 +32,15 @@ class @UsersSelect
if showNullUser
nullUser = {
name: 'Unassigned',
avatar: null,
username: 'none',
id: 0
}
data.results.unshift(nullUser)
if showAnyUser
name = showAnyUser
name = 'Any User' if name == true
anyUser = {
name: 'Any',
avatar: null,
username: 'none',
name: name,
id: null
}
data.results.unshift(anyUser)
......@@ -50,7 +48,6 @@ class @UsersSelect
if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
emailUser = {
name: "Invite \"#{query.term}\"",
avatar: null,
username: query.term,
id: query.term
}
......@@ -82,10 +79,10 @@ class @UsersSelect
else
avatar = gon.default_avatar_url
"<div class='user-result'>
"<div class='user-result #{'no-username' unless user.username}'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
<div class='user-name'>#{user.name}</div>
<div class='user-username'>#{user.username}</div>
<div class='user-username'>#{user.username || ""}</div>
</div>"
formatSelection: (user) ->
......
......@@ -116,6 +116,11 @@
position: absolute;
top: 10px;
right: 10px;
&.left {
left: 10px;
right: auto;
}
}
}
......
......@@ -341,10 +341,6 @@ table {
text-align: center;
}
.task-status {
margin-left: 10px;
}
#nprogress .spinner {
top: 15px !important;
right: 10px !important;
......
......@@ -91,9 +91,17 @@ label {
}
.input-group {
.select2-container {
display: table-cell;
width: 200px !important;
}
.input-group-addon {
background-color: #f7f8fa;
}
.input-group-addon:not(:first-child):not(:last-child) {
border-left: 0;
border-right: 0;
}
}
.help-block {
......
......@@ -6,15 +6,17 @@ header {
transition-duration: .3s;
&.navbar-empty {
height: 58px;
background: #FFF;
border-bottom: 1px solid #EEE;
.center-logo {
margin: 8px 0;
margin: 11px 0;
text-align: center;
img {
height: 32px;
#tanuki-logo, img {
width: 36px;
height: 36px;
}
}
}
......
......@@ -6,6 +6,10 @@ html {
body {
background-color: #EAEBEC !important;
&.navless {
background-color: white !important;
}
}
.container {
......@@ -18,8 +22,8 @@ body {
}
.navless-container {
padding-top: $header-height;
margin-top: 30px;
margin-top: $header-height;
padding-top: $gl-padding * 2;
}
.container-limited {
......
......@@ -15,6 +15,16 @@
border-left: none;
padding-top: 5px;
}
.select2-chosen {
color: $gl-text-color;
}
&.select2-default {
.select2-chosen {
color: #999;
}
}
}
}
......@@ -23,6 +33,7 @@
border: 1px solid #e7e9ed;
}
.select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
@include border-radius (0px);
......@@ -48,17 +59,38 @@
color: #313236;
}
.select2-container-multi .select2-choices {
.select2-container-multi {
.select2-choices {
@include border-radius(2px);
border-color: #CCC;
}
border-color: $input-border;
background: white;
padding-left: $gl-padding / 2;
.select2-container-multi .select2-choices .select2-search-field input {
padding: 8px 14px;
.select2-search-field input {
padding: $gl-padding / 2;
font-size: 13px;
line-height: 18px;
height: auto;
font-family: inherit;
font-size: inherit;
}
.select2-search-choice {
margin: 8px 0 0 8px;
background: white;
box-shadow: none;
border-color: $input-border;
color: $gl-text-color;
line-height: 15px;
.select2-search-choice-close {
top: 5px;
}
&.select2-search-choice-focus {
border-color: $gl-text-color;
}
}
}
}
.select2-drop-active {
......@@ -123,10 +155,16 @@
}
.user-result {
min-height: 24px;
.user-image {
float: left;
}
&.no-username {
.user-name {
line-height: 24px;
}
}
}
......
.page-with-sidebar {
padding-top: $header-height;
transition-duration: .3s;
.sidebar-wrapper {
position: fixed;
......@@ -16,7 +17,6 @@
.sidebar-wrapper {
z-index: 99;
background: $background-color;
transition-duration: .3s;
}
.content-wrapper {
......@@ -35,6 +35,83 @@
}
}
.sidebar-wrapper {
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
position: fixed;
z-index: 999;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: 11px 0 11px 22px;
overflow: hidden;
outline: none;
transition-duration: .3s;
img {
width: 36px;
height: 36px;
}
#tanuki-logo, img {
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
.sidebar-user {
padding: 9px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
.username {
margin-left: 10px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
.nav-sidebar {
margin-top: 14 + $header-height;
margin-bottom: 100px;
......@@ -61,7 +138,7 @@
color: $gray;
display: block;
text-decoration: none;
padding-left: 22px;
padding-left: 23px;
font-weight: normal;
outline: none;
......@@ -85,6 +162,10 @@
padding: 0px 8px;
@include border-radius(6px);
}
&.back-link i {
transition-duration: .3s;
}
}
}
}
......@@ -100,7 +181,6 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_width;
......@@ -114,16 +194,15 @@
&.back-link {
i {
visibility: hidden;
opacity: 0;
}
}
}
}
}
@mixin folded-sidebar {
padding-left: 60px;
transition-duration: .3s;
@mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width;
.sidebar-wrapper {
width: $sidebar_collapsed_width;
......@@ -132,7 +211,7 @@
width: $sidebar_collapsed_width;
a {
padding-left: 12px;
padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
......@@ -143,19 +222,23 @@
.nav-sidebar {
width: $sidebar_collapsed_width;
li a {
li {
width: auto;
a {
span {
display: none;
}
}
}
}
.collapse-nav a {
width: $sidebar_collapsed_width;
}
.sidebar-user {
padding-left: 12px;
padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
.username {
......@@ -186,11 +269,11 @@
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
@include folded-sidebar;
@include collapsed-sidebar;
}
.page-sidebar-expanded {
@include folded-sidebar;
@include collapsed-sidebar;
}
.collapse-nav {
......@@ -200,83 +283,10 @@
@media(min-width: $screen-md-max) {
.page-sidebar-collapsed {
@include folded-sidebar;
@include collapsed-sidebar;
}
.page-sidebar-expanded {
@include expanded-sidebar;
}
}
.sidebar-user {
padding: 9px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
.username {
margin-left: 10px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
}
.sidebar-wrapper {
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: 10px 22px;
overflow: hidden;
outline: none;
img {
width: 36px;
height: 36px;
}
#tanuki-logo, img {
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
......@@ -90,6 +90,17 @@
}
}
.issuable-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
.cross-project-reference {
text-align: center;
width: 100%;
......
......@@ -56,17 +56,6 @@
}
}
.issue-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
form.edit-issue {
margin: 0;
}
......
/* Login Page */
.login-page {
background-color: white;
.container {
max-width: 960px;
}
......@@ -21,6 +19,7 @@
h1:first-child {
font-weight: normal;
margin-bottom: 30px;
margin-top: 0;
}
img {
......
......@@ -173,27 +173,12 @@
line-height: 1.1;
}
.merge-request-form-info {
padding-top: 15px;
}
// hide mr close link for inline diff comment form
.diff-file .close-mr-link,
.diff-file .reopen-mr-link {
display: none;
}
.merge-request-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
.merge-request-form .select2-container {
width: 250px !important;
}
......
......@@ -5,7 +5,7 @@
font-weight: normal;
}
}
.no-ssh-key-message {
.no-ssh-key-message, .project-limit-message {
background-color: #f28d35;
margin-bottom: 16px;
}
......@@ -84,7 +84,7 @@
@extend .btn-gray;
color: $gray;
cursor: auto;
cursor: default;
i {
color: inherit;
......
......@@ -4,3 +4,8 @@
margin-right: auto;
padding-right: 7px;
}
.wiki-last-edit-by {
font-size: 80%;
font-weight: normal;
}
......@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
:email,
:hide_no_password,
:hide_no_ssh_key,
:hide_project_limit,
:linkedin,
:location,
:name,
......
......@@ -3,7 +3,7 @@ class Projects::BranchesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:create, :destroy]
before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index
@sort = params[:sort] || 'name'
......
......@@ -2,7 +2,7 @@ class Projects::TagsController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:create]
before_action :authorize_push_code!, only: [:new, :create]
before_action :authorize_admin_project!, only: [:destroy]
def index
......
......@@ -6,7 +6,7 @@
#
# For example instead of this:
#
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
#
# We can simply use shortcut:
#
......
......@@ -27,16 +27,20 @@ module IconsHelper
end
end
def public_icon
icon('globe fw')
def visibility_level_icon(level, fw: true)
name =
case level
when Gitlab::VisibilityLevel::PRIVATE
'lock'
when Gitlab::VisibilityLevel::INTERNAL
'shield'
else # Gitlab::VisibilityLevel::PUBLIC
'globe'
end
def internal_icon
icon('shield fw')
end
name << " fw" if fw
def private_icon
icon('lock fw')
icon(name)
end
def file_type_icon_class(type, mode, name)
......
......@@ -44,14 +44,17 @@ module IssuesHelper
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])
milestones = project_active_milestones.to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(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)
milestones = object.project.milestones.active.to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
def issue_box_class(item)
......@@ -84,7 +87,11 @@ module IssuesHelper
end
def merge_requests_sentence(merge_requests)
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
# Sorting based on the `!123` or `group/project!123` reference will sort
# local merge requests first.
merge_requests.map do |merge_request|
merge_request.to_reference(@project)
end.sort.to_sentence(last_word_connector: ', or ')
end
def url_to_emoji(name)
......
......@@ -39,7 +39,11 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
issues.map(&:to_reference).to_sentence
# Sorting based on the `#123` or `group/project#123` reference will sort
# local issues first.
issues.map do |issue|
issue.to_reference(@project)
end.sort.to_sentence
end
def mr_change_branches_path(merge_request)
......@@ -49,18 +53,21 @@ module MergeRequestsHelper
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
}
target_branch: @merge_request.target_branch,
},
change_branches: true
)
end
def source_branch_with_namespace(merge_request)
branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
if merge_request.for_fork?
namespace = link_to(merge_request.source_project_namespace,
project_path(merge_request.source_project))
namespace + ":#{merge_request.source_branch}"
namespace + ":" + branch
else
merge_request.source_branch
branch
end
end
......
module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default)
def namespaces_options(selected = :current_user, display_path: false)
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]} ]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ]
users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ]
options = []
options << group_opts
......
......@@ -4,6 +4,14 @@ module NavHelper
end
def nav_sidebar_class
if nav_menu_collapsed?
"sidebar-collapsed"
else
"sidebar-expanded"
end
end
def page_sidebar_class
if nav_menu_collapsed?
"page-sidebar-collapsed"
else
......
......@@ -21,7 +21,7 @@ module ProjectsHelper
end
def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author' }
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
return "(deleted)" unless author
......@@ -39,7 +39,8 @@ module ProjectsHelper
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
title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe
end
end
......
......@@ -15,12 +15,14 @@ module SelectsHelper
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
data: {
placeholder: placeholder,
null_user: null_user,
any_user: any_user,
email_user: email_user,
first_user: first_user,
current_user: current_user
}
}
unless opts[:scope] == :all
......
......@@ -25,48 +25,24 @@ module VisibilityLevelHelper
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."
"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."
"The project can be cloned by any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The project can be cloned"
haml_concat "without any"
haml_concat "authentication."
end
end
"The project can be cloned without any authentication."
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
"The snippet is visible only for me."
when Gitlab::VisibilityLevel::INTERNAL
internal_icon
"The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
public_icon
"The snippet can be accessed without any authentication."
end
end
......
......@@ -30,6 +30,8 @@
#
class ApplicationSetting < ActiveRecord::Base
CACHE_KEY = 'application_setting.last'
serialize :restricted_visibility_levels
serialize :import_sources
serialize :restricted_signup_domains, Array
......@@ -73,21 +75,17 @@ class ApplicationSetting < ActiveRecord::Base
end
after_commit do
Rails.cache.write(cache_key, self)
Rails.cache.write(CACHE_KEY, self)
end
def self.current
Rails.cache.fetch(cache_key) do
Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
end
end
def self.expire
Rails.cache.delete(cache_key)
end
def self.cache_key
'application_setting.last'
Rails.cache.delete(CACHE_KEY)
end
def self.create_from_defaults
......
......@@ -12,17 +12,18 @@
module Ci
class ApplicationSetting < ActiveRecord::Base
extend Ci::Model
CACHE_KEY = 'ci_application_setting.last'
after_commit do
Rails.cache.write(cache_key, self)
Rails.cache.write(CACHE_KEY, self)
end
def self.expire
Rails.cache.delete(cache_key)
Rails.cache.delete(CACHE_KEY)
end
def self.current
Rails.cache.fetch(cache_key) do
Rails.cache.fetch(CACHE_KEY) do
Ci::ApplicationSetting.last
end
end
......@@ -33,9 +34,5 @@ module Ci
add_pusher: Settings.gitlab_ci['add_pusher'],
)
end
def self.cache_key
'ci_application_setting.last'
end
end
end
......@@ -78,11 +78,23 @@ class Commit
}x
end
def self.link_reference_pattern
super("commit", /(?<commit>\h{6,40})/)
end
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
"#{project.to_reference}@#{id}"
project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end
def reference_link_text(from_project = nil)
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.short_id
else
id
self.short_id
end
end
......
......@@ -2,36 +2,38 @@
#
# Examples:
#
# range = CommitRange.new('f3f85602...e86e1013')
# range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013"
#
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013"
#
# # Assuming `project` is a Project with a repository containing both commits:
# range.project = project
# # Assuming the specified project has a repository containing both commits:
# range.valid_commits? # => true
#
class CommitRange
include ActiveModel::Conversion
include Referable
attr_reader :sha_from, :notation, :sha_to
attr_reader :commit_from, :notation, :commit_to
attr_reader :ref_from, :ref_to
# Optional Project model
attr_accessor :project
# See `exclude_start?`
attr_reader :exclude_start
# The beginning and ending SHAs can be between 6 and 40 hex characters, and
# The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be SHAs
# between 6 and 40 hex characters.
STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
def self.reference_prefix
'@'
......@@ -43,27 +45,40 @@ class CommitRange
def self.reference_pattern
%r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{PATTERN})
(?<commit_range>#{STRICT_PATTERN})
}x
end
def self.link_reference_pattern
super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange
#
# range_string - The String commit range.
# project - An optional Project model.
#
# Raises ArgumentError if `range_string` does not match `PATTERN`.
def initialize(range_string, project = nil)
def initialize(range_string, project)
@project = project
range_string.strip!
unless range_string.match(/\A#{PATTERN}\z/)
unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}"
end
@exclude_start = !range_string.include?('...')
@sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
@ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
@project = project
if project.valid_repo?
@commit_from = project.commit(@ref_from)
@commit_to = project.commit(@ref_to)
end
if valid_commits?
@ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
@ref_to = Commit.truncate_sha(sha_to) if sha_to.start_with?(@ref_to)
end
end
def inspect
......@@ -71,15 +86,24 @@ class CommitRange
end
def to_s
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
sha_from + notation + sha_to
end
alias_method :id, :to_s
def to_reference(from_project = nil)
# Not using to_s because we want the full SHAs
reference = sha_from + notation + sha_to
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end
def reference_link_text(from_project = nil)
reference = ref_from + notation + ref_to
if cross_project_reference?(from_project)
reference = project.to_reference + '@' + reference
reference = project.to_reference + self.class.reference_prefix + reference
end
reference
......@@ -87,46 +111,58 @@ class CommitRange
# Returns a String for use in a link's title attribute
def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}"
"Commits #{sha_start} through #{sha_to}"
end
# Return a Hash of parameters for passing to a URL helper
#
# See `namespace_project_compare_url`
def to_param
{ from: suffixed_sha_from, to: sha_to }
{ from: sha_start, to: sha_to }
end
def exclude_start?
exclude_start
@notation == '..'
end
# Check if both the starting and ending commit IDs exist in a project's
# repository
#
# project - An optional Project to check (default: `project`)
def valid_commits?(project = project)
return nil unless project.present?
return false unless project.valid_repo?
commit_from.present? && commit_to.present?
def valid_commits?
commit_start.present? && commit_end.present?
end
def persisted?
true
end
def commit_from
@commit_from ||= project.repository.commit(suffixed_sha_from)
def sha_from
return nil unless @commit_from
@commit_from.id
end
def sha_to
return nil unless @commit_to
@commit_to.id
end
def commit_to
@commit_to ||= project.repository.commit(sha_to)
def sha_start
return nil unless sha_from
exclude_start? ? sha_from + '^' : sha_from
end
private
def commit_start
return nil unless sha_start
def suffixed_sha_from
sha_from + (exclude_start? ? '^' : '')
if exclude_start?
@commit_start ||= project.commit(sha_start)
else
commit_from
end
end
alias_method :sha_end, :sha_to
alias_method :commit_end, :commit_to
end
......@@ -62,7 +62,12 @@ module Mentionable
return [] if text.blank?
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits) - [local_reference]
refs = (refs.issues + refs.merge_requests + refs.commits)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
refs.reject { |ref| ref == local_reference }
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
......@@ -21,6 +21,10 @@ module Referable
''
end
def reference_link_text(from_project = nil)
to_reference(from_project)
end
module ClassMethods
# The character that prefixes the actual reference identifier
#
......@@ -44,6 +48,25 @@ module Referable
def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}"
end
def link_reference_pattern(route, pattern)
%r{
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
(\/[a-z0-9_=-]+)*
)?
(?<query>
\?[a-z0-9_=-]+
(&[a-z0-9_=-]+)*
)?
(?<anchor>\#[a-z0-9_-]+)?
)
}x
end
end
private
......
......@@ -201,7 +201,7 @@ class Event < ActiveRecord::Base
elsif commented?
"commented on"
elsif created_project?
if project.import?
if project.external_import?
"imported"
else
"created"
......
......@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
......
......@@ -17,7 +17,7 @@ class Label < ActiveRecord::Base
# Requests that have no label assigned.
LabelStruct = Struct.new(:title, :name)
None = LabelStruct.new('No Label', 'No Label')
Any = LabelStruct.new('Any', '')
Any = LabelStruct.new('Any Label', '')
DEFAULT_COLOR = '#428BCA'
......
......@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("merge_requests", /(?<merge_request>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
......@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description))
issues.uniq.sort_by(&:id)
issues.uniq
else
[]
end
......
......@@ -16,9 +16,9 @@
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
MilestoneStruct = Struct.new(:title, :name)
None = MilestoneStruct.new('No Milestone', 'No Milestone')
Any = MilestoneStruct.new('Any', '')
MilestoneStruct = Struct.new(:title, :name, :id)
None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
Any = MilestoneStruct.new('Any Milestone', '', -1)
include InternalId
include Sortable
......
......@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}"
......
......@@ -10,7 +10,7 @@
#
class UsersStarProject < ActiveRecord::Base
belongs_to :project, counter_cache: :star_count
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
validates :user, presence: true
......
......@@ -145,6 +145,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients.delete(note.author)
recipients = recipients.uniq
# build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
......
......@@ -125,7 +125,7 @@ class SystemNoteService
# Returns the created Note object
def self.change_status(noteable, project, author, status, source)
body = "Status changed to #{status}"
body += " by #{source.gfm_reference}" if source
body += " by #{source.gfm_reference(project)}" if source
create_note(noteable: noteable, project: project, author: author, note: body)
end
......
......@@ -12,7 +12,7 @@
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group
= f.label :color, "Background Color", class: 'control-label'
= f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
......
- page_title "Edit", @label.name, "Labels"
%h3
Edit label
%span.light #{@label.name}
.back-link
= link_to admin_labels_path do
&larr; To labels list
%h3.page-title
Edit Label
%hr
= render 'form'
- page_title "New Label"
%h3 New label
.back-link
= link_to admin_labels_path do
&larr; To labels list
%h3.page-title
New Label
%hr
= render 'form'
- page_title "Edit", @user.name, "Users"
%h3.page-title
Edit user: #{@user.name}
.back-link
= link_to admin_user_path(@user) do
&larr; Back to user page
%hr
= render 'form'
= content_for :flash_message do
= render 'shared/project_limit'
%ul.center-top-menu
= nav_link(path: ['projects#index', 'root#index']) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
......
- page_title @milestone.title, "Milestones"
%h4.page-title
- header_title "Milestones", dashboard_milestones_path
.issuable-details
.page-title
.issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
......@@ -7,13 +10,14 @@
Open
Milestone #{@milestone.title}
%hr
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
- if @milestone.complete? && @milestone.active?
.alert.alert-success
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
.description
.table-holder
%table.table
%thead
......@@ -44,7 +48,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
......@@ -58,25 +62,39 @@
Participants
%span.badge= @milestone.participants.count
.pull-right
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All issues in this milestone
.row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All merge requests in this milestone
.row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
.gray-content-block.middle-block
.oneline
All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
......
......@@ -3,7 +3,7 @@
= auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity")
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
......
- page_title "Starred Projects"
- header_title "Projects", projects_path
- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
......
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
......
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
......
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
......
......@@ -3,8 +3,7 @@
.panel.panel-default
.panel-heading
%strong= @group.name
group settings:
Group settings
.panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- if @group.errors.any?
......@@ -45,4 +44,5 @@
%br
%strong Removed group can not be restored!
.form-actions
= link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
......@@ -14,8 +14,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
%p.hint Required
= f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
......
- page_title @milestone.title, "Milestones"
= render "header_title"
%h4.page-title
.issuable-details
.page-title
.issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
......@@ -11,17 +12,18 @@
.pull-right
- if can?(current_user, :admin_milestones, @group)
- if @milestone.active?
= link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
= link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
= link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
= link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
%hr
- if @milestone.complete? && @milestone.active?
.alert.alert-success
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
.description
.table-holder
%table.table
%thead
......@@ -52,7 +54,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
......@@ -66,25 +68,40 @@
Participants
%span.badge= @milestone.participants.count
.pull-right
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All issues in this milestone
.row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All merge requests in this milestone
.row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
.gray-content-block.middle-block
.oneline
All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
......
- page_title 'New Group'
- header_title 'New Group'
- header_title "Groups", dashboard_groups_path
%h3.page-title
New Group
%hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any?
.alert.alert-danger
......@@ -18,3 +23,4 @@
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
= link_to 'Cancel', dashboard_groups_path, class: 'btn btn-cancel'
.page-with-sidebar{ class: nav_sidebar_class }
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
......@@ -17,8 +17,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
= image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
......
- page_title "Admin area"
- header_title "Admin area", admin_root_path
- page_title "Admin Area"
- header_title "Admin Area", admin_root_path
- sidebar "admin"
= render template: "layouts/application"
.page-with-sidebar{ class: nav_sidebar_class }
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
......@@ -14,8 +14,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
= image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
......
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body.ui_charcoal.login-page.application
%body.ui_charcoal.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
.content
= render "layouts/flash"
.row.prepend-top-20
.row
.col-sm-5.pull-right
= yield
.col-sm-7.brand-holder.pull-left
......
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{user_application_theme} application"}
%body{class: "#{user_application_theme} application navless"}
= render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
......
......@@ -11,27 +11,27 @@
%li.hidden-sm.hidden-xs
= render 'layouts/search'
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if session[:impersonator_id]
%li.impersonation
= link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop impersonation', data: { toggle: 'tooltip', placement: 'bottom' } do
= link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
= link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
- if current_user.can_create_project?
%li
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom'} do
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
%li
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('sign-out')
%h1.title= title
......
......@@ -5,78 +5,78 @@
%span
Overview
= nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do
= link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw')
%span
Projects
= nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users', data: {placement: 'right'} do
= link_to admin_users_path, title: 'Users' do
= icon('user fw')
%span
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups', data: {placement: 'right'} do
= link_to admin_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys', data: {placement: 'right'} do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
= nav_link do
= link_to ci_admin_projects_path, title: 'Continuous Integration', data: {placement: 'right'} do
= link_to ci_admin_projects_path, title: 'Continuous Integration' do
= icon('building fw')
%span
Continuous Integration
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do
= link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
%span
Logs
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Broadcast Messages', data: {placement: 'right'} do
= link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
%span
Messages
= nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks', data: {placement: 'right'} do
= link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw')
%span
Hooks
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs', data: {placement: 'right'} do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw')
%span
Background Jobs
= nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications', data: {placement: 'right'} do
= link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates', data: {placement: 'right'} do
= link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw')
%span
Service Templates
= nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do
= link_to admin_labels_path, title: 'Labels' do
= icon('tags fw')
%span
Labels
= nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse reports" do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw')
%span
Abuse Reports
%span.count= AbuseReport.count(:all)
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings', data: {placement: 'right'} do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span
Settings
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
= link_to dashboard_projects_path, title: 'Projects', data: {placement: 'right'} do
= link_to dashboard_projects_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity', data: {placement: 'right'} do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
= icon('dashboard fw')
%span
Activity
= nav_link(controller: :groups) do
= link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do
= link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :milestones) do
= link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
= link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
%li.separate-item
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
Profile Settings
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects', data: {placement: 'right'} do
= link_to explore_root_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(controller: :groups) do
= link_to explore_groups_path, title: 'Groups', data: {placement: 'right'} do
= link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets', data: {placement: 'right'} do
= link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
%ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
......@@ -8,39 +8,39 @@
%li.separate-item
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
= link_to group_path(@group), title: 'Home' do
= icon('dashboard fw')
%span
Group
- if can?(current_user, :read_group, @group)
- if current_user
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
= link_to group_milestones_path(@group), title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do
= link_to issues_group_path(@group), title: 'Issues' do
= icon('exclamation-circle fw')
%span
Issues
- if current_user
%span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw')
%span
Merge Requests
- if current_user
%span.count= MergeRequest.opened.of_group(@group).count
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do
= link_to group_group_members_path(@group), title: 'Members' do
= icon('users fw')
%span
Members
- if can?(current_user, :admin_group, @group)
= nav_link(html_options: { class: "separate-item" }) do
= link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do
= link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw')
%span
Settings
%ul.nav.nav-sidebar
= nav_link do
= link_to group_path(@group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
= link_to group_path(@group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
......@@ -9,12 +9,12 @@
%ul.sidebar-subnav
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group), title: 'Group Settings', data: {placement: 'right'} do
= link_to edit_group_path(@group), title: 'Group Settings' do
= icon ('pencil-square-o fw')
%span
Group Settings
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
= link_to projects_group_path(@group), title: 'Projects' do
= icon('folder fw')
%span
Projects
%ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
......@@ -8,52 +8,52 @@
%li.separate-item
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
Profile Settings
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
%span
Account
= nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new', 'applications#create']) do
= link_to applications_profile_path, title: 'Applications', data: {placement: 'right'} do
= link_to applications_profile_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails', data: {placement: 'right'} do
= link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
%span
Emails
%span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password', data: {placement: 'right'} do
= link_to edit_profile_password_path, title: 'Password' do
= icon('lock fw')
%span
Password
= nav_link(controller: :notifications) do
= link_to profile_notifications_path, title: 'Notifications', data: {placement: 'right'} do
= link_to profile_notifications_path, title: 'Notifications' do
= icon('inbox fw')
%span
Notifications
= nav_link(controller: :keys) do
= link_to profile_keys_path, title: 'SSH Keys', data: {placement: 'right'} do
= link_to profile_keys_path, title: 'SSH Keys' do
= icon('key fw')
%span
SSH Keys
%span.count= current_user.keys.count
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
= icon('image fw')
%span
Preferences
= nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
= link_to audit_log_profile_path, title: 'Audit Log' do
= icon('history fw')
%span
Audit Log
%ul.nav.nav-sidebar
- if @project.group
= nav_link do
= link_to group_path(@project.group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
= link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
- else
= nav_link do
= link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
......@@ -15,32 +15,32 @@
%li.separate-item
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('home fw')
%span
Project
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
= icon('dashboard fw')
%span
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
= link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
= link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
= icon('files-o fw')
%span
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches releases)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
= icon('history fw')
%span
Commits
- if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
= icon('cubes fw')
%span
Builds
......@@ -48,28 +48,28 @@
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
= icon('code-fork fw')
%span
Network
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
= icon('area-chart fw')
%span
Graphs
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones', data: {placement: 'right'} do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
- if project_nav_tab? :issues
= nav_link(controller: :issues) do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
......@@ -78,7 +78,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
......@@ -86,35 +86,35 @@
- if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
%span
Members
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
= icon('tags fw')
%span
Labels
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki', data: {placement: 'right'} do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
= icon('book fw')
%span
Wiki
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
= icon('clipboard fw')
%span
Snippets
- if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
= link_to edit_project_path(@project), title: 'Settings', data: {placement: 'right'} do
= link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
Settings
%ul.nav.nav-sidebar
= nav_link do
= link_to project_path(@project), title: 'Go to project', data: {placement: 'right'}, class: 'back-link' do
= link_to project_path(@project), title: 'Go to project', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to project
......@@ -9,59 +9,59 @@
%ul.sidebar-subnav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), title: 'Project Settings', data: {placement: 'right'} do
= link_to edit_project_path(@project), title: 'Project Settings' do
= icon('pencil-square-o fw')
%span
Project Settings
= nav_link(controller: :deploy_keys) do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
= nav_link(controller: :hooks) do
= link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do
= link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
= icon('link fw')
%span
Web Hooks
= nav_link(controller: :services) do
= link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do
= link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
= icon('cogs fw')
%span
Services
= nav_link(controller: :protected_branches) do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
= icon('lock fw')
%span
Protected Branches
- if @project.builds_enabled?
= nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
= icon('cog fw')
%span
Runners
= nav_link(controller: :variables) do
= link_to namespace_project_variables_path(@project.namespace, @project) do
= link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
= icon('code fw')
%span
Variables
= nav_link path: 'triggers#index' do
= link_to namespace_project_triggers_path(@project.namespace, @project) do
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw')
%span
Triggers
= nav_link path: 'ci_web_hooks#index' do
= link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do
= link_to namespace_project_ci_web_hooks_path(@project.namespace, @project), title: 'CI Web Hooks' do
= icon('link fw')
%span
CI Web Hooks
= nav_link path: 'ci_settings#edit' do
= link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do
= link_to edit_namespace_project_ci_settings_path(@project.namespace, @project), title: 'CI Settings' do
= icon('building fw')
%span
CI Settings
= nav_link controller: 'ci_services' do
= link_to namespace_project_ci_services_path(@project.namespace, @project) do
= link_to namespace_project_ci_services_path(@project.namespace, @project), title: 'CI Services' do
= icon('share fw')
%span
CI Services
......@@ -23,10 +23,13 @@
%p.cgray
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
%div
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
.form-actions
- if current_user.private_token
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
= f.submit 'Generate', class: "btn btn-default btn-build-token"
- unless current_user.ldap_user?
......@@ -54,7 +57,8 @@
%p
Each time you log in you’ll be required to provide your username and
password as usual, plus a randomly-generated code from your phone.
%div
.form-actions
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if button_based_providers.any?
......@@ -81,15 +85,16 @@
%p
Changing your username will change path to all personal projects!
%div
.input-group
.input-group-addon
= "#{root_url}u/"
= f.text_field :username, required: true, class: 'form-control'
&nbsp;
.loading-gif.hide
%p
= icon('spinner spin')
Saving new username
%p.light
= user_url(@user)
%div
.form-actions
= f.submit 'Save username', class: "btn btn-warning"
- if signup_enabled?
......@@ -104,6 +109,7 @@
- rp = current_user.personal_projects.count
- unless rp.zero?
%li #{pluralize rp, 'personal project'} will be removed and cannot be restored
.form-actions
= link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
- else
- if @user.solo_owned_groups.present?
......
%div
= form_for [:profile, @key], html: { class: 'form-horizontal' } do |f|
= form_for [:profile, @key], html: { class: 'form-horizontal js-requires-input' } do |f|
- if @key.errors.any?
.alert.alert-danger
%ul
......@@ -9,12 +9,11 @@
.form-group
= f.label :key, class: 'control-label'
.col-sm-10
= f.text_area :key, class: "form-control", rows: 8
= f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, class: "form-control"
.col-sm-10= f.text_field :title, class: "form-control", required: true
.form-actions
= f.submit 'Add key', class: "btn btn-create"
= link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
......@@ -54,4 +54,4 @@
.help-block
Choose what content you want to see on a project's home page.
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
= f.submit 'Save changes', class: 'btn btn-save'
......@@ -43,7 +43,7 @@
.form-group
= f.label :public_email, class: "control-label"
.col-sm-10
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show in profile'}, class: "form-control"
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2"
%span.help-block This email will be displayed on your public profile.
.form-group
= f.label :skype, class: "control-label"
......@@ -96,8 +96,6 @@
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
.row
.col-md-7
.form-group
.col-sm-offset-2.col-sm-10
.form-actions
= f.submit 'Save changes', class: "btn btn-success"
= link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
.form-actions
.commit-button-annotation
= button_tag 'Commit Changes',
class: 'btn commit-btn js-commit-button btn-create'
= button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
......@@ -12,11 +12,20 @@
Forked from
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
.cover-controls
.visibility-level-label
= visibility_level_icon(@project.visibility_level)
.cover-controls.left
.visibility-level-label.has_tooltip{title: project_visibility_level_description(@project.visibility_level), data: { container: 'body' } }
= visibility_level_icon(@project.visibility_level, fw: false)
= visibility_level_label(@project.visibility_level)
.cover-controls
- if can?(current_user, :admin_project, @project)
= link_to edit_project_path(@project), class: 'btn btn-gray' do
= icon('pencil')
- if current_user
&nbsp;
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do
= icon('rss')
.project-repo-buttons
.split-one
= render 'projects/buttons/star'
......
......@@ -5,19 +5,17 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Create New Directory
.modal-body
= form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form' do
= form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
.form-group
= label_tag :dir_name, 'Directory Name', class: 'control-label'
= label_tag :dir_name, 'Directory name', class: 'control-label'
.col-sm-10
= text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
= text_field_tag :dir_name, params[:dir_name], required: true, class: 'form-control'
= render 'shared/new_commit_form', placeholder: "Add new directory"
.form-group
.col-sm-offset-2.col-sm-10
= submit_tag "Create directory", class: 'btn btn-primary btn-create'
.form-actions
= submit_tag "Create directory", class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
disableButtonIfAnyEmptyField($(".js-create-dir-form"), ".form-control", ".btn-create");
new NewCommitForm($('.js-create-dir-form'))
......@@ -16,9 +16,8 @@
= render 'shared/new_commit_form', placeholder: placeholder
.form-group
.col-sm-offset-2.col-sm-10
= button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
.form-actions
= button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
......
- page_title "New File", @path.presence, @ref
= render "header_title"
.gray-content-block.top-block
%h3.page-title
Create New File
%h3.page-title
New File
.file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do
......
......@@ -6,25 +6,27 @@
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @error
%h3.page-title
%i.fa.fa-code-fork
New branch
New Branch
%hr
= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do
.form-group
= label_tag :branch_name, 'Name for new branch', class: 'control-label'
= label_tag :branch_name, nil, class: 'control-label'
.col-sm-10
= text_field_tag :branch_name, params[:branch_name], placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control'
= text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
= text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
= text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
.help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
var availableTags = #{@project.repository.ref_names.to_json};
var availableRefs = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
source: availableTags,
source: availableRefs,
minLength: 1
});
%div
= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
-if @key.errors.any?
.alert.alert-danger
%ul
......@@ -8,16 +8,15 @@
.form-group
= f.label :title, class: "control-label"
.col-sm-10= f.text_field :title, class: 'form-control'
.col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true
.form-group
= f.label :key, class: "control-label"
.col-sm-10
%p.light
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh", "README")
= f.text_area :key, class: "form-control thin_area", rows: 5
= f.text_area :key, class: "form-control thin_area", rows: 5, required: true
.form-actions
= f.submit 'Create', class: "btn-create btn"
= f.submit 'Create Deploy Key', class: "btn-create btn"
= link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
- page_title "New Deploy Key"
%h3.page-title New Deploy key
%h3.page-title New Deploy Key
%hr
= render 'form'
......@@ -14,7 +14,7 @@
= f.label :name, class: 'control-label' do
Project name
.col-sm-10
= f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
= f.text_field :name, class: "form-control", id: "project_name_edit"
.form-group
......@@ -22,7 +22,7 @@
Project description
%span.light (optional)
.col-sm-10
= f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250
= f.text_area :description, class: "form-control", rows: 3, maxlength: 250
- if @project.repository.exists? && @project.repository.branch_names.any?
.form-group
......@@ -130,7 +130,9 @@
The project can be committed to.
%br
%strong Once active this project shows up in the search and on the dashboard.
= link_to 'Unarchive', unarchive_namespace_project_path(@project.namespace, @project),
.form-actions
= link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
method: :post, class: "btn btn-success"
- else
......@@ -144,7 +146,9 @@
It is hidden from the dashboard and doesn't show up in searches.
%br
%strong Archived projects cannot be committed to!
= link_to 'Archive', archive_namespace_project_path(@project.namespace, @project),
.form-actions
= link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
method: :post, class: "btn btn-warning"
- else
......@@ -160,7 +164,7 @@
Project name
.col-sm-9
.form-group
= f.text_field :name, placeholder: "Example Project", class: "form-control"
= f.text_field :name, class: "form-control"
.form-group
= f.label :path, class: 'control-label' do
%span Path
......@@ -170,12 +174,11 @@
.input-group-addon
#{URI.join(root_url, @project.namespace.path)}/
= f.text_field :path, class: 'form-control'
%span.input-group-addon .git
%ul
%li Be careful. Renaming a project's repository can have unintended side effects.
%li You will need to update your local repositories to point to the new location.
.form-actions
= f.submit 'Rename', class: "btn btn-warning"
= f.submit 'Rename project', class: "btn btn-warning"
- if can?(current_user, :change_namespace, @project)
.panel.panel-default.panel.panel-danger
......@@ -194,7 +197,7 @@
%li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
.form-actions
= f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
= f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else
.nothing-here-block Only the project owner can transfer a project
......@@ -209,6 +212,7 @@
#{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
%br
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
.form-actions
= button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
- else
.nothing-here-block Only the project owner can remove the fork relationship.
......@@ -222,7 +226,7 @@
Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br
%strong Removed projects cannot be restored!
.form-actions
= button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else
.nothing-here-block Only the project owner can remove a project.
......
.issue-closed-by-widget
= icon('check')
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted.
......@@ -29,10 +29,3 @@
.issuable-affix
.context
= render 'shared/issuable/context', issuable: @issue
- if @issue.labels.any?
.issuable-context-title
%label Labels
.issue-show-labels
- @issue.labels.each do |label|
= link_to_label(label)
%div.issue-form-holder
%h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}"
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
:javascript
......
......@@ -6,35 +6,39 @@
.issue-title
%span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title"
.issue-labels
- issue.labels.each do |label|
= link_to_label(label, project: issue.project)
.pull-right.light
- if issue.closed?
%span
CLOSED
- if issue.assignee
= link_to_member(@project, issue.assignee, name: false)
= link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
- note_count = issue.notes.user.count
- if note_count > 0
&nbsp;
%span
%i.fa.fa-comments
= link_to issue_path(issue) + "#notes" do
= icon('comments')
= note_count
- else
&nbsp;
%span.issue-no-comments
%i.fa.fa-comments
= link_to issue_path(issue) + "#notes", class: "issue-no-comments" do
= icon('comments')
= 0
.issue-info
= "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
#{issue.to_reference} &middot;
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
by #{link_to_member(@project, issue.author, avatar: false)}
- if issue.milestone
&nbsp;
%span
%i.fa.fa-clock-o
= link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
= icon('clock-o')
= issue.milestone.title
- if issue.labels.any?
&nbsp;
- issue.labels.each do |label|
= link_to_label(label, project: issue.project)
- if issue.tasks?
&nbsp;
%span.task-status
= issue.task_status
......
- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
= render "header_title"
%h3.page-title
Edit Issue ##{@issue.iid}
%hr
= render "form"
- page_title "New Issue"
= render "header_title"
%h3.page-title
New Issue
%hr
= render "form"
......@@ -11,7 +11,8 @@
Open
%span.issue-id Issue ##{@issue.iid}
%span.creator
&middot; created by #{link_to_member(@project, @issue.author, size: 24)}
&middot;
opened by #{link_to_member(@project, @issue.author, size: 24)}
&middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
......
......@@ -10,9 +10,9 @@
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, class: "form-control js-quick-submit", required: true
= f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group
= f.label :color, "Background Color", class: 'control-label'
= f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
......@@ -28,6 +28,8 @@
&nbsp;
.form-actions
= f.submit 'Save', class: 'btn btn-save js-save-button'
- if @label.persisted?
= f.submit 'Save changes', class: 'btn btn-save js-save-button'
- else
= f.submit 'Create Label', class: 'btn btn-create js-save-button'
= link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
- page_title "Edit", @label.name, "Labels"
= render "header_title"
%h3
Edit label
%span.light #{@label.name}
.back-link
= link_to namespace_project_labels_path(@project.namespace, @project) do
&larr; To labels list
%h3.page-title
Edit Label
%hr
= render 'form'
- page_title "New Label"
= render "header_title"
%h3 New label
.back-link
= link_to namespace_project_labels_path(@project.namespace, @project) do
&larr; To labels list
%h3.page-title
New Label
%hr
= render 'form'
......@@ -26,10 +26,3 @@
.issuable-affix
.context
= render 'shared/issuable/context', issuable: @merge_request
- if @merge_request.labels.any?
.issuable-context-title
%label Labels
.merge-request-show-labels
- @merge_request.labels.each do |label|
= link_to_label(label)
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
.merge-request-form-info
= render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
......
......@@ -3,48 +3,52 @@
.merge-request-title
%span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
.merge-request-labels
- merge_request.labels.each do |label|
= link_to_label(label, project: merge_request.project)
.pull-right.light
- if ci_commit
= render_ci_status(ci_commit)
- if merge_request.merged?
%span
%i.fa.fa-check
= icon('check')
MERGED
- elsif merge_request.closed?
%span
%i.fa.fa-ban
= icon('ban')
CLOSED
- note_count = merge_request.mr_and_commit_notes.user.count
- if merge_request.assignee
&nbsp;
= link_to_member(merge_request.source_project, merge_request.assignee, name: false)
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
- if note_count > 0
&nbsp;
%span
%i.fa.fa-comments
= link_to merge_request_path(merge_request) + "#notes" do
= icon('comments')
= note_count
- else
&nbsp;
%span.merge-request-no-comments
%i.fa.fa-comments
= link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do
= icon('comments')
= 0
.merge-request-info
= "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
- if merge_request.milestone_id?
&nbsp;
%span
%i.fa.fa-clock-o
= merge_request.milestone.title
\##{merge_request.iid} &middot;
opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch
&nbsp;
%span
%i.fa.fa-code-fork
= link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
= icon('code-fork')
= merge_request.target_branch
- if merge_request.milestone
&nbsp;
= link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
= icon('clock-o')
= merge_request.milestone.title
- if merge_request.labels.any?
&nbsp;
- merge_request.labels.each do |label|
= link_to_label(label, project: merge_request.project)
- if merge_request.tasks?
&nbsp;
%span.task-status
= merge_request.task_status
......
%p.lead Compare branches for new Merge Request
%h3.page-title
New Merge Request
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors
......@@ -10,7 +11,7 @@
.panel-body
= f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
= f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2', required: true})
= f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } })
.panel-footer
.mr_source_commit
......@@ -22,7 +23,7 @@
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
= f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2', required: true})
= f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } })
.panel-footer
.mr_target_commit
......@@ -51,8 +52,8 @@
are the same.
%div
= f.submit 'Compare branches', class: "btn btn-new mr-compare-btn"
.form-actions
= f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
var source_branch = $("#merge_request_source_branch")
......
%h3.page-title
New merge request
New Merge Request
%p.slead
- source_title, target_title = format_mr_branch_names(@merge_request)
From
......@@ -11,7 +11,6 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
.merge-request-form-info
= render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
......
......@@ -26,15 +26,17 @@
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal
%span Request to merge
%span.label-branch #{source_branch_with_namespace(@merge_request)}
%span.label-branch
= source_branch_with_namespace(@merge_request)
%span into
%span.label-branch #{@merge_request.target_branch}
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
- if @merge_request.open? && @merge_request.can_be_merged?
.light.append-bottom-20
- if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
.light.prepend-top-default
You can also accept this merge request manually using the
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
......
......@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
= "Edit merge request ##{@merge_request.iid}"
Edit Merge Request ##{@merge_request.iid}
%hr
= render 'form'
- if @status
:plain
merge_request_widget.mergeInProgress();
merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'});
- else
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}");
- page_title "New Merge Request"
= render "header_title"
- if @merge_request.can_be_created
- if @merge_request.can_be_created && !params[:change_branches]
= render 'new_submit'
- else
= render 'new_compare'
......@@ -4,7 +4,7 @@
%span.issue-id Merge Request ##{@merge_request.iid}
%span.creator
&middot;
created by #{link_to_member(@project, @merge_request.author, size: 24)}
opened by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot;
= time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
......
......@@ -7,10 +7,11 @@
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
%div
- if !@merge_request.source_branch_exists?
- if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true')
= succeed '.' do
The changes were merged into
%span.label-branch= @merge_request.target_branch
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
The source branch has been removed.
- elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch)
......@@ -18,7 +19,8 @@
%p
= succeed '.' do
The changes were merged into
%span.label-branch= @merge_request.target_branch
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
You can remove the source branch now.
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
%i.fa.fa-times
......
%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}"
.back-link
= link_to namespace_project_milestones_path(@project.namespace, @project) do
&larr; To milestones
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
......@@ -16,8 +9,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
%p.hint Required
= f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
......
- page_title "Edit", @milestone.title, "Milestones"
= render "header_title"
%h3.page-title
Edit Milestone ##{@milestone.iid}
%hr
= render "form"
- page_title "New Milestone"
= render "header_title"
%h3.page-title
New Milestone
%hr
= render "form"
- page_title @milestone.title, "Milestones"
= render "header_title"
%h4.page-title
.issuable-details
.page-title
.issue-box{ class: issue_box_class(@milestone) }
- if @milestone.closed?
Closed
......@@ -10,37 +11,40 @@
- else
Open
Milestone ##{@milestone.iid}
%small.creator
- if @milestone.expires_at
%span.creator
&middot;
= @milestone.expires_at
.pull-right
- if can?(current_user, :admin_milestone, @project)
= link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
- if @milestone.active?
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
= link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
%i.fa.fa-trash-o
Delete
%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success
%span All issues for this milestone are closed. You may close milestone now.
%h3.issue-title
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
%div
%div
- if @milestone.description.present?
.description
.wiki
= preserve do
= markdown @milestone.description
%hr
.context
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
.context.prepend-top-default
%p.lead
Progress:
#{@milestone.closed_items_count} closed
......@@ -51,8 +55,7 @@
%span.pull-right= @milestone.expires_at
= milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
......@@ -66,17 +69,21 @@
Participants
%span.badge= @users.count
.tab-content
.tab-pane.active#tab-issues
.gray-content-block.middle-block
.pull-right
- if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
New Issue
- if can?(current_user, :read_issue, @project)
= link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
= link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
.tab-content
.tab-pane.active#tab-issues
.row
.oneline
All issues in this milestone
.row.prepend-top-default
.col-md-4
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
.col-md-4
......@@ -85,7 +92,15 @@
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
.tab-pane#tab-merge-requests
.row
.gray-content-block.middle-block
.pull-right
- if can?(current_user, :read_merge_request, @project)
= link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All merge requests in this milestone
.row.prepend-top-default
.col-md-3
= render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
.col-md-3
......@@ -100,6 +115,10 @@
= render 'merge_request', merge_request: merge_request
.tab-pane#tab-participants
.gray-content-block.middle-block
.oneline
All participants to this milestone
%ul.bordered-list
- @users.each do |user|
%li
......
- page_title 'New Project'
- header_title 'New Project'
- header_title "Projects", root_path
%h3.page-title
New Project
%hr
.project-edit-container
.project-edit-errors
= render 'projects/errors'
......@@ -11,16 +16,21 @@
Project path
.col-sm-10
.input-group
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2', tabindex: 1}
.input-group-addon
\/
- else
.input-group-addon
\.git
#{root_url}#{current_user.username}/
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if current_user.can_select_namespace?
.form-group
= f.label :namespace_id, class: 'control-label' do
%span Namespace
.col-sm-10
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
- if current_user.can_create_group?
.help-block
Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path
- if import_sources_enabled?
.project-import.js-toggle-container
......@@ -90,19 +100,12 @@
Description
%span.light (optional)
.col-sm-10
= f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250, tabindex: 3
= f.text_area :description, class: "form-control", rows: 3, maxlength: 250, tabindex: 3
= render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
.form-actions
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
- if current_user.can_create_group?
.pull-right
.light.inline
.space-right
Need a group for several dependent projects?
= link_to new_group_path, class: "btn" do
Create a group
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
.save-project-loader.hide
.center
......
......@@ -6,6 +6,5 @@
= render 'projects/notes/hints'
.note-form-actions
.buttons
= f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
= link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
......@@ -12,8 +12,7 @@
= render 'projects/notes/hints'
.error-alert
.note-form-actions
.buttons.clearfix
= f.submit 'Add Comment', class: "btn btn-green comment-btn btn-grouped js-comment-button"
.note-form-actions.clearfix
= f.submit 'Add Comment', class: "btn btn-create comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
%a.btn.btn-cancel.js-close-discussion-note-form Cancel
......@@ -22,7 +22,7 @@
.form-group
= f.label :name, "Branch", class: 'control-label'
.col-sm-10
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
......@@ -33,4 +33,3 @@
.form-actions
= f.submit 'Protect', class: "btn-create btn"
= render 'branches_list'
......@@ -26,4 +26,4 @@
= f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
.help-block You can setup jobs to only use runners with specific tags
.form-actions
= f.submit 'Save', class: 'btn btn-save'
= f.submit 'Save changes', class: 'btn btn-save'
......@@ -4,18 +4,15 @@
%p= @service.description
.back-link
= link_to namespace_project_services_path(@project.namespace, @project) do
&larr; to services
%hr
= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
= render 'shared/service_settings', form: form
.form-actions
= form.submit 'Save', class: 'btn btn-save'
= form.submit 'Save changes', class: 'btn btn-save'
&nbsp;
- if @service.valid? && @service.activated?
- disabled = @service.can_test? ? '':'disabled'
= link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
= link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
......@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
Edit snippet
Edit Snippet
%hr
= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level
......@@ -2,6 +2,6 @@
= render "header_title"
%h3.page-title
New snippet
New Snippet
%hr
= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility
......@@ -7,24 +7,24 @@
= @error
%h3.page-title
New git tag
New Tag
%hr
= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form" do
= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-requires-input" do
.form-group
= label_tag :tag_name, 'Name for new tag', class: 'control-label'
= label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
= text_field_tag :tag_name, params[:tag_name], placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control'
= text_field_tag :tag_name, params[:tag_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
= text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
= text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
.help-block Branch name or commit SHA
.form-group
= label_tag :message, 'Message', class: 'control-label'
= label_tag :message, nil, class: 'control-label'
.col-sm-10
= text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
.help-block (Optional) Entering a message will create an annotated tag.
= text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
.help-block Optionally, enter a message to create an annotated tag.
%hr
.form-group
= label_tag :release_description, 'Release notes', class: 'control-label'
......@@ -32,16 +32,15 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control'
= render 'projects/notes/hints'
.help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page
.help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create");
var availableTags = #{@project.repository.ref_names.to_json};
var availableRefs = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
source: availableTags,
source: availableRefs,
minLength: 1
});
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
......@@ -11,14 +11,7 @@
.col-sm-10
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
.row
.col-sm-offset-2.col-sm-10
%p.cgray
To link to a (new) page you can just type
%code [Link Title](page-slug)
\.
.form-group.wiki-content
.form-group
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
......@@ -27,6 +20,11 @@
.clearfix
.error-alert
.help-block
To link to a (new) page, simply type
%code [Link Title](page-slug)
\.
.form-group
= f.label :commit_message, class: 'control-label'
.col-sm-10= f.text_field :message, class: 'form-control', rows: 18
......
%span.pull-right
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do
%i.fa.fa-plus
New Page
- if (@page && @page.persisted?)
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History
......@@ -11,5 +6,7 @@
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
= render 'projects/wikis/new'
- if can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
= icon('trash')
Delete
%ul.center-top-menu
.project-issuable-filter
.controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
%i.fa.fa-plus
New Page
= render 'projects/wikis/new'
%ul.center-top-menu
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
......
......@@ -12,5 +12,5 @@
The page slug is invalid. Please don't use characters other then: a-z 0-9 _ - and /
%p.hint
Please don't use spaces.
.modal-footer
= link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
.form-actions
= link_to 'Create Page', '#', class: 'build-new-wiki btn btn-create'
- page_title "Edit", @page.title, "Wiki"
- page_title "Edit", @page.title.capitalize, "Wiki"
= render "header_title"
= render 'nav'
.pull-right
.gray-content-block
.pull-right
= render 'main_links'
%h3.page-title
Editing -
%span.light #{@page.title}
%hr
= render 'form'
.pull-right
- if @page.persisted? && can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do
Delete this page
%h3.page-title.oneline
%span.light Edit Page
- if @page.persisted?
= link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
- else
= @page.title
= render 'form'
......@@ -5,7 +5,7 @@
.gray-content-block
.row
.col-sm-6
%h3.page-title
%h3.page-title.oneline
Git access for
%strong= @project_wiki.path_with_namespace
......
- page_title "All Pages", "Wiki"
- page_title "Pages", "Wiki"
= render "header_title"
= render 'nav'
.gray-content-block
= render 'main_links'
%h3.page-title
All Pages
All pages in this wiki are listed below.
%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
......
......@@ -5,11 +5,12 @@
.gray-content-block
= render 'main_links'
%h3.page-title
%h3.page-title.oneline
= @page.title.capitalize
.wiki-last-edit-by
Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
%span.wiki-last-edit-by
&middot;
last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- if @page.historical?
.warning_message
......@@ -21,8 +22,3 @@
.wiki
= preserve do
= render_wiki_content(@page)
.gray-content-block.footer-block
.wiki-last-edit-by
Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
......@@ -3,7 +3,8 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h4 Confirmation required
%h3.page-title
Confirmation required
.modal-body
%p.cred.lead.js-confirm-text
......@@ -18,5 +19,5 @@
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
.form-group
.form-actions
= submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
......@@ -23,7 +23,7 @@
%li It will change the git path to repositories under this group.
.form-group.group-description-holder
= f.label :description, 'Details', class: 'control-label'
= f.label :description, class: 'control-label'
.col-sm-10
= f.text_area :description, maxlength: 250,
class: 'form-control js-gfm-input', rows: 4
......@@ -3,8 +3,10 @@
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to_project project
= link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right'
= link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
- if can?(current_user, :create_issue, project)
.pull-right
= link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
%ul.well-list.issues-list
- group[1].each do |issue|
......@@ -12,4 +14,3 @@
= paginate @issues, theme: "gitlab"
- else
.nothing-here-block No issues to show
......@@ -3,8 +3,11 @@
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to_project project
= link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right'
= link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
- if can?(current_user, :create_merge_request, project)
.pull-right
= link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
%ul.well-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
......
......@@ -2,10 +2,9 @@
- unless @project.empty_repo?
.form-group.branch
= label_tag 'branch', class: 'control-label' do
Branch
= label_tag 'new_branch', 'Target branch', class: 'control-label'
.col-sm-10
= text_field_tag 'new_branch', @new_branch || @ref, class: "form-control js-new-branch"
= text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch"
.form-group.js-create-merge-request-form-group
.col-sm-offset-2.col-sm-10
......
- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project?
.project-limit-message.alert.alert-warning.hidden-xs
You won't be able to create new projects because you have reached your project limit.
.pull-right
= link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link'
|
= link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link'
......@@ -9,7 +9,7 @@
none
.issuable-context-selectbox
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true)
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
%div.prepend-top-default.clearfix
.issuable-context-title
......@@ -25,25 +25,32 @@
none
.issuable-context-selectbox
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
- if issuable.labels.any?
%div.prepend-top-default.clearfix
.issuable-context-title
%label Labels
.issuable-show-labels
- issuable.labels.each do |label|
= link_to_label(label)
- if current_user
- subscribed = issuable.subscribed?(current_user)
%div.prepend-top-default.clearfix
.issuable-context-title
%label
Subscription:
%button.btn.btn-block.subscribe-button{:type => 'button'}
= icon('eye')
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
%label Subscription
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
.subscription-status{data: {status: subscribtion_status}}
.description-block.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.description-block.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
%button.btn.btn-block.subscribe-button{:type => 'button'}
= icon('eye')
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
:javascript
new Subscription("#{toggle_subscription_path(issuable)}");
......
......@@ -31,11 +31,11 @@
.issues-other-filters
.filter-item.inline
= users_select_tag(:assignee_id, selected: params[:assignee_id],
placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true, current_user: true)
placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
.filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id],
placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true, current_user: true)
placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
.filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options,
......@@ -53,11 +53,15 @@
- if controller.controller_name == 'issues'
.issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
= select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control')
.filter-item.inline
= select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), include_blank: true, data: { placeholder: "Status" })
.filter-item.inline
= users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
= select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
.filter-item.inline
= select_tag('update[milestone_id]', bulk_update_milestone_options, include_blank: true, data: { placeholder: "Milestone" })
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
:javascript
......
......@@ -6,8 +6,7 @@
%span= msg
%br
.form-group
= f.label :title, class: 'control-label' do
%strong= 'Title *'
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input js-quick-submit', required: true
......@@ -30,29 +29,25 @@
= render 'projects/notes/hints'
.clearfix
.error-alert
%hr
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
%hr
.form-group
.issue-assignee
= f.label :assignee_id, class: 'control-label' do
%i.fa.fa-user
Assign to
= f.label :assignee_id, "Assignee", class: 'control-label'
.col-sm-10
= users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
selected: issuable.assignee_id, project: @target_project || @project,
first_user: true, current_user: true)
first_user: true, current_user: true, include_blank: true)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group
.issue-milestone
= f.label :milestone_id, class: 'control-label' do
%i.fa.fa-clock-o
Milestone
= f.label :milestone_id, "Milestone", class: 'control-label'
.col-sm-10
- if milestone_options(issuable).present?
= f.select(:milestone_id, milestone_options(issuable),
{ include_blank: 'Select milestone' }, { class: 'select2' })
{ include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- else
.prepend-top-10
%span.light No open milestones available.
......@@ -60,13 +55,11 @@
- if can? current_user, :admin_milestone, issuable.project
= link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group
= f.label :label_ids, class: 'control-label' do
%i.fa.fa-tag
Labels
= f.label :label_ids, "Labels", class: 'control-label'
.col-sm-10
- if issuable.project.labels.any?
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2'
{ selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
.prepend-top-10
%span.light No labels yet.
......@@ -78,32 +71,30 @@
%hr
- if @merge_request.new_record?
.form-group
= f.label :source_branch, class: 'control-label' do
%i.fa.fa-code-fork
Source Branch
= f.label :source_branch, class: 'control-label'
.col-sm-10
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group
= f.label :target_branch, class: 'control-label' do
%i.fa.fa-code-fork
Target Branch
= f.label :target_branch, class: 'control-label'
.col-sm-10
= f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? })
= f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
- if @merge_request.new_record?
%p.help-block
= link_to 'Change branches', mr_change_branches_path(@merge_request)
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")}
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted?
%p
Please review the
%strong #{link_to 'guidelines for contribution', guide_url}
to this repository.
- if issuable.new_record?
= f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
= f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else
= f.submit 'Save changes', class: 'btn btn-save'
- if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
.inline.prepend-left-10
Please review the
%strong #{link_to 'contribution guidelines', guide_url}
for this project.
- if issuable.new_record?
- cancel_project = issuable.source_project
- else
......
.snippet-form-holder
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form" } do |f|
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
- if @snippet.errors.any?
.alert.alert-danger
%ul
......@@ -8,7 +8,8 @@
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
.col-sm-10
= f.text_field :title, class: 'form-control', required: true, autofocus: true
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
......@@ -27,7 +28,7 @@
- if @snippet.new_record?
= f.submit 'Create snippet', class: "btn-create btn"
- else
= f.submit 'Save', class: "btn-save btn"
= f.submit 'Save changes', class: "btn-save btn"
- if @snippet.project_id
= link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
......
.issuable-details
.page-title
.snippet-box{class: visibility_level_color(@snippet.visibility_level)}
= visibility_level_icon(@snippet.visibility_level)
.snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level), data: { container: 'body' }}
= visibility_level_icon(@snippet.visibility_level, fw: false)
= visibility_level_label(@snippet.visibility_level)
Snippet ##{@snippet.id}
%span.creator
......
- page_title "Edit", @snippet.title, "Snippets"
%h3.page-title
Edit snippet
Edit Snippet
%hr
= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level
- page_title "New Snippet"
%h3.page-title
New snippet
New Snippet
%hr
= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility
......@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq()
{
bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
}
load_ok()
......
......@@ -164,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
......
class AddHideProjectLimitToUsers < ActiveRecord::Migration
def change
add_column :users, :hide_project_limit, :boolean, default: false
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151118162244) do
ActiveRecord::Schema.define(version: 20151203162133) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -814,6 +814,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
t.integer "project_view", default: 0
t.integer "consumed_timestep"
t.integer "layout", default: 0
t.boolean "hide_project_limit", default: false
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
......@@ -101,6 +101,43 @@ Parameters:
}
```
## Get single MR commits
Get a list of merge request commits.
```
GET /projects/:id/merge_request/:merge_request_id/commits
```
Parameters:
- `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of MR
```json
[
{
"id": "ed899a2f4b50b4370feeea94676502b42383c746",
"short_id": "ed899a2f4b5",
"title": "Replace sanitize with escape once",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dzaporozhets@sphereconsultinginc.com",
"created_at": "2012-09-20T11:50:22+03:00",
"message": "Replace sanitize with escape once"
},
{
"id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
"short_id": "6104942438c",
"title": "Sanitize for network graph",
"author_name": "randx",
"author_email": "dmitriy.zaporozhets@gmail.com",
"created_at": "2012-09-20T09:06:12+03:00",
"message": "Sanitize for network graph"
}
]
```
## Get single MR changes
Shows information about the merge request including its files and changes.
......@@ -159,7 +196,7 @@ Parameters:
"updated_at": "2015-02-02T19:49:26.013Z",
"due_date": null
},
"files": [
"changes": [
{
"old_path": "VERSION",
"new_path": "VERSION",
......
......@@ -35,7 +35,9 @@ Parameters:
"created_at": "2013-10-02T09:22:45Z",
"system": true,
"upvote": false,
"downvote": false
"downvote": false,
"noteable_id": 377,
"noteable_type": "Issue"
},
{
"id": 305,
......@@ -52,7 +54,9 @@ Parameters:
"created_at": "2013-10-02T09:56:03Z",
"system": true,
"upvote": false,
"downvote": false
"downvote": false,
"noteable_id": 121,
"noteable_type": "Issue"
}
]
```
......@@ -219,7 +223,12 @@ Parameters:
"state": "active",
"created_at": "2013-09-30T13:46:01Z"
},
"created_at": "2013-10-02T08:57:14Z"
"created_at": "2013-10-02T08:57:14Z",
"system": false,
"upvote": false,
"downvote": false,
"noteable_id": 2,
"noteable_type": "MergeRequest"
}
```
......
......@@ -245,9 +245,17 @@ Parameters:
"target_id": 830,
"target_type": "Issue",
"author_id": 1,
"author_username": "john",
"data": null,
"target_title": "Public project search field"
"target_title": "Public project search field",
"author": {
"name": "Dmitriy Zaporozhets",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
"web_url": "http://localhost:3000/u/root"
},
"author_username": "root"
},
{
"title": null,
......@@ -256,6 +264,14 @@ Parameters:
"target_id": null,
"target_type": null,
"author_id": 1,
"author": {
"name": "Dmitriy Zaporozhets",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
"web_url": "http://localhost:3000/u/root"
},
"author_username": "john",
"data": {
"before": "50d4420237a9de7be1304607147aec22e4a14af7",
......@@ -292,9 +308,56 @@ Parameters:
"target_id": 840,
"target_type": "Issue",
"author_id": 1,
"author_username": "john",
"data": null,
"target_title": "Finish & merge Code search PR"
"target_title": "Finish & merge Code search PR",
"author": {
"name": "Dmitriy Zaporozhets",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
"web_url": "http://localhost:3000/u/root"
},
"author_username": "root"
},
{
"title": null,
"project_id": 15,
"action_name": "commented on",
"target_id": 1312,
"target_type": "Note",
"author_id": 1,
"data": null,
"target_title": null,
"created_at": "2015-12-04T10:33:58.089Z",
"note": {
"id": 1312,
"body": "What an awesome day!",
"attachment": null,
"author": {
"name": "Dmitriy Zaporozhets",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
"web_url": "http://localhost:3000/u/root"
},
"created_at": "2015-12-04T10:33:56.698Z",
"system": false,
"upvote": false,
"downvote": false,
"noteable_id": 377,
"noteable_type": "Issue"
},
"author": {
"name": "Dmitriy Zaporozhets",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
"web_url": "http://localhost:3000/u/root"
},
"author_username": "root"
}
]
```
......
......@@ -60,11 +60,11 @@ This is image that have fully preconfigured `wordpress` and have `MySQL` server
```
Next time when you run your application the `tutum/wordpress` will be started
and you will have access to it from your build container under hostname: `tutum_wordpress`.
and you will have access to it from your build container under hostname: `tutum__wordpress`.
Alias hostname for the service is made from the image name:
1. Everything after `:` is stripped,
2. '/' is replaced to `_`.
2. '/' is replaced with `__`.
### Configuring services
Many services accept environment variables, which allow you to easily change database names or set account names depending on the environment.
......
......@@ -15,7 +15,8 @@ Note: It is a best practice to use a password for an SSH key, but it is not
required and you can skip creating a password by pressing enter. Note that
the password you choose here can't be altered or retrieved.
To generate a new SSH key, use the following commandGitLab```bash
To generate a new SSH key, use the following command:
```bash
ssh-keygen -t rsa -C "$your_email"
```
This command will prompt you for a location and filename to store the key
......
Feature: Project Merge Requests Acceptance
Background:
Given There is an open Merge Request
And I am signed in as a developer of the project
@javascript
Scenario: Accepting the Merge Request and removing the source branch
Given I am on the Merge Request detail page
When I click on "Remove source branch" option
And I click on Accept Merge Request
Then I should not see the Remove Source Branch button
@javascript
Scenario: Accepting the Merge Request without removing the source branch
Given I am on the Merge Request detail page
When I click on Accept Merge Request
Then I should see the Remove Source Branch button
......@@ -45,21 +45,21 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
step 'I submit new label \'support\'' do
visit new_admin_label_path
fill_in 'Title', with: 'support'
fill_in 'Background Color', with: '#F95610'
fill_in 'Background color', with: '#F95610'
click_button 'Save'
end
step 'I submit new label \'bug\'' do
visit new_admin_label_path
fill_in 'Title', with: 'bug'
fill_in 'Background Color', with: '#F95610'
fill_in 'Background color', with: '#F95610'
click_button 'Save'
end
step 'I submit new label with invalid color' do
visit new_admin_label_path
fill_in 'Title', with: 'support'
fill_in 'Background Color', with: '#12'
fill_in 'Background color', with: '#12'
click_button 'Save'
end
......@@ -101,7 +101,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
step 'I change label \'bug\' to \'fix\'' do
fill_in 'Title', with: 'fix'
fill_in 'Background Color', with: '#F15610'
fill_in 'Background color', with: '#F15610'
click_button 'Save'
end
......
......@@ -39,14 +39,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
select "fix", from: "merge_request_source_branch"
select "master", from: "merge_request_target_branch"
click_button "Compare branches"
click_button "Compare branches and continue"
expect(page).to have_content "New merge request"
expect(page).to have_content "New Merge Request"
fill_in "merge_request_title", with: "Merge Request On Forked Project"
end
step 'I submit the merge request' do
click_button "Submit new merge request"
click_button "Submit merge request"
end
step 'I follow the target commit link' do
......@@ -112,11 +112,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
select "Select branch", from: "merge_request_target_branch"
expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s
expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s
expect(find(:select, "merge_request_source_branch", {}).value).to eq ""
expect(find(:select, "merge_request_target_branch", {}).value).to eq ""
expect(find(:select, "merge_request_target_branch", {}).value).to eq "master"
click_button "Compare branches"
end
......
......@@ -65,20 +65,20 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'I see current user as the first user' do
expect(page).to have_selector('.user-result', visible: true, count: 4)
users = page.all('.user-name')
expect(users[0].text).to eq 'Any'
expect(users[0].text).to eq 'Any Assignee'
expect(users[1].text).to eq 'Unassigned'
expect(users[2].text).to eq current_user.name
end
step 'I submit new issue "500 error on profile"' do
fill_in "issue_title", with: "500 error on profile"
click_button "Submit new issue"
click_button "Submit issue"
end
step 'I submit new issue "500 error on profile" with label \'bug\'' do
fill_in "issue_title", with: "500 error on profile"
select 'bug', from: "Labels"
click_button "Submit new issue"
click_button "Submit issue"
end
step 'I click link "500 error on profile"' do
......@@ -86,7 +86,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see label \'bug\' with issue' do
page.within '.issue-show-labels' do
page.within '.issuable-show-labels' do
expect(page).to have_content 'bug'
end
end
......
......@@ -31,20 +31,20 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I submit new label \'support\'' do
fill_in 'Title', with: 'support'
fill_in 'Background Color', with: '#F95610'
click_button 'Save'
fill_in 'Background color', with: '#F95610'
click_button 'Create Label'
end
step 'I submit new label \'bug\'' do
fill_in 'Title', with: 'bug'
fill_in 'Background Color', with: '#F95610'
click_button 'Save'
fill_in 'Background color', with: '#F95610'
click_button 'Create Label'
end
step 'I submit new label with invalid color' do
fill_in 'Title', with: 'support'
fill_in 'Background Color', with: '#12'
click_button 'Save'
fill_in 'Background color', with: '#12'
click_button 'Create Label'
end
step 'I should see label label exist error message' do
......@@ -85,8 +85,8 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I change label \'bug\' to \'fix\'' do
fill_in 'Title', with: 'fix'
fill_in 'Background Color', with: '#F15610'
click_button 'Save'
fill_in 'Background color', with: '#F15610'
click_button 'Save changes'
end
step 'I should see label \'fix\'' do
......
......@@ -86,7 +86,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
select "feature", from: "merge_request_target_branch"
click_button "Compare branches"
fill_in "merge_request_title", with: "Wiki Feature"
click_button "Submit new merge request"
click_button "Submit merge request"
end
step 'project "Shop" have "Bug NS-04" open merge request' do
......
class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
include LoginHelpers
include GitlabRoutingHelper
step 'I am on the Merge Request detail page' do
visit merge_request_path(@merge_request)
end
step 'I click on "Remove source branch" option' do
check('Remove source branch')
end
step 'I click on Accept Merge Request' do
click_button('Accept Merge Request')
end
step 'I should see the Remove Source Branch button' do
expect(page).to have_link('Remove Source Branch')
end
step 'I should not see the Remove Source Branch button' do
expect(page).not_to have_link('Remove Source Branch')
end
step 'There is an open Merge Request' do
@user = create(:user)
@project = create(:project, :public)
@project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER)
@merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
end
step 'I am signed in as a developer of the project' do
login_as(@user)
end
end
......@@ -142,7 +142,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I can see new file page' do
expect(page).to have_content "Create New File"
expect(page).to have_content "New File"
expect(page).to have_content "Commit message"
end
......
......@@ -238,7 +238,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see new wiki page named test' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test")
expect(page).to have_content "Editing"
expect(page).to have_content "Edit Page test"
end
When 'I go back to wiki page home' do
......@@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see Gitlab API document' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
expect(page).to have_content "Editing"
expect(page).to have_content "Edit Page api"
end
step 'I click on Rake tasks link' do
......@@ -261,7 +261,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see Rake tasks directory' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
expect(page).to have_content "Editing"
expect(page).to have_content "Edit Page raketasks"
end
step 'I go directory which contains README file' do
......
......@@ -5,7 +5,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
include SharedPaths
step 'I click on the Cancel button' do
page.within(:css, ".form-actions") do
page.within(:css, ".wiki-form .form-actions") do
click_on "Cancel"
end
end
......@@ -24,7 +24,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
expect(page).to have_content "link test"
click_link "link test"
expect(page).to have_content "Editing"
expect(page).to have_content "Edit Page"
end
step 'I have an existing Wiki page' do
......@@ -68,7 +68,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I click on the "Delete this page" button' do
click_on "Delete this page"
click_on "Delete"
end
step 'The page should be deleted' do
......@@ -120,13 +120,13 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
step 'I should see the new wiki page form' do
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
expect(page).to have_content('Editing - image.jpg')
expect(page).to have_content('Edit Page image.jpg')
end
step 'I create a New page with paths' do
click_on 'New Page'
fill_in 'Page slug', with: 'one/two/three'
click_on 'Build'
click_on 'Create Page'
fill_in "wiki_content", with: 'wiki content'
click_on "Create page"
expect(current_path).to include 'one/two/three'
......@@ -135,7 +135,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
step 'I create a New page with an invalid name' do
click_on 'New Page'
fill_in 'Page slug', with: 'invalid name'
click_on 'Build'
click_on 'Create Page'
end
step 'I should see an error message' do
......@@ -156,7 +156,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see the Editing page' do
expect(page).to have_content('Editing')
expect(page).to have_content('Edit Page')
end
step 'I view the page history of a Wiki page that has a path' do
......
......@@ -194,6 +194,7 @@ module API
expose :author, using: Entities::UserBasic
expose :created_at
expose :system?, as: :system
expose :noteable_id, :noteable_type
# upvote? and downvote? are deprecated, always return false
expose :upvote?, as: :upvote
expose :downvote?, as: :downvote
......@@ -224,6 +225,8 @@ module API
expose :target_id, :target_type, :author_id
expose :data, :target_title
expose :created_at
expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
expose :author_username do |event, options|
if event.author
......
......@@ -76,6 +76,22 @@ module API
present merge_request, with: Entities::MergeRequest
end
# Show MR commits
#
# Parameters:
# id (required) - The ID of a project
# merge_request_id (required) - The ID of MR
#
# Example:
# GET /projects/:id/merge_request/:merge_request_id/commits
#
get ':id/merge_request/:merge_request_id/commits' do
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.commits, with: Entities::RepoCommit
end
# Show MR changes
#
# Parameters:
......
module Gitlab
class ClosingIssueExtractor
ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
ISSUE_CLOSING_REGEX = begin
link_pattern = URI.regexp(%w(http https))
pattern = Gitlab.config.gitlab.issue_closing_pattern
pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))")
Regexp.new(pattern).freeze
end
def initialize(project, current_user = nil)
@extractor = Gitlab::ReferenceExtractor.new(project, current_user)
......@@ -9,10 +15,12 @@ module Gitlab
def closed_by_message(message)
return [] if message.nil?
closing_statements = message.scan(ISSUE_CLOSING_REGEX).
map { |ref| ref[0] }.join(" ")
closing_statements = []
message.scan(ISSUE_CLOSING_REGEX) do
closing_statements << Regexp.last_match[0]
end
@extractor.analyze(closing_statements)
@extractor.analyze(closing_statements.join(" "))
@extractor.issues
end
......
......@@ -178,7 +178,6 @@ module Gitlab
Gitlab::Markdown::SanitizationFilter,
Gitlab::Markdown::UploadLinkFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter,
......@@ -193,6 +192,8 @@ module Gitlab
Gitlab::Markdown::CommitReferenceFilter,
Gitlab::Markdown::LabelReferenceFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::TaskListFilter
]
end
......
......@@ -2,8 +2,8 @@ require 'gitlab/markdown'
module Gitlab
module Markdown
# Issues, Snippets and Merge Requests shares similar functionality in refernce filtering.
# All this functionality moved to this class
# Issues, Merge Requests, Snippets, Commits and Commit Ranges share
# similar functionality in reference filtering.
class AbstractReferenceFilter < ReferenceFilter
include CrossProjectReference
......@@ -26,21 +26,20 @@ module Gitlab
# Public: Find references in text (like `!123` for merge requests)
#
# AnyReferenceFilter.references_in(text) do |match, object|
# "<a href=...>PREFIX#{object}</a>"
# AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
# object = find_object(project_ref, id)
# "<a href=...>#{object.to_reference}</a>"
# end
#
# PREFIX - symbol that detects reference (like ! for merge requests)
# object - reference object (snippet, merget request etc)
# text - String text to search.
#
# Yields the String match, the Integer referenced object ID, and an optional String
# of the external project reference.
# Yields the String match, the Integer referenced object ID, an optional String
# of the external project reference, and all of the matchdata.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(object_class.reference_pattern) do |match|
yield match, $~[object_sym].to_i, $~[:project]
def self.references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[object_sym].to_i, $~[:project], $~
end
end
......@@ -61,8 +60,27 @@ module Gitlab
end
def call
# `#123`
replace_text_nodes_matching(object_class.reference_pattern) do |content|
object_link_filter(content)
object_link_filter(content, object_class.reference_pattern)
end
# `[Issue](#123)`, which is turned into
# `<a href="#123">Issue</a>`
replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
object_link_filter(link, object_class.reference_pattern, link_text: text)
end
# `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
# `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
object_link_filter(text, object_class.link_reference_pattern)
end
# `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
# `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
object_link_filter(link, object_class.link_reference_pattern, link_text: text)
end
end
......@@ -70,30 +88,57 @@ module Gitlab
# to the referenced object's details page.
#
# text - String text to replace references in.
# pattern - Reference pattern to match against.
# link_text - Original content of the link being replaced.
#
# Returns a String with references replaced with links. All links
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text)
references_in(text) do |match, id, project_ref|
def object_link_filter(text, pattern, link_text: nil)
references_in(text, pattern) do |match, id, project_ref, matches|
project = project_from_ref(project_ref)
if project && object = find_object(project, id)
title = escape_once("#{object_title}: #{object.title}")
title = escape_once(object_link_title(object))
klass = reference_class(object_sym)
data = data_attribute(project: project.id, object_sym => object.id)
url = url_for_object(object, project)
data = data_attribute(
original: link_text || match,
project: project.id,
object_sym => object.id
)
url = matches[:url] if matches.names.include?("url")
url ||= url_for_object(object, project)
text = link_text
unless text
text = object.reference_link_text(context[:project])
extras = object_link_text_extras(object, matches)
text += " (#{extras.join(", ")})" if extras.any?
end
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{match}</a>)
class="#{klass}">#{text}</a>)
else
match
end
end
end
def object_title
object_class.name.titleize
def object_link_text_extras(object, matches)
extras = []
if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/
extras << "comment #{$1}"
end
extras
end
def object_link_title(object)
"#{object_class.name.titleize}: #{object.title}"
end
end
end
......
......@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit range references with links.
#
# This filter supports cross-project references.
class CommitRangeReferenceFilter < ReferenceFilter
include CrossProjectReference
class CommitRangeReferenceFilter < AbstractReferenceFilter
def self.object_class
CommitRange
end
# Public: Find commit range references in text
#
# CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
# "<a href=...>#{commit_range}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, the String commit range, and an optional String
# of the external project reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(CommitRange.reference_pattern) do |match|
yield match, $~[:commit_range], $~[:project]
def self.references_in(text, pattern = CommitRange.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit_range], $~[:project], $~
end
end
......@@ -31,9 +21,9 @@ module Gitlab
return unless project
id = node.attr("data-commit-range")
range = CommitRange.new(id, project)
range = find_object(project, id)
return unless range.valid_commits?
return unless range
{ commit_range: range }
end
......@@ -44,49 +34,25 @@ module Gitlab
@commit_map = {}
end
def call
replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
commit_range_link_filter(content)
end
end
# Replace commit range references in text with links to compare the commit
# ranges.
#
# text - String text to replace references in.
#
# Returns a String with commit range references replaced with links. All
# links have `gfm` and `gfm-commit_range` class names attached for
# styling.
def commit_range_link_filter(text)
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
def self.find_object(project, id)
range = CommitRange.new(id, project)
if range.valid_commits?
url = url_for_commit_range(project, range)
title = range.reference_title
klass = reference_class(:commit_range)
data = data_attribute(project: project.id, commit_range: id)
project_ref += '@' if project_ref
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{project_ref}#{range}</a>)
else
match
end
range.valid_commits? ? range : nil
end
def find_object(*args)
self.class.find_object(*args)
end
def url_for_commit_range(project, range)
def url_for_object(range, project)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_compare_url(project.namespace, project,
range.to_param.merge(only_path: context[:only_path]))
end
def object_link_title(range)
range.reference_title
end
end
end
end
......@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit references with links.
#
# This filter supports cross-project references.
class CommitReferenceFilter < ReferenceFilter
include CrossProjectReference
class CommitReferenceFilter < AbstractReferenceFilter
def self.object_class
Commit
end
# Public: Find commit references in text
#
# CommitReferenceFilter.references_in(text) do |match, commit, project_ref|
# "<a href=...>#{commit}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, the String commit identifier, and an optional
# String of the external project reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(Commit.reference_pattern) do |match|
yield match, $~[:commit], $~[:project]
def self.references_in(text, pattern = Commit.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit], $~[:project], $~
end
end
......@@ -31,58 +21,32 @@ module Gitlab
return unless project
id = node.attr("data-commit")
commit = commit_from_ref(project, id)
commit = find_object(project, id)
return unless commit
{ commit: commit }
end
def call
replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content)
end
end
# Replace commit references in text with links to the commit specified.
#
# text - String text to replace references in.
#
# Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text)
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
if commit = self.class.commit_from_ref(project, id)
url = url_for_commit(project, commit)
title = escape_once(commit.link_title)
klass = reference_class(:commit)
data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{project_ref}#{commit.short_id}</a>)
else
match
end
end
end
def self.commit_from_ref(project, id)
def self.find_object(project, id)
if project && project.valid_repo?
project.commit(id)
end
end
def url_for_commit(project, commit)
def find_object(*args)
self.class.find_object(*args)
end
def url_for_object(commit, project)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path])
end
def object_link_title(commit)
commit.link_title
end
end
end
end
......@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
issue_link_filter(content)
end
replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
issue_link_filter(link, link_text: text)
end
end
# Replace `JIRA-123` issue references in text with links to the referenced
......@@ -39,7 +43,7 @@ module Gitlab
#
# Returns a String with `JIRA-123` references replaced with links. All
# links have `gfm` and `gfm-issue` class names attached for styling.
def issue_link_filter(text)
def issue_link_filter(text, link_text: nil)
project = context[:project]
self.class.references_in(text) do |match, issue|
......@@ -49,9 +53,11 @@ module Gitlab
klass = reference_class(:issue)
data = data_attribute(project: project.id)
text = link_text || match
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{match}</a>)
class="#{klass}">#{text}</a>)
end
end
......
......@@ -8,9 +8,9 @@ module Gitlab
class ExternalLinkFilter < HTML::Pipeline::Filter
def call
doc.search('a').each do |node|
next unless node.has_attribute?('href')
link = node.attr('href')
link = node.attribute('href').value
next unless link
# Skip non-HTTP(S) links
next unless link.start_with?('http')
......
......@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content)
end
replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
label_link_filter(link, link_text: text)
end
end
# Replace label references in text with links to the label specified.
......@@ -38,7 +42,7 @@ module Gitlab
#
# Returns a String with label references replaced with links. All links
# have `gfm` and `gfm-label` class names attached for styling.
def label_link_filter(text)
def label_link_filter(text, link_text: nil)
project = context[:project]
self.class.references_in(text) do |match, id, name|
......@@ -47,10 +51,16 @@ module Gitlab
if label = project.labels.find_by(params)
url = url_for_label(project, label)
klass = reference_class(:label)
data = data_attribute(project: project.id, label: label.id)
data = data_attribute(
original: link_text || match,
project: project.id,
label: label.id
)
text = link_text || render_colored_label(label)
%(<a href="#{url}" #{data}
class="#{klass}">#{render_colored_label(label)}</a>)
class="#{klass}">#{text}</a>)
else
match
end
......@@ -59,8 +69,8 @@ module Gitlab
def url_for_label(project, label)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_issues_path(project.namespace, project,
label_name: label.name)
h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
only_path: context[:only_path])
end
def render_colored_label(label)
......
......@@ -20,6 +20,16 @@ module Gitlab
h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path])
end
def object_link_text_extras(object, matches)
extras = super
if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs'
extras.unshift "diffs"
end
extras
end
end
end
end
......@@ -12,7 +12,10 @@ module Gitlab
def call
doc.css('a.gfm').each do |node|
unless user_can_reference?(node)
node.replace(node.text)
# The reference should be replaced by the original text,
# which is not always the same as the rendered text.
text = node.attr('data-original') || node.text
node.replace(text)
end
end
......
......@@ -122,6 +122,80 @@ module Gitlab
doc
end
# Iterate through the document's link nodes, yielding the current node's
# content if:
#
# * The `project` context value is present AND
# * The node's content matches `pattern`
#
# pattern - Regex pattern against which to match the node's content
#
# Yields the current node's String contents. The result of the block will
# replace the node and update the current document.
#
# Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_link_nodes_with_text(pattern)
return doc if project.nil?
doc.search('a').each do |node|
klass = node.attr('class')
next if klass && klass.include?('gfm')
link = node.attr('href')
text = node.text
next unless link && text
link = URI.decode(link)
# Ignore ending punctionation like periods or commas
next unless link == text && text =~ /\A#{pattern}/
html = yield text
next if html == text
node.replace(html)
end
doc
end
# Iterate through the document's link nodes, yielding the current node's
# content if:
#
# * The `project` context value is present AND
# * The node's HREF matches `pattern`
#
# pattern - Regex pattern against which to match the node's HREF
#
# Yields the current node's String HREF and String content.
# The result of the block will replace the node and update the current document.
#
# Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_link_nodes_with_href(pattern)
return doc if project.nil?
doc.search('a').each do |node|
klass = node.attr('class')
next if klass && klass.include?('gfm')
link = node.attr('href')
text = node.text
next unless link && text
link = URI.decode(link)
next unless link && link =~ /\A#{pattern}\z/
html = yield link, text
next if html == link
node.replace(html)
end
doc
end
# Ensure that a :project key exists in context
#
# Note that while the key might exist, its value could be nil!
......
......@@ -17,6 +17,9 @@ module Gitlab
return doc unless linkable_files?
doc.search('a').each do |el|
klass = el.attr('class')
next if klass && klass.include?('gfm')
process_link_attr el.attribute('href')
end
......
......@@ -52,6 +52,10 @@ module Gitlab
replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
end
replace_link_nodes_with_href(User.reference_pattern) do |link, text|
user_link_filter(link, link_text: text)
end
end
# Replace `@user` user references in text with links to the referenced
......@@ -61,12 +65,12 @@ module Gitlab
#
# Returns a String with `@user` references replaced with links. All links
# have `gfm` and `gfm-project_member` class names attached for styling.
def user_link_filter(text)
def user_link_filter(text, link_text: nil)
self.class.references_in(text) do |match, username|
if username == 'all'
link_to_all
link_to_all(link_text: link_text)
elsif namespace = Namespace.find_by(path: username)
link_to_namespace(namespace) || match
link_to_namespace(namespace, link_text: link_text) || match
else
match
end
......@@ -83,36 +87,36 @@ module Gitlab
reference_class(:project_member)
end
def link_to_all
def link_to_all(link_text: nil)
project = context[:project]
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
data = data_attribute(project: project.id)
text = User.reference_prefix + 'all'
text = link_text || User.reference_prefix + 'all'
link_tag(url, data, text)
end
def link_to_namespace(namespace)
def link_to_namespace(namespace, link_text: nil)
if namespace.is_a?(Group)
link_to_group(namespace.path, namespace)
link_to_group(namespace.path, namespace, link_text: link_text)
else
link_to_user(namespace.path, namespace)
link_to_user(namespace.path, namespace, link_text: link_text)
end
end
def link_to_group(group, namespace)
def link_to_group(group, namespace, link_text: nil)
url = urls.group_url(group, only_path: context[:only_path])
data = data_attribute(group: namespace.id)
text = Group.reference_prefix + group
text = link_text || Group.reference_prefix + group
link_tag(url, data, text)
end
def link_to_user(user, namespace)
def link_to_user(user, namespace, link_text: nil)
url = urls.user_url(user, only_path: context[:only_path])
data = data_attribute(user: namespace.owner_id)
text = User.reference_prefix + user
text = link_text || User.reference_prefix + user
link_tag(url, data, text)
end
......
......@@ -58,7 +58,15 @@ module Gitlab
reference_filter: filter
}
pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
# We need to autolink first to finds links to referables, and to prevent
# numeric anchors to be parsed as issue references.
filters = [
Gitlab::Markdown::AutolinkFilter,
filter,
Gitlab::Markdown::ReferenceGathererFilter
]
pipeline = HTML::Pipeline.new(filters, context)
result = pipeline.call(@text)
values = result[:references][filter_type].uniq
......
......@@ -327,7 +327,7 @@ print_status() {
printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
fi
fi
if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
fi
}
......
......@@ -61,7 +61,7 @@ describe 'Issues', feature: true do
it 'allows user to select unasigned', js: true do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
expect(page).to have_content "Assign to #{@user.name}"
expect(page).to have_content "Assignee #{@user.name}"
first('#s2id_issue_assignee_id').click
sleep 2 # wait for ajax stuff to complete
......
......@@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Ignores invalid: <%= User.reference_prefix %>fake_user
- Ignored in code: `<%= user.to_reference %>`
- Ignored in links: [Link to <%= user.to_reference %>](#user-link)
- Link to user by reference: [User](<%= user.to_reference %>)
#### IssueReferenceFilter
......@@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Issue in another project: <%= xissue.to_reference(project) %>
- Ignored in code: `<%= issue.to_reference %>`
- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>
- Link to issue by reference: [Issue](<%= issue.to_reference %>)
- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
#### MergeRequestReferenceFilter
......@@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Merge request in another project: <%= xmerge_request.to_reference(project) %>
- Ignored in code: `<%= merge_request.to_reference %>`
- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>
- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>)
- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
#### SnippetReferenceFilter
......@@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Snippet in another project: <%= xsnippet.to_reference(project) %>
- Ignored in code: `<%= snippet.to_reference %>`
- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>
- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>)
- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
#### CommitRangeReferenceFilter
......@@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Range in another project: <%= xcommit_range.to_reference(project) %>
- Ignored in code: `<%= commit_range.to_reference %>`
- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>
- Link to range by reference: [Range](<%= commit_range.to_reference %>)
- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
#### CommitReferenceFilter
......@@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Commit in another project: <%= xcommit.to_reference(project) %>
- Ignored in code: `<%= commit.to_reference %>`
- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>
- Link to commit by reference: [Commit](<%= commit.to_reference %>)
- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
#### LabelReferenceFilter
......@@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Label by name in quotes: <%= label.to_reference(:name) %>
- Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
- Link to label by reference: [Label](<%= label.to_reference %>)
### Task Lists
......
......@@ -2,11 +2,18 @@ require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) }
let(:project2) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference }
let(:cross_reference) { issue2.to_reference(project) }
subject { described_class.new(project, project.creator) }
before do
project2.team << [project.creator, :master]
end
describe "#closed_by_message" do
context 'with a single reference' do
it do
......@@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do
end
end
context "with a cross-project reference" do
it do
message = "Closes #{cross_reference}"
expect(subject.closed_by_message(message)).to eq([issue2])
end
end
context "with a cross-project URL" do
it do
message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}"
expect(subject.closed_by_message(message)).to eq([issue2])
end
end
context "with an invalid URL" do
it do
message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
expect(subject.closed_by_message(message)).to eq([])
end
end
context 'with multiple references' do
let(:other_issue) { create(:issue, project: project) }
let(:third_issue) { create(:issue, project: project) }
......@@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do
expect(subject.closed_by_message(message)).
to match_array([issue, other_issue, third_issue])
end
it "fetches cross-project references" do
message = "Closes #{reference} and #{cross_reference}"
expect(subject.closed_by_message(message)).
to match_array([issue, issue2])
end
it "fetches cross-project URL references" do
message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
expect(subject.closed_by_message(message)).
to match_array([issue, issue2])
end
it "ignores invalid cross-project URL references" do
message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
expect(subject.closed_by_message(message)).
to match_array([issue])
end
end
end
def urls
Gitlab::Application.routes.url_helpers
end
end
......@@ -5,11 +5,11 @@ module Gitlab::Markdown
include FilterSpecHelper
let(:project) { create(:project, :public) }
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
let(:commit1) { project.commit("HEAD~2") }
let(:commit2) { project.commit }
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") }
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) }
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) }
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
......@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -27,14 +27,14 @@ module Gitlab::Markdown
let(:reference2) { range2.to_reference }
it 'links to a valid two-dot reference' do
doc = filter("See #{reference2}")
doc = reference_filter("See #{reference2}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
end
it 'links to a valid three-dot reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
......@@ -46,14 +46,14 @@ module Gitlab::Markdown
exp = commit1.short_id + '...' + commit2.short_id
expect(filter("See #{reference}").css('a').first.text).to eq exp
expect(filter("See #{reference2}").css('a').first.text).to eq exp
expect(reference_filter("See #{reference}").css('a').first.text).to eq exp
expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
doc = reference_filter("See (#{reference}.)")
exp = Regexp.escape(range.to_s)
exp = Regexp.escape(range.reference_link_text)
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
......@@ -62,21 +62,22 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(commit1.id.reverse)
expect(filter(act).to_html).to eq exp
expect(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq range.reference_title
end
it 'includes default classes' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end
it 'includes a data-project attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -84,15 +85,15 @@ module Gitlab::Markdown
end
it 'includes a data-commit-range attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit-range')
expect(link.attr('data-commit-range')).to eq range.to_reference
expect(link.attr('data-commit-range')).to eq range.to_s
end
it 'supports an :only_path option' do
doc = filter("See #{reference}", only_path: true)
doc = reference_filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -115,25 +116,63 @@ module Gitlab::Markdown
end
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}")
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end
end
context 'cross-project URL reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:range) { CommitRange.new("#{commit1.id}...master", project) }
let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
before do
range.project = project2
end
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq reference
end
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(range.reference_link_text(project))
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
expect(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
......
......@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -24,7 +24,7 @@ module Gitlab::Markdown
# Let's test a variety of commit SHA sizes just to be paranoid
[6, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do
doc = filter("See #{reference[0...size]}")
doc = reference_filter("See #{reference[0...size]}")
expect(doc.css('a').first.text).to eq commit.short_id
expect(doc.css('a').first.attr('href')).
......@@ -33,15 +33,15 @@ module Gitlab::Markdown
end
it 'always uses the short ID as the link text' do
doc = filter("See #{commit.id}")
doc = reference_filter("See #{commit.id}")
expect(doc.text).to eq "See #{commit.short_id}"
doc = filter("See #{commit.id[0...6]}")
doc = reference_filter("See #{commit.id[0...6]}")
expect(doc.text).to eq "See #{commit.short_id}"
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
end
......@@ -51,28 +51,28 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(invalid)
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq commit.link_title
end
it 'escapes the title attribute' do
allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.text).to eq "See #{commit.short_id}"
end
it 'includes default classes' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end
it 'includes a data-project attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -80,7 +80,7 @@ module Gitlab::Markdown
end
it 'includes a data-commit attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit')
......@@ -88,7 +88,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
doc = filter("See #{reference}", only_path: true)
doc = reference_filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -108,14 +108,14 @@ module Gitlab::Markdown
let(:reference) { commit.to_reference(project) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(project2.to_reference)
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
......@@ -123,7 +123,37 @@ module Gitlab::Markdown
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end
end
context 'cross-project URL reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit }
let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{commit.reference_link_text(project)}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
act = "Committed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end
it 'adds to the results hash' do
......
......@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -29,18 +29,18 @@ module Gitlab::Markdown
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = filter("Fixed #{reference}")
doc = reference_filter("Fixed #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
......@@ -48,28 +48,28 @@ module Gitlab::Markdown
invalid = invalidate_reference(reference)
exp = act = "Fixed #{invalid}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Issue #{reference}")
doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
end
it 'escapes the title attribute' do
issue.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Issue #{reference}")
doc = reference_filter("Issue #{reference}")
expect(doc.text).to eq "Issue #{reference}"
end
it 'includes default classes' do
doc = filter("Issue #{reference}")
doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end
it 'includes a data-project attribute' do
doc = filter("Issue #{reference}")
doc = reference_filter("Issue #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -77,7 +77,7 @@ module Gitlab::Markdown
end
it 'includes a data-issue attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-issue')
......@@ -85,7 +85,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
doc = filter("Issue #{reference}", only_path: true)
doc = reference_filter("Issue #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -109,25 +109,97 @@ module Gitlab::Markdown
with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
context 'cross-project URL reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq reference
end
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
end
it 'adds to the results hash' do
result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
context 'cross-project reference in link href' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { %Q{<a href="#{issue.to_reference(project)}">Reference</a>} }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
it 'adds to the results hash' do
result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
context 'cross-project URL in link href' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { %Q{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2) + "#note_123"
end
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end
it 'adds to the results hash' do
......
......@@ -16,17 +16,17 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Label #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
it 'includes default classes' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end
it 'includes a data-project attribute' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -34,7 +34,7 @@ module Gitlab::Markdown
end
it 'includes a data-label attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-label')
......@@ -42,7 +42,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
doc = filter("Label #{reference}", only_path: true)
doc = reference_filter("Label #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -56,33 +56,33 @@ module Gitlab::Markdown
describe 'label span element' do
it 'includes default classes' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
end
it 'includes a style attribute' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
end
end
context 'Integer-based references' do
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_path(project.namespace, project, label_name: label.name)
namespace_project_issues_url(project.namespace, project, label_name: label.name)
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label IDs' do
exp = act = "Label #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -91,22 +91,22 @@ module Gitlab::Markdown
let(:reference) { "#{Label.reference_prefix}#{label.name}" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_path(project.namespace, project, label_name: label.name)
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm'
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -115,29 +115,66 @@ module Gitlab::Markdown
let(:reference) { label.to_reference(:name) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_path(project.namespace, project, label_name: label.name)
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
describe 'edge cases' do
it 'gracefully handles non-references matching the pattern' do
exp = act = '(format nil "~0f" 3.0) ; 3.0'
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
describe 'referencing a label in a link href' do
let(:reference) { %Q{<a href="#{label.to_reference}">Label</a>} }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
end
it 'links with adjacent text' do
doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+>Label</a>\.\)))
end
it 'includes a data-project attribute' do
doc = reference_filter("Label #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-label attribute' do
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-label')
expect(link.attr('data-label')).to eq label.id.to_s
end
it 'adds to the results hash' do
result = reference_pipeline_result("Label #{reference}")
expect(result[:references][:label]).to eq [label]
end
end
end
......
......@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -22,42 +22,42 @@ module Gitlab::Markdown
let(:reference) { merge.to_reference }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_merge_request_url(project.namespace, project, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Merge #{reference}")
doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
end
it 'escapes the title attribute' do
merge.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Merge #{reference}")
doc = reference_filter("Merge #{reference}")
expect(doc.text).to eq "Merge #{reference}"
end
it 'includes default classes' do
doc = filter("Merge #{reference}")
doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
it 'includes a data-project attribute' do
doc = filter("Merge #{reference}")
doc = reference_filter("Merge #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -65,7 +65,7 @@ module Gitlab::Markdown
end
it 'includes a data-merge-request attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-merge-request')
......@@ -73,7 +73,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
doc = filter("Merge #{reference}", only_path: true)
doc = reference_filter("Merge #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -89,26 +89,50 @@ module Gitlab::Markdown
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
let(:reference) { merge.to_reference(project) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge)
project2, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end
end
context 'cross-project URL reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq reference
end
it 'links with adjacent text' do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
end
it 'adds to the results hash' do
......
......@@ -15,48 +15,48 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
context 'internal reference' do
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_snippet_url(project.namespace, project, snippet)
end
it 'links with adjacent text' do
doc = filter("Snippet (#{reference}.)")
doc = reference_filter("Snippet (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs' do
exp = act = "Snippet #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Snippet #{reference}")
doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
end
it 'escapes the title attribute' do
snippet.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Snippet #{reference}")
doc = reference_filter("Snippet #{reference}")
expect(doc.text).to eq "Snippet #{reference}"
end
it 'includes default classes' do
doc = filter("Snippet #{reference}")
doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end
it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}")
doc = reference_filter("Snippet #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -64,7 +64,7 @@ module Gitlab::Markdown
end
it 'includes a data-snippet attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-snippet')
......@@ -72,7 +72,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
doc = filter("Snippet #{reference}", only_path: true)
doc = reference_filter("Snippet #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -92,21 +92,51 @@ module Gitlab::Markdown
let(:reference) { snippet.to_reference(project) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end
end
context 'cross-project URL reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'links with adjacent text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
act = "See #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end
it 'adds to the results hash' do
......
......@@ -14,13 +14,13 @@ module Gitlab::Markdown
it 'ignores invalid users' do
exp = act = "Hey #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq(exp)
expect(reference_filter(act).to_html).to eq(exp)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -32,7 +32,7 @@ module Gitlab::Markdown
end
it 'supports a special @all mention' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project)
......@@ -46,26 +46,26 @@ module Gitlab::Markdown
context 'mentioning a user' do
it 'links to a User' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
it 'links to a User with a period' do
user = create(:user, name: 'alphA.Beta')
doc = filter("Hey #{user.to_reference}")
doc = reference_filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
it 'links to a User with an underscore' do
user = create(:user, name: 'ping_pong_king')
doc = filter("Hey #{user.to_reference}")
doc = reference_filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
it 'includes a data-user attribute' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-user')
......@@ -83,12 +83,12 @@ module Gitlab::Markdown
let(:reference) { group.to_reference }
it 'links to the Group' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end
it 'includes a data-group attribute' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-group')
......@@ -102,21 +102,48 @@ module Gitlab::Markdown
end
it 'links with adjacent text' do
doc = filter("Mention me (#{reference}.)")
doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end
it 'includes default classes' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
end
it 'supports an :only_path context' do
doc = filter("Hey #{reference}", only_path: true)
doc = reference_filter("Hey #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.user_path(user)
end
context 'referencing a user in a link href' do
let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} }
it 'links to a User' do
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>User<\/a>\.\)/)
end
it 'includes a data-user attribute' do
doc = reference_filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-user')
expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end
it 'adds to the results hash' do
result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [user]
end
end
end
end
......@@ -7,50 +7,72 @@ describe CommitRange do
it { is_expected.to include_module(Referable) }
end
let(:sha_from) { 'f3f85602' }
let(:sha_to) { 'e86e1013' }
let!(:project) { create(:project, :public) }
let!(:commit1) { project.commit("HEAD~2") }
let!(:commit2) { project.commit }
let(:range) { described_class.new("#{sha_from}...#{sha_to}") }
let(:range2) { described_class.new("#{sha_from}..#{sha_to}") }
let(:sha_from) { commit1.short_id }
let(:sha_to) { commit2.short_id }
let(:full_sha_from) { commit1.id }
let(:full_sha_to) { commit2.id }
let(:range) { described_class.new("#{sha_from}...#{sha_to}", project) }
let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) }
it 'raises ArgumentError when given an invalid range string' do
expect { described_class.new("Foo") }.to raise_error(ArgumentError)
expect { described_class.new("Foo", project) }.to raise_error(ArgumentError)
end
describe '#to_s' do
it 'is correct for three-dot syntax' do
expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}"
expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}"
end
it 'is correct for two-dot syntax' do
expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}"
expect(range2.to_s).to eq "#{full_sha_from}..#{full_sha_to}"
end
end
describe '#to_reference' do
let(:project) { double('project', to_reference: 'namespace1/project') }
let(:cross) { create(:project) }
before do
range.project = project
it 'returns a String reference to the object' do
expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}"
end
it 'returns a String reference to the object' do
expect(range.to_reference).to eq range.to_s
expect(range2.to_reference).to eq "#{full_sha_from}..#{full_sha_to}"
end
it 'supports a cross-project reference' do
cross = double('project')
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}"
end
end
describe '#reference_link_text' do
let(:cross) { create(:project) }
it 'returns a String reference to the object' do
expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}"
end
it 'returns a String reference to the object' do
expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}"
end
it 'supports a cross-project reference' do
expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}"
end
end
describe '#reference_title' do
it 'returns the correct String for three-dot ranges' do
expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}"
expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}"
end
it 'returns the correct String for two-dot ranges' do
expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}"
expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}"
end
end
......@@ -60,11 +82,11 @@ describe CommitRange do
end
it 'includes the correct values for a three-dot range' do
expect(range.to_param).to eq({ from: sha_from, to: sha_to })
expect(range.to_param).to eq({ from: full_sha_from, to: full_sha_to })
end
it 'includes the correct values for a two-dot range' do
expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to })
expect(range2.to_param).to eq({ from: full_sha_from + '^', to: full_sha_to })
end
end
......@@ -79,51 +101,26 @@ describe CommitRange do
end
describe '#valid_commits?' do
context 'without a project' do
it 'returns nil' do
expect(range.valid_commits?).to be_nil
end
end
it 'accepts an optional project argument' do
project1 = double('project1').as_null_object
project2 = double('project2').as_null_object
# project1 gets assigned through the accessor, but ignored when not given
# as an argument to `valid_commits?`
expect(project1).not_to receive(:present?)
range.project = project1
# project2 gets passed to `valid_commits?`
expect(project2).to receive(:present?).and_return(false)
range.valid_commits?(project2)
end
context 'with a project' do
let(:project) { double('project', repository: double('repository')) }
context 'with a valid repo' do
before do
expect(project).to receive(:valid_repo?).and_return(true)
range.project = project
end
it 'is false when `sha_from` is invalid' do
expect(project.repository).to receive(:commit).with(sha_from).and_return(false)
expect(project.repository).not_to receive(:commit).with(sha_to)
expect(project).to receive(:commit).with(sha_from).and_return(nil)
expect(project).to receive(:commit).with(sha_to).and_call_original
expect(range).not_to be_valid_commits
end
it 'is false when `sha_to` is invalid' do
expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
expect(project.repository).to receive(:commit).with(sha_to).and_return(false)
expect(project).to receive(:commit).with(sha_from).and_call_original
expect(project).to receive(:commit).with(sha_to).and_return(nil)
expect(range).not_to be_valid_commits
end
it 'is true when both `sha_from` and `sha_to` are valid' do
expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
expect(project.repository).to receive(:commit).with(sha_to).and_return(true)
expect(range).to be_valid_commits
end
end
......@@ -131,7 +128,6 @@ describe CommitRange do
context 'without a valid repo' do
before do
expect(project).to receive(:valid_repo?).and_return(false)
range.project = project
end
it 'returns false' do
......@@ -139,5 +135,4 @@ describe CommitRange do
end
end
end
end
end
......@@ -24,6 +24,17 @@ describe Commit do
end
end
describe '#reference_link_text' do
it 'returns a String reference to the object' do
expect(commit.reference_link_text).to eq commit.short_id
end
it 'supports a cross-project reference' do
cross = double('project')
expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}"
end
end
describe '#title' do
it "returns no_commit_message when safe_message is blank" do
allow(commit).to receive(:safe_message).and_return('')
......@@ -77,14 +88,10 @@ eos
let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do
allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}")
expect(commit.closes_issues).to eq([issue])
end
it 'does not detect issues from other projects' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}")
expect(commit.closes_issues).to be_empty
allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
expect(commit.closes_issues).to include(issue)
expect(commit.closes_issues).to include(other_issue)
end
end
......
......@@ -131,6 +131,23 @@ describe API::API, api: true do
end
end
describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
context 'valid merge request' do
before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
let(:commit) { merge_request.commits.first }
it { expect(response.status).to eq 200 }
it { expect(json_response.size).to eq(merge_request.commits.size) }
it { expect(json_response.first['id']).to eq(commit.id) }
it { expect(json_response.first['title']).to eq(commit.title) }
end
it 'returns a 404 when merge_request_id not found' do
get api("/projects/#{project.id}/merge_request/999/commits", user)
expect(response.status).to eq(404)
end
end
describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
it 'should return the change information of the merge_request' do
get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
......
......@@ -389,14 +389,30 @@ describe API::API, api: true do
describe 'GET /projects/:id/events' do
before { project_member2 }
it 'should return a project events' do
context 'valid request' do
before do
note = create(:note_on_issue, note: 'What an awesome day!', project: project)
EventCreateService.new.leave_note(note, note.author)
get api("/projects/#{project.id}/events", user)
expect(response.status).to eq(200)
json_event = json_response.first
end
it { expect(response.status).to eq(200) }
context 'joined event' do
let(:json_event) { json_response[1] }
expect(json_event['action_name']).to eq('joined')
expect(json_event['project_id'].to_i).to eq(project.id)
expect(json_event['author_username']).to eq(user3.username)
it { expect(json_event['action_name']).to eq('joined') }
it { expect(json_event['project_id'].to_i).to eq(project.id) }
it { expect(json_event['author_username']).to eq(user3.username) }
it { expect(json_event['author']['name']).to eq(user3.name) }
end
context 'comment event' do
let(:json_event) { json_response.first }
it { expect(json_event['action_name']).to eq('commented on') }
it { expect(json_event['note']['body']).to eq('What an awesome day!') }
end
end
it 'should return a 404 error if not found' do
......
......@@ -45,6 +45,7 @@ describe NotificationService do
project.team << [issue.author, :master]
project.team << [issue.assignee, :master]
project.team << [note.author, :master]
create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy')
end
describe :new_note do
......@@ -60,6 +61,7 @@ describe NotificationService do
should_email(note.noteable.assignee)
should_email(@u_mentioned)
should_email(@subscriber)
should_email(@subscribed_participant)
should_not_email(note.author)
should_not_email(@u_participating)
should_not_email(@u_disabled)
......@@ -381,18 +383,19 @@ describe NotificationService do
def add_users_with_subscription(project, issuable)
@subscriber = create :user
@unsubscriber = create :user
@subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING)
project.team << [@subscribed_participant, :master]
project.team << [@subscriber, :master]
project.team << [@unsubscriber, :master]
issuable.subscriptions.create(user: @subscriber, subscribed: true)
issuable.subscriptions.create(user: @subscribed_participant, subscribed: true)
issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
end
def sent_to_user?(user)
ActionMailer::Base.deliveries.any? do |message|
message.to.include?(user.email)
end
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
end
def should_email(user)
......
......@@ -35,11 +35,24 @@ module FilterSpecHelper
pipeline.call(body)
end
def reference_pipeline_result(body, contexts = {})
def reference_pipeline(contexts = {})
contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
pipeline.call(body)
filters = [
Gitlab::Markdown::AutolinkFilter,
described_class,
Gitlab::Markdown::ReferenceGathererFilter
]
HTML::Pipeline.new(filters, contexts)
end
def reference_pipeline_result(body, contexts = {})
reference_pipeline(contexts).call(body)
end
def reference_filter(html, contexts = {})
reference_pipeline(contexts).to_document(html)
end
# Modify a String reference to make it invalid
......
......@@ -93,6 +93,10 @@ class MarkdownFeature
end
end
def urls
Gitlab::Application.routes.url_helpers
end
def raw_markdown
markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
ERB.new(markdown).result(binding)
......
......@@ -71,7 +71,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
expect(actual).to have_selector('a.gfm.gfm-project_member', count: 4)
end
end
......@@ -80,7 +80,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
expect(actual).to have_selector('a.gfm.gfm-issue', count: 6)
end
end
......@@ -89,7 +89,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 6)
expect(actual).to have_selector('em a.gfm-merge_request')
end
end
......@@ -99,7 +99,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
expect(actual).to have_selector('a.gfm.gfm-snippet', count: 5)
end
end
......@@ -108,7 +108,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 5)
end
end
......@@ -117,7 +117,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
expect(actual).to have_selector('a.gfm.gfm-commit', count: 5)
end
end
......@@ -126,7 +126,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
expect(actual).to have_selector('a.gfm.gfm-label', count: 4)
end
end
......
......@@ -10,12 +10,12 @@ def common_mentionable_setup
let(:mentioned_issue) { create(:issue, project: project) }
let!(:mentioned_mr) { create(:merge_request, :simple, source_project: project) }
let(:mentioned_commit) { project.commit }
let(:mentioned_commit) { project.commit("HEAD~1") }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) }
let(:ext_commit) { ext_proj.commit }
let(:ext_commit) { ext_proj.commit("HEAD~2") }
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
......@@ -45,14 +45,11 @@ def common_mentionable_setup
before do
# Wire the project's repository to return the mentioned commit, and +nil+
# for any unrecognized commits.
commitmap = {
mentioned_commit.id => mentioned_commit
}
extra_commits.each { |c| commitmap[c.short_id] = c }
allow(Project).to receive(:find).and_call_original
allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
allow_any_instance_of(::Repository).to receive(:commit).and_call_original
allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit)
extra_commits.each do |commit|
allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit)
end
set_mentionable_text.call(ref_string)
end
......
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