Commit ae7b2ef6 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into issue_12658

# Conflicts:
#	app/models/issue.rb
#	app/views/projects/_home_panel.html.haml
#	app/views/shared/projects/_project.html.haml
#	db/schema.rb
#	spec/models/project_spec.rb
parents 8d544645 0305dd98
This diff is collapsed.
Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased)
- Add ability to move issue to another project
- Prevent tokens in the import URL to be showed by the UI
- Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu)
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu)
- Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu)
- Support Golang subpackage fetching (Stan Hu)
- Bump Capybara gem to 2.6.2 (Stan Hu)
- New branch button appears on issues where applicable
......@@ -57,6 +61,7 @@ v 8.6.0 (unreleased)
- User deletion is now done in the background so the request can not time out
- Canceled builds are now ignored in compound build status if marked as `allowed to fail`
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
v 8.5.8
- Bump Git version requirement to 2.7.4
......
......@@ -222,6 +222,8 @@ gem 'net-ssh', '~> 3.0.1'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
gem 'premailer-rails', '~> 1.9.0'
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
......@@ -285,7 +287,7 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.35.0', require: false
gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false
......
......@@ -61,9 +61,7 @@ GEM
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
asciidoctor (1.5.3)
ast (2.1.0)
astrolabe (1.3.1)
parser (~> 2.2)
ast (2.2.0)
attr_encrypted (1.3.4)
encryptor (>= 1.3.0)
attr_required (1.0.0)
......@@ -150,6 +148,8 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
css_parser (1.3.7)
addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
daemons (1.2.3)
......@@ -423,6 +423,7 @@ GEM
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
htmlentities (4.3.4)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.5.3)
......@@ -554,8 +555,8 @@ GEM
orm_adapter (0.5.0)
paranoia (2.1.4)
activerecord (~> 4.0)
parser (2.2.3.0)
ast (>= 1.1, < 3.0)
parser (2.3.0.6)
ast (~> 2.2)
pg (0.18.4)
poltergeist (1.9.0)
capybara (~> 2.1)
......@@ -564,6 +565,12 @@ GEM
websocket-driver (>= 0.2.0)
posix-spawn (0.3.11)
powerpack (0.1.1)
premailer (1.8.6)
css_parser (>= 1.3.6)
htmlentities (>= 4.0.0)
premailer-rails (1.9.0)
actionmailer (>= 3, < 5)
premailer (~> 1.7, >= 1.7.9)
pry (0.10.3)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
......@@ -615,7 +622,7 @@ GEM
activesupport (= 4.2.5.2)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
rainbow (2.1.0)
raindrops (0.15.0)
rake (10.5.0)
raphael-rails (2.1.2)
......@@ -687,13 +694,12 @@ GEM
rspec-retry (0.4.5)
rspec-core
rspec-support (3.3.0)
rubocop (0.35.1)
astrolabe (~> 1.3)
parser (>= 2.2.3.0, < 3.0)
rubocop (0.38.0)
parser (>= 2.3.0.6, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
tins (<= 1.6.0)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
......@@ -843,6 +849,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
unicode-display_width (1.0.2)
unicorn (4.9.0)
kgio (~> 2.6)
rack
......@@ -992,6 +999,7 @@ DEPENDENCIES
paranoia (~> 2.0)
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
pry-rails
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.1)
......@@ -1013,7 +1021,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0)
rspec-retry
rubocop (~> 0.35.0)
rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
......
......@@ -7,6 +7,7 @@
#= require jquery
#= require jquery-ui/autocomplete
#= require jquery-ui/datepicker
#= require jquery-ui/draggable
#= require jquery-ui/effect-highlight
#= require jquery-ui/sortable
#= require jquery_ujs
......@@ -138,7 +139,7 @@ $ ->
# Initialize tooltips
$('body').tooltip(
selector: '.has_tooltip, [data-toggle="tooltip"]'
selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
$el.data('placement') || 'bottom'
......
......@@ -5,7 +5,6 @@ class @Aside
e.preventDefault()
btn = $(e.currentTarget)
icon = btn.find('i')
console.log('1')
if icon.hasClass('fa-angle-left')
btn.parent().find('section').hide()
......
......@@ -122,7 +122,7 @@ class @AwardsHandler
nodes = []
nodes.push(
"<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>",
"<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>",
"</button>"
......
......@@ -167,7 +167,11 @@ class GitLabDropdown
hidden: =>
if @options.filterable
@dropdown.find(".dropdown-input-field").blur().val("")
@dropdown
.find(".dropdown-input-field")
.blur()
.val("")
.trigger("keyup")
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
......
class @IssuableForm
issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?'
wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i
constructor: (@form) ->
GitLab.GfmAutoComplete.setup()
new UsersSelect()
......@@ -7,12 +9,13 @@ class @IssuableForm
@titleField = @form.find("input[name*='[title]']")
@descriptionField = @form.find("textarea[name*='[description]']")
@issueMoveField = @form.find("#move_to_project_id")
return unless @titleField.length && @descriptionField.length
@initAutosave()
@form.on "submit", @resetAutosave
@form.on "submit", @handleSubmit
@form.on "click", ".btn-cancel", @resetAutosave
@initWip()
......@@ -30,6 +33,12 @@ class @IssuableForm
"description"
]
handleSubmit: =>
if (parseInt(@issueMoveField?.val()) ? 0) > 0
return false unless confirm(@issueMoveConfirmMsg)
@resetAutosave()
resetAutosave: =>
@titleField.data("autosave").reset()
@descriptionField.data("autosave").reset()
......
......@@ -3,6 +3,8 @@
# Handles persisting and restoring the current tab selection and lazily-loading
# content on the MergeRequests#show page.
#
#= require jquery.cookie
#
# ### Example Markup
#
# <ul class="nav-links merge-request-tabs">
......@@ -68,11 +70,15 @@ class @MergeRequestTabs
if action == 'commits'
@loadCommits($target.attr('href'))
@expandView()
else if action == 'diffs'
@loadDiff($target.attr('href'))
@shrinkView()
else if action == 'builds'
@loadBuilds($target.attr('href'))
@expandView()
else
@expandView()
@setCurrentAction(action)
......@@ -189,11 +195,24 @@ class @MergeRequestTabs
$('.container-fluid').removeClass('container-limited')
shrinkView: ->
$gutterIcon = $('.js-sidebar-toggle i')
$gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set
setTimeout( ->
# Only when sidebar is collapsed
# Only when sidebar is expanded
if $gutterIcon.is('.fa-angle-double-right')
$gutterIcon.closest('a').trigger('click',[true])
$gutterIcon.closest('a').trigger('click', [true])
, 0)
# Expand the issuable sidebar unless the user explicitly collapsed it
expandView: ->
return if $.cookie('collapsed_gutter') == 'true'
$gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set
setTimeout( ->
# Only when sidebar is collapsed
if $gutterIcon.is('.fa-angle-double-left')
$gutterIcon.closest('a').trigger('click', [true])
, 0)
......@@ -361,14 +361,12 @@ class @Notes
showEditForm: (e) ->
e.preventDefault()
note = $(this).closest(".note")
note.find(".note-body > .note-text").hide()
note.find(".note-header").hide()
note.addClass "is-editting"
form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)')
if isNewForm
form.addClass('gfm-form')
form.addClass('current-note-edit-form')
form.show()
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
......@@ -402,11 +400,9 @@ class @Notes
cancelEdit: (e) ->
e.preventDefault()
note = $(this).closest(".note")
note.find(".note-body > .note-text").show()
note.find(".note-header").show()
note.removeClass "is-editting"
note.find(".current-note-edit-form")
.removeClass("current-note-edit-form")
.hide()
###
Called in response to deleting a note of any kind.
......
......@@ -11,7 +11,6 @@ class @Project
$(@).toggleClass('active')
url = $("#project_clone").val()
console.log("url",url)
# Update the input field
$('#project_clone').val(url)
......
......@@ -16,7 +16,7 @@
}
&.group-avatar, &.project-avatar, &.avatar-tile {
@include border-radius(0px);
@include border-radius(0);
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
......
......@@ -107,7 +107,7 @@
margin: 0;
font-size: 23px;
font-weight: normal;
margin: 16px 0 5px 0;
margin: 16px 0 5px;
color: #4c4e54;
font-size: 23px;
line-height: 1.1;
......
......@@ -75,7 +75,7 @@
width: 240px;
margin-top: 2px;
margin-bottom: 0;
padding: 10px 10px;
padding: 10px;
font-size: 14px;
font-weight: normal;
background-color: $dropdown-bg;
......
......@@ -16,7 +16,7 @@ body {
}
.container .content {
margin: 0 0;
margin: 0;
}
.navless-container {
......
/**
* Generic mixins
*/
@mixin box-shadow($shadow) {
@mixin box-shadow($shadow) {
-webkit-box-shadow: $shadow;
-moz-box-shadow: $shadow;
-ms-box-shadow: $shadow;
......
......@@ -41,7 +41,7 @@
}
.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) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
@include border-radius ($border-radius-default);
border: none;
}
......
......@@ -39,8 +39,8 @@
h1 {
font-size: 1.3em;
font-weight: 600;
margin: 24px 0 12px 0;
padding: 0 0 10px 0;
margin: 24px 0 12px;
padding: 0 0 10px;
border-bottom: 1px solid #e7e9ed;
color: #313236;
}
......@@ -48,27 +48,27 @@
h2 {
font-size: 1.2em;
font-weight: 600;
margin: 24px 0 12px 0;
margin: 24px 0 12px;
color: #313236;
}
h3 {
margin: 24px 0 12px 0;
margin: 24px 0 12px;
font-size: 1.1em;
}
h4 {
margin: 24px 0 12px 0;
margin: 24px 0 12px;
font-size: 0.98em;
}
h5 {
margin: 24px 0 12px 0;
margin: 24px 0 12px;
font-size: 0.95em;
}
h6 {
margin: 24px 0 12px 0;
margin: 24px 0 12px;
font-size: 0.90em;
}
......@@ -76,7 +76,7 @@
color: #7f8fa4;
font-size: inherit;
padding: 8px 21px;
margin: 12px 0 12px;
margin: 12px 0;
border-left: 3px solid #e7e9ed;
}
......@@ -88,13 +88,13 @@
p {
color: #5c5d5e;
margin: 6px 0 0 0;
margin: 6px 0 0;
}
table {
@extend .table;
@extend .table-bordered;
margin: 12px 0 12px 0;
margin: 12px 0;
color: #5c5d5e;
th {
background: #f8fafc;
......@@ -102,7 +102,7 @@
}
pre {
margin: 12px 0 12px 0;
margin: 12px 0;
font-size: 13px;
line-height: 1.6em;
overflow-x: auto;
......@@ -191,7 +191,7 @@ body {
line-height: 1.3;
font-size: 1.25em;
font-weight: 600;
margin: 12px 7px 12px 7px;
margin: 12px 7px;
}
h1, h2, h3, h4, h5, h6 {
......
img {
max-width: 100%;
height: auto;
}
p.details {
font-style:italic;
color:#777
}
.footer p {
font-size:small;
color:#777
}
pre.commit-message {
white-space: pre-wrap;
}
.file-stats a {
text-decoration: none;
}
.file-stats .new-file {
color: #090;
}
.file-stats .deleted-file {
color: #B00;
}
......@@ -132,7 +132,7 @@
}
.image-info {
font-size: 12px;
margin: 5px 0 0 0;
margin: 5px 0 0;
color: grey;
}
......
......@@ -183,7 +183,7 @@
.block {
width: $sidebar_collapsed_width - 1px;
margin-left: -19px;
padding: 15px 0 0 0;
padding: 15px 0 0;
border-bottom: none;
overflow: hidden;
}
......@@ -273,12 +273,12 @@
}
.participants-list {
margin: -5px -5px;
margin: -5px;
}
.participants-author {
display: inline-block;
padding: 5px 5px;
padding: 5px;
.author_link {
display: block;
......
......@@ -45,7 +45,7 @@
.login-heading h3 {
font-weight: 300;
line-height: 1.5;
margin: 0 0 10px 0;
margin: 0 0 10px;
}
.login-footer {
......
......@@ -26,7 +26,7 @@
display: none;
}
.new_note, .edit_note {
.new_note, .note-edit-form {
.note-form-actions {
margin-top: $gl-padding;
}
......
......@@ -100,6 +100,18 @@ ul.notes {
display: block;
position: relative;
&.is-editting {
.note-header,
.note-text,
.edited-text {
display: none;
}
.note-edit-form {
display: block;
}
}
.note-body {
overflow: auto;
......
......@@ -54,7 +54,7 @@
}
.account-well {
padding: 10px 10px;
padding: 10px;
background-color: $help-well-bg;
border: 1px solid $help-well-border;
border-radius: $border-radius-base;
......
......@@ -37,7 +37,7 @@
.dropdown-menu {
left: auto;
width: auto;
right: 0px;
right: 0;
max-width: 240px;
}
}
......@@ -311,7 +311,7 @@ pre.light-well {
}
.git-empty {
margin: 0 7px 0 7px;
margin: 0 7px;
h5 {
color: #5c5d5e;
......
......@@ -16,7 +16,7 @@
#contributors {
.contributors-list {
margin: 0 0 10px 0;
margin: 0 0 10px;
list-style: none;
padding: 0;
}
......
......@@ -5,12 +5,12 @@ class Admin::GroupsController < Admin::ApplicationController
@groups = Group.all
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page]).per(PER_PAGE)
@groups = @groups.page(params[:page])
end
def show
@members = @group.members.order("access_level DESC").page(params[:members_page]).per(PER_PAGE)
@projects = @group.projects.page(params[:projects_page]).per(PER_PAGE)
@members = @group.members.order("access_level DESC").page(params[:members_page])
@projects = @group.projects.page(params[:projects_page])
end
def new
......
......@@ -2,7 +2,7 @@ class Admin::LabelsController < Admin::ApplicationController
before_action :set_label, only: [:show, :edit, :update, :destroy]
def index
@labels = Label.templates.page(params[:page]).per(PER_PAGE)
@labels = Label.templates.page(params[:page])
end
def show
......
......@@ -11,15 +11,15 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE)
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
end
def show
if @group
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(PER_PAGE)
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page])
end
@project_members = @project.project_members.page(params[:project_members_page]).per(PER_PAGE)
@project_members = @project.project_members.page(params[:project_members_page])
end
def transfer
......
......@@ -6,8 +6,6 @@ class ApplicationController < ActionController::Base
include GitlabRoutingHelper
include PageLayoutHelper
PER_PAGE = 20
before_action :authenticate_user_from_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
......
......@@ -7,7 +7,7 @@ class AutocompleteController < ApplicationController
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.reorder(:name)
@users = @users.page(params[:page]).per(PER_PAGE)
@users = @users.page(params[:page])
if params[:search].blank?
# Include current user if available to filter by "Me"
......
......@@ -6,7 +6,7 @@ module GlobalMilestones
@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)
@milestones = Kaminari.paginate_array(@milestones).page(params[:page])
end
def milestone
......
module IssuableActions
extend ActiveSupport::Concern
included do
before_action :authorize_destroy_issuable!, only: :destroy
end
def destroy
issuable.destroy
name = issuable.class.name.titleize.downcase
flash[:notice] = "The #{name} was successfully deleted."
redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
end
private
def authorize_destroy_issuable!
unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
end
end
end
......@@ -3,7 +3,7 @@ module IssuesAction
def issues
@issues = get_issues_collection.non_archived
@issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE)
@issues = @issues.page(params[:page])
@issues = @issues.preload(:author, :project)
@label = @issuable_finder.labels.first
......
......@@ -3,7 +3,7 @@ module MergeRequestsAction
def merge_requests
@merge_requests = get_merge_requests_collection.non_archived
@merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE)
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:author, :target_project)
@label = @issuable_finder.labels.first
......
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
@group_members = current_user.group_members.page(params[:page]).per(PER_PAGE)
@group_members = current_user.group_members.page(params[:page])
end
end
......@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.page(params[:page])
@last_push = current_user.recent_push
......@@ -32,7 +32,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.page(params[:page])
@last_push = current_user.recent_push
@groups = []
......
......@@ -6,6 +6,6 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
user: current_user,
scope: params[:scope]
)
@snippets = @snippets.page(params[:page]).per(PER_PAGE)
@snippets = @snippets.page(params[:page])
end
end
......@@ -2,7 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy, :destroy_all]
def index
@todos = @todos.page(params[:page]).per(PER_PAGE)
@todos = @todos.page(params[:page])
end
def destroy
......
......@@ -3,6 +3,6 @@ class Explore::GroupsController < Explore::ApplicationController
@groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]).per(PER_PAGE)
@groups = @groups.page(params[:page])
end
end
......@@ -8,7 +8,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
@projects = @projects.includes(:namespace).page(params[:page])
respond_to do |format|
format.html
......@@ -23,7 +23,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
@projects = TrendingProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.page(params[:page])
respond_to do |format|
format.html
......@@ -39,7 +39,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = ProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects)
@projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.page(params[:page])
respond_to do |format|
format.html
......
class Explore::SnippetsController < Explore::ApplicationController
def index
@snippets = SnippetsFinder.new.execute(current_user, filter: :all)
@snippets = @snippets.page(params[:page]).per(PER_PAGE)
@snippets = @snippets.page(params[:page])
end
end
......@@ -42,7 +42,7 @@ class GroupsController < Groups::ApplicationController
@projects = @projects.includes(:namespace)
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
@shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
......
......@@ -34,8 +34,7 @@ class ProfilesController < Profiles::ApplicationController
def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC").
page(params[:page]).
per(PER_PAGE)
page(params[:page])
end
def update_username
......
......@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController
def index
@sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort)
@branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE)
@branches = Kaminari.paginate_array(@branches).page(params[:page])
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
......
......@@ -15,7 +15,7 @@ class Projects::ForksController < Projects::ApplicationController
@sort = params[:sort] || 'id_desc'
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
@forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE)
@forks = @forks.order_by(@sort).page(params[:page])
respond_to do |format|
format.html
......
class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction
include IssuableActions
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show]
......@@ -33,7 +34,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
@issues = @issues.page(params[:page]).per(PER_PAGE)
@issues = @issues.page(params[:page])
@label = @project.labels.find_by(title: params[:label_name])
respond_to do |format|
......@@ -90,6 +91,12 @@ class Projects::IssuesController < Projects::ApplicationController
def update
@issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue)
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
move_service = Issues::MoveService.new(project, current_user)
@issue = move_service.execute(@issue, new_project)
end
respond_to do |format|
format.js
format.html do
......@@ -127,6 +134,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
......
......@@ -11,7 +11,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to :js, :html
def index
@labels = @project.labels.page(params[:page]).per(PER_PAGE)
@labels = @project.labels.page(params[:page])
respond_to do |format|
format.html
......
class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
include DiffHelper
include IssuableActions
before_action :module_enabled
before_action :merge_request, only: [
......@@ -34,7 +35,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
@label = @project.labels.find_by(title: params[:label_name])
......@@ -255,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end
alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request
def closes_issues
@closes_issues ||= @merge_request.closes_issues
......
......@@ -22,7 +22,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to do |format|
format.html do
@milestones = @milestones.page(params[:page]).per(PER_PAGE)
@milestones = @milestones.page(params[:page])
end
format.json do
render json: @milestones
......
......@@ -21,7 +21,7 @@ class Projects::SnippetsController < Projects::ApplicationController
filter: :by_project,
project: @project
})
@snippets = @snippets.page(params[:page]).per(PER_PAGE)
@snippets = @snippets.page(params[:page])
end
def new
......
......@@ -7,7 +7,7 @@ class Projects::TagsController < Projects::ApplicationController
def index
sorted = VersionSorter.rsort(@repository.tag_names)
@tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE)
@tags = Kaminari.paginate_array(sorted).page(params[:page])
@releases = project.releases.where(tag: @tags)
end
......
......@@ -7,7 +7,7 @@ class Projects::WikisController < Projects::ApplicationController
before_action :load_project_wiki
def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE)
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
end
def show
......
......@@ -25,7 +25,7 @@ class SnippetsController < ApplicationController
filter: :by_user,
user: @user,
scope: params[:scope] }).
page(params[:page]).per(PER_PAGE)
page(params[:page])
render 'index'
else
......
......@@ -100,7 +100,7 @@ class UsersController < ApplicationController
def load_projects
@projects =
PersonalProjectsFinder.new(@user).execute(current_user)
.page(params[:page]).per(PER_PAGE)
.page(params[:page])
end
def load_contributed_projects
......
......@@ -27,7 +27,7 @@ module BlobHelper
link_opts)
if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn'
elsif can?(current_user, :fork_project, project)
......@@ -50,9 +50,9 @@ module BlobHelper
return unless blob
if !on_top_of_branch?(project, ref)
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
......
......@@ -23,36 +23,34 @@ module ButtonHelper
end
def http_clone_button(project)
klass = 'btn js-protocol-switch'
klass << ' active' if default_clone_protocol == 'http'
klass << ' has_tooltip' if current_user.try(:require_password?)
klass = 'http-selector'
klass << ' has-tooltip' if current_user.try(:require_password?)
protocol = gitlab_config.protocol.upcase
content_tag :button, protocol,
content_tag :a, protocol,
class: klass,
href: @project.http_url_to_repo,
data: {
clone: project.http_url_to_repo,
html: true,
placement: 'right',
container: 'body',
html: 'true',
title: "Set a password on your account<br>to pull or push via #{protocol}"
},
type: :button
}
end
def ssh_clone_button(project)
klass = 'btn js-protocol-switch'
klass << ' active' if default_clone_protocol == 'ssh'
klass << ' has_tooltip' if current_user.try(:require_ssh_key?)
klass = 'ssh-selector'
klass << ' has-tooltip' if current_user.try(:require_ssh_key?)
content_tag :button, 'SSH',
content_tag :a, 'SSH',
class: klass,
href: project.ssh_url_to_repo,
data: {
clone: project.ssh_url_to_repo,
html: true,
placement: 'right',
container: 'body',
html: 'true',
title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
},
type: :button
}
end
end
......@@ -182,7 +182,7 @@ module CommitsHelper
end
options = {
class: "commit-#{options[:source]}-link has_tooltip",
class: "commit-#{options[:source]}-link has-tooltip",
data: { 'original-title'.to_sym => sanitize(source_email) }
}
......
......@@ -57,6 +57,19 @@ module IssuesHelper
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
def project_options(issuable, current_user, ability: :read_project)
projects = current_user.authorized_projects
projects = projects.select do |project|
current_user.can?(ability, project)
end
no_project = OpenStruct.new(id: 0, name_with_namespace: 'No project')
projects.unshift(no_project)
projects.delete(issuable.project)
options_from_collection_for_select(projects, :id, :name_with_namespace)
end
def status_box_class(item)
if item.respond_to?(:expired?) && item.expired?
'status-box-expired'
......
......@@ -56,7 +56,7 @@ module LabelsHelper
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="label color-label #{"has_tooltip" if tooltip}" ) +
span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
%(style="background-color: #{label_color}; color: #{text_color}" ) +
%(title="#{escape_once(label.description)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
......
......@@ -52,7 +52,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
end
end
......@@ -209,7 +209,7 @@ module ProjectsHelper
def default_clone_protocol
if !current_user || current_user.require_ssh_key?
"http"
gitlab_config.protocol
else
"ssh"
end
......
......@@ -36,6 +36,14 @@ module Emails
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
end
def issue_moved_email(recipient, issue, new_issue, updated_by_user)
setup_issue_mail(issue.id, recipient.id)
@new_issue = new_issue
@new_project = new_issue.project
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id))
end
private
def setup_issue_mail(issue_id, recipient_id)
......
......@@ -234,7 +234,9 @@ class Ability
:rename_project,
:remove_project,
:archive_project,
:remove_fork_project
:remove_fork_project,
:destroy_merge_request,
:destroy_issue
]
end
......
......@@ -7,7 +7,10 @@ module InternalId
end
def set_iid
max_iid = project.send(self.class.name.tableize).maximum(:iid)
records = project.send(self.class.name.tableize)
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid)
self.iid = max_iid.to_i + 1
end
......
......@@ -58,6 +58,8 @@ module Issuable
attr_mentionable :description, cache: true
participant :author, :assignee, :notes_with_associations
strip_attributes :title
acts_as_paranoid
end
module ClassMethods
......@@ -209,4 +211,13 @@ module Issuable
Taskable.get_updated_tasks(old_content: previous_changes['description'].first,
new_content: description)
end
##
# Method that checks if issuable can be moved to another project.
#
# Should be overridden if issuable can be moved.
#
def can_move?(*)
false
end
end
......@@ -16,6 +16,7 @@
# state :string(255)
# iid :integer
# updated_by_id :integer
# moved_to_id :integer
#
require 'carrierwave/orm/activerecord'
......@@ -31,6 +32,8 @@ class Issue < ActiveRecord::Base
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
validates :project, presence: true
scope :cared, ->(user) { where(assignee_id: user) }
......@@ -102,9 +105,9 @@ class Issue < ActiveRecord::Base
end
def related_branches
return [] if self.project.empty_repo?
self.project.repository.branch_names.select { |branch| branch.end_with?("-#{iid}") }
project.repository.branch_names.select do |branch|
branch.end_with?("-#{iid}")
end
end
# Reset issue events cache
......@@ -134,6 +137,18 @@ class Issue < ActiveRecord::Base
end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
end
def moved?
!moved_to.nil?
end
def can_move?(user, to_project = nil)
if to_project
return false unless user.can?(:admin_issue, to_project)
end
!moved? && user.can?(:admin_issue, self.project)
end
def to_branch_name
"#{title.parameterize}-#{iid}"
end
......
......@@ -431,6 +431,7 @@ class Project < ActiveRecord::Base
def safe_import_url
result = URI.parse(self.import_url)
result.password = '*****' unless result.password.nil?
result.user = '*****' unless result.user.nil? || result.user == "git" #tokens or other data may be saved as user
result.to_s
rescue
self.import_url
......@@ -887,6 +888,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
unless forked?
if gitlab_shell.add_repository(path_with_namespace)
repository.after_create
true
else
errors.add(:base, 'Failed to create repository via gitlab-shell')
......
......@@ -123,23 +123,27 @@ class ProjectWiki
end
def repository
Repository.new(path_with_namespace, @project)
@repository ||= Repository.new(path_with_namespace, @project)
end
def default_branch
wiki.class.default_ref
end
private
def create_repo!
if init_repo(path_with_namespace)
Gollum::Wiki.new(path_to_repo)
wiki = Gollum::Wiki.new(path_to_repo)
else
raise CouldNotCreateWikiError
end
repository.after_create
wiki
end
private
def init_repo(path_with_namespace)
gitlab_shell.add_repository(path_with_namespace)
end
......
......@@ -42,13 +42,16 @@ class Repository
end
def exists?
return false unless raw_repository
return @exists unless @exists.nil?
raw_repository.rugged
true
@exists = cache.fetch(:exists?) do
begin
raw_repository && raw_repository.rugged ? true : false
rescue Gitlab::Git::Repository::NoRepository
false
end
end
end
def empty?
return @empty unless @empty.nil?
......@@ -320,12 +323,23 @@ class Repository
@avatar = nil
end
def expire_exists_cache
cache.expire(:exists?)
@exists = nil
end
# Runs code after a repository has been created.
def after_create
expire_exists_cache
end
# Runs code just before a repository is deleted.
def before_delete
expire_cache if exists?
expire_root_ref_cache
expire_emptiness_caches
expire_exists_cache
end
# Runs code just before the HEAD of a repository is changed.
......@@ -351,6 +365,7 @@ class Repository
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
expire_exists_cache
end
# Runs code after a new commit has been pushed.
......
......@@ -435,7 +435,7 @@ class User < ActiveRecord::Base
Group.where("namespaces.id IN (#{union.to_sql})")
end
# Returns the groups a user is authorized to access.
# Returns projects user is authorized to access.
def authorized_projects
Project.where("projects.id IN (#{projects_union.to_sql})")
end
......
......@@ -120,7 +120,7 @@ class GitPushService < BaseService
closed_issues = commit.closes_issues(current_user)
closed_issues.each do |issue|
if can?(current_user, :update_issue, issue)
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit: commit)
end
end
end
......
module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
def execute(issue, commit: nil, notifications: true, system_note: true)
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(commit, issue)
todo_service.close_issue(issue, current_user)
......@@ -9,8 +9,8 @@ module Issues
if project.default_issues_tracker? && issue.close
event_service.close_issue(issue, current_user)
create_note(issue, commit)
notification_service.close_issue(issue, current_user)
create_note(issue, commit) if system_note
notification_service.close_issue(issue, current_user) if notifications
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
end
......
......@@ -4,7 +4,7 @@ module Issues
filter_params
label_params = params[:label_ids]
issue = project.issues.new(params.except(:label_ids))
issue.author = current_user
issue.author = params[:author] || current_user
if issue.save
issue.update_attributes(label_ids: label_params)
......
module Issues
class MoveService < Issues::BaseService
class MoveError < StandardError; end
def execute(issue, new_project)
@old_issue = issue
@old_project = @project
@new_project = new_project
unless issue.can_move?(current_user, new_project)
raise MoveError, 'Cannot move issue due to insufficient permissions!'
end
if @project == new_project
raise MoveError, 'Cannot move issue to project it originates from!'
end
# Using transaction because of a high resources footprint
# on rewriting notes (unfolding references)
#
ActiveRecord::Base.transaction do
# New issue tasks
#
@new_issue = create_new_issue
rewrite_notes
add_note_moved_from
# Old issue tasks
#
add_note_moved_to
close_issue
mark_as_moved
end
notify_participants
@new_issue
end
private
def create_new_issue
new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
project: @new_project, author: @old_issue.author,
description: unfold_references(@old_issue.description) }
new_params = @old_issue.serializable_hash.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
end
def rewrite_notes
@old_issue.notes.find_each do |note|
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
note: unfold_references(new_note.note),
created_at: note.created_at }
new_note.update(new_params)
end
end
def close_issue
close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false)
end
def add_note_moved_from
SystemNoteService.noteable_moved(@new_issue, @new_project,
@old_issue, @current_user,
direction: :from)
end
def add_note_moved_to
SystemNoteService.noteable_moved(@old_issue, @old_project,
@new_issue, @current_user,
direction: :to)
end
def unfold_references(content)
rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
@current_user)
rewriter.rewrite(@new_project)
end
def notify_participants
notification_service.issue_moved(@old_issue, @new_issue, @current_user)
end
def mark_as_moved
@old_issue.update(moved_to: @new_issue)
end
end
end
......@@ -22,7 +22,7 @@ module MergeRequests
closed_issues = merge_request.closes_issues(current_user)
closed_issues.each do |issue|
if can?(current_user, :update_issue, issue)
Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
Issues::CloseService.new(project, current_user, {}).execute(issue, commit: merge_request)
end
end
end
......
......@@ -236,6 +236,16 @@ class NotificationService
end
end
def issue_moved(issue, new_issue, current_user)
recipients = build_recipients(issue, issue.project, current_user)
recipients.map do |recipient|
email = mailer.issue_moved_email(recipient, issue, new_issue, current_user)
email.deliver_later
email
end
end
protected
# Get project users with WATCH notification level
......
......@@ -411,4 +411,26 @@ class SystemNoteService
body = "Marked the task **#{new_task.source}** as #{status_label}"
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when noteable has been moved to another project
#
# direction - symbol, :to or :from
# noteable - Noteable object
# noteable_ref - Referenced noteable
# author - User performing the move
#
# Example Note text:
#
# "Moved to some_namespace/project_new#11"
#
# Returns the created Note object
def self.noteable_moved(noteable, project, noteable_ref, author, direction:)
unless [:to, :from].include?(direction)
raise ArgumentError, "Invalid direction `#{direction}`"
end
cross_reference = noteable_ref.to_reference(project)
body = "Moved #{direction} #{cross_reference}"
create_note(noteable: noteable, project: project, author: author, note: body)
end
end
%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' do
= icon('home fw')
= icon('bookmark fw')
%span
Projects
= nav_link(controller: :todos) do
......
%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' do
= icon('home fw')
= icon('bookmark fw')
%span
Projects
= nav_link(controller: :groups) do
......
......@@ -3,31 +3,7 @@
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
%title
GitLab
:css
img {
max-width: 100%;
height: auto;
}
p.details {
font-style:italic;
color:#777
}
.footer p {
font-size:small;
color:#777
}
pre.commit-message {
white-space: pre-wrap;
}
.file-stats a {
text-decoration: none;
}
.file-stats .new-file {
color: #090;
}
.file-stats .deleted-file {
color: #B00;
}
= stylesheet_link_tag 'notify'
%body
%div.content
= yield
......
%p
Issue was moved to another project.
%p
New issue:
= link_to namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) do
= @new_issue.title
Issue was moved to another project.
New issue location:
<%= namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) %>
......@@ -8,7 +8,7 @@
= key.fingerprint
.pull-right
%span.key-created-at
created #{time_ago_with_tooltip(key.created_at)} ago
created #{time_ago_with_tooltip(key.created_at)}
= link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
%span.sr-only Remove
= icon('trash')
......@@ -11,7 +11,7 @@
- if branch.name == @repository.root_ref
%span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name
%span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
%span.label.label-info.has-tooltip(title="Merged into #{@repository.root_ref}")
merged
- if @project.protected_branch? branch.name
......@@ -30,7 +30,7 @@
Compare
- if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if branch.name != @repository.root_ref
......
- unless @project.empty_repo?
- if can? current_user, :download_code, @project
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
= icon('download')
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
%div.count-with-arrow
......@@ -9,7 +9,7 @@
%span.count
= @project.forks_count
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
%div.count-with-arrow
......
......@@ -14,7 +14,7 @@
= notification_list_item(level, @membership)
- when GroupMember
.btn.disabled.notifications-btn.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
.btn.disabled.notifications-btn.has-tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
= icon('bell')
= notification_label(@membership)
= icon('angle-down')
- if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: "Star project" do
- if current_user.starred?(@project)
= icon('star fw')
%span.starred Unstar
......@@ -12,7 +12,7 @@
= @project.star_count
- else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
= link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw')
Star
%div.count-with-arrow
......
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