Commit 5a9a8d03 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into ui/dashboard-new-issue

parents cbcb8dbe bd5fb1b4
......@@ -4,8 +4,23 @@ v 8.3.0 (unreleased)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
- Add ignore whitespace change option to commit view
- Fire update hook from GitLab
- Style warning about mentioning many people in a comment
- 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
- Run custom Git hooks when branch is created or deleted.
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
v 8.2.3
- Webhook payload has an added, modified and removed properties for each commit
v 8.2.2
- Fix 404 in redirection after removing a project (Stan Hu)
......@@ -13,6 +28,9 @@ v 8.2.2
- Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
- Fix: Raw private snippets access workflow
- Prevent "413 Request entity too large" errors when pushing large files with LFS
- Fix invalid links within projects dashboard header
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
- Fix: duplicate email notifications on issue comments
v 8.2.1
- Forcefully update builds that didn't want to update with state machine
......
......@@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4"
# Sanitize user input
gem "sanitize", '~> 2.0'
gem 'babosa', '~> 1.0.2'
# Protect against bruteforcing
gem "rack-attack", '~> 4.3.0'
......
......@@ -73,6 +73,7 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
bcrypt (3.1.10)
benchmark-ips (2.3.0)
better_errors (1.0.1)
......@@ -823,6 +824,7 @@ DEPENDENCIES
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
benchmark-ips
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
......
# For DEVELOPMENT only. Production uses Runit in
# https://gitlab.com/gitlab-org/omnibus-gitlab or the init scripts in
# lib/support/init.d, which call scripts in bin/ .
#
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q mailers -q default
worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
......@@ -135,17 +135,25 @@ $ ->
), 1
# Initialize tooltips
$('body').tooltip({
selector: '.has_tooltip, [data-toggle="tooltip"], .page-sidebar-collapsed .nav-sidebar a'
$('body').tooltip(
selector: '.has_tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
if $el.attr('id') == 'js-shortcuts-home'
# Place the logo tooltip on the right when collapsed, bottom when expanded
$el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
else
# Otherwise use the data-placement attribute, or 'bottom' if undefined
$el.data('placement') or 'bottom'
})
$el.data('placement') || 'bottom'
)
$('.header-logo .home').tooltip(
placement: (_, el) ->
$el = $(el)
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
container: 'body'
)
$('.page-with-sidebar').tooltip(
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
placement: 'right'
container: 'body'
)
# Form submitter
$('.trigger-submit').on 'change', ->
......
......@@ -89,3 +89,8 @@ class @AwardsHandler
findEmojiIcon: (emoji) ->
$(".icon[data-emoji='" + emoji + "']")
scrollToAwards: ->
$('body, html').animate({
scrollTop: $('.awards').offset().top - 80
}, 200)
class @Flash
constructor: (message, type)->
flash = $(".flash-container")
flash.html("")
@flash = $(".flash-container")
@flash.html("")
$('<div/>',
innerDiv = $('<div/>',
class: "flash-#{type}",
text: message
).appendTo(".flash-container")
)
innerDiv.appendTo(".flash-container")
flash.click -> $(@).fadeOut()
flash.show()
@flash.click -> $(@).fadeOut()
@flash.show()
pin: ->
@flash.addClass('flash-pinned flash-raised')
......@@ -29,7 +29,7 @@
$('#filter_issue_search').val($('#issue_search').val())
initSelects: ->
$("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
......
......@@ -10,17 +10,20 @@ class @MergeRequestWidget
constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false)
mergeInProgress: ->
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
type: 'GET'
url: $('.merge-request').data('url')
success: (data) =>
if data.state == "merged"
location.reload()
urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
window.location.href = window.location.href + urlSuffix
else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else
setTimeout(merge_request_widget.mergeInProgress, 2000)
callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
setTimeout(callback, 2000)
dataType: 'json'
getMergeStatus: ->
......
......@@ -111,6 +111,12 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote
###
renderNote: (note) ->
unless note.valid
if note.award
flash = new Flash('You have already used this award emoji!', 'alert')
flash.pin()
return
# render note if it not present in loaded list
# or skip if rendered
if @isNewNote(note) && !note.award
......@@ -122,6 +128,7 @@ class @Notes
if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
awards_handler.scrollToAwards()
###
Check if note does not exists on page
......
......@@ -5,6 +5,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
)
......@@ -2,3 +2,9 @@ class @User
constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList()
$('.hide-project-limit-message').on 'click', (e) ->
path = '/'
$.cookie('hide_project_limit_message', 'false', { path: path })
$(@).parents('.project-limit-message').remove()
e.preventDefault()
......@@ -32,17 +32,15 @@ class @UsersSelect
if showNullUser
nullUser = {
name: 'Unassigned',
avatar: null,
username: 'none',
id: 0
}
data.results.unshift(nullUser)
if showAnyUser
name = showAnyUser
name = 'Any User' if name == true
anyUser = {
name: 'Any',
avatar: null,
username: 'none',
name: name,
id: null
}
data.results.unshift(anyUser)
......@@ -50,7 +48,6 @@ class @UsersSelect
if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
emailUser = {
name: "Invite \"#{query.term}\"",
avatar: null,
username: query.term,
id: query.term
}
......@@ -82,10 +79,10 @@ class @UsersSelect
else
avatar = gon.default_avatar_url
"<div class='user-result'>
"<div class='user-result #{'no-username' unless user.username}'>
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
<div class='user-name'>#{user.name}</div>
<div class='user-username'>#{user.username}</div>
<div class='user-username'>#{user.username || ""}</div>
</div>"
formatSelection: (user) ->
......
......@@ -116,6 +116,11 @@
position: absolute;
top: 10px;
right: 10px;
&.left {
left: 10px;
right: auto;
}
}
}
......
......@@ -341,10 +341,6 @@ table {
text-align: center;
}
.task-status {
margin-left: 10px;
}
#nprogress .spinner {
top: 15px !important;
right: 10px !important;
......
......@@ -15,3 +15,13 @@
@extend .alert-danger;
}
}
.flash-pinned {
position: fixed;
top: 80px;
width: 80%;
}
.flash-raised {
z-index: 1000;
}
......@@ -91,9 +91,17 @@ label {
}
.input-group {
.select2-container {
display: table-cell;
width: 200px !important;
}
.input-group-addon {
background-color: #f7f8fa;
}
.input-group-addon:not(:first-child):not(:last-child) {
border-left: 0;
border-right: 0;
}
}
.help-block {
......
......@@ -6,15 +6,17 @@ header {
transition-duration: .3s;
&.navbar-empty {
height: 58px;
background: #FFF;
border-bottom: 1px solid #EEE;
.center-logo {
margin: 8px 0;
margin: 11px 0;
text-align: center;
img {
height: 32px;
#tanuki-logo, img {
width: 36px;
height: 36px;
}
}
}
......
......@@ -6,6 +6,10 @@ html {
body {
background-color: #EAEBEC !important;
&.navless {
background-color: white !important;
}
}
.container {
......@@ -18,8 +22,8 @@ body {
}
.navless-container {
padding-top: $header-height;
margin-top: 30px;
margin-top: $header-height;
padding-top: $gl-padding * 2;
}
.container-limited {
......
......@@ -73,11 +73,8 @@
}
.referenced-users {
padding: 10px 0;
color: #999;
margin-left: 10px;
margin-top: 1px;
margin-right: 130px;
color: #4c4e54;
padding-top: 10px;
}
.md-preview-holder {
......
......@@ -15,6 +15,16 @@
border-left: none;
padding-top: 5px;
}
.select2-chosen {
color: $gl-text-color;
}
&.select2-default {
.select2-chosen {
color: #999;
}
}
}
}
......@@ -23,6 +33,7 @@
border: 1px solid #e7e9ed;
}
.select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
@include border-radius (0px);
......@@ -48,17 +59,38 @@
color: #313236;
}
.select2-container-multi .select2-choices {
.select2-container-multi {
.select2-choices {
@include border-radius(2px);
border-color: #CCC;
}
border-color: $input-border;
background: white;
padding-left: $gl-padding / 2;
.select2-container-multi .select2-choices .select2-search-field input {
padding: 8px 14px;
.select2-search-field input {
padding: $gl-padding / 2;
font-size: 13px;
line-height: 18px;
height: auto;
font-family: inherit;
font-size: inherit;
}
.select2-search-choice {
margin: 8px 0 0 8px;
background: white;
box-shadow: none;
border-color: $input-border;
color: $gl-text-color;
line-height: 15px;
.select2-search-choice-close {
top: 5px;
}
&.select2-search-choice-focus {
border-color: $gl-text-color;
}
}
}
}
.select2-drop-active {
......@@ -123,10 +155,16 @@
}
.user-result {
min-height: 24px;
.user-image {
float: left;
}
&.no-username {
.user-name {
line-height: 24px;
}
}
}
......
.page-with-sidebar {
padding-top: $header-height;
transition-duration: .3s;
.sidebar-wrapper {
position: fixed;
......@@ -16,7 +17,6 @@
.sidebar-wrapper {
z-index: 99;
background: $background-color;
transition-duration: .3s;
}
.content-wrapper {
......@@ -35,6 +35,83 @@
}
}
.sidebar-wrapper {
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
position: fixed;
z-index: 999;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: 11px 0 11px 22px;
overflow: hidden;
outline: none;
transition-duration: .3s;
img {
width: 36px;
height: 36px;
}
#tanuki-logo, img {
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
.sidebar-user {
padding: 9px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
.username {
margin-left: 10px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
.nav-sidebar {
margin-top: 14 + $header-height;
margin-bottom: 100px;
......@@ -61,7 +138,7 @@
color: $gray;
display: block;
text-decoration: none;
padding-left: 22px;
padding-left: 23px;
font-weight: normal;
outline: none;
......@@ -85,6 +162,10 @@
padding: 0px 8px;
@include border-radius(6px);
}
&.back-link i {
transition-duration: .3s;
}
}
}
}
......@@ -100,7 +181,6 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_width;
......@@ -114,16 +194,15 @@
&.back-link {
i {
visibility: hidden;
opacity: 0;
}
}
}
}
}
@mixin folded-sidebar {
padding-left: 60px;
transition-duration: .3s;
@mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width;
.sidebar-wrapper {
width: $sidebar_collapsed_width;
......@@ -132,7 +211,7 @@
width: $sidebar_collapsed_width;
a {
padding-left: 12px;
padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
......@@ -143,19 +222,23 @@
.nav-sidebar {
width: $sidebar_collapsed_width;
li a {
li {
width: auto;
a {
span {
display: none;
}
}
}
}
.collapse-nav a {
width: $sidebar_collapsed_width;
}
.sidebar-user {
padding-left: 12px;
padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
.username {
......@@ -186,11 +269,11 @@
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
@include folded-sidebar;
@include collapsed-sidebar;
}
.page-sidebar-expanded {
@include folded-sidebar;
@include collapsed-sidebar;
}
.collapse-nav {
......@@ -200,83 +283,10 @@
@media(min-width: $screen-md-max) {
.page-sidebar-collapsed {
@include folded-sidebar;
@include collapsed-sidebar;
}
.page-sidebar-expanded {
@include expanded-sidebar;
}
}
.sidebar-user {
padding: 9px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
.username {
margin-left: 10px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
}
.sidebar-wrapper {
.header-logo {
border-bottom: 1px solid transparent;
float: left;
height: $header-height;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: 10px 22px;
overflow: hidden;
outline: none;
img {
width: 36px;
height: 36px;
}
#tanuki-logo, img {
float: left;
}
.gitlab-text-container {
width: 230px;
h3 {
width: 158px;
float: left;
margin: 0;
margin-left: 14px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
}
}
}
&:hover {
background-color: #EEE;
}
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
......@@ -90,6 +90,17 @@
}
}
.issuable-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
.cross-project-reference {
text-align: center;
width: 100%;
......@@ -158,6 +169,7 @@
min-width: 214px;
> li {
cursor: pointer;
margin: 5px;
}
}
......
......@@ -56,17 +56,6 @@
}
}
.issue-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
form.edit-issue {
margin: 0;
}
......
/* Login Page */
.login-page {
background-color: white;
.container {
max-width: 960px;
}
......@@ -21,6 +19,7 @@
h1:first-child {
font-weight: normal;
margin-bottom: 30px;
margin-top: 0;
}
img {
......
......@@ -173,27 +173,12 @@
line-height: 1.1;
}
.merge-request-form-info {
padding-top: 15px;
}
// hide mr close link for inline diff comment form
.diff-file .close-mr-link,
.diff-file .reopen-mr-link {
display: none;
}
.merge-request-show-labels {
a {
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
.color-label {
padding: 6px 10px;
}
}
}
.merge-request-form .select2-container {
width: 250px !important;
}
......
......@@ -5,7 +5,7 @@
font-weight: normal;
}
}
.no-ssh-key-message {
.no-ssh-key-message, .project-limit-message {
background-color: #f28d35;
margin-bottom: 16px;
}
......@@ -84,7 +84,7 @@
@extend .btn-gray;
color: $gray;
cursor: auto;
cursor: default;
i {
color: inherit;
......
......@@ -4,3 +4,8 @@
margin-right: auto;
padding-right: 7px;
}
.wiki-last-edit-by {
font-size: 80%;
font-weight: normal;
}
......@@ -2,8 +2,10 @@ module GlobalMilestones
extend ActiveSupport::Concern
def milestones
epoch = DateTime.parse('1970-01-01')
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end
......
......@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def milestone_path(title)
group_milestone_path(@group, title.parameterize, title: title)
group_milestone_path(@group, title.to_slug.to_s, title: title)
end
def projects
......
......@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
:email,
:hide_no_password,
:hide_no_ssh_key,
:hide_project_limit,
:linkedin,
:location,
:name,
......
......@@ -3,7 +3,7 @@ class Projects::BranchesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:create, :destroy]
before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index
@sort = params[:sort] || 'name'
......
......@@ -131,7 +131,9 @@ class Projects::NotesController < Projects::ApplicationController
end
def render_note_json(note)
if note.valid?
render json: {
valid: true,
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
......@@ -141,6 +143,13 @@ class Projects::NotesController < Projects::ApplicationController
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
else
render json: {
valid: false,
award: note.is_award,
errors: note.errors
}
end
end
def authorize_admin_note!
......
......@@ -2,7 +2,7 @@ class Projects::TagsController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:create]
before_action :authorize_push_code!, only: [:new, :create]
before_action :authorize_admin_project!, only: [:destroy]
def index
......
class MilestonesFinder
def execute(projects, params)
milestones = Milestone.of_projects(projects)
milestones = milestones.order("due_date ASC")
milestones = milestones.reorder("due_date ASC")
case params[:state]
when 'closed' then milestones.closed
......
......@@ -6,7 +6,7 @@
#
# For example instead of this:
#
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
#
# We can simply use shortcut:
#
......
......@@ -27,16 +27,20 @@ module IconsHelper
end
end
def public_icon
icon('globe fw')
def visibility_level_icon(level, fw: true)
name =
case level
when Gitlab::VisibilityLevel::PRIVATE
'lock'
when Gitlab::VisibilityLevel::INTERNAL
'shield'
else # Gitlab::VisibilityLevel::PUBLIC
'globe'
end
def internal_icon
icon('shield fw')
end
name << " fw" if fw
def private_icon
icon('lock fw')
icon(name)
end
def file_type_icon_class(type, mode, name)
......
......@@ -44,14 +44,17 @@ module IssuesHelper
end
def bulk_update_milestone_options
options_for_select([['None (backlog)', -1]]) +
options_from_collection_for_select(project_active_milestones, 'id',
'title', params[:milestone_id])
milestones = project_active_milestones.to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end
def milestone_options(object)
options_from_collection_for_select(object.project.milestones.active,
'id', 'title', object.milestone_id)
milestones = object.project.milestones.active.to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
def issue_box_class(item)
......@@ -84,7 +87,11 @@ module IssuesHelper
end
def merge_requests_sentence(merge_requests)
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
# Sorting based on the `!123` or `group/project!123` reference will sort
# local merge requests first.
merge_requests.map do |merge_request|
merge_request.to_reference(@project)
end.sort.to_sentence(last_word_connector: ', or ')
end
def url_to_emoji(name)
......@@ -96,7 +103,7 @@ module IssuesHelper
def emoji_author_list(notes, current_user)
list = notes.map do |note|
note.author == current_user ? "me" : note.author.username
note.author == current_user ? "me" : note.author.name
end
list.join(", ")
......
......@@ -39,7 +39,11 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
issues.map(&:to_reference).to_sentence
# Sorting based on the `#123` or `group/project#123` reference will sort
# local issues first.
issues.map do |issue|
issue.to_reference(@project)
end.sort.to_sentence
end
def mr_change_branches_path(merge_request)
......@@ -49,18 +53,21 @@ module MergeRequestsHelper
source_project_id: @merge_request.source_project_id,
target_project_id: @merge_request.target_project_id,
source_branch: @merge_request.source_branch,
target_branch: nil
}
target_branch: @merge_request.target_branch,
},
change_branches: true
)
end
def source_branch_with_namespace(merge_request)
branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
if merge_request.for_fork?
namespace = link_to(merge_request.source_project_namespace,
project_path(merge_request.source_project))
namespace + ":#{merge_request.source_branch}"
namespace + ":" + branch
else
merge_request.source_branch
branch
end
end
......
......@@ -28,7 +28,9 @@ module MilestonesHelper
Milestone.where(project_id: @projects)
end.active
epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any)
......
module NamespacesHelper
def namespaces_options(selected = :current_user, scope = :default)
def namespaces_options(selected = :current_user, display_path: false)
groups = current_user.owned_groups + current_user.masters_groups
users = [current_user.namespace]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ]
users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ]
options = []
options << group_opts
......
......@@ -4,6 +4,14 @@ module NavHelper
end
def nav_sidebar_class
if nav_menu_collapsed?
"sidebar-collapsed"
else
"sidebar-expanded"
end
end
def page_sidebar_class
if nav_menu_collapsed?
"page-sidebar-collapsed"
else
......
......@@ -21,7 +21,7 @@ module ProjectsHelper
end
def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author' }
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
return "(deleted)" unless author
......@@ -39,7 +39,8 @@ module ProjectsHelper
if opts[:name]
link_to(author_html, user_path(author), class: "author_link").html_safe
else
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe
title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe
end
end
......
......@@ -15,12 +15,14 @@ module SelectsHelper
html = {
class: css_class,
'data-placeholder' => placeholder,
'data-null-user' => null_user,
'data-any-user' => any_user,
'data-email-user' => email_user,
'data-first-user' => first_user,
'data-current-user' => current_user
data: {
placeholder: placeholder,
null_user: null_user,
any_user: any_user,
email_user: email_user,
first_user: first_user,
current_user: current_user
}
}
unless opts[:scope] == :all
......
......@@ -25,48 +25,24 @@ module VisibilityLevelHelper
end
def project_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "Project access must be granted explicitly for each user."
"Project access must be granted explicitly for each user."
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The project can be cloned by"
haml_concat "any logged in user."
"The project can be cloned by any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The project can be cloned"
haml_concat "without any"
haml_concat "authentication."
end
end
"The project can be cloned without any authentication."
end
end
def snippet_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "The snippet is visible only for me."
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The snippet can be accessed"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level)
case level
when Gitlab::VisibilityLevel::PRIVATE
private_icon
"The snippet is visible only for me."
when Gitlab::VisibilityLevel::INTERNAL
internal_icon
"The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
public_icon
"The snippet can be accessed without any authentication."
end
end
......
......@@ -30,6 +30,8 @@
#
class ApplicationSetting < ActiveRecord::Base
CACHE_KEY = 'application_setting.last'
serialize :restricted_visibility_levels
serialize :import_sources
serialize :restricted_signup_domains, Array
......@@ -73,21 +75,17 @@ class ApplicationSetting < ActiveRecord::Base
end
after_commit do
Rails.cache.write(cache_key, self)
Rails.cache.write(CACHE_KEY, self)
end
def self.current
Rails.cache.fetch(cache_key) do
Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
end
end
def self.expire
Rails.cache.delete(cache_key)
end
def self.cache_key
'application_setting.last'
Rails.cache.delete(CACHE_KEY)
end
def self.create_from_defaults
......
......@@ -12,17 +12,18 @@
module Ci
class ApplicationSetting < ActiveRecord::Base
extend Ci::Model
CACHE_KEY = 'ci_application_setting.last'
after_commit do
Rails.cache.write(cache_key, self)
Rails.cache.write(CACHE_KEY, self)
end
def self.expire
Rails.cache.delete(cache_key)
Rails.cache.delete(CACHE_KEY)
end
def self.current
Rails.cache.fetch(cache_key) do
Rails.cache.fetch(CACHE_KEY) do
Ci::ApplicationSetting.last
end
end
......@@ -33,9 +34,5 @@ module Ci
add_pusher: Settings.gitlab_ci['add_pusher'],
)
end
def self.cache_key
'ci_application_setting.last'
end
end
end
......@@ -78,11 +78,23 @@ class Commit
}x
end
def self.link_reference_pattern
super("commit", /(?<commit>\h{6,40})/)
end
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
"#{project.to_reference}@#{id}"
project.to_reference + self.class.reference_prefix + self.id
else
id
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
self.short_id
end
end
......@@ -135,10 +147,10 @@ class Commit
description.present?
end
def hook_attrs
def hook_attrs(with_changed_files: false)
path_with_namespace = project.path_with_namespace
{
data = {
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
......@@ -148,6 +160,12 @@ class Commit
email: author_email
}
}
if with_changed_files
data.merge!(repo_changes)
end
data
end
# Discover issues should be closed when this commit is pushed to a project's
......@@ -196,4 +214,22 @@ class Commit
def status
ci_commit.try(:status) || :not_found
end
private
def repo_changes
changes = { added: [], modified: [], removed: [] }
diffs.each do |diff|
if diff.deleted_file
changes[:removed] << diff.old_path
elsif diff.renamed_file || diff.new_file
changes[:added] << diff.new_path
else
changes[:modified] << diff.new_path
end
end
changes
end
end
......@@ -2,36 +2,38 @@
#
# Examples:
#
# range = CommitRange.new('f3f85602...e86e1013')
# range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013"
#
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013"
#
# # Assuming `project` is a Project with a repository containing both commits:
# range.project = project
# # Assuming the specified project has a repository containing both commits:
# range.valid_commits? # => true
#
class CommitRange
include ActiveModel::Conversion
include Referable
attr_reader :sha_from, :notation, :sha_to
attr_reader :commit_from, :notation, :commit_to
attr_reader :ref_from, :ref_to
# Optional Project model
attr_accessor :project
# See `exclude_start?`
attr_reader :exclude_start
# The beginning and ending SHAs can be between 6 and 40 hex characters, and
# The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be SHAs
# between 6 and 40 hex characters.
STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
def self.reference_prefix
'@'
......@@ -43,27 +45,40 @@ class CommitRange
def self.reference_pattern
%r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{PATTERN})
(?<commit_range>#{STRICT_PATTERN})
}x
end
def self.link_reference_pattern
super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange
#
# range_string - The String commit range.
# project - An optional Project model.
#
# Raises ArgumentError if `range_string` does not match `PATTERN`.
def initialize(range_string, project = nil)
def initialize(range_string, project)
@project = project
range_string.strip!
unless range_string.match(/\A#{PATTERN}\z/)
unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}"
end
@exclude_start = !range_string.include?('...')
@sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
@ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
@project = project
if project.valid_repo?
@commit_from = project.commit(@ref_from)
@commit_to = project.commit(@ref_to)
end
if valid_commits?
@ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
@ref_to = Commit.truncate_sha(sha_to) if sha_to.start_with?(@ref_to)
end
end
def inspect
......@@ -71,15 +86,24 @@ class CommitRange
end
def to_s
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
sha_from + notation + sha_to
end
alias_method :id, :to_s
def to_reference(from_project = nil)
# Not using to_s because we want the full SHAs
reference = sha_from + notation + sha_to
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end
def reference_link_text(from_project = nil)
reference = ref_from + notation + ref_to
if cross_project_reference?(from_project)
reference = project.to_reference + '@' + reference
reference = project.to_reference + self.class.reference_prefix + reference
end
reference
......@@ -87,46 +111,58 @@ class CommitRange
# Returns a String for use in a link's title attribute
def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}"
"Commits #{sha_start} through #{sha_to}"
end
# Return a Hash of parameters for passing to a URL helper
#
# See `namespace_project_compare_url`
def to_param
{ from: suffixed_sha_from, to: sha_to }
{ from: sha_start, to: sha_to }
end
def exclude_start?
exclude_start
@notation == '..'
end
# Check if both the starting and ending commit IDs exist in a project's
# repository
#
# project - An optional Project to check (default: `project`)
def valid_commits?(project = project)
return nil unless project.present?
return false unless project.valid_repo?
commit_from.present? && commit_to.present?
def valid_commits?
commit_start.present? && commit_end.present?
end
def persisted?
true
end
def commit_from
@commit_from ||= project.repository.commit(suffixed_sha_from)
def sha_from
return nil unless @commit_from
@commit_from.id
end
def sha_to
return nil unless @commit_to
@commit_to.id
end
def commit_to
@commit_to ||= project.repository.commit(sha_to)
def sha_start
return nil unless sha_from
exclude_start? ? sha_from + '^' : sha_from
end
private
def commit_start
return nil unless sha_start
def suffixed_sha_from
sha_from + (exclude_start? ? '^' : '')
if exclude_start?
@commit_start ||= project.commit(sha_start)
else
commit_from
end
end
alias_method :sha_end, :sha_to
alias_method :commit_end, :commit_to
end
......@@ -62,7 +62,12 @@ module Mentionable
return [] if text.blank?
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits) - [local_reference]
refs = (refs.issues + refs.merge_requests + refs.commits)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
refs.reject { |ref| ref == local_reference }
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
......@@ -21,6 +21,10 @@ module Referable
''
end
def reference_link_text(from_project = nil)
to_reference(from_project)
end
module ClassMethods
# The character that prefixes the actual reference identifier
#
......@@ -44,6 +48,25 @@ module Referable
def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}"
end
def link_reference_pattern(route, pattern)
%r{
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
(\/[a-z0-9_=-]+)*
)?
(?<query>
\?[a-z0-9_=-]+
(&[a-z0-9_=-]+)*
)?
(?<anchor>\#[a-z0-9_-]+)?
)
}x
end
end
private
......
......@@ -201,7 +201,7 @@ class Event < ActiveRecord::Base
elsif commented?
"commented on"
elsif created_project?
if project.import?
if project.external_import?
"imported"
else
"created"
......
......@@ -16,7 +16,15 @@ class GlobalMilestone
end
def safe_title
@title.parameterize
@title.to_slug.to_s
end
def expired?
if due_date
due_date.past?
else
false
end
end
def projects
......@@ -98,4 +106,25 @@ class GlobalMilestone
def complete?
total_items_count == closed_items_count
end
def due_date
return @due_date if defined?(@due_date)
@due_date =
if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
@milestones.first.due_date
else
nil
end
end
def expires_at
if due_date
if due_date.past?
"expired at #{due_date.stamp("Aug 21, 2011")}"
else
"expires at #{due_date.stamp("Aug 21, 2011")}"
end
end
end
end
......@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
......
......@@ -17,7 +17,7 @@ class Label < ActiveRecord::Base
# Requests that have no label assigned.
LabelStruct = Struct.new(:title, :name)
None = LabelStruct.new('No Label', 'No Label')
Any = LabelStruct.new('Any', '')
Any = LabelStruct.new('Any Label', '')
DEFAULT_COLOR = '#428BCA'
......
......@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("merge_requests", /(?<merge_request>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
......@@ -291,7 +295,7 @@ class MergeRequest < ActiveRecord::Base
work_in_progress: work_in_progress?
}
unless last_commit.nil?
if last_commit
attrs.merge!(last_commit: last_commit.hook_attrs)
end
......@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description))
issues.uniq.sort_by(&:id)
issues.uniq
else
[]
end
......
......@@ -16,9 +16,9 @@
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
MilestoneStruct = Struct.new(:title, :name)
None = MilestoneStruct.new('No Milestone', 'No Milestone')
Any = MilestoneStruct.new('Any', '')
MilestoneStruct = Struct.new(:title, :name, :id)
None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
Any = MilestoneStruct.new('Any Milestone', '', -1)
include InternalId
include Sortable
......
......@@ -39,8 +39,11 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
before_validation :set_award!
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
......@@ -348,4 +351,31 @@ class Note < ActiveRecord::Base
def editable?
!system?
end
# Checks if note is an award added as a comment
#
# If note is an award, this method sets is_award to true
# and changes content of the note to award name.
#
# Method is executed as a before_validation callback.
#
def set_award!
return unless awards_supported? && contains_emoji_only?
self.is_award = true
self.note = award_emoji_name
end
private
def awards_supported?
noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
end
def contains_emoji_only?
note =~ /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/
end
def award_emoji_name
note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
end
end
require 'securerandom'
class Repository
class PreReceiveError < StandardError; end
class CommitError < StandardError; end
include Gitlab::ShellAdapter
......@@ -108,10 +107,19 @@ class Repository
tags.find { |tag| tag.name == name }
end
def add_branch(branch_name, ref)
expire_branches_cache
def add_branch(user, branch_name, target)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
target = commit(target).try(:id)
return false unless target
gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
rugged.branches.create(branch_name, target)
end
expire_branches_cache
find_branch(branch_name)
end
def add_tag(tag_name, ref, message = nil)
......@@ -120,10 +128,20 @@ class Repository
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(branch_name)
def rm_branch(user, branch_name)
expire_branches_cache
gitlab_shell.rm_branch(path_with_namespace, branch_name)
branch = find_branch(branch_name)
oldrev = branch.try(:target)
newrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
rugged.branches.delete(branch_name)
end
expire_branches_cache
true
end
def rm_tag(tag_name)
......@@ -550,7 +568,6 @@ class Repository
def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
gl_id = Gitlab::ShellEnv.gl_id(current_user)
was_empty = empty?
# Create temporary ref
......@@ -569,15 +586,7 @@ class Repository
raise CommitError.new('Failed to create commit')
end
# Run GitLab pre-receive hook
pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
# Run GitLab update hook
update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
if pre_receive_hook_status && update_hook_status
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty
# Create branch
rugged.references.create(ref, newrev)
......@@ -592,16 +601,11 @@ class Repository
raise CommitError.new('Commit was rejected because branch received new push')
end
end
# Run GitLab post receive hook
post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
else
end
rescue GitHooksService::PreReceiveError
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
raise PreReceiveError.new('Commit was rejected by git hook')
end
raise
end
private
......
......@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}"
......
......@@ -10,7 +10,7 @@
#
class UsersStarProject < ActiveRecord::Base
belongs_to :project, counter_cache: :star_count
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
validates :user, presence: true
......
......@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
return error('Branch already exists')
end
repository.add_branch(branch_name, ref)
new_branch = repository.find_branch(branch_name)
new_branch = repository.add_branch(current_user, branch_name, ref)
if new_branch
push_data = build_push_data(project, current_user, new_branch)
......@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
else
error('Invalid reference name')
end
rescue GitHooksService::PreReceiveError
error('Branch creation was rejected by Git hook')
end
def success(branch)
......
......@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
return error('You dont have push access to repo', 405)
end
if repository.rm_branch(branch_name)
if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch)
EventCreateService.new.push(project, current_user, push_data)
......@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
else
error('Failed to remove branch')
end
rescue GitHooksService::PreReceiveError
error('Branch deletion was rejected by Git hook')
end
def error(message, return_code = 400)
......
......@@ -26,7 +26,7 @@ module Files
else
error("Something went wrong. Your changes were not committed")
end
rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message)
end
......
class GitHooksService
PreReceiveError = Class.new(StandardError)
def execute(user, repo_path, oldrev, newrev, ref)
@repo_path = repo_path
@user = Gitlab::ShellEnv.gl_id(user)
@oldrev = oldrev
@newrev = newrev
@ref = ref
%w(pre-receive update).each do |hook_name|
unless run_hook(hook_name)
raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook")
end
end
yield
run_hook('post-receive')
end
private
def run_hook(name)
hook = Gitlab::Git::Hook.new(name, @repo_path)
hook.trigger(@user, @oldrev, @newrev, @ref)
end
end
......@@ -5,11 +5,6 @@ module Notes
note.author = current_user
note.system = false
if contains_emoji_only?(params[:note])
note.is_award = true
note.note = emoji_name(params[:note])
end
if note.save
notification_service.new_note(note)
......@@ -33,13 +28,5 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks)
end
def contains_emoji_only?(note)
note =~ /\A:[-_+[:alnum:]]*:\s?\z/
end
def emoji_name(note)
note.match(/\A:([-_+[:alnum:]]*):\s?/)[1]
end
end
end
......@@ -145,6 +145,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients.delete(note.author)
recipients = recipients.uniq
# build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
......
......@@ -125,7 +125,7 @@ class SystemNoteService
# Returns the created Note object
def self.change_status(noteable, project, author, status, source)
body = "Status changed to #{status}"
body += " by #{source.gfm_reference}" if source
body += " by #{source.gfm_reference(project)}" if source
create_note(noteable: noteable, project: project, author: author, note: body)
end
......
......@@ -12,7 +12,7 @@
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group
= f.label :color, "Background Color", class: 'control-label'
= f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
......
- page_title "Edit", @label.name, "Labels"
%h3
Edit label
%span.light #{@label.name}
.back-link
= link_to admin_labels_path do
&larr; To labels list
%h3.page-title
Edit Label
%hr
= render 'form'
- page_title "New Label"
%h3 New label
.back-link
= link_to admin_labels_path do
&larr; To labels list
%h3.page-title
New Label
%hr
= render 'form'
- page_title "Edit", @user.name, "Users"
%h3.page-title
Edit user: #{@user.name}
.back-link
= link_to admin_user_path(@user) do
&larr; Back to user page
%hr
= render 'form'
= content_for :flash_message do
= render 'shared/project_limit'
%ul.center-top-menu
= nav_link(path: ['projects#index', 'root#index']) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
......
......@@ -16,6 +16,9 @@
= milestone_progress_bar(milestone)
.row
.col-sm-6
.expiration
= render 'shared/milestone_expired', milestone: milestone
.projects
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
......
- page_title @milestone.title, "Milestones"
%h4.page-title
- header_title "Milestones", dashboard_milestones_path
.issuable-details
.page-title
.issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
......@@ -7,13 +10,14 @@
Open
Milestone #{@milestone.title}
%hr
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
- if @milestone.complete? && @milestone.active?
.alert.alert-success
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
.description
.table-holder
%table.table
%thead
......@@ -44,7 +48,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
......@@ -58,25 +62,39 @@
Participants
%span.badge= @milestone.participants.count
.pull-right
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All issues in this milestone
.row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All merge requests in this milestone
.row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
.gray-content-block.middle-block
.oneline
All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
......
......@@ -3,7 +3,7 @@
= auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity")
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
......
- page_title "Starred Projects"
- header_title "Projects", projects_path
- header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head'
......
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
......
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
......
- page_title "Projects"
- header_title "Projects", root_path
- header_title "Projects", dashboard_projects_path
- if current_user
= render 'dashboard/projects_head'
......
......@@ -3,8 +3,7 @@
.panel.panel-default
.panel-heading
%strong= @group.name
group settings:
Group settings
.panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- if @group.errors.any?
......@@ -45,4 +44,5 @@
%br
%strong Removed group can not be restored!
.form-actions
= link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
......@@ -14,8 +14,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
%p.hint Required
= f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
......
- page_title @milestone.title, "Milestones"
= render "header_title"
%h4.page-title
.issuable-details
.page-title
.issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
......@@ -11,17 +12,18 @@
.pull-right
- if can?(current_user, :admin_milestones, @group)
- if @milestone.active?
= link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
= link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
= link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
= link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
%hr
- if @milestone.complete? && @milestone.active?
.alert.alert-success
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
.description
.table-holder
%table.table
%thead
......@@ -52,7 +54,7 @@
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
%ul.center-top-menu.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
......@@ -66,25 +68,40 @@
Participants
%span.badge= @milestone.participants.count
.pull-right
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All issues in this milestone
.row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.row
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All merge requests in this milestone
.row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
.gray-content-block.middle-block
.oneline
All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
......
- page_title 'New Group'
- header_title 'New Group'
- header_title "Groups", dashboard_groups_path
%h3.page-title
New Group
%hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any?
.alert.alert-danger
......@@ -18,3 +23,4 @@
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
= link_to 'Cancel', dashboard_groups_path, class: 'btn btn-cancel'
.page-with-sidebar{ class: nav_sidebar_class }
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
......@@ -17,8 +17,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
= image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
......
- page_title "Admin area"
- header_title "Admin area", admin_root_path
- page_title "Admin Area"
- header_title "Admin Area", admin_root_path
- sidebar "admin"
= render template: "layouts/application"
.page-with-sidebar{ class: nav_sidebar_class }
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
= brand_header_logo
.gitlab-text-container
%h3 GitLab
......@@ -14,8 +14,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
= image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
= link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
......
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body.ui_charcoal.login-page.application
%body.ui_charcoal.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
.content
= render "layouts/flash"
.row.prepend-top-20
.row
.col-sm-5.pull-right
= yield
.col-sm-7.brand-holder.pull-left
......
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{user_application_theme} application"}
%body{class: "#{user_application_theme} application navless"}
= render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
......
......@@ -11,27 +11,27 @@
%li.hidden-sm.hidden-xs
= render 'layouts/search'
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if session[:impersonator_id]
%li.impersonation
= link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop impersonation', data: { toggle: 'tooltip', placement: 'bottom' } do
= link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
= link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
- if current_user.can_create_project?
%li
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom'} do
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
%li
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('sign-out')
%h1.title= title
......
......@@ -5,78 +5,78 @@
%span
Overview
= nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do
= link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw')
%span
Projects
= nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users', data: {placement: 'right'} do
= link_to admin_users_path, title: 'Users' do
= icon('user fw')
%span
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups', data: {placement: 'right'} do
= link_to admin_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys', data: {placement: 'right'} do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
= nav_link do
= link_to ci_admin_projects_path, title: 'Continuous Integration', data: {placement: 'right'} do
= link_to ci_admin_projects_path, title: 'Continuous Integration' do
= icon('building fw')
%span
Continuous Integration
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do
= link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
%span
Logs
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Broadcast Messages', data: {placement: 'right'} do
= link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
%span
Messages
= nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks', data: {placement: 'right'} do
= link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw')
%span
Hooks
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs', data: {placement: 'right'} do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw')
%span
Background Jobs
= nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications', data: {placement: 'right'} do
= link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates', data: {placement: 'right'} do
= link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw')
%span
Service Templates
= nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do
= link_to admin_labels_path, title: 'Labels' do
= icon('tags fw')
%span
Labels
= nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse reports" do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw')
%span
Abuse Reports
%span.count= AbuseReport.count(:all)
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings', data: {placement: 'right'} do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
%span
Settings
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
= link_to dashboard_projects_path, title: 'Projects', data: {placement: 'right'} do
= link_to dashboard_projects_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity', data: {placement: 'right'} do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
= icon('dashboard fw')
%span
Activity
= nav_link(controller: :groups) do
= link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do
= link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :milestones) do
= link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
%span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
= link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
%li.separate-item
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
Profile Settings
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects', data: {placement: 'right'} do
= link_to explore_root_path, title: 'Projects' do
= icon('home fw')
%span
Projects
= nav_link(controller: :groups) do
= link_to explore_groups_path, title: 'Groups', data: {placement: 'right'} do
= link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
= nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets', data: {placement: 'right'} do
= link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
%span
Help
%ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
......@@ -8,39 +8,39 @@
%li.separate-item
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
= link_to group_path(@group), title: 'Home' do
= icon('dashboard fw')
%span
Group
- if can?(current_user, :read_group, @group)
- if current_user
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
= link_to group_milestones_path(@group), title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do
= link_to issues_group_path(@group), title: 'Issues' do
= icon('exclamation-circle fw')
%span
Issues
- if current_user
%span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw')
%span
Merge Requests
- if current_user
%span.count= MergeRequest.opened.of_group(@group).count
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do
= link_to group_group_members_path(@group), title: 'Members' do
= icon('users fw')
%span
Members
- if can?(current_user, :admin_group, @group)
= nav_link(html_options: { class: "separate-item" }) do
= link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do
= link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw')
%span
Settings
%ul.nav.nav-sidebar
= nav_link do
= link_to group_path(@group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
= link_to group_path(@group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
......@@ -9,12 +9,12 @@
%ul.sidebar-subnav
= nav_link(path: 'groups#edit') do
= link_to edit_group_path(@group), title: 'Group Settings', data: {placement: 'right'} do
= link_to edit_group_path(@group), title: 'Group Settings' do
= icon ('pencil-square-o fw')
%span
Group Settings
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
= link_to projects_group_path(@group), title: 'Projects' do
= icon('folder fw')
%span
Projects
%ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
......@@ -8,52 +8,52 @@
%li.separate-item
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
Profile Settings
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
%span
Account
= nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new', 'applications#create']) do
= link_to applications_profile_path, title: 'Applications', data: {placement: 'right'} do
= link_to applications_profile_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails', data: {placement: 'right'} do
= link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
%span
Emails
%span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password', data: {placement: 'right'} do
= link_to edit_profile_password_path, title: 'Password' do
= icon('lock fw')
%span
Password
= nav_link(controller: :notifications) do
= link_to profile_notifications_path, title: 'Notifications', data: {placement: 'right'} do
= link_to profile_notifications_path, title: 'Notifications' do
= icon('inbox fw')
%span
Notifications
= nav_link(controller: :keys) do
= link_to profile_keys_path, title: 'SSH Keys', data: {placement: 'right'} do
= link_to profile_keys_path, title: 'SSH Keys' do
= icon('key fw')
%span
SSH Keys
%span.count= current_user.keys.count
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
= icon('image fw')
%span
Preferences
= nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
= link_to audit_log_profile_path, title: 'Audit Log' do
= icon('history fw')
%span
Audit Log
%ul.nav.nav-sidebar
- if @project.group
= nav_link do
= link_to group_path(@project.group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
= link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
- else
= nav_link do
= link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
......@@ -15,32 +15,32 @@
%li.separate-item
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('home fw')
%span
Project
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
= icon('dashboard fw')
%span
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
= link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
= link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
= icon('files-o fw')
%span
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches releases)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
= icon('history fw')
%span
Commits
- if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
= icon('cubes fw')
%span
Builds
......@@ -48,28 +48,28 @@
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
= icon('code-fork fw')
%span
Network
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
= icon('area-chart fw')
%span
Graphs
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones', data: {placement: 'right'} do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
= icon('clock-o fw')
%span
Milestones
- if project_nav_tab? :issues
= nav_link(controller: :issues) do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
= icon('exclamation-circle fw')
%span
Issues
......@@ -78,7 +78,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
......@@ -86,35 +86,35 @@
- if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
%span
Members
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
= icon('tags fw')
%span
Labels
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki', data: {placement: 'right'} do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
= icon('book fw')
%span
Wiki
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
= icon('clipboard fw')
%span
Snippets
- if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
= link_to edit_project_path(@project), title: 'Settings', data: {placement: 'right'} do
= link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw')
%span
Settings
%ul.nav.nav-sidebar
= nav_link do
= link_to project_path(@project), title: 'Go to project', data: {placement: 'right'}, class: 'back-link' do
= link_to project_path(@project), title: 'Go to project', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to project
......@@ -9,59 +9,59 @@
%ul.sidebar-subnav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), title: 'Project Settings', data: {placement: 'right'} do
= link_to edit_project_path(@project), title: 'Project Settings' do
= icon('pencil-square-o fw')
%span
Project Settings
= nav_link(controller: :deploy_keys) do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
= icon('key fw')
%span
Deploy Keys
= nav_link(controller: :hooks) do
= link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do
= link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
= icon('link fw')
%span
Web Hooks
= nav_link(controller: :services) do
= link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do
= link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
= icon('cogs fw')
%span
Services
= nav_link(controller: :protected_branches) do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
= icon('lock fw')
%span
Protected Branches
- if @project.builds_enabled?
= nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
= icon('cog fw')
%span
Runners
= nav_link(controller: :variables) do
= link_to namespace_project_variables_path(@project.namespace, @project) do
= link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
= icon('code fw')
%span
Variables
= nav_link path: 'triggers#index' do
= link_to namespace_project_triggers_path(@project.namespace, @project) do
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw')
%span
Triggers
= nav_link path: 'ci_web_hooks#index' do
= link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do
= link_to namespace_project_ci_web_hooks_path(@project.namespace, @project), title: 'CI Web Hooks' do
= icon('link fw')
%span
CI Web Hooks
= nav_link path: 'ci_settings#edit' do
= link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do
= link_to edit_namespace_project_ci_settings_path(@project.namespace, @project), title: 'CI Settings' do
= icon('building fw')
%span
CI Settings
= nav_link controller: 'ci_services' do
= link_to namespace_project_ci_services_path(@project.namespace, @project) do
= link_to namespace_project_ci_services_path(@project.namespace, @project), title: 'CI Services' do
= icon('share fw')
%span
CI Services
......@@ -23,10 +23,13 @@
%p.cgray
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
%div
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
.form-actions
- if current_user.private_token
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
= f.submit 'Generate', class: "btn btn-default btn-build-token"
- unless current_user.ldap_user?
......@@ -54,7 +57,8 @@
%p
Each time you log in you’ll be required to provide your username and
password as usual, plus a randomly-generated code from your phone.
%div
.form-actions
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if button_based_providers.any?
......@@ -81,15 +85,16 @@
%p
Changing your username will change path to all personal projects!
%div
.input-group
.input-group-addon
= "#{root_url}u/"
= f.text_field :username, required: true, class: 'form-control'
&nbsp;
.loading-gif.hide
%p
= icon('spinner spin')
Saving new username
%p.light
= user_url(@user)
%div
.form-actions
= f.submit 'Save username', class: "btn btn-warning"
- if signup_enabled?
......@@ -104,6 +109,7 @@
- rp = current_user.personal_projects.count
- unless rp.zero?
%li #{pluralize rp, 'personal project'} will be removed and cannot be restored
.form-actions
= link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
- else
- if @user.solo_owned_groups.present?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment