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