Merge remote-tracking branch 'ee_com/8-2-stable-ee' into ce_upstream

This commit is contained in:
Valery Sizov
2015-11-24 15:00:14 +02:00
206 changed files with 3323 additions and 1767 deletions
+18
View File
@@ -3,6 +3,15 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased)
v 8.2.0
v 8.0.1
v 8.1.0 (unreleased)
v 8.2.0 (unreleased)
v 8.3.0 (unreleased)
v 8.2.0
- Improved performance of finding projects and groups in various places
- Improved performance of rendering user profile pages and Atom feeds
- Fix grouping of contributors by email in graph.
- Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu)
- Fix Drone CI service template not saving properly (Stan Hu)
- Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu)
@@ -10,6 +19,10 @@ v 8.2.0
- Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu)
- Improved performance of finding users by one of their Email addresses
- Add allow_failure field to commit status API (Stan Hu)
- Commits without .gitlab-ci.yml are marked as skipped
- Save detailed error when YAML syntax is invalid
- Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml
- Added build artifacts
- Improved performance of replacing references in comments
- Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL
@@ -27,6 +40,7 @@ v 8.2.0
- Allow to define cache in `.gitlab-ci.yml`
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
- Remove deprecated CI events from project settings page
- Improve personal snippet access workflow (Douglas Alexandre)
- [API] Add ability to fetch the commit ID of the last commit that actually touched a file
- Fix omniauth documentation setting for omnibus configuration (Jon Cairns)
- Add "New file" link to dropdown on project page
@@ -34,6 +48,7 @@ v 8.2.0
- Add "added", "modified" and "removed" properties to commit object in webhook
- Rename "Back to" links to "Go to" because its not always a case it point to place user come from
- Allow groups to appear in the search results if the group owner allows it
- Add email notification to former assignee upon unassignment (Adam Lieskovský)
- New design for project graphs page
- Remove deprecated dumped yaml file generated from previous job definitions
- Fix incoming email config defaults
@@ -47,6 +62,9 @@ v 8.2.0
- Fix trailing whitespace issue in merge request/issue title
- Fix bug when milestone/label filter was empty for dashboard issues page
- Add ability to create milestone in group projects from single form
- Add option to create merge request when editing/creating a file (Dirceu Tiegs)
- Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez)
- Add Award Emoji to issue and merge request pages
v 8.1.4
- Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu)
+4 -4
View File
@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic
## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Closing policy for issues and merge requests
@@ -35,7 +35,7 @@ The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab
Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](https://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
### Issue tracker guidelines
@@ -72,7 +72,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. If you have multiple commits please combine them into one commit by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork
1. Submit a merge request (MR) to the master branch
1. The MR title should describe the change you want to make
@@ -181,4 +181,4 @@ This code of conduct applies both within project spaces and in public spaces whe
Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/)
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/)
+1 -1
View File
@@ -1 +1 @@
2.6.6
2.6.7
-1
View File
@@ -1 +0,0 @@
0.4.1
+1 -1
View File
@@ -1 +1 @@
0.3.1
0.4.2
@@ -0,0 +1,91 @@
class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id) ->
addAward: (emoji) ->
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
addAwardToEmojiBar: (emoji, custom_path = '') ->
if @exist(emoji)
if @isActive(emoji)
@decrementCounter(emoji)
else
counter = @findEmojiIcon(emoji).siblings(".counter")
counter.text(parseInt(counter.text()) + 1)
counter.parent().addClass("active")
@addMeToAuthorList(emoji)
else
@createEmoji(emoji, custom_path)
exist: (emoji) ->
@findEmojiIcon(emoji).length > 0
isActive: (emoji) ->
@findEmojiIcon(emoji).parent().hasClass("active")
decrementCounter: (emoji) ->
counter = @findEmojiIcon(emoji).siblings(".counter")
if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1)
counter.parent().removeClass("active")
@removeMeFromAuthorList(emoji)
else
award = counter.parent()
award.tooltip("destroy")
award.remove()
removeMeFromAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent()
authors = award_block.attr("data-original-title").split(", ")
authors = _.without(authors, "me").join(", ")
award_block.attr("title", authors)
@resetTooltip(award_block)
addMeToAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent()
authors = award_block.attr("data-original-title").split(", ")
authors.push("me")
award_block.attr("title", authors.join(", "))
@resetTooltip(award_block)
resetTooltip: (award) ->
award.tooltip("destroy")
# "destroy" call is asynchronous, this is why we need to set timeout.
setTimeout (->
award.tooltip()
), 200
createEmoji: (emoji, custom_path) ->
nodes = []
nodes.push("<div class='award active' title='me'>")
nodes.push("<div class='icon' data-emoji='" + emoji + "'>")
nodes.push(@getImage(emoji, custom_path))
nodes.push("</div>")
nodes.push("<div class='counter'>1")
nodes.push("</div></div>")
$(".awards-controls").before(nodes.join("\n"))
$(".award").tooltip()
getImage: (emoji, custom_path) ->
if custom_path
$("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
else
$("li[data-emoji='" + emoji + "']").html()
postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: {
note: ":" + emoji + ":"
noteable_type: @noteable_type
noteable_id: @noteable_id
}},(data) ->
if data.ok
callback.call()
findEmojiIcon: (emoji) ->
$(".icon[data-emoji='" + emoji + "']")
@@ -23,18 +23,6 @@ class @BlobFileDropzone
init: ->
this.on 'addedfile', (file) ->
$('.dropzone-alerts').html('').hide()
commit_message = form.find('#commit_message')[0]
if /^Upload/.test(commit_message.placeholder)
commit_message.placeholder = 'Upload ' + file.name
return
this.on 'removedfile', (file) ->
commit_message = form.find('#commit_message')[0]
if /^Upload/.test(commit_message.placeholder)
commit_message.placeholder = 'Upload new file'
return
@@ -47,8 +35,9 @@ class @BlobFileDropzone
return
this.on 'sending', (file, xhr, formData) ->
formData.append('new_branch', form.find('#new_branch').val())
formData.append('commit_message', form.find('#commit_message').val())
formData.append('new_branch', form.find('.js-new-branch').val())
formData.append('create_merge_request', form.find('.js-create-merge-request').val())
formData.append('commit_message', form.find('.js-commit-message').val())
return
# Override behavior of adding error underneath preview
@@ -9,13 +9,24 @@ $ ->
clipboard.on 'success', (e) ->
$(e.trigger).
tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!').
tooltip('show')
tooltip('show').
one('mouseleave', -> $(this).tooltip('hide'))
# Clear the selection and blur the trigger so it loses its border
e.clearSelection()
$(e.trigger).blur()
# Manually hide the tooltip after 1 second
setTimeout(->
$(e.trigger).tooltip('hide')
, 1000)
# Safari doesn't support `execCommand`, so instead we inform the user to
# copy manually.
#
# See http://clipboardjs.com/#browser-support
clipboard.on 'error', (e) ->
if /Mac/i.test(navigator.userAgent)
title = "Press &#8984;-C to copy"
else
title = "Press Ctrl-C to copy"
$(e.trigger).
tooltip(trigger: 'manual', placement: 'auto bottom', html: true, title: title).
tooltip('show').
one('mouseleave', -> $(this).tooltip('hide'))
@@ -28,6 +28,8 @@ class Dispatcher
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
new DropzoneInput($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
when 'projects:compare:show'
new Diff()
when 'projects:issues:new','projects:issues:edit'
@@ -0,0 +1,21 @@
class @NewCommitForm
constructor: (form) ->
@newBranch = form.find('.js-new-branch')
@originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request')
@createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
@renderDestination()
@newBranch.keyup @renderDestination
renderDestination: =>
different = @newBranch.val() != @originalBranch.val()
if different
@createMergeRequestFormGroup.show()
@createMergeRequest.prop('checked', true) unless @wasDifferent
else
@createMergeRequestFormGroup.hide()
@createMergeRequest.prop('checked', false)
@wasDifferent = different
+11 -11
View File
@@ -29,6 +29,7 @@ class @Notes
$(document).on "ajax:success", "form.edit_note", @updateNote
# Edit note link
$(document).on "click", ".js-note-edit", @showEditForm
$(document).on "click", ".note-edit-cancel", @cancelEdit
# Reopen and close actions for Issue/MR combined with note form submit
@@ -66,6 +67,7 @@ class @Notes
$(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form"
$(document).off "ajax:success", "form.edit_note"
$(document).off "click", ".js-note-edit"
$(document).off "click", ".note-edit-cancel"
$(document).off "click", ".js-note-delete"
$(document).off "click", ".js-note-attachment-delete"
@@ -111,13 +113,16 @@ class @Notes
renderNote: (note) ->
# render note if it not present in loaded list
# or skip if rendered
if @isNewNote(note)
if @isNewNote(note) && !note.award
@note_ids.push(note.id)
$('ul.main-notes-list').
append(note.html).
syntaxHighlight()
@initTaskList()
if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
###
Check if note does not exists on page
###
@@ -253,7 +258,6 @@ class @Notes
###
addNote: (xhr, note, status) =>
@renderNote(note)
@updateVotes()
###
Called in response to the new note form being submitted
@@ -285,14 +289,13 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels
###
showEditForm: (note, formHTML) ->
nodeText = note.find(".note-text");
nodeText.hide()
note.find('.note-edit-form').remove()
nodeText.after(formHTML)
showEditForm: (e) ->
e.preventDefault()
note = $(this).closest(".note")
note.find(".note-body > .note-text").hide()
note.find(".note-header").hide()
form = note.find(".note-edit-form")
base_form = note.find(".note-edit-form")
form = base_form.clone().insertAfter(base_form)
form.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove()
@@ -472,9 +475,6 @@ class @Notes
form = $(e.target).closest(".js-discussion-note-form")
@removeDiscussionNoteForm(form)
updateVotes: ->
true
###
Called after an attachment file has been selected.
@@ -6,7 +6,7 @@ window.ContributorsStatGraphUtil =
for entry in log
@add_date(entry.date, total) unless total[entry.date]?
data = by_author[entry.author_name] #|| by_email[entry.author_email]
data = by_author[entry.author_name] || by_email[entry.author_email]
data ?= @add_author(entry, by_author, by_email)
@add_date(entry.date, data) unless data[entry.date]
@@ -95,5 +95,4 @@ window.ContributorsStatGraphUtil =
if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
true
else
false
false
+1
View File
@@ -56,6 +56,7 @@
li {
padding: 3px 0px;
line-height: 20px;
}
}
.new-file {
@@ -101,3 +101,71 @@
background-color: $background-color;
}
}
.awards {
@include clearfix;
line-height: 34px;
margin: 2px 0;
.award {
@include border-radius(5px);
border: 1px solid;
padding: 0px 10px;
float: left;
margin: 0 5px;
border-color: $border-color;
cursor: pointer;
&.active {
border-color: $border-gray-light;
background-color: $gray-light;
.counter {
font-weight: bold;
}
}
.icon {
float: left;
margin-right: 10px;
}
.counter {
float: left;
}
}
.awards-controls {
margin-left: 10px;
float: left;
.add-award {
font-size: 24px;
color: $gl-gray;
position: relative;
top: 2px;
&:hover,
&:link {
text-decoration: none;
}
}
.awards-menu {
padding: $gl-padding;
min-width: 214px;
> li {
margin: 5px;
}
}
}
.awards-menu{
li {
float: left;
margin: 3px;
}
}
}
+2 -2
View File
@@ -15,10 +15,10 @@ module Ci
@builds = @config_processor.builds
@status = true
end
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
@error = e.message
@status = false
rescue Exception
rescue
@error = "Undefined error"
@status = false
end
@@ -0,0 +1,28 @@
module CreatesMergeRequestForCommit
extend ActiveSupport::Concern
def new_merge_request_path
if @project.forked?
target_project = @project.forked_from_project || @project
target_branch = target_project.repository.root_ref
else
target_project = @project
target_branch = @ref
end
new_namespace_project_merge_request_path(
@project.namespace,
@project,
merge_request: {
source_project_id: @project.id,
target_project_id: target_project.id,
source_branch: @new_branch,
target_branch: target_branch
}
)
end
def create_merge_request?
params[:create_merge_request] && @new_branch != @ref
end
end
@@ -0,0 +1,19 @@
module GlobalMilestones
extend ActiveSupport::Concern
def milestones
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end
def milestone
milestones = Milestone.of_projects(@projects).where(title: params[:title])
if milestones.present?
@milestone = GlobalMilestone.new(params[:title], milestones)
else
render_404
end
end
end
@@ -1,34 +1,19 @@
class Dashboard::MilestonesController < Dashboard::ApplicationController
before_action :load_projects
include GlobalMilestones
before_action :projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show]
def index
project_milestones = case params[:state]
when 'all'; state
when 'closed'; state('closed')
else state('active')
end
@dashboard_milestones = Milestones::GroupService.new(project_milestones).execute
@dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(PER_PAGE)
end
def show
project_milestones = Milestone.where(project_id: @projects).order("due_date ASC")
@dashboard_milestone = Milestones::GroupService.new(project_milestones).milestone(title)
end
private
def load_projects
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
end
def title
params[:title]
end
def state(state = nil)
conditions = { project_id: @projects }
conditions.reverse_merge!(state: state) if state
Milestone.where(conditions).order("title ASC")
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end
+5
View File
@@ -1,5 +1,6 @@
class DashboardController < Dashboard::ApplicationController
before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests]
respond_to :html
@@ -47,4 +48,8 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end
@@ -1,5 +1,6 @@
class Groups::ApplicationController < ApplicationController
layout 'group'
before_action :group
private
+1 -3
View File
@@ -1,8 +1,6 @@
class Groups::AvatarsController < ApplicationController
class Groups::AvatarsController < Groups::ApplicationController
def destroy
@group = Group.find_by(path: params[:group_id])
@group.remove_avatar!
@group.save
redirect_to edit_group_path(@group)
@@ -1,11 +1,9 @@
class Groups::GroupMembersController < Groups::ApplicationController
skip_before_action :authenticate_user!, only: [:index]
before_action :group
# Authorize
before_action :authorize_read_group!
before_action :authorize_admin_group!, except: [:index, :leave]
before_action :authorize_admin_group_member!, only: [:create, :resend_invite]
before_action :authorize_admin_group_member!, except: [:index, :leave]
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@@ -18,7 +16,8 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
@members = @members.order('access_level DESC').page(params[:page]).per(50)
@group_member = GroupMember.new
@group_member = @group.group_members.new
end
def create
@@ -36,30 +35,28 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def update
@member = @group.group_members.find(params[:id])
@group_member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :update_group_member, @member)
return render_403 unless can?(current_user, :update_group_member, @group_member)
old_access_level = @member.human_access
old_access_level = @group_member.human_access
if @member.update_attributes(member_params)
log_audit_event(@member, action: :update, old_access_level: old_access_level)
if @group_member.update_attributes(member_params)
log_audit_event(@group_member, action: :update, old_access_level: old_access_level)
end
end
def destroy
@group_member = @group.group_members.find(params[:id])
if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner.
@group_member.destroy
log_audit_event(@group_member, action: :destroy)
return render_403 unless can?(current_user, :destroy_group_member, @group_member)
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
end
else
return render_403
@group_member.destroy
log_audit_event(@group_member, action: :destroy)
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
end
end
@@ -78,7 +75,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def leave
@group_member = @group.group_members.where(user_id: current_user.id).first
@group_member = @group.group_members.find_by(user_id: current_user)
if can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
+37 -36
View File
@@ -1,54 +1,55 @@
class Groups::MilestonesController < Groups::ApplicationController
before_action :authorize_group_milestone!, only: :update
include GlobalMilestones
before_action :projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show, :update]
before_action :authorize_group_milestone!, only: [:create, :update]
def index
project_milestones = case params[:state]
when 'all'; state
when 'closed'; state('closed')
else state('active')
end
@group_milestones = Milestones::GroupService.new(project_milestones).execute
@group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE)
end
def new
@milestone = Milestone.new
end
def create
project_ids = params[:milestone][:project_ids]
title = milestone_params[:title]
@group.projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute
end
redirect_to milestone_path(title)
end
def show
project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC")
@group_milestone = Milestones::GroupService.new(project_milestones).milestone(title)
end
def update
project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC")
@group_milestones = Milestones::GroupService.new(project_milestones).milestone(title)
@group_milestones.milestones.each do |milestone|
Milestones::UpdateService.new(milestone.project, current_user, params[:milestone]).execute(milestone)
@milestone.milestones.each do |milestone|
Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone)
end
respond_to do |format|
format.js
format.html do
redirect_to group_milestones_path(group)
end
end
redirect_back_or_default(default: milestone_path(@milestone.title))
end
private
def group
@group ||= Group.find_by(path: params[:group_id])
end
def title
params[:title]
end
def state(state = nil)
conditions = { project_id: group.projects }
conditions.reverse_merge!(state: state) if state
Milestone.where(conditions).order("title ASC")
end
def authorize_group_milestone!
return render_404 unless can?(current_user, :admin_group, group)
return render_404 unless can?(current_user, :admin_milestones, group)
end
def milestone_params
params.require(:milestone).permit(:title, :description, :due_date, :state_event)
end
def milestone_path(title)
group_milestone_path(@group, title.parameterize, title: title)
end
def projects
@projects ||= @group.projects
end
end
+50 -37
View File
@@ -1,6 +1,7 @@
# Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
@@ -22,21 +23,9 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
result = Files::CreateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "The changes have been successfully committed"
respond_to do |format|
format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }
format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } }
end
else
flash[:alert] = result[:message]
respond_to do |format|
format.html { render :new }
format.json { render json: { message: "failed", filePath: namespace_project_blob_path(@project.namespace, @project, @id) } }
end
end
create_commit(Files::CreateService, success_path: after_create_path,
failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end
def show
@@ -47,21 +36,9 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
result = Files::UpdateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
respond_to do |format|
format.html { redirect_to after_edit_path }
format.json { render json: { message: "success", filePath: after_edit_path } }
end
else
flash[:alert] = result[:message]
respond_to do |format|
format.html { render :edit }
format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
end
end
create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end
def preview
@@ -77,7 +54,7 @@ class Projects::BlobController < Projects::ApplicationController
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
redirect_to after_destroy_path
else
flash[:alert] = result[:message]
render :show
@@ -131,15 +108,51 @@ class Projects::BlobController < Projects::ApplicationController
render_404
end
def create_commit(service, success_path:, failure_view:, failure_path:)
result = service.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
respond_to do |format|
format.html { redirect_to success_path }
format.json { render json: { message: "success", filePath: success_path } }
end
else
flash[:alert] = result[:message]
respond_to do |format|
format.html { render failure_view }
format.json { render json: { message: "failed", filePath: failure_path } }
end
end
end
def after_create_path
@after_create_path ||=
if create_merge_request?
new_merge_request_path
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path))
end
end
def after_edit_path
@after_edit_path ||=
if from_merge_request
if create_merge_request?
new_merge_request_path
elsif from_merge_request && @new_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
elsif @target_branch.present?
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else
namespace_project_blob_path(@project.namespace, @project, @id)
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path))
end
end
def after_destroy_path
@after_destroy_path ||=
if create_merge_request?
new_merge_request_path
else
namespace_project_tree_path(@project.namespace, @project, @new_branch)
end
end
@@ -154,7 +167,7 @@ class Projects::BlobController < Projects::ApplicationController
def editor_variables
@current_branch = @ref
@target_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
@new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
@file_path =
if action_name.to_s == 'create'
@@ -174,7 +187,7 @@ class Projects::BlobController < Projects::ApplicationController
@commit_params = {
file_path: @file_path,
current_branch: @current_branch,
target_branch: @target_branch,
target_branch: @new_branch,
commit_message: params[:commit_message],
file_content: params[:content],
file_content_encoding: params[:encoding]
@@ -20,8 +20,8 @@ class Projects::CompareController < Projects::ApplicationController
if compare_result
@commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
@commit = @commits.last
@first_commit = @commits.first
@commit = @project.commit(head_ref)
@first_commit = @project.commit(base_ref)
@line_notes = []
end
end
@@ -66,7 +66,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show
@participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.with_associations.fresh
@notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
respond_with(@issue)
@@ -294,7 +294,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
@discussions = Note.discussions_from_notes(@notes)
@noteable = @merge_request
+28 -6
View File
@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :find_current_user_notes, except: [:destroy, :edit, :delete_attachment]
before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]
def index
current_fetched_at = Time.now.to_i
@@ -29,11 +29,6 @@ class Projects::NotesController < Projects::ApplicationController
end
end
def edit
@note = note
render layout: false
end
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
@@ -63,6 +58,30 @@ class Projects::NotesController < Projects::ApplicationController
end
end
def award_toggle
noteable = if note_params[:noteable_type] == "issue"
project.issues.find(note_params[:noteable_id])
else
project.merge_requests.find(note_params[:noteable_id])
end
data = {
author: current_user,
is_award: true,
note: note_params[:note].gsub(":", '')
}
note = noteable.notes.find_by(data)
if note
note.destroy
else
Notes::CreateService.new(project, current_user, note_params).execute
end
render json: { ok: true }
end
private
def note
@@ -116,6 +135,9 @@ class Projects::NotesController < Projects::ApplicationController
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
award: note.is_award,
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
@@ -1,6 +1,6 @@
class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!, except: :leave
before_action :authorize_admin_project_member!, except: :leave
def index
@project_members = @project.project_members
@@ -30,10 +30,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project_group_links = @project.project_group_links
end
def new
@project_member = @project.project_members.new
end
def create
@project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user)
members = @project.project_members.where(user_id: params[:user_ids].split(','))
@@ -47,6 +43,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def update
@project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :update_project_member, @project_member)
old_access_level = @project_member.human_access
if @project_member.update_attributes(member_params)
@@ -56,7 +55,11 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def destroy
@project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_project_member, @project_member)
@project_member.destroy
log_audit_event(@project_member, action: :destroy)
respond_to do |format|
@@ -82,18 +85,24 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def leave
if @project.namespace == current_user.namespace
message = 'You can not leave your own project. Transfer or delete the project.'
return redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
end
@project_member = @project.project_members.find_by(user_id: current_user)
@project_member.destroy
log_audit_event(@project_member, action: :destroy)
respond_to do |format|
format.html { redirect_to dashboard_projects_path }
format.js { render nothing: true }
if can?(current_user, :destroy_project_member, @project_member)
@project_member.destroy
log_audit_event(@project_member, action: :destroy)
respond_to do |format|
format.html { redirect_to dashboard_projects_path, notice: "You left the project." }
format.js { render nothing: true }
end
else
if current_user == @project.owner
message = 'You can not leave your own project. Transfer or delete the project.'
redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
else
render_403
end
end
end
+12 -1
View File
@@ -1,6 +1,7 @@
# Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create]
@@ -43,7 +44,7 @@ class Projects::TreeController < Projects::ApplicationController
if result && result[:status] == :success
flash[:notice] = "The directory has been successfully created"
respond_to do |format|
format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name)) }
format.html { redirect_to after_create_dir_path }
end
else
flash[:alert] = message
@@ -53,6 +54,8 @@ class Projects::TreeController < Projects::ApplicationController
end
end
private
def assign_dir_vars
@new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref
@dir_name = File.join(@path, params[:dir_name])
@@ -63,4 +66,12 @@ class Projects::TreeController < Projects::ApplicationController
commit_message: params[:commit_message],
}
end
def after_create_dir_path
if create_merge_request?
new_merge_request_path
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name))
end
end
end
+8 -1
View File
@@ -1,6 +1,9 @@
class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
before_action :authorize_read_snippet!, only: [:show]
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -79,10 +82,14 @@ class SnippetsController < ApplicationController
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
PersonalSnippet.are_public.find(params[:id])
PersonalSnippet.find(params[:id])
end
end
def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
end
def authorize_update_snippet!
return render_404 unless can?(current_user, :update_personal_snippet, @snippet)
end
+11 -18
View File
@@ -3,14 +3,11 @@ class UsersController < ApplicationController
before_action :set_user
def show
@contributed_projects = contributed_projects.joined(@user).
reject(&:forked?)
@contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
@projects = @user.personal_projects.
where(id: authorized_projects_ids).includes(:namespace)
@projects = PersonalProjectsFinder.new(@user).execute(current_user)
# Collect only groups common for both users
@groups = @user.groups & GroupsFinder.new.execute(current_user)
@groups = JoinedGroupsFinder.new(@user).execute(current_user)
respond_to do |format|
format.html
@@ -53,16 +50,8 @@ class UsersController < ApplicationController
@user = User.find_by_username!(params[:username])
end
def authorized_projects_ids
# Projects user can view
@authorized_projects_ids ||=
ProjectsFinder.new.execute(current_user).pluck(:id)
end
def contributed_projects
@contributed_projects = Project.
where(id: authorized_projects_ids & @user.contributed_projects_ids).
includes(:namespace)
ContributedProjectsFinder.new(@user).execute(current_user)
end
def contributions_calendar
@@ -73,9 +62,13 @@ class UsersController < ApplicationController
def load_events
# Get user activity feed for projects common for both users
@events = @user.recent_events.
where(project_id: authorized_projects_ids).
with_associations
merge(projects_for_current_user).
references(:project).
with_associations.
limit_recent(20, params[:offset])
end
@events = @events.limit(20).offset(params[:offset] || 0)
def projects_for_current_user
ProjectsFinder.new.execute(current_user)
end
end
@@ -0,0 +1,37 @@
class ContributedProjectsFinder
def initialize(user)
@user = user
end
# Finds the projects "@user" contributed to, limited to either public projects
# or projects visible to the given user.
#
# current_user - When given the list of the projects is limited to those only
# visible by this user.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = projects_visible_to_user(current_user)
else
relation = public_projects
end
relation.includes(:namespace).order_id_desc
end
private
def projects_visible_to_user(current_user)
authorized = @user.contributed_projects.visible_to_user(current_user)
union = Gitlab::SQL::Union.
new([authorized.select(:id), public_projects.select(:id)])
Project.where("projects.id IN (#{union.to_sql})")
end
def public_projects
@user.contributed_projects.public_only
end
end
+36 -31
View File
@@ -1,39 +1,44 @@
class GroupsFinder
def execute(current_user, options = {})
all_groups(current_user)
# Finds the groups available to the given user.
#
# current_user - The user to find the groups for.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = groups_visible_to_user(current_user)
else
relation = public_groups
end
relation.order_id_desc
end
private
def all_groups(current_user)
group_ids = if current_user
if current_user.authorized_groups.any?
# User has access to groups
#
# Return only:
# groups with public projects
# groups with internal projects
# groups with joined projects
#
Project.public_and_internal_only.pluck(:namespace_id) +
current_user.authorized_groups.pluck(:id)
else
# User has no group membership
#
# Return only:
# groups with public projects
# groups with internal projects
#
Project.public_and_internal_only.pluck(:namespace_id)
end
else
# Not authenticated
#
# Return only:
# groups with public projects
Project.public_only.pluck(:namespace_id)
end
# This method returns the groups "current_user" can see.
def groups_visible_to_user(current_user)
base = groups_for_projects(public_and_internal_projects)
Group.where("public IS TRUE OR id IN(?)", group_ids)
union = Gitlab::SQL::Union.
new([base.select(:id), current_user.authorized_groups.select(:id)])
Group.where("namespaces.id IN (#{union.to_sql})")
end
def public_groups
groups_for_projects(public_projects)
end
def groups_for_projects(projects)
Group.public_and_given_groups(projects.select(:namespace_id))
end
def public_projects
Project.unscoped.public_only
end
def public_and_internal_projects
Project.unscoped.public_and_internal_only
end
end
+49
View File
@@ -0,0 +1,49 @@
# Class for finding the groups a user is a member of.
class JoinedGroupsFinder
def initialize(user = nil)
@user = user
end
# Finds the groups of the source user, optionally limited to those visible to
# the current user.
#
# current_user - If given the groups of "@user" will only include the groups
# "current_user" can also see.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = groups_visible_to_user(current_user)
else
relation = public_groups
end
relation.order_id_desc
end
private
# Returns the groups the user in "current_user" can see.
#
# This list includes all public/internal projects as well as the projects of
# "@user" that "current_user" also has access to.
def groups_visible_to_user(current_user)
base = @user.authorized_groups.visible_to_user(current_user)
extra = public_and_internal_groups
union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)])
Group.where("namespaces.id IN (#{union.to_sql})")
end
def public_groups
groups_for_projects(@user.authorized_projects.public_only)
end
def public_and_internal_groups
groups_for_projects(@user.authorized_projects.public_and_internal_only)
end
def groups_for_projects(projects)
@user.groups.public_and_given_groups(projects.select(:namespace_id))
end
end
+12
View File
@@ -0,0 +1,12 @@
class MilestonesFinder
def execute(projects, params)
milestones = Milestone.of_projects(projects)
milestones = milestones.order("due_date ASC")
case params[:state]
when 'closed' then milestones.closed
when 'all' then milestones
else milestones.active
end
end
end
+2 -2
View File
@@ -12,9 +12,9 @@ class NotesFinder
when "commit"
project.notes.for_commit_id(target_id).not_inline
when "issue"
project.issues.find(target_id).notes.inc_author
project.issues.find(target_id).notes.nonawards.inc_author
when "merge_request"
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
project.merge_requests.find(target_id).mr_and_commit_notes.nonawards.inc_author
when "snippet", "project_snippet"
project.snippets.find(target_id).notes
else
+41
View File
@@ -0,0 +1,41 @@
class PersonalProjectsFinder
def initialize(user)
@user = user
end
# Finds the projects belonging to the user in "@user", limited to either
# public projects or projects visible to the given user.
#
# current_user - When given the list of projects is limited to those only
# visible by this user.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = projects_visible_to_user(current_user)
else
relation = public_projects
end
relation.includes(:namespace).order_id_desc
end
private
def projects_visible_to_user(current_user)
authorized = @user.personal_projects.visible_to_user(current_user)
union = Gitlab::SQL::Union.
new([authorized.select(:id), public_and_internal_projects.select(:id)])
Project.where("projects.id IN (#{union.to_sql})")
end
def public_projects
@user.personal_projects.public_only
end
def public_and_internal_projects
@user.personal_projects.public_and_internal_only
end
end
+55 -79
View File
@@ -1,11 +1,39 @@
class ProjectsFinder
def execute(current_user, options = {})
# Returns all projects, optionally including group projects a user has access
# to.
#
# ## Examples
#
# Retrieving all public projects:
#
# ProjectsFinder.new.execute
#
# Retrieving all public/internal projects and those the given user has access
# to:
#
# ProjectsFinder.new.execute(some_user)
#
# Retrieving all public/internal projects as well as the group's projects the
# user has access to:
#
# ProjectsFinder.new.execute(some_user, group: some_group)
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil, options = {})
group = options[:group]
if group
group_projects(current_user, group)
segments = group_projects(current_user, group)
else
all_projects(current_user)
segments = all_projects(current_user)
end
if segments.length > 1
union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
Project.where("projects.id IN (#{union.to_sql})")
else
segments.first
end
end
@@ -13,89 +41,37 @@ class ProjectsFinder
def group_projects(current_user, group)
if current_user
if group.users.include?(current_user)
# User is group member
#
# Return ALL group projects
group.projects
else
projects_members = ProjectMember.in_projects(group.projects).
with_user(current_user)
if projects_members.any?
# User is a project member
#
# Return only:
# public projects
# internal projects
# joined projects
#
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_members.pluck(:source_id),
Project.public_and_internal_levels
)
else
# User has no access to group or group projects
# or has access through shared project
#
# Return only:
# public projects
# internal projects
# shared projects
projects_ids = []
ProjectGroupLink.where(project_id: group.projects).each do |shared_project|
if shared_project.group.users.include?(current_user) || shared_project.project.users.include?(current_user)
projects_ids << shared_project.project.id
end
end
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_ids,
Project.public_and_internal_levels
)
end
end
[
group_projects_for_user(current_user, group),
group.projects.public_and_internal_only,
group.shared_projects.visible_to_user(current_user)
]
else
# Not authenticated
#
# Return only:
# public projects
group.projects.public_only
[group.projects.public_only]
end
end
def all_projects(current_user)
if current_user
if current_user.authorized_projects.any?
# User has access to private projects
#
# Return only:
# public projects
# internal projects
# joined projects
#
Project.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
current_user.authorized_projects.pluck(:id),
Project.public_and_internal_levels
)
else
# User has no access to private projects
#
# Return only:
# public projects
# internal projects
#
Project.public_and_internal_only
end
[current_user.authorized_projects, public_and_internal_projects]
else
# Not authenticated
#
# Return only:
# public projects
Project.public_only
[Project.public_only]
end
end
def group_projects_for_user(current_user, group)
if group.users.include?(current_user)
group.projects
else
group.projects.visible_to_user(current_user)
end
end
def public_projects
Project.unscoped.public_only
end
def public_and_internal_projects
Project.unscoped.public_and_internal_only
end
end
+7 -3
View File
@@ -1,4 +1,8 @@
module DiffHelper
def diff_view
params[:view] == 'parallel' ? 'parallel' : 'inline'
end
def allowed_diff_size
if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_FILES
@@ -137,7 +141,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do
link_to url_for(params_copy), id: "inline-diff-btn", class: (diff_view == 'inline' ? 'btn active' : 'btn') do
'Inline'
end
end
@@ -148,7 +152,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do
link_to url_for(params_copy), id: "parallel-diff-btn", class: (diff_view == 'parallel' ? 'btn active' : 'btn') do
'Side-by-side'
end
end
@@ -171,7 +175,7 @@ module DiffHelper
def commit_for_diff(diff)
if diff.deleted_file
first_commit = @first_commit || @commit
first_commit.parent
first_commit.parent || @first_commit
else
@commit
end
+1 -1
View File
@@ -12,7 +12,7 @@ module GroupsHelper
end
def should_user_see_group_roles?(user, group)
if user
if user && group
user.is_admin? || group.members.exists?(user_id: user.id)
else
false
+27
View File
@@ -87,6 +87,33 @@ module IssuesHelper
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
end
def url_to_emoji(name)
emoji_path = ::AwardEmoji.path_to_emoji_image(name)
url_to_image(emoji_path)
rescue StandardError
""
end
def emoji_author_list(notes, current_user)
list = notes.map do |note|
note.author == current_user ? "me" : note.author.username
end
list.join(", ")
end
def emoji_list
::AwardEmoji::EMOJI_LIST
end
def note_active_class(notes, current_user)
if current_user && notes.pluck(:author_id).include?(current_user.id)
"active"
else
""
end
end
# Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue
end
+1 -1
View File
@@ -100,7 +100,7 @@ module LabelsHelper
Label.where(project_id: @projects)
end
grouped_labels = Labels::GroupService.new(labels).execute
grouped_labels = GlobalLabel.build_collection(labels)
grouped_labels.unshift(Label::None)
grouped_labels.unshift(Label::Any)
-8
View File
@@ -8,14 +8,6 @@ module MergeRequestsHelper
)
end
def new_mr_path_for_fork_from_push_event(event)
new_namespace_project_merge_request_path(
event.project.namespace,
event.project,
new_mr_from_push_event(event, event.project.forked_from_project)
)
end
def new_mr_from_push_event(event, target_project)
{
merge_request: {
+1 -1
View File
@@ -28,7 +28,7 @@ module MilestonesHelper
Milestone.where(project_id: @projects)
end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute
grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any)
+114 -51
View File
@@ -1,21 +1,22 @@
class Ability
class << self
def allowed(user, subject)
return not_auth_abilities(user, subject) if user.nil?
return [] unless user.kind_of?(User)
return anonymous_abilities(user, subject) if user.nil?
return [] unless user.is_a?(User)
return [] if user.blocked?
abilities =
abilities =
case subject.class.name
when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(user, subject)
when "ProjectSnippet" then project_snippet_abilities(user, subject)
when "PersonalSnippet" then personal_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
when "GroupMember" then group_member_abilities(user, subject)
when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(user, subject)
when "ProjectSnippet" then project_snippet_abilities(user, subject)
when "PersonalSnippet" then personal_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
when "GroupMember" then group_member_abilities(user, subject)
when "ProjectMember" then project_member_abilities(user, subject)
else []
end
@@ -35,15 +36,25 @@ class Ability
]
end
# List of possible abilities
# for non-authenticated user
def not_auth_abilities(user, subject)
project = if subject.kind_of?(Project)
# List of possible abilities for anonymous user
def anonymous_abilities(user, subject)
case true
when subject.is_a?(PersonalSnippet)
anonymous_personal_snippet_abilities(subject)
when subject.is_a?(Project) || subject.respond_to?(:project)
anonymous_project_abilities(subject)
when subject.is_a?(Group) || subject.respond_to?(:group)
anonymous_group_abilities(subject)
else
[]
end
end
def anonymous_project_abilities(subject)
project = if subject.is_a?(Project)
subject
elsif subject.respond_to?(:project)
subject.project
else
nil
subject.project
end
if project && project.public?
@@ -63,19 +74,29 @@ class Ability
rules - project_disabled_features_rules(project)
else
group = if subject.kind_of?(Group)
subject
elsif subject.respond_to?(:group)
subject.group
else
nil
end
[]
end
end
if group && group.public_profile?
[:read_group]
else
[]
end
def anonymous_group_abilities(subject)
group = if subject.is_a?(Group)
subject
else
subject.group
end
if group && group.public_profile?
[:read_group]
else
[]
end
end
def anonymous_personal_snippet_abilities(snippet)
if snippet.public?
[:read_personal_snippet]
else
[]
end
end
@@ -247,21 +268,22 @@ class Ability
# Only group masters and group owners can create new projects in group
if group.has_master?(user) || group.has_owner?(user) || user.admin?
rules.push(*[
rules += [
:create_projects,
])
:admin_milestones
]
end
# Only group owner and administrators can admin group
if group.has_owner?(user) || user.admin?
rules.push(*[
rules += [
:admin_group,
:admin_namespace,
:admin_group_member
])
]
unless group.ldap_synced?
rules << :admin_group_member
if group.ldap_synced?
rules.delete(:admin_group_member)
end
end
@@ -273,16 +295,15 @@ class Ability
# Only namespace owner and administrators can admin it
if namespace.owner == user || user.admin?
rules.push(*[
rules += [
:create_projects,
:admin_namespace
])
]
end
rules.flatten
end
[:issue, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject|
rules = []
@@ -299,7 +320,7 @@ class Ability
end
end
[:note, :project_snippet, :personal_snippet].each do |name|
[:note, :project_snippet].each do |name|
define_method "#{name}_abilities" do |user, subject|
rules = []
@@ -319,19 +340,61 @@ class Ability
end
end
def personal_snippet_abilities(user, snippet)
rules = []
if snippet.author == user
rules += [
:read_personal_snippet,
:update_personal_snippet,
:admin_personal_snippet
]
end
if snippet.public? || snippet.internal?
rules << :read_personal_snippet
end
rules
end
def group_member_abilities(user, subject)
rules = []
target_user = subject.user
group = subject.group
can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && (user != target_user)
rules << :update_group_member
rules << :destroy_group_member
unless group.last_owner?(target_user)
can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && user != target_user
rules << :update_group_member
rules << :destroy_group_member
end
if user == target_user
rules << :destroy_group_member
end
end
if !group.last_owner?(user) && (can_manage || (user == target_user))
rules << :destroy_group_member
rules
end
def project_member_abilities(user, subject)
rules = []
target_user = subject.user
project = subject.project
unless target_user == project.owner
can_manage = project_abilities(user, project).include?(:admin_project_member)
if can_manage && user != target_user
rules << :update_project_member
rules << :destroy_project_member
end
if user == target_user
rules << :destroy_project_member
end
end
rules
@@ -339,10 +402,10 @@ class Ability
def abilities
@abilities ||= begin
abilities = Six.new
abilities << self
abilities
end
abilities = Six.new
abilities << self
abilities
end
end
private
+4 -4
View File
@@ -188,13 +188,13 @@ module Ci
end
def config_processor
return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace)
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message)
nil
rescue Exception => e
logger.error e.message + "\n" + e.backtrace.join("\n")
save_yaml_error("Undefined yaml error")
rescue
save_yaml_error("Undefined error")
nil
end
+4 -43
View File
@@ -89,39 +89,14 @@ module Issuable
opened? || reopened?
end
#
# Votes
#
# Return the number of -1 comments (downvotes)
# Deprecated. Still exists to preserve API compatibility.
def downvotes
filter_superceded_votes(notes.select(&:downvote?), notes).size
0
end
def downvotes_in_percent
if votes_count.zero?
0
else
100.0 - upvotes_in_percent
end
end
# Return the number of +1 comments (upvotes)
# Deprecated. Still exists to preserve API compatibility.
def upvotes
filter_superceded_votes(notes.select(&:upvote?), notes).size
end
def upvotes_in_percent
if votes_count.zero?
0
else
100.0 / votes_count * upvotes
end
end
# Return the total number of votes
def votes_count
upvotes + downvotes
0
end
def subscribed?(user)
@@ -183,18 +158,4 @@ module Issuable
def notes_with_associations
notes.includes(:author, :project)
end
private
def filter_superceded_votes(votes, notes)
filteredvotes = [] + votes
votes.each do |vote|
if vote.superceded?(notes)
filteredvotes.delete(vote)
end
end
filteredvotes
end
end
+2 -1
View File
@@ -8,8 +8,9 @@ module Sortable
included do
# By default all models should be ordered
# by created_at field starting from newest
default_scope { order(id: :desc) }
default_scope { order_id_desc }
scope :order_id_desc, -> { reorder(id: :desc) }
scope :order_created_desc, -> { reorder(created_at: :desc) }
scope :order_created_asc, -> { reorder(created_at: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc) }
+10
View File
@@ -63,6 +63,16 @@ class Event < ActiveRecord::Base
Event::PUSHED, ["MergeRequest", "Issue"],
[Event::CREATED, Event::CLOSED, Event::MERGED])
end
def latest_update_time
row = select(:updated_at, :project_id).reorder(id: :desc).take
row ? row.updated_at : nil
end
def limit_recent(limit = 20, offset = nil)
recent.limit(limit).offset(offset)
end
end
def proper?
+17
View File
@@ -0,0 +1,17 @@
class GlobalLabel
attr_accessor :title, :labels
alias_attribute :name, :title
def self.build_collection(labels)
labels = labels.group_by(&:title)
labels.map do |title, label|
new(title, label)
end
end
def initialize(title, labels)
@title = title
@labels = labels
end
end
@@ -1,7 +1,15 @@
class GroupMilestone
class GlobalMilestone
attr_accessor :title, :milestones
alias_attribute :name, :title
def self.build_collection(milestones)
milestones = milestones.group_by(&:title)
milestones.map do |title, milestones|
new(title, milestones)
end
end
def initialize(title, milestones)
@title = title
@milestones = milestones
@@ -10,7 +18,7 @@ class GroupMilestone
def safe_title
@title.parameterize
end
def projects
milestones.map { |milestone| milestone.project }
end
@@ -60,15 +68,15 @@ class GroupMilestone
end
def issues
@group_issues ||= milestones.map(&:issues).flatten.group_by(&:state)
@issues ||= milestones.map(&:issues).flatten.group_by(&:state)
end
def merge_requests
@group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
@merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
end
def participants
@group_participants ||= milestones.map(&:participants).flatten.compact.uniq
@participants ||= milestones.map(&:participants).flatten.compact.uniq
end
def opened_issues
@@ -86,4 +94,8 @@ class GroupMilestone
def closed_merge_requests
merge_requests.values_at("closed", "merged", "locked").compact.flatten
end
def complete?
total_items_count == closed_items_count
end
end
+10 -5
View File
@@ -20,8 +20,9 @@ require 'file_size_validator'
class Group < Namespace
include Gitlab::ConfigHelper
include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
alias_method :members, :group_members
has_many :users, through: :group_members
has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project
@@ -52,6 +53,14 @@ class Group < Namespace
def reference_pattern
User.reference_pattern
end
def public_and_given_groups(ids)
where('public IS TRUE OR namespaces.id IN (?)', ids)
end
def visible_to_user(user)
where(id: user.authorized_groups.select(:id).reorder(nil))
end
end
def to_reference(_from_project = nil)
@@ -114,10 +123,6 @@ class Group < Namespace
has_owner?(user) && owners.size == 1
end
def members
group_members
end
def avatar_type
unless self.avatar.image?
self.errors.add :avatar, "only images allowed"
-9
View File
@@ -1,9 +0,0 @@
class GroupLabel
attr_accessor :title, :labels
alias_attribute :name, :title
def initialize(title, labels)
@title = title
@labels = labels
end
end
+30 -10
View File
@@ -31,13 +31,22 @@ class Member < ActiveRecord::Base
validates :user, presence: true, unless: :invite?
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source",
allow_nil: true }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :invite_email, presence: { if: :invite? },
email: { strict_mode: true, allow_nil: true },
uniqueness: { scope: [:source_type, :source_id], allow_nil: true }
validates :invite_email,
presence: {
if: :invite?
},
email: {
strict_mode: true,
allow_nil: true
},
uniqueness: {
scope: [:source_type, :source_id],
allow_nil: true
}
scope :invite, -> { where(user_id: nil) }
scope :non_invite, -> { where("user_id IS NOT NULL") }
@@ -74,7 +83,7 @@ class Member < ActiveRecord::Base
def add_user(members, user_id, access_level, current_user = nil, skip_notification: false)
user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited
if user.is_a?(User)
member = members.find_or_initialize_by(user_id: user.id)
@@ -83,12 +92,23 @@ class Member < ActiveRecord::Base
member.invite_email = user
end
member.created_by ||= current_user
member.access_level = access_level
if can_update_member?(current_user, member)
member.created_by ||= current_user
member.access_level = access_level
member.skip_notification = skip_notification
member.skip_notification = skip_notification
member.save
member.save
end
end
private
def can_update_member?(current_user, member)
# There is no current user for bulk actions, in which case anything is allowed
!current_user ||
current_user.can?(:update_group_member, member) ||
current_user.can?(:update_project_member, member)
end
end
@@ -98,7 +118,7 @@ class Member < ActiveRecord::Base
def accept_invite!(new_user)
return false unless invite?
self.invite_token = nil
self.invite_accepted_at = Time.now.utc
+1 -1
View File
@@ -545,7 +545,7 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
if last_commit
if last_commit and source_project
source_project.ci_commit(last_commit.id)
end
end
+20 -38
View File
@@ -40,16 +40,20 @@ class Note < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, 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 }
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
validates :author, presence: true
mount_uploader :attachment, AttachmentUploader
# Scopes
scope :awards, ->{ where(is_award: true) }
scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: [nil, '']) }
@@ -97,6 +101,12 @@ class Note < ActiveRecord::Base
def search(query)
where("LOWER(note) like :query", query: "%#{query.downcase}%")
end
def grouped_awards
awards.select(:note).distinct.map do |note|
[ note.note, where(note: note.note) ]
end
end
end
def cross_reference?
@@ -288,44 +298,6 @@ class Note < ActiveRecord::Base
nil
end
DOWNVOTES = %w(-1 :-1: :thumbsdown: :thumbs_down_sign:)
# Check if the note is a downvote
def downvote?
votable? && note.start_with?(*DOWNVOTES)
end
UPVOTES = %w(+1 :+1: :thumbsup: :thumbs_up_sign:)
# Check if the note is an upvote
def upvote?
votable? && note.start_with?(*UPVOTES)
end
def superceded?(notes)
return false unless vote?
notes.each do |note|
next if note == self
if note.vote? &&
self[:author_id] == note[:author_id] &&
self[:created_at] <= note[:created_at]
return true
end
end
false
end
def vote?
upvote? || downvote?
end
def votable?
for_issue? || (for_merge_request? && !for_diff_line?)
end
# Mentionable override.
def gfm_reference(from_project = nil)
noteable.gfm_reference(from_project)
@@ -363,6 +335,16 @@ class Note < ActiveRecord::Base
read_attribute(:system)
end
# Deprecated. Still exists to preserve API compatibility.
def downvote?
false
end
# Deprecated. Still exists to preserve API compatibility.
def upvote?
false
end
def editable?
!system?
end
+4
View File
@@ -313,6 +313,10 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC')
end
def visible_to_user(user)
where(id: user.authorized_projects.select(:id).reorder(nil))
end
end
def team
@@ -32,6 +32,8 @@ class DroneCiService < CiService
def compose_service_hook
hook = service_hook || build_service_hook
# If using a service template, project may not be available
hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
hook.enable_ssl_verification = enable_ssl_verification
hook.save
end
+10
View File
@@ -86,6 +86,8 @@ class ProjectWiki
commit = commit_details(:created, message, title)
wiki.write_page(title, format, content, commit)
update_project_activity
rescue Gollum::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}"
return false
@@ -95,10 +97,14 @@ class ProjectWiki
commit = commit_details(:updated, message, page.title)
wiki.update_page(page, page.name, format, content, commit)
update_project_activity
end
def delete_page(page, message = nil)
wiki.delete_page(page, commit_details(:deleted, message, page.title))
update_project_activity
end
def page_title_and_dir(title)
@@ -146,4 +152,8 @@ class ProjectWiki
def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
end
def update_project_activity
@project.touch(:last_activity_at)
end
end
+48 -35
View File
@@ -417,43 +417,23 @@ class User < ActiveRecord::Base
end
end
# Groups user has access to
# Returns the groups a user has access to
def authorized_groups
@authorized_groups ||= begin
group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
Group.where(id: group_ids)
end
union = Gitlab::SQL::Union.
new([groups.select(:id), authorized_projects.select(:namespace_id)])
Group.where("namespaces.id IN (#{union.to_sql})")
end
def authorized_projects_id
@authorized_projects_id ||= begin
project_ids = personal_projects.pluck(:id)
project_ids.push(*groups_projects.pluck(:id))
project_ids.push(*projects.pluck(:id).uniq)
project_ids.push(*groups.joins(:shared_projects).pluck(:project_id))
end
end
def master_or_owner_projects_id
@master_or_owner_projects_id ||= begin
scope = { access_level: [ Gitlab::Access::MASTER, Gitlab::Access::OWNER ] }
project_ids = personal_projects.pluck(:id)
project_ids.push(*groups_projects.where(members: scope).pluck(:id))
project_ids.push(*projects.where(members: scope).pluck(:id).uniq)
end
end
# Projects user has access to
# Returns the groups a user is authorized to access.
def authorized_projects
@authorized_projects ||= Project.where(id: authorized_projects_id)
Project.where("projects.id IN (#{projects_union.to_sql})")
end
def owned_projects
@owned_projects ||=
begin
namespace_ids = owned_groups.pluck(:id).push(namespace.id)
Project.in_namespace(namespace_ids).joins(:namespace)
end
Project.where('namespace_id IN (?) OR namespace_id = ?',
owned_groups.select(:id), namespace.id).joins(:namespace)
end
# Team membership in authorized projects
@@ -772,12 +752,25 @@ class User < ActiveRecord::Base
Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
end
def contributed_projects_ids
Event.contributions.where(author_id: self).
# Returns the projects a user contributed to in the last year.
#
# This method relies on a subquery as this performs significantly better
# compared to a JOIN when coupled with, for example,
# `Project.visible_to_user`. That is, consider the following code:
#
# some_user.contributed_projects.visible_to_user(other_user)
#
# If this method were to use a JOIN the resulting query would take roughly 200
# ms on a database with a similar size to GitLab.com's database. On the other
# hand, using a subquery means we can get the exact same data in about 40 ms.
def contributed_projects
events = Event.select(:project_id).
contributions.where(author_id: self).
where("created_at > ?", Time.now - 1.year).
reorder(project_id: :desc).
select(:project_id).
uniq.map(&:project_id)
uniq.
reorder(nil)
Project.where(id: events)
end
def restricted_signup_domains
@@ -810,8 +803,28 @@ class User < ActiveRecord::Base
def ci_authorized_runners
@ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject.joins(:project).
where(ci_projects: { gitlab_id: master_or_owner_projects_id }).select(:runner_id)
where("ci_projects.gitlab_id IN (#{ci_projects_union.to_sql})").
select(:runner_id)
Ci::Runner.specific.where(id: runner_ids)
end
end
private
def projects_union
Gitlab::SQL::Union.new([personal_projects.select(:id),
groups_projects.select(:id),
projects.select(:id),
groups.joins(:shared_projects).select(:project_id)])
end
def ci_projects_union
scope = { access_level: [Gitlab::Access::MASTER, Gitlab::Access::OWNER] }
groups = groups_projects.where(members: scope)
other = projects.where(members: scope)
Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
other.select(:id)])
end
end
+31
View File
@@ -0,0 +1,31 @@
require_relative 'base_service'
class CreateReleaseService < BaseService
def execute(tag_name, release_description)
repository = project.repository
existing_tag = repository.find_tag(tag_name)
# Only create a release if the tag exists
if existing_tag
release = project.releases.find_by(tag: tag_name)
if release
error('Release already exists', 409)
else
release = project.releases.new({ tag: tag_name, description: release_description })
release.save
success(release)
end
else
error('Tag does not exist', 404)
end
end
def success(release)
out = super()
out[:release] = release
out
end
end
+5 -5
View File
@@ -19,16 +19,16 @@ class CreateTagService < BaseService
new_tag = repository.find_tag(tag_name)
if new_tag
if release_description
release = project.releases.find_or_initialize_by(tag: tag_name)
release.update_attributes(description: release_description)
end
push_data = create_push_data(project, current_user, new_tag)
EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :tag_push_hooks)
project.execute_services(push_data.dup, :tag_push_hooks)
if release_description
CreateReleaseService.new(@project, @current_user).
execute(tag_name, release_description)
end
success(new_tag)
else
error('Invalid reference name')
-12
View File
@@ -58,12 +58,6 @@ class GitPushService
@push_data = build_push_data(oldrev, newrev, ref)
# If CI was disabled but .gitlab-ci.yml file was pushed
# we enable CI automatically
if !project.builds_enabled? && gitlab_ci_yaml?(newrev)
project.enable_ci
end
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
@@ -134,10 +128,4 @@ class GitPushService
def commit_user(commit)
commit.author || user
end
def gitlab_ci_yaml?(sha)
@project.repository.blob_at(sha, '.gitlab-ci.yml')
rescue Rugged::ReferenceError
nil
end
end
-26
View File
@@ -1,26 +0,0 @@
module Labels
class GroupService < ::BaseService
def initialize(project_labels)
@project_labels = project_labels.group_by(&:title)
end
def execute
build(@project_labels)
end
def label(title)
if title
group_label = @project_labels[title].group_by(&:title)
build(group_label).first
else
nil
end
end
private
def build(label)
label.map { |title, labels| GroupLabel.new(title, labels) }
end
end
end
-26
View File
@@ -1,26 +0,0 @@
module Milestones
class GroupService < Milestones::BaseService
def initialize(project_milestones)
@project_milestones = project_milestones.group_by(&:title)
end
def execute
build(@project_milestones)
end
def milestone(title)
if title
group_milestone = @project_milestones[title].group_by(&:title)
build(group_milestone).first
else
nil
end
end
private
def build(milestone)
milestone.map{ |title, milestones| GroupMilestone.new(title, milestones) }
end
end
end
+15 -2
View File
@@ -5,11 +5,16 @@ 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)
# Skip system notes, like status changes and cross-references.
unless note.system
# Skip system notes, like status changes and cross-references and awards
unless note.system || note.is_award
event_service.leave_note(note, note.author)
note.create_cross_references!
execute_hooks(note)
@@ -28,5 +33,13 @@ 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
+9 -4
View File
@@ -102,6 +102,7 @@ class NotificationService
# ignore gitlab service messages
return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true
return true if note.is_award
target = note.noteable
@@ -361,11 +362,13 @@ class NotificationService
end
def reassign_resource_email(target, project, current_user, method)
assignee_id_was = previous_record(target, "assignee_id")
recipients = build_recipients(target, project, current_user)
previous_assignee_id = previous_record(target, "assignee_id")
previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
recipients = build_recipients(target, project, current_user, [previous_assignee])
recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, assignee_id_was, current_user.id)
mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id)
end
end
@@ -377,9 +380,11 @@ class NotificationService
end
end
def build_recipients(target, project, current_user)
def build_recipients(target, project, current_user, extra_recipients = nil)
recipients = target.participants(current_user)
recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients
recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project)
recipients = reject_muted_users(recipients, project)
+29
View File
@@ -0,0 +1,29 @@
require_relative 'base_service'
class UpdateReleaseService < BaseService
def execute(tag_name, release_description)
repository = project.repository
existing_tag = repository.find_tag(tag_name)
if existing_tag
release = project.releases.find_by(tag: tag_name)
if release
release.update_attributes(description: release_description)
success(release)
else
error('Release does not exist', 404)
end
else
error('Tag does not exist', 404)
end
end
def success(release)
out = super()
out[:release] = release
out
end
end
+2 -2
View File
@@ -16,11 +16,11 @@
- unless user.linkedin.blank?
%li
%span.light LinkedIn:
%strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}"
%strong= link_to user.linkedin, "https://www.linkedin.com/in/#{user.linkedin}"
- unless user.twitter.blank?
%li
%span.light Twitter:
%strong= link_to user.twitter, "http://www.twitter.com/#{user.twitter}"
%strong= link_to user.twitter, "https://twitter.com/#{user.twitter}"
- unless user.website_url.blank?
%li
%span.light Website:
@@ -10,10 +10,10 @@
.milestones
%ul.content-list
- if @dashboard_milestones.blank?
- if @milestones.blank?
%li
.nothing-here-block No milestones to show
- else
- @dashboard_milestones.each do |milestone|
- @milestones.each do |milestone|
= render 'milestone', milestone: milestone
= paginate @dashboard_milestones, theme: "gitlab"
= paginate @milestones, theme: "gitlab"
+18 -18
View File
@@ -1,14 +1,14 @@
- page_title @dashboard_milestone.title, "Milestones"
- page_title @milestone.title, "Milestones"
%h4.page-title
.issue-box{ class: "issue-box-#{@dashboard_milestone.closed? ? 'closed' : 'open'}" }
- if @dashboard_milestone.closed?
.issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
- else
Open
Milestone #{@dashboard_milestone.title}
Milestone #{@milestone.title}
%hr
- if (@dashboard_milestone.total_items_count == @dashboard_milestone.closed_items_count) && @dashboard_milestone.active?
- if @milestone.complete? && @milestone.active?
.alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now.
@@ -22,7 +22,7 @@
%th Open issues
%th State
%th Due date
- @dashboard_milestone.milestones.each do |milestone|
- @milestone.milestones.each do |milestone|
%tr
%td
= link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
@@ -39,46 +39,46 @@
.context
%p.lead
Progress:
#{@dashboard_milestone.closed_items_count} closed
#{@milestone.closed_items_count} closed
&ndash;
#{@dashboard_milestone.open_items_count} open
= milestone_progress_bar(@dashboard_milestone)
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
%span.badge= @dashboard_milestone.issue_count
%span.badge= @milestone.issue_count
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests
%span.badge= @dashboard_milestone.merge_requests_count
%span.badge= @milestone.merge_requests_count
%li
= link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants
%span.badge= @dashboard_milestone.participants.count
%span.badge= @milestone.participants.count
.pull-right
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @dashboard_milestone.title), class: "btn edit-milestone-link btn-grouped"
= 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
.col-md-6
= render 'issues', title: "Open", issues: @dashboard_milestone.opened_issues
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @dashboard_milestone.closed_issues
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.row
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @dashboard_milestone.opened_merge_requests
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @dashboard_milestone.closed_merge_requests
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
%ul.bordered-list
- @dashboard_milestone.participants.each do |user|
- @milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html"
xml.id dashboard_projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
@@ -1,6 +1,5 @@
- user = member.user
- return unless user || member.invite?
- show_roles = true if show_roles.nil?
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
@@ -25,11 +24,11 @@
= link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
- if show_controls && can?(current_user, :admin_group_member, member)
- if show_controls && can?(current_user, :admin_group_member, @group)
= link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite
- if show_roles
- if should_user_see_group_roles?(current_user, @group)
%span.pull-right
%strong= member.human_access
- if show_controls
@@ -37,6 +36,7 @@
= button_tag class: "btn-xs btn js-toggle-button",
title: 'Edit access level', type: 'button' do
%i.fa.fa-pencil-square-o
- if can?(current_user, :destroy_group_member, member)
&nbsp;
- if current_user == user
@@ -1,8 +1,6 @@
- page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group))
- show_roles = should_user_see_group_roles?(current_user, @group)
- if show_roles
- if should_user_see_group_roles?(current_user, @group)
%p.light
Members of group have access to all group projects.
Read more about permissions
@@ -49,7 +47,7 @@
(#{@members.total_count})
%ul.well-list
- @members.each do |member|
= render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true
= render 'groups/group_members/group_member', member: member, show_controls: true
= paginate @members, theme: 'gitlab'
@@ -1,2 +1,2 @@
:plain
$("##{dom_id(@member)}").replaceWith('#{escape_javascript(render(@member, member: @member, show_controls: true))}');
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member, show_controls: true))}');
@@ -22,7 +22,7 @@
%span.label.label-gray
= milestone.project.name
.col-sm-6
- if can?(current_user, :admin_group, @group)
- if can?(current_user, :admin_milestones, @group)
- if milestone.closed?
= link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
- else
+13 -6
View File
@@ -3,15 +3,22 @@
= render 'shared/milestones_filter'
.gray-content-block
Only milestones from
%strong #{@group.name}
group are listed here.
- if can?(current_user, :admin_milestones, @group)
.pull-right
%span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
New Milestone
.oneline
Only milestones from
%strong #{@group.name}
group are listed here.
.milestones
%ul.content-list
- if @group_milestones.blank?
- if @milestones.blank?
%li
.nothing-here-block No milestones to show
- else
- @group_milestones.each do |milestone|
- @milestones.each do |milestone|
= render 'milestone', milestone: milestone
= paginate @group_milestones, theme: "gitlab"
= paginate @milestones, theme: "gitlab"
+48
View File
@@ -0,0 +1,48 @@
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
%h3.page-title
New Milestone
%p.light
This will create milestone in every selected project
%hr
= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f|
.row
.col-md-6
.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
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
.clearfix
.error-alert
.form-group
= f.label :projects, "Projects", class: "control-label"
.col-sm-10
= f.collection_select :project_ids, @group.projects, :id, :name,
{ selected: @group.projects.map(&:id) }, multiple: true, class: 'select2'
.col-md-6
.form-group
= f.label :due_date, "Due Date", class: "control-label"
.col-sm-10= f.hidden_field :due_date
.col-sm-10
.datepicker
.form-actions
= f.submit 'Create Milestone', class: "btn-create btn"
= link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
:javascript
$(".datepicker").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
+22 -22
View File
@@ -1,22 +1,22 @@
- page_title @group_milestone.title, "Milestones"
- page_title @milestone.title, "Milestones"
= render "header_title"
%h4.page-title
.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" }
- if @group_milestone.closed?
.issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @milestone.closed?
Closed
- else
Open
Milestone #{@group_milestone.title}
Milestone #{@milestone.title}
.pull-right
- if can?(current_user, :admin_group, @group)
- if @group_milestone.active?
= link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
- 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"
- else
= link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_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-sm btn-grouped btn-reopen"
%hr
- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active?
- if @milestone.complete? && @milestone.active?
.alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now.
@@ -30,7 +30,7 @@
%th Open issues
%th State
%th Due date
- @group_milestone.milestones.each do |milestone|
- @milestone.milestones.each do |milestone|
%tr
%td
= link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
@@ -47,46 +47,46 @@
.context
%p.lead
Progress:
#{@group_milestone.closed_items_count} closed
#{@milestone.closed_items_count} closed
&ndash;
#{@group_milestone.open_items_count} open
= milestone_progress_bar(@group_milestone)
#{@milestone.open_items_count} open
= milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
%span.badge= @group_milestone.issue_count
%span.badge= @milestone.issue_count
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests
%span.badge= @group_milestone.merge_requests_count
%span.badge= @milestone.merge_requests_count
%li
= link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants
%span.badge= @group_milestone.participants.count
%span.badge= @milestone.participants.count
.pull-right
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @group_milestone.title), class: "btn edit-milestone-link btn-grouped"
= 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
.col-md-6
= render 'issues', title: "Open", issues: @group_milestone.opened_issues
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @group_milestone.closed_issues
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.row
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @group_milestone.opened_merge_requests
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @group_milestone.closed_merge_requests
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
%ul.bordered-list
- @group_milestone.participants.each do |user|
- @milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
+1 -1
View File
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html"
xml.id group_url(@group)
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
@@ -1,4 +1,4 @@
Project #{@old_path_with_namespace} was moved to another location
Project <%= @old_path_with_namespace %> was moved to another location
The project is now located under
<%= namespace_project_url(@project.namespace, @project) %>
+6 -3
View File
@@ -2,9 +2,12 @@
%input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
= f.text_area attr, class: classes, placeholder: ''
- if defined?(f) && f
= f.text_area attr, class: classes, placeholder: ''
- else
= text_area_tag attr, nil, class: classes, placeholder: ''
%a.zen-enter-link(tabindex="-1" href="#")
%i.fa.fa-expand
= icon('expand')
Edit in fullscreen
%a.zen-leave-link(href="#")
%i.fa.fa-compress
= icon('compress')
+1 -1
View File
@@ -19,4 +19,4 @@
- if allowed_tree_edit?
.btn-group{ role: "group" }
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
%button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Remove
%button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
+6 -8
View File
@@ -5,21 +5,19 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Create New Directory
.modal-body
= form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, id: 'dir-create-form', class: 'form-horizontal' do
= form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form' do
.form-group
= label_tag :dir_name, 'Directory Name', class: 'control-label'
.col-sm-10
= text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
= render 'shared/commit_message_container', params: params, placeholder: ''
- unless @project.empty_repo?
.form-group
= label_tag :branch_name, 'Branch', class: 'control-label'
.col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control"
= render 'shared/new_commit_form', placeholder: "Add new directory"
.form-group
.col-sm-offset-2.col-sm-10
= submit_tag "Create directory", class: 'btn btn-primary btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
disableButtonIfAnyEmptyField($("#dir-create-form"), ".form-control", ".btn-create");
disableButtonIfAnyEmptyField($(".js-create-dir-form"), ".form-control", ".btn-create");
new NewCommitForm($('.js-create-dir-form'))
+8 -8
View File
@@ -3,16 +3,16 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Remove #{@blob.name}
%p.light
From branch
%strong= @ref
%h3.page-title Delete #{@blob.name}
.modal-body
= form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do
= render 'shared/commit_message_container', params: params,
placeholder: 'Removed this file because...'
= form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-requires-input' do
= render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
.form-group
.col-sm-offset-2.col-sm-10
= button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= button_tag 'Delete file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
new NewCommitForm($('.js-replace-blob-form'))
+7 -11
View File
@@ -5,7 +5,7 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title #{title}
.modal-body
= form_tag form_path, method: method, class: 'blob-file-upload-form-js form-horizontal' do
= form_tag form_path, method: method, class: 'js-upload-blob-form form-horizontal' do
.dropzone
.dropzone-previews.blob-upload-dropzone-previews
%p.dz-message.light
@@ -13,19 +13,15 @@
= link_to 'click to upload', '#', class: "markdown-selector"
%br
.dropzone-alerts{class: "alert alert-danger data", style: "display:none"}
= render 'shared/commit_message_container', params: params,
placeholder: placeholder
- unless @project.empty_repo?
.form-group.branch
= label_tag 'branch', class: 'control-label' do
Branch
.col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control"
= render 'shared/new_commit_form', placeholder: placeholder
.form-group
.col-sm-offset-2.col-sm-10
= button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
disableButtonIfEmptyField($('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file');
new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}');
disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
new NewCommitForm($('.js-upload-blob-form'))
+3 -8
View File
@@ -13,15 +13,9 @@
%i.fa.fa-eye
= editing_preview_title(@blob.name)
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input') do
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}"
.form-group.branch
= label_tag 'branch', class: 'control-label' do
Branch
.col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control"
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
@@ -30,3 +24,4 @@
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
new NewCommitForm($('.js-edit-blob-form'))
+5 -11
View File
@@ -2,20 +2,13 @@
= render "header_title"
.gray-content-block.top-block
Create a new file
%h3.page-title
Create New File
.file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do
= render 'projects/blob/editor', ref: @ref
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
- unless @project.empty_repo?
.form-group.branch
= label_tag 'branch', class: 'control-label' do
Branch
.col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit"
= render 'shared/new_commit_form', placeholder: "Add new file"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
@@ -23,3 +16,4 @@
:javascript
blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
new NewCommitForm($('.js-new-blob-form'))
+1 -3
View File
@@ -10,6 +10,4 @@
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
= render 'projects/blob/upload', title: title, placeholder: title,
button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id),
method: :put
= render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
+1 -1
View File
@@ -3,4 +3,4 @@
= render "commit_box"
= render "ci_menu" if @ci_commit
= render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form", view: params[:view]
= render "projects/notes/notes_with_form"
+1 -1
View File
@@ -1,4 +1,4 @@
- if params[:view] == 'parallel'
- if diff_view == 'parallel'
- fluid_layout true
- diff_files = safe_diff_files(diffs)
+1 -2
View File
@@ -33,7 +33,7 @@
-# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?')
- if blob.text?
- if params[:view] == 'parallel'
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
@@ -42,4 +42,3 @@
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
- else
.nothing-here-block No preview for this file type
@@ -7,7 +7,7 @@
= render 'shared/show_aside'
.gray-content-block.second-block
.gray-content-block.second-block.oneline-block
.row
.col-md-9
.votes-holder.pull-right
@@ -29,8 +29,6 @@
.issue-info
= "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- if issue.milestone
&nbsp;
%span
@@ -14,8 +14,10 @@
#votes= render 'votes/votes_block', votable: @merge_request
= render "projects/merge_requests/show/participants"
.col-md-3
%span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
.input-group.cross-project-reference
%span.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
= clipboard_button
.row
%section.col-md-9
@@ -34,8 +34,6 @@
.merge-request-info
= "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
- if merge_request.votes_count > 0
= render 'votes/votes_inline', votable: merge_request
- if merge_request.milestone_id?
&nbsp;
%span
@@ -23,9 +23,7 @@
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
.hint
.pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
.pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
= render 'projects/notes/hints'
.clearfix
.error-alert
.col-md-6
@@ -45,7 +43,7 @@
:javascript
$( ".datepicker" ).datepicker({
$(".datepicker").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
+1 -1
View File
@@ -1,5 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
= hidden_field_tag :view, params[:view]
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
= note_target_fields(@note)
= f.hidden_field :commit_id
+3 -21
View File
@@ -7,7 +7,7 @@
.note-header
- if note_editable?(note)
.note-actions
= link_to edit_namespace_project_note_path(note.project.namespace, note.project, note), title: 'Edit comment', remote: true, class: 'js-note-edit' do
= link_to '#', title: 'Edit comment', class: 'js-note-edit' do
= icon('pencil-square-o')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do
@@ -35,30 +35,12 @@
- if note.updated_by && note.updated_by != note.author
by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)}
- if note.superceded?(@notes)
- if note.upvote?
%span.vote.upvote.label.label-gray.strikethrough
= icon('thumbs-up')
\+1
- if note.downvote?
%span.vote.downvote.label.label-gray.strikethrough
= icon('thumbs-down')
\-1
- else
- if note.upvote?
%span.vote.upvote.label.label-success
= icon('thumbs-up')
\+1
- if note.downvote?
%span.vote.downvote.label.label-danger
= icon('thumbs-down')
\-1
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
- if note.attachment.url
.note-attachment

Some files were not shown because too many files have changed in this diff Show More