Commit 3eb15786 authored by Valery Sizov's avatar Valery Sizov

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

parents c2d03df8 07140614
...@@ -3,6 +3,15 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -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.3.0 (unreleased)
v 8.2.0 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) - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu)
- Fix Drone CI service template not saving properly (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) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu)
...@@ -10,6 +19,10 @@ v 8.2.0 ...@@ -10,6 +19,10 @@ v 8.2.0
- Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - 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 - Improved performance of finding users by one of their Email addresses
- Add allow_failure field to commit status API (Stan Hu) - 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 - Improved performance of replacing references in comments
- Show last project commit to default branch on project home page - Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL - Highlight comment based on anchor in URL
...@@ -27,6 +40,7 @@ v 8.2.0 ...@@ -27,6 +40,7 @@ v 8.2.0
- Allow to define cache in `.gitlab-ci.yml` - Allow to define cache in `.gitlab-ci.yml`
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
- Remove deprecated CI events from project settings page - 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 - [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) - Fix omniauth documentation setting for omnibus configuration (Jon Cairns)
- Add "New file" link to dropdown on project page - Add "New file" link to dropdown on project page
...@@ -34,6 +48,7 @@ v 8.2.0 ...@@ -34,6 +48,7 @@ v 8.2.0
- Add "added", "modified" and "removed" properties to commit object in webhook - 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 - 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 - 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 - New design for project graphs page
- Remove deprecated dumped yaml file generated from previous job definitions - Remove deprecated dumped yaml file generated from previous job definitions
- Fix incoming email config defaults - Fix incoming email config defaults
...@@ -47,6 +62,9 @@ v 8.2.0 ...@@ -47,6 +62,9 @@ v 8.2.0
- Fix trailing whitespace issue in merge request/issue title - Fix trailing whitespace issue in merge request/issue title
- Fix bug when milestone/label filter was empty for dashboard issues page - Fix bug when milestone/label filter was empty for dashboard issues page
- Add ability to create milestone in group projects from single form - 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 v 8.1.4
- Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu)
......
...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic
## Security vulnerability disclosure ## 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 ## 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 ...@@ -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. 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 ### Issue tracker guidelines
...@@ -72,7 +72,7 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -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. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) 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 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. Push the commit to your fork
1. Submit a merge request (MR) to the master branch 1. Submit a merge request (MR) to the master branch
1. The MR title should describe the change you want to make 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 ...@@ -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 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/)
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 + "']")
\ No newline at end of file
...@@ -23,18 +23,6 @@ class @BlobFileDropzone ...@@ -23,18 +23,6 @@ class @BlobFileDropzone
init: -> init: ->
this.on 'addedfile', (file) -> this.on 'addedfile', (file) ->
$('.dropzone-alerts').html('').hide() $('.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 return
...@@ -47,8 +35,9 @@ class @BlobFileDropzone ...@@ -47,8 +35,9 @@ class @BlobFileDropzone
return return
this.on 'sending', (file, xhr, formData) -> this.on 'sending', (file, xhr, formData) ->
formData.append('new_branch', form.find('#new_branch').val()) formData.append('new_branch', form.find('.js-new-branch').val())
formData.append('commit_message', form.find('#commit_message').val()) formData.append('create_merge_request', form.find('.js-create-merge-request').val())
formData.append('commit_message', form.find('.js-commit-message').val())
return return
# Override behavior of adding error underneath preview # Override behavior of adding error underneath preview
......
...@@ -9,13 +9,24 @@ $ -> ...@@ -9,13 +9,24 @@ $ ->
clipboard.on 'success', (e) -> clipboard.on 'success', (e) ->
$(e.trigger). $(e.trigger).
tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!'). 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 # Clear the selection and blur the trigger so it loses its border
e.clearSelection() e.clearSelection()
$(e.trigger).blur() $(e.trigger).blur()
# Manually hide the tooltip after 1 second # Safari doesn't support `execCommand`, so instead we inform the user to
setTimeout(-> # copy manually.
$(e.trigger).tooltip('hide') #
, 1000) # 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 ...@@ -28,6 +28,8 @@ class Dispatcher
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.milestone-form')) new DropzoneInput($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
when 'projects:compare:show' when 'projects:compare:show'
new Diff() new Diff()
when 'projects:issues:new','projects:issues:edit' when 'projects:issues:new','projects:issues:edit'
......
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
...@@ -29,6 +29,7 @@ class @Notes ...@@ -29,6 +29,7 @@ class @Notes
$(document).on "ajax:success", "form.edit_note", @updateNote $(document).on "ajax:success", "form.edit_note", @updateNote
# Edit note link # Edit note link
$(document).on "click", ".js-note-edit", @showEditForm
$(document).on "click", ".note-edit-cancel", @cancelEdit $(document).on "click", ".note-edit-cancel", @cancelEdit
# Reopen and close actions for Issue/MR combined with note form submit # Reopen and close actions for Issue/MR combined with note form submit
...@@ -66,6 +67,7 @@ class @Notes ...@@ -66,6 +67,7 @@ class @Notes
$(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", ".js-discussion-note-form"
$(document).off "ajax:success", "form.edit_note" $(document).off "ajax:success", "form.edit_note"
$(document).off "click", ".js-note-edit"
$(document).off "click", ".note-edit-cancel" $(document).off "click", ".note-edit-cancel"
$(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-delete"
$(document).off "click", ".js-note-attachment-delete" $(document).off "click", ".js-note-attachment-delete"
...@@ -111,13 +113,16 @@ class @Notes ...@@ -111,13 +113,16 @@ class @Notes
renderNote: (note) -> renderNote: (note) ->
# render note if it not present in loaded list # render note if it not present in loaded list
# or skip if rendered # or skip if rendered
if @isNewNote(note) if @isNewNote(note) && !note.award
@note_ids.push(note.id) @note_ids.push(note.id)
$('ul.main-notes-list'). $('ul.main-notes-list').
append(note.html). append(note.html).
syntaxHighlight() syntaxHighlight()
@initTaskList() @initTaskList()
if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
### ###
Check if note does not exists on page Check if note does not exists on page
### ###
...@@ -253,7 +258,6 @@ class @Notes ...@@ -253,7 +258,6 @@ class @Notes
### ###
addNote: (xhr, note, status) => addNote: (xhr, note, status) =>
@renderNote(note) @renderNote(note)
@updateVotes()
### ###
Called in response to the new note form being submitted Called in response to the new note form being submitted
...@@ -285,14 +289,13 @@ class @Notes ...@@ -285,14 +289,13 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels if the user cancels
### ###
showEditForm: (note, formHTML) -> showEditForm: (e) ->
nodeText = note.find(".note-text"); e.preventDefault()
nodeText.hide() note = $(this).closest(".note")
note.find('.note-edit-form').remove()
nodeText.after(formHTML)
note.find(".note-body > .note-text").hide() note.find(".note-body > .note-text").hide()
note.find(".note-header").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.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove() form.find('.div-dropzone').remove()
...@@ -472,9 +475,6 @@ class @Notes ...@@ -472,9 +475,6 @@ class @Notes
form = $(e.target).closest(".js-discussion-note-form") form = $(e.target).closest(".js-discussion-note-form")
@removeDiscussionNoteForm(form) @removeDiscussionNoteForm(form)
updateVotes: ->
true
### ###
Called after an attachment file has been selected. Called after an attachment file has been selected.
......
...@@ -6,7 +6,7 @@ window.ContributorsStatGraphUtil = ...@@ -6,7 +6,7 @@ window.ContributorsStatGraphUtil =
for entry in log for entry in log
@add_date(entry.date, total) unless total[entry.date]? @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) data ?= @add_author(entry, by_author, by_email)
@add_date(entry.date, data) unless data[entry.date] @add_date(entry.date, data) unless data[entry.date]
...@@ -95,5 +95,4 @@ window.ContributorsStatGraphUtil = ...@@ -95,5 +95,4 @@ window.ContributorsStatGraphUtil =
if date_range is null || date_range[0] <= new Date(date) <= date_range[1] if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
true true
else else
false false
\ No newline at end of file
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
li { li {
padding: 3px 0px; padding: 3px 0px;
line-height: 20px;
} }
} }
.new-file { .new-file {
......
...@@ -101,3 +101,71 @@ ...@@ -101,3 +101,71 @@
background-color: $background-color; 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;
}
}
}
...@@ -15,10 +15,10 @@ module Ci ...@@ -15,10 +15,10 @@ module Ci
@builds = @config_processor.builds @builds = @config_processor.builds
@status = true @status = true
end end
rescue Ci::GitlabCiYamlProcessor::ValidationError => e rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
@error = e.message @error = e.message
@status = false @status = false
rescue Exception rescue
@error = "Undefined error" @error = "Undefined error"
@status = false @status = false
end end
......
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
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
class Dashboard::MilestonesController < Dashboard::ApplicationController 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 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 end
def show def show
project_milestones = Milestone.where(project_id: @projects).order("due_date ASC")
@dashboard_milestone = Milestones::GroupService.new(project_milestones).milestone(title)
end end
private private
def load_projects def projects
@projects = current_user.authorized_projects.sorted_by_activity.non_archived @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")
end end
end end
class DashboardController < Dashboard::ApplicationController class DashboardController < Dashboard::ApplicationController
before_action :event_filter, only: :activity before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests]
respond_to :html respond_to :html
...@@ -47,4 +48,8 @@ class DashboardController < Dashboard::ApplicationController ...@@ -47,4 +48,8 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations @events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
end end
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end end
class Groups::ApplicationController < ApplicationController class Groups::ApplicationController < ApplicationController
layout 'group' layout 'group'
before_action :group
private private
......
class Groups::AvatarsController < ApplicationController class Groups::AvatarsController < Groups::ApplicationController
def destroy def destroy
@group = Group.find_by(path: params[:group_id])
@group.remove_avatar! @group.remove_avatar!
@group.save @group.save
redirect_to edit_group_path(@group) redirect_to edit_group_path(@group)
......
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
skip_before_action :authenticate_user!, only: [:index] skip_before_action :authenticate_user!, only: [:index]
before_action :group
# Authorize # Authorize
before_action :authorize_read_group! before_action :authorize_read_group!
before_action :authorize_admin_group!, except: [:index, :leave] before_action :authorize_admin_group_member!, except: [:index, :leave]
before_action :authorize_admin_group_member!, only: [:create, :resend_invite]
def index def index
@project = @group.projects.find(params[:project_id]) if params[:project_id] @project = @group.projects.find(params[:project_id]) if params[:project_id]
...@@ -18,7 +16,8 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -18,7 +16,8 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
@members = @members.order('access_level DESC').page(params[:page]).per(50) @members = @members.order('access_level DESC').page(params[:page]).per(50)
@group_member = GroupMember.new
@group_member = @group.group_members.new
end end
def create def create
...@@ -36,30 +35,28 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -36,30 +35,28 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def update 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) if @group_member.update_attributes(member_params)
log_audit_event(@member, action: :update, old_access_level: old_access_level) log_audit_event(@group_member, action: :update, old_access_level: old_access_level)
end end
end end
def destroy def destroy
@group_member = @group.group_members.find(params[:id]) @group_member = @group.group_members.find(params[:id])
if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner. return render_403 unless can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
log_audit_event(@group_member, action: :destroy)
respond_to do |format| @group_member.destroy
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } log_audit_event(@group_member, action: :destroy)
format.js { render nothing: true }
end respond_to do |format|
else format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
return render_403 format.js { render nothing: true }
end end
end end
...@@ -78,7 +75,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -78,7 +75,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def leave 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) if can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy @group_member.destroy
......
class Groups::MilestonesController < Groups::ApplicationController 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 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 end
def show def new
project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") @milestone = Milestone.new
@group_milestone = Milestones::GroupService.new(project_milestones).milestone(title)
end end
def update def create
project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") project_ids = params[:milestone][:project_ids]
@group_milestones = Milestones::GroupService.new(project_milestones).milestone(title) title = milestone_params[:title]
@group_milestones.milestones.each do |milestone| @group.projects.where(id: project_ids).each do |project|
Milestones::UpdateService.new(milestone.project, current_user, params[:milestone]).execute(milestone) Milestones::CreateService.new(project, current_user, milestone_params).execute
end end
respond_to do |format| redirect_to milestone_path(title)
format.js end
format.html do
redirect_to group_milestones_path(group) def show
end end
def update
@milestone.milestones.each do |milestone|
Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone)
end end
redirect_back_or_default(default: milestone_path(@milestone.title))
end end
private private
def group def authorize_group_milestone!
@group ||= Group.find_by(path: params[:group_id]) return render_404 unless can?(current_user, :admin_milestones, group)
end end
def title def milestone_params
params[:title] params.require(:milestone).permit(:title, :description, :due_date, :state_event)
end end
def state(state = nil) def milestone_path(title)
conditions = { project_id: group.projects } group_milestone_path(@group, title.parameterize, title: title)
conditions.reverse_merge!(state: state) if state
Milestone.where(conditions).order("title ASC")
end end
def authorize_group_milestone! def projects
return render_404 unless can?(current_user, :admin_group, group) @projects ||= @group.projects
end end
end end
# Controller for viewing a file's blame # Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController class Projects::BlobController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path # Raised when given an invalid file path
...@@ -22,21 +23,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -22,21 +23,9 @@ class Projects::BlobController < Projects::ApplicationController
end end
def create def create
result = Files::CreateService.new(@project, current_user, @commit_params).execute create_commit(Files::CreateService, success_path: after_create_path,
failure_view: :new,
if result[:status] == :success failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
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
end end
def show def show
...@@ -47,21 +36,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -47,21 +36,9 @@ class Projects::BlobController < Projects::ApplicationController
end end
def update def update
result = Files::UpdateService.new(@project, current_user, @commit_params).execute create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit,
if result[:status] == :success failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
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
end end
def preview def preview
...@@ -77,7 +54,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -77,7 +54,7 @@ class Projects::BlobController < Projects::ApplicationController
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" 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 else
flash[:alert] = result[:message] flash[:alert] = result[:message]
render :show render :show
...@@ -131,15 +108,51 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -131,15 +108,51 @@ class Projects::BlobController < Projects::ApplicationController
render_404 render_404
end 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 def after_edit_path
@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) + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}" "#file-path-#{hexdigest(@path)}"
elsif @target_branch.present?
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else 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
end end
...@@ -154,7 +167,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -154,7 +167,7 @@ class Projects::BlobController < Projects::ApplicationController
def editor_variables def editor_variables
@current_branch = @ref @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 = @file_path =
if action_name.to_s == 'create' if action_name.to_s == 'create'
...@@ -174,7 +187,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -174,7 +187,7 @@ class Projects::BlobController < Projects::ApplicationController
@commit_params = { @commit_params = {
file_path: @file_path, file_path: @file_path,
current_branch: @current_branch, current_branch: @current_branch,
target_branch: @target_branch, target_branch: @new_branch,
commit_message: params[:commit_message], commit_message: params[:commit_message],
file_content: params[:content], file_content: params[:content],
file_content_encoding: params[:encoding] file_content_encoding: params[:encoding]
......
...@@ -20,8 +20,8 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -20,8 +20,8 @@ class Projects::CompareController < Projects::ApplicationController
if compare_result if compare_result
@commits = Commit.decorate(compare_result.commits, @project) @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs @diffs = compare_result.diffs
@commit = @commits.last @commit = @project.commit(head_ref)
@first_commit = @commits.first @first_commit = @project.commit(base_ref)
@line_notes = [] @line_notes = []
end end
end end
......
...@@ -66,7 +66,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -66,7 +66,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show def show
@participants = @issue.participants(current_user) @participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.with_associations.fresh @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue @noteable = @issue
respond_with(@issue) respond_with(@issue)
......
...@@ -294,7 +294,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -294,7 +294,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @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) @discussions = Note.discussions_from_notes(@notes)
@noteable = @merge_request @noteable = @merge_request
......
...@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_read_note! before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy] 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 def index
current_fetched_at = Time.now.to_i current_fetched_at = Time.now.to_i
...@@ -29,11 +29,6 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -29,11 +29,6 @@ class Projects::NotesController < Projects::ApplicationController
end end
end end
def edit
@note = note
render layout: false
end
def update def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
...@@ -63,6 +58,30 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -63,6 +58,30 @@ class Projects::NotesController < Projects::ApplicationController
end end
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 private
def note def note
...@@ -116,6 +135,9 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -116,6 +135,9 @@ class Projects::NotesController < Projects::ApplicationController
id: note.id, id: note.id,
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
html: note_to_html(note), 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_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note) discussion_with_diff_html: note_to_discussion_with_diff_html(note)
} }
......
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project!, except: :leave before_action :authorize_admin_project_member!, except: :leave
def index def index
@project_members = @project.project_members @project_members = @project.project_members
...@@ -30,10 +30,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -30,10 +30,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project_group_links = @project.project_group_links @project_group_links = @project.project_group_links
end end
def new
@project_member = @project.project_members.new
end
def create def create
@project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user) @project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user)
members = @project.project_members.where(user_id: params[:user_ids].split(',')) members = @project.project_members.where(user_id: params[:user_ids].split(','))
...@@ -47,6 +43,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -47,6 +43,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def update def update
@project_member = @project.project_members.find(params[:id]) @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 old_access_level = @project_member.human_access
if @project_member.update_attributes(member_params) if @project_member.update_attributes(member_params)
...@@ -56,7 +55,11 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -56,7 +55,11 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def destroy def destroy
@project_member = @project.project_members.find(params[:id]) @project_member = @project.project_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_project_member, @project_member)
@project_member.destroy @project_member.destroy
log_audit_event(@project_member, action: :destroy) log_audit_event(@project_member, action: :destroy)
respond_to do |format| respond_to do |format|
...@@ -82,18 +85,24 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -82,18 +85,24 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end end
def leave 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 = @project.project_members.find_by(user_id: current_user)
@project_member.destroy
log_audit_event(@project_member, action: :destroy)
respond_to do |format| if can?(current_user, :destroy_project_member, @project_member)
format.html { redirect_to dashboard_projects_path } @project_member.destroy
format.js { render nothing: true }
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
end end
......
# Controller for viewing a repository's file structure # Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController class Projects::TreeController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
...@@ -43,7 +44,7 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -43,7 +44,7 @@ class Projects::TreeController < Projects::ApplicationController
if result && result[:status] == :success if result && result[:status] == :success
flash[:notice] = "The directory has been successfully created" flash[:notice] = "The directory has been successfully created"
respond_to do |format| 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 end
else else
flash[:alert] = message flash[:alert] = message
...@@ -53,6 +54,8 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -53,6 +54,8 @@ class Projects::TreeController < Projects::ApplicationController
end end
end end
private
def assign_dir_vars def assign_dir_vars
@new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref @new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref
@dir_name = File.join(@path, params[:dir_name]) @dir_name = File.join(@path, params[:dir_name])
...@@ -63,4 +66,12 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -63,4 +66,12 @@ class Projects::TreeController < Projects::ApplicationController
commit_message: params[:commit_message], commit_message: params[:commit_message],
} }
end 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 end
class SnippetsController < ApplicationController class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
before_action :authorize_read_snippet!, only: [:show]
# Allow modify snippet # Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update] before_action :authorize_update_snippet!, only: [:edit, :update]
...@@ -79,10 +82,14 @@ class SnippetsController < ApplicationController ...@@ -79,10 +82,14 @@ class SnippetsController < ApplicationController
[Snippet::PUBLIC, Snippet::INTERNAL]). [Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id]) find(params[:id])
else else
PersonalSnippet.are_public.find(params[:id]) PersonalSnippet.find(params[:id])
end end
end end
def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
end
def authorize_update_snippet! def authorize_update_snippet!
return render_404 unless can?(current_user, :update_personal_snippet, @snippet) return render_404 unless can?(current_user, :update_personal_snippet, @snippet)
end end
......
...@@ -3,14 +3,11 @@ class UsersController < ApplicationController ...@@ -3,14 +3,11 @@ class UsersController < ApplicationController
before_action :set_user before_action :set_user
def show def show
@contributed_projects = contributed_projects.joined(@user). @contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
reject(&:forked?)
@projects = @user.personal_projects. @projects = PersonalProjectsFinder.new(@user).execute(current_user)
where(id: authorized_projects_ids).includes(:namespace)
# Collect only groups common for both users @groups = JoinedGroupsFinder.new(@user).execute(current_user)
@groups = @user.groups & GroupsFinder.new.execute(current_user)
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -53,16 +50,8 @@ class UsersController < ApplicationController ...@@ -53,16 +50,8 @@ class UsersController < ApplicationController
@user = User.find_by_username!(params[:username]) @user = User.find_by_username!(params[:username])
end end
def authorized_projects_ids
# Projects user can view
@authorized_projects_ids ||=
ProjectsFinder.new.execute(current_user).pluck(:id)
end
def contributed_projects def contributed_projects
@contributed_projects = Project. ContributedProjectsFinder.new(@user).execute(current_user)
where(id: authorized_projects_ids & @user.contributed_projects_ids).
includes(:namespace)
end end
def contributions_calendar def contributions_calendar
...@@ -73,9 +62,13 @@ class UsersController < ApplicationController ...@@ -73,9 +62,13 @@ class UsersController < ApplicationController
def load_events def load_events
# Get user activity feed for projects common for both users # Get user activity feed for projects common for both users
@events = @user.recent_events. @events = @user.recent_events.
where(project_id: authorized_projects_ids). merge(projects_for_current_user).
with_associations 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
end end
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
class GroupsFinder class GroupsFinder
def execute(current_user, options = {}) # Finds the groups available to the given user.
all_groups(current_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 end
private private
def all_groups(current_user) # This method returns the groups "current_user" can see.
group_ids = if current_user def groups_visible_to_user(current_user)
if current_user.authorized_groups.any? base = groups_for_projects(public_and_internal_projects)
# User has access to groups
# union = Gitlab::SQL::Union.
# Return only: new([base.select(:id), current_user.authorized_groups.select(:id)])
# groups with public projects
# groups with internal projects Group.where("namespaces.id IN (#{union.to_sql})")
# groups with joined projects end
#
Project.public_and_internal_only.pluck(:namespace_id) + def public_groups
current_user.authorized_groups.pluck(:id) groups_for_projects(public_projects)
else end
# User has no group membership
# def groups_for_projects(projects)
# Return only: Group.public_and_given_groups(projects.select(:namespace_id))
# groups with public projects end
# groups with internal projects
# def public_projects
Project.public_and_internal_only.pluck(:namespace_id) Project.unscoped.public_only
end end
else
# Not authenticated def public_and_internal_projects
# Project.unscoped.public_and_internal_only
# Return only:
# groups with public projects
Project.public_only.pluck(:namespace_id)
end
Group.where("public IS TRUE OR id IN(?)", group_ids)
end end
end end
# 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
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
...@@ -12,9 +12,9 @@ class NotesFinder ...@@ -12,9 +12,9 @@ class NotesFinder
when "commit" when "commit"
project.notes.for_commit_id(target_id).not_inline project.notes.for_commit_id(target_id).not_inline
when "issue" when "issue"
project.issues.find(target_id).notes.inc_author project.issues.find(target_id).notes.nonawards.inc_author
when "merge_request" 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" when "snippet", "project_snippet"
project.snippets.find(target_id).notes project.snippets.find(target_id).notes
else else
......
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
class ProjectsFinder 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] group = options[:group]
if group if group
group_projects(current_user, group) segments = group_projects(current_user, group)
else 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
end end
...@@ -13,89 +41,37 @@ class ProjectsFinder ...@@ -13,89 +41,37 @@ class ProjectsFinder
def group_projects(current_user, group) def group_projects(current_user, group)
if current_user if current_user
if group.users.include?(current_user) [
# User is group member group_projects_for_user(current_user, group),
# group.projects.public_and_internal_only,
# Return ALL group projects group.shared_projects.visible_to_user(current_user)
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
else else
# Not authenticated [group.projects.public_only]
#
# Return only:
# public projects
group.projects.public_only
end end
end end
def all_projects(current_user) def all_projects(current_user)
if current_user if current_user
if current_user.authorized_projects.any? [current_user.authorized_projects, public_and_internal_projects]
# User has access to private projects else
# [Project.public_only]
# Return only: end
# public projects end
# internal projects
# joined projects def group_projects_for_user(current_user, group)
# if group.users.include?(current_user)
Project.where( group.projects
"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
else else
# Not authenticated group.projects.visible_to_user(current_user)
#
# Return only:
# public projects
Project.public_only
end end
end end
def public_projects
Project.unscoped.public_only
end
def public_and_internal_projects
Project.unscoped.public_and_internal_only
end
end end
module DiffHelper module DiffHelper
def diff_view
params[:view] == 'parallel' ? 'parallel' : 'inline'
end
def allowed_diff_size def allowed_diff_size
if diff_hard_limit_enabled? if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_FILES Commit::DIFF_HARD_LIMIT_FILES
...@@ -137,7 +141,7 @@ module DiffHelper ...@@ -137,7 +141,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button # Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format) 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' 'Inline'
end end
end end
...@@ -148,7 +152,7 @@ module DiffHelper ...@@ -148,7 +152,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button # Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format) 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' 'Side-by-side'
end end
end end
...@@ -171,7 +175,7 @@ module DiffHelper ...@@ -171,7 +175,7 @@ module DiffHelper
def commit_for_diff(diff) def commit_for_diff(diff)
if diff.deleted_file if diff.deleted_file
first_commit = @first_commit || @commit first_commit = @first_commit || @commit
first_commit.parent first_commit.parent || @first_commit
else else
@commit @commit
end end
......
...@@ -12,7 +12,7 @@ module GroupsHelper ...@@ -12,7 +12,7 @@ module GroupsHelper
end end
def should_user_see_group_roles?(user, group) def should_user_see_group_roles?(user, group)
if user if user && group
user.is_admin? || group.members.exists?(user_id: user.id) user.is_admin? || group.members.exists?(user_id: user.id)
else else
false false
......
...@@ -87,6 +87,33 @@ module IssuesHelper ...@@ -87,6 +87,33 @@ module IssuesHelper
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ') merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
end 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 # Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue module_function :url_for_issue
end end
...@@ -100,7 +100,7 @@ module LabelsHelper ...@@ -100,7 +100,7 @@ module LabelsHelper
Label.where(project_id: @projects) Label.where(project_id: @projects)
end end
grouped_labels = Labels::GroupService.new(labels).execute grouped_labels = GlobalLabel.build_collection(labels)
grouped_labels.unshift(Label::None) grouped_labels.unshift(Label::None)
grouped_labels.unshift(Label::Any) grouped_labels.unshift(Label::Any)
......
...@@ -8,14 +8,6 @@ module MergeRequestsHelper ...@@ -8,14 +8,6 @@ module MergeRequestsHelper
) )
end 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) def new_mr_from_push_event(event, target_project)
{ {
merge_request: { merge_request: {
......
...@@ -28,7 +28,7 @@ module MilestonesHelper ...@@ -28,7 +28,7 @@ module MilestonesHelper
Milestone.where(project_id: @projects) Milestone.where(project_id: @projects)
end.active end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones.unshift(Milestone::None) grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any) grouped_milestones.unshift(Milestone::Any)
......
class Ability class Ability
class << self class << self
def allowed(user, subject) def allowed(user, subject)
return not_auth_abilities(user, subject) if user.nil? return anonymous_abilities(user, subject) if user.nil?
return [] unless user.kind_of?(User) return [] unless user.is_a?(User)
return [] if user.blocked? return [] if user.blocked?
abilities = abilities =
case subject.class.name case subject.class.name
when "Project" then project_abilities(user, subject) when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(user, subject) when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(user, subject) when "Note" then note_abilities(user, subject)
when "ProjectSnippet" then project_snippet_abilities(user, subject) when "ProjectSnippet" then project_snippet_abilities(user, subject)
when "PersonalSnippet" then personal_snippet_abilities(user, subject) when "PersonalSnippet" then personal_snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(user, subject) when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject) when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject)
when "GroupMember" then group_member_abilities(user, subject) when "GroupMember" then group_member_abilities(user, subject)
when "ProjectMember" then project_member_abilities(user, subject)
else [] else []
end end
...@@ -35,15 +36,25 @@ class Ability ...@@ -35,15 +36,25 @@ class Ability
] ]
end end
# List of possible abilities # List of possible abilities for anonymous user
# for non-authenticated user def anonymous_abilities(user, subject)
def not_auth_abilities(user, subject) case true
project = if subject.kind_of?(Project) 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 subject
elsif subject.respond_to?(:project)
subject.project
else else
nil subject.project
end end
if project && project.public? if project && project.public?
...@@ -63,19 +74,29 @@ class Ability ...@@ -63,19 +74,29 @@ class Ability
rules - project_disabled_features_rules(project) rules - project_disabled_features_rules(project)
else else
group = if subject.kind_of?(Group) []
subject end
elsif subject.respond_to?(:group) end
subject.group
else
nil
end
if group && group.public_profile? def anonymous_group_abilities(subject)
[:read_group] group = if subject.is_a?(Group)
else subject
[] else
end 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
end end
...@@ -247,21 +268,22 @@ class Ability ...@@ -247,21 +268,22 @@ class Ability
# Only group masters and group owners can create new projects in group # Only group masters and group owners can create new projects in group
if group.has_master?(user) || group.has_owner?(user) || user.admin? if group.has_master?(user) || group.has_owner?(user) || user.admin?
rules.push(*[ rules += [
:create_projects, :create_projects,
]) :admin_milestones
]
end end
# Only group owner and administrators can admin group # Only group owner and administrators can admin group
if group.has_owner?(user) || user.admin? if group.has_owner?(user) || user.admin?
rules.push(*[ rules += [
:admin_group, :admin_group,
:admin_namespace, :admin_namespace,
:admin_group_member :admin_group_member
]) ]
unless group.ldap_synced? if group.ldap_synced?
rules << :admin_group_member rules.delete(:admin_group_member)
end end
end end
...@@ -273,16 +295,15 @@ class Ability ...@@ -273,16 +295,15 @@ class Ability
# Only namespace owner and administrators can admin it # Only namespace owner and administrators can admin it
if namespace.owner == user || user.admin? if namespace.owner == user || user.admin?
rules.push(*[ rules += [
:create_projects, :create_projects,
:admin_namespace :admin_namespace
]) ]
end end
rules.flatten rules.flatten
end end
[:issue, :merge_request].each do |name| [:issue, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject| define_method "#{name}_abilities" do |user, subject|
rules = [] rules = []
...@@ -299,7 +320,7 @@ class Ability ...@@ -299,7 +320,7 @@ class Ability
end end
end end
[:note, :project_snippet, :personal_snippet].each do |name| [:note, :project_snippet].each do |name|
define_method "#{name}_abilities" do |user, subject| define_method "#{name}_abilities" do |user, subject|
rules = [] rules = []
...@@ -319,19 +340,61 @@ class Ability ...@@ -319,19 +340,61 @@ class Ability
end end
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) def group_member_abilities(user, subject)
rules = [] rules = []
target_user = subject.user target_user = subject.user
group = subject.group group = subject.group
can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && (user != target_user) unless group.last_owner?(target_user)
rules << :update_group_member can_manage = group_abilities(user, group).include?(:admin_group_member)
rules << :destroy_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 end
if !group.last_owner?(user) && (can_manage || (user == target_user)) rules
rules << :destroy_group_member 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 end
rules rules
...@@ -339,10 +402,10 @@ class Ability ...@@ -339,10 +402,10 @@ class Ability
def abilities def abilities
@abilities ||= begin @abilities ||= begin
abilities = Six.new abilities = Six.new
abilities << self abilities << self
abilities abilities
end end
end end
private private
......
...@@ -188,13 +188,13 @@ module Ci ...@@ -188,13 +188,13 @@ module Ci
end end
def config_processor def config_processor
return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace) @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) save_yaml_error(e.message)
nil nil
rescue Exception => e rescue
logger.error e.message + "\n" + e.backtrace.join("\n") save_yaml_error("Undefined error")
save_yaml_error("Undefined yaml error")
nil nil
end end
......
...@@ -89,39 +89,14 @@ module Issuable ...@@ -89,39 +89,14 @@ module Issuable
opened? || reopened? opened? || reopened?
end end
# # Deprecated. Still exists to preserve API compatibility.
# Votes
#
# Return the number of -1 comments (downvotes)
def downvotes def downvotes
filter_superceded_votes(notes.select(&:downvote?), notes).size 0
end end
def downvotes_in_percent # Deprecated. Still exists to preserve API compatibility.
if votes_count.zero?
0
else
100.0 - upvotes_in_percent
end
end
# Return the number of +1 comments (upvotes)
def upvotes def upvotes
filter_superceded_votes(notes.select(&:upvote?), notes).size 0
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
end end
def subscribed?(user) def subscribed?(user)
...@@ -183,18 +158,4 @@ module Issuable ...@@ -183,18 +158,4 @@ module Issuable
def notes_with_associations def notes_with_associations
notes.includes(:author, :project) notes.includes(:author, :project)
end 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 end
...@@ -8,8 +8,9 @@ module Sortable ...@@ -8,8 +8,9 @@ module Sortable
included do included do
# By default all models should be ordered # By default all models should be ordered
# by created_at field starting from newest # 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_desc, -> { reorder(created_at: :desc) }
scope :order_created_asc, -> { reorder(created_at: :asc) } scope :order_created_asc, -> { reorder(created_at: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc) } scope :order_updated_desc, -> { reorder(updated_at: :desc) }
......
...@@ -63,6 +63,16 @@ class Event < ActiveRecord::Base ...@@ -63,6 +63,16 @@ class Event < ActiveRecord::Base
Event::PUSHED, ["MergeRequest", "Issue"], Event::PUSHED, ["MergeRequest", "Issue"],
[Event::CREATED, Event::CLOSED, Event::MERGED]) [Event::CREATED, Event::CLOSED, Event::MERGED])
end 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 end
def proper? def proper?
......
class GroupLabel class GlobalLabel
attr_accessor :title, :labels attr_accessor :title, :labels
alias_attribute :name, :title 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) def initialize(title, labels)
@title = title @title = title
@labels = labels @labels = labels
......
class GroupMilestone class GlobalMilestone
attr_accessor :title, :milestones attr_accessor :title, :milestones
alias_attribute :name, :title 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) def initialize(title, milestones)
@title = title @title = title
@milestones = milestones @milestones = milestones
...@@ -10,7 +18,7 @@ class GroupMilestone ...@@ -10,7 +18,7 @@ class GroupMilestone
def safe_title def safe_title
@title.parameterize @title.parameterize
end end
def projects def projects
milestones.map { |milestone| milestone.project } milestones.map { |milestone| milestone.project }
end end
...@@ -60,15 +68,15 @@ class GroupMilestone ...@@ -60,15 +68,15 @@ class GroupMilestone
end end
def issues def issues
@group_issues ||= milestones.map(&:issues).flatten.group_by(&:state) @issues ||= milestones.map(&:issues).flatten.group_by(&:state)
end end
def merge_requests 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 end
def participants def participants
@group_participants ||= milestones.map(&:participants).flatten.compact.uniq @participants ||= milestones.map(&:participants).flatten.compact.uniq
end end
def opened_issues def opened_issues
...@@ -86,4 +94,8 @@ class GroupMilestone ...@@ -86,4 +94,8 @@ class GroupMilestone
def closed_merge_requests def closed_merge_requests
merge_requests.values_at("closed", "merged", "locked").compact.flatten merge_requests.values_at("closed", "merged", "locked").compact.flatten
end end
def complete?
total_items_count == closed_items_count
end
end end
...@@ -20,8 +20,9 @@ require 'file_size_validator' ...@@ -20,8 +20,9 @@ require 'file_size_validator'
class Group < Namespace class Group < Namespace
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Referable include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
alias_method :members, :group_members
has_many :users, through: :group_members has_many :users, through: :group_members
has_many :project_group_links, dependent: :destroy has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project has_many :shared_projects, through: :project_group_links, source: :project
...@@ -52,6 +53,14 @@ class Group < Namespace ...@@ -52,6 +53,14 @@ class Group < Namespace
def reference_pattern def reference_pattern
User.reference_pattern User.reference_pattern
end 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 end
def to_reference(_from_project = nil) def to_reference(_from_project = nil)
...@@ -114,10 +123,6 @@ class Group < Namespace ...@@ -114,10 +123,6 @@ class Group < Namespace
has_owner?(user) && owners.size == 1 has_owner?(user) && owners.size == 1
end end
def members
group_members
end
def avatar_type def avatar_type
unless self.avatar.image? unless self.avatar.image?
self.errors.add :avatar, "only images allowed" self.errors.add :avatar, "only images allowed"
......
...@@ -31,13 +31,22 @@ class Member < ActiveRecord::Base ...@@ -31,13 +31,22 @@ class Member < ActiveRecord::Base
validates :user, presence: true, unless: :invite? validates :user, presence: true, unless: :invite?
validates :source, presence: true 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", message: "already exists in source",
allow_nil: true } allow_nil: true }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :invite_email, presence: { if: :invite? }, validates :invite_email,
email: { strict_mode: true, allow_nil: true }, presence: {
uniqueness: { scope: [:source_type, :source_id], allow_nil: true } 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 :invite, -> { where(user_id: nil) }
scope :non_invite, -> { where("user_id IS NOT NULL") } scope :non_invite, -> { where("user_id IS NOT NULL") }
...@@ -74,7 +83,7 @@ class Member < ActiveRecord::Base ...@@ -74,7 +83,7 @@ class Member < ActiveRecord::Base
def add_user(members, user_id, access_level, current_user = nil, skip_notification: false) def add_user(members, user_id, access_level, current_user = nil, skip_notification: false)
user = user_for_id(user_id) user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited # `user` can be either a User object or an email to be invited
if user.is_a?(User) if user.is_a?(User)
member = members.find_or_initialize_by(user_id: user.id) member = members.find_or_initialize_by(user_id: user.id)
...@@ -83,12 +92,23 @@ class Member < ActiveRecord::Base ...@@ -83,12 +92,23 @@ class Member < ActiveRecord::Base
member.invite_email = user member.invite_email = user
end end
member.created_by ||= current_user if can_update_member?(current_user, member)
member.access_level = access_level member.created_by ||= current_user
member.access_level = access_level
member.skip_notification = skip_notification
member.save
end
end
member.skip_notification = skip_notification private
member.save 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
end end
...@@ -98,7 +118,7 @@ class Member < ActiveRecord::Base ...@@ -98,7 +118,7 @@ class Member < ActiveRecord::Base
def accept_invite!(new_user) def accept_invite!(new_user)
return false unless invite? return false unless invite?
self.invite_token = nil self.invite_token = nil
self.invite_accepted_at = Time.now.utc self.invite_accepted_at = Time.now.utc
......
...@@ -545,7 +545,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -545,7 +545,7 @@ class MergeRequest < ActiveRecord::Base
end end
def ci_commit def ci_commit
if last_commit if last_commit and source_project
source_project.ci_commit(last_commit.id) source_project.ci_commit(last_commit.id)
end end
end end
......
...@@ -40,16 +40,20 @@ class Note < ActiveRecord::Base ...@@ -40,16 +40,20 @@ class Note < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
validates :note, :project, presence: 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 validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader # Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size } validates :attachment, file_size: { maximum: :max_attachment_size }
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } 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 :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
validates :author, presence: true
mount_uploader :attachment, AttachmentUploader mount_uploader :attachment, AttachmentUploader
# Scopes # 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 :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") } scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: [nil, '']) } scope :not_inline, ->{ where(line_code: [nil, '']) }
...@@ -97,6 +101,12 @@ class Note < ActiveRecord::Base ...@@ -97,6 +101,12 @@ class Note < ActiveRecord::Base
def search(query) def search(query)
where("LOWER(note) like :query", query: "%#{query.downcase}%") where("LOWER(note) like :query", query: "%#{query.downcase}%")
end end
def grouped_awards
awards.select(:note).distinct.map do |note|
[ note.note, where(note: note.note) ]
end
end
end end
def cross_reference? def cross_reference?
...@@ -288,44 +298,6 @@ class Note < ActiveRecord::Base ...@@ -288,44 +298,6 @@ class Note < ActiveRecord::Base
nil nil
end 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. # Mentionable override.
def gfm_reference(from_project = nil) def gfm_reference(from_project = nil)
noteable.gfm_reference(from_project) noteable.gfm_reference(from_project)
...@@ -363,6 +335,16 @@ class Note < ActiveRecord::Base ...@@ -363,6 +335,16 @@ class Note < ActiveRecord::Base
read_attribute(:system) read_attribute(:system)
end 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? def editable?
!system? !system?
end end
......
...@@ -313,6 +313,10 @@ class Project < ActiveRecord::Base ...@@ -313,6 +313,10 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC') joins(join_body).reorder('join_note_counts.amount DESC')
end end
def visible_to_user(user)
where(id: user.authorized_projects.select(:id).reorder(nil))
end
end end
def team def team
......
...@@ -32,6 +32,8 @@ class DroneCiService < CiService ...@@ -32,6 +32,8 @@ class DroneCiService < CiService
def compose_service_hook def compose_service_hook
hook = service_hook || build_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.enable_ssl_verification = enable_ssl_verification
hook.save hook.save
end end
......
...@@ -86,6 +86,8 @@ class ProjectWiki ...@@ -86,6 +86,8 @@ class ProjectWiki
commit = commit_details(:created, message, title) commit = commit_details(:created, message, title)
wiki.write_page(title, format, content, commit) wiki.write_page(title, format, content, commit)
update_project_activity
rescue Gollum::DuplicatePageError => e rescue Gollum::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}" @error_message = "Duplicate page: #{e.message}"
return false return false
...@@ -95,10 +97,14 @@ class ProjectWiki ...@@ -95,10 +97,14 @@ class ProjectWiki
commit = commit_details(:updated, message, page.title) commit = commit_details(:updated, message, page.title)
wiki.update_page(page, page.name, format, content, commit) wiki.update_page(page, page.name, format, content, commit)
update_project_activity
end end
def delete_page(page, message = nil) def delete_page(page, message = nil)
wiki.delete_page(page, commit_details(:deleted, message, page.title)) wiki.delete_page(page, commit_details(:deleted, message, page.title))
update_project_activity
end end
def page_title_and_dir(title) def page_title_and_dir(title)
...@@ -146,4 +152,8 @@ class ProjectWiki ...@@ -146,4 +152,8 @@ class ProjectWiki
def path_to_repo def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
end end
def update_project_activity
@project.touch(:last_activity_at)
end
end end
...@@ -417,43 +417,23 @@ class User < ActiveRecord::Base ...@@ -417,43 +417,23 @@ class User < ActiveRecord::Base
end end
end end
# Groups user has access to # Returns the groups a user has access to
def authorized_groups def authorized_groups
@authorized_groups ||= begin union = Gitlab::SQL::Union.
group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) new([groups.select(:id), authorized_projects.select(:namespace_id)])
Group.where(id: group_ids)
end
end
def authorized_projects_id Group.where("namespaces.id IN (#{union.to_sql})")
@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 end
# Projects user has access to # Returns the groups a user is authorized to access.
def authorized_projects def authorized_projects
@authorized_projects ||= Project.where(id: authorized_projects_id) Project.where("projects.id IN (#{projects_union.to_sql})")
end end
def owned_projects def owned_projects
@owned_projects ||= @owned_projects ||=
begin Project.where('namespace_id IN (?) OR namespace_id = ?',
namespace_ids = owned_groups.pluck(:id).push(namespace.id) owned_groups.select(:id), namespace.id).joins(:namespace)
Project.in_namespace(namespace_ids).joins(:namespace)
end
end end
# Team membership in authorized projects # Team membership in authorized projects
...@@ -772,12 +752,25 @@ class User < ActiveRecord::Base ...@@ -772,12 +752,25 @@ class User < ActiveRecord::Base
Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil) Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
end end
def contributed_projects_ids # Returns the projects a user contributed to in the last year.
Event.contributions.where(author_id: self). #
# 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). where("created_at > ?", Time.now - 1.year).
reorder(project_id: :desc). uniq.
select(:project_id). reorder(nil)
uniq.map(&:project_id)
Project.where(id: events)
end end
def restricted_signup_domains def restricted_signup_domains
...@@ -810,8 +803,28 @@ class User < ActiveRecord::Base ...@@ -810,8 +803,28 @@ class User < ActiveRecord::Base
def ci_authorized_runners def ci_authorized_runners
@ci_authorized_runners ||= begin @ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject.joins(:project). 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) Ci::Runner.specific.where(id: runner_ids)
end end
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 end
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
...@@ -19,16 +19,16 @@ class CreateTagService < BaseService ...@@ -19,16 +19,16 @@ class CreateTagService < BaseService
new_tag = repository.find_tag(tag_name) new_tag = repository.find_tag(tag_name)
if new_tag 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) push_data = create_push_data(project, current_user, new_tag)
EventCreateService.new.push(project, current_user, push_data) EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :tag_push_hooks) project.execute_hooks(push_data.dup, :tag_push_hooks)
project.execute_services(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) success(new_tag)
else else
error('Invalid reference name') error('Invalid reference name')
......
...@@ -58,12 +58,6 @@ class GitPushService ...@@ -58,12 +58,6 @@ class GitPushService
@push_data = build_push_data(oldrev, newrev, ref) @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) EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks) project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks)
...@@ -134,10 +128,4 @@ class GitPushService ...@@ -134,10 +128,4 @@ class GitPushService
def commit_user(commit) def commit_user(commit)
commit.author || user commit.author || user
end end
def gitlab_ci_yaml?(sha)
@project.repository.blob_at(sha, '.gitlab-ci.yml')
rescue Rugged::ReferenceError
nil
end
end end
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
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
...@@ -5,11 +5,16 @@ module Notes ...@@ -5,11 +5,16 @@ module Notes
note.author = current_user note.author = current_user
note.system = false note.system = false
if contains_emoji_only?(params[:note])
note.is_award = true
note.note = emoji_name(params[:note])
end
if note.save if note.save
notification_service.new_note(note) notification_service.new_note(note)
# Skip system notes, like status changes and cross-references. # Skip system notes, like status changes and cross-references and awards
unless note.system unless note.system || note.is_award
event_service.leave_note(note, note.author) event_service.leave_note(note, note.author)
note.create_cross_references! note.create_cross_references!
execute_hooks(note) execute_hooks(note)
...@@ -28,5 +33,13 @@ module Notes ...@@ -28,5 +33,13 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks) note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks) note.project.execute_services(note_data, :note_hooks)
end 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
end end
...@@ -102,6 +102,7 @@ class NotificationService ...@@ -102,6 +102,7 @@ class NotificationService
# ignore gitlab service messages # ignore gitlab service messages
return true if note.note.start_with?('Status changed to closed') return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true return true if note.cross_reference? && note.system == true
return true if note.is_award
target = note.noteable target = note.noteable
...@@ -361,11 +362,13 @@ class NotificationService ...@@ -361,11 +362,13 @@ class NotificationService
end end
def reassign_resource_email(target, project, current_user, method) def reassign_resource_email(target, project, current_user, method)
assignee_id_was = previous_record(target, "assignee_id") previous_assignee_id = previous_record(target, "assignee_id")
recipients = build_recipients(target, project, current_user) 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| 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
end end
...@@ -377,9 +380,11 @@ class NotificationService ...@@ -377,9 +380,11 @@ class NotificationService
end end
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 = target.participants(current_user)
recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients
recipients = add_project_watchers(recipients, project) recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project) recipients = reject_mention_users(recipients, project)
recipients = reject_muted_users(recipients, project) recipients = reject_muted_users(recipients, project)
......
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
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
- unless user.linkedin.blank? - unless user.linkedin.blank?
%li %li
%span.light LinkedIn: %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? - unless user.twitter.blank?
%li %li
%span.light Twitter: %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? - unless user.website_url.blank?
%li %li
%span.light Website: %span.light Website:
......
...@@ -10,10 +10,10 @@ ...@@ -10,10 +10,10 @@
.milestones .milestones
%ul.content-list %ul.content-list
- if @dashboard_milestones.blank? - if @milestones.blank?
%li %li
.nothing-here-block No milestones to show .nothing-here-block No milestones to show
- else - else
- @dashboard_milestones.each do |milestone| - @milestones.each do |milestone|
= render 'milestone', milestone: milestone = render 'milestone', milestone: milestone
= paginate @dashboard_milestones, theme: "gitlab" = paginate @milestones, theme: "gitlab"
- page_title @dashboard_milestone.title, "Milestones" - page_title @milestone.title, "Milestones"
%h4.page-title %h4.page-title
.issue-box{ class: "issue-box-#{@dashboard_milestone.closed? ? 'closed' : 'open'}" } .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @dashboard_milestone.closed? - if @milestone.closed?
Closed Closed
- else - else
Open Open
Milestone #{@dashboard_milestone.title} Milestone #{@milestone.title}
%hr %hr
- if (@dashboard_milestone.total_items_count == @dashboard_milestone.closed_items_count) && @dashboard_milestone.active? - if @milestone.complete? && @milestone.active?
.alert.alert-success .alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now. %span All issues for this milestone are closed. You may close the milestone now.
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
%th Open issues %th Open issues
%th State %th State
%th Due date %th Due date
- @dashboard_milestone.milestones.each do |milestone| - @milestone.milestones.each do |milestone|
%tr %tr
%td %td
= link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
...@@ -39,46 +39,46 @@ ...@@ -39,46 +39,46 @@
.context .context
%p.lead %p.lead
Progress: Progress:
#{@dashboard_milestone.closed_items_count} closed #{@milestone.closed_items_count} closed
&ndash; &ndash;
#{@dashboard_milestone.open_items_count} open #{@milestone.open_items_count} open
= milestone_progress_bar(@dashboard_milestone) = milestone_progress_bar(@milestone)
%ul.nav.nav-tabs %ul.nav.nav-tabs
%li.active %li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do = link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues Issues
%span.badge= @dashboard_milestone.issue_count %span.badge= @milestone.issue_count
%li %li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests Merge Requests
%span.badge= @dashboard_milestone.merge_requests_count %span.badge= @milestone.merge_requests_count
%li %li
= link_to '#tab-participants', 'data-toggle' => 'tab' do = link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants Participants
%span.badge= @dashboard_milestone.participants.count %span.badge= @milestone.participants.count
.pull-right .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-content
.tab-pane.active#tab-issues .tab-pane.active#tab-issues
.row .row
.col-md-6 .col-md-6
= render 'issues', title: "Open", issues: @dashboard_milestone.opened_issues = render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6 .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 .tab-pane#tab-merge-requests
.row .row
.col-md-6 .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 .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 .tab-pane#tab-participants
%ul.bordered-list %ul.bordered-list
- @dashboard_milestone.participants.each do |user| - @milestone.participants.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32" = 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 ...@@ -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(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.link href: dashboard_projects_url, rel: "alternate", type: "text/html"
xml.id dashboard_projects_url 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| @events.each do |event|
event_to_atom(xml, event) event_to_atom(xml, event)
......
- user = member.user - user = member.user
- return unless user || member.invite? - return unless user || member.invite?
- show_roles = true if show_roles.nil?
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)} %span{class: ("list-item-name" if show_controls)}
...@@ -25,11 +24,11 @@ ...@@ -25,11 +24,11 @@
= link_to member.created_by.name, user_path(member.created_by) = link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at) = 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 = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite Resend invite
- if show_roles - if should_user_see_group_roles?(current_user, @group)
%span.pull-right %span.pull-right
%strong= member.human_access %strong= member.human_access
- if show_controls - if show_controls
...@@ -37,6 +36,7 @@ ...@@ -37,6 +36,7 @@
= button_tag class: "btn-xs btn js-toggle-button", = button_tag class: "btn-xs btn js-toggle-button",
title: 'Edit access level', type: 'button' do title: 'Edit access level', type: 'button' do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
- if can?(current_user, :destroy_group_member, member) - if can?(current_user, :destroy_group_member, member)
&nbsp; &nbsp;
- if current_user == user - if current_user == user
......
- page_title "Members" - page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group)) - header_title group_title(@group, "Members", group_group_members_path(@group))
- show_roles = should_user_see_group_roles?(current_user, @group) - if should_user_see_group_roles?(current_user, @group)
- if show_roles
%p.light %p.light
Members of group have access to all group projects. Members of group have access to all group projects.
Read more about permissions Read more about permissions
...@@ -49,7 +47,7 @@ ...@@ -49,7 +47,7 @@
(#{@members.total_count}) (#{@members.total_count})
%ul.well-list %ul.well-list
- @members.each do |member| - @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' = paginate @members, theme: 'gitlab'
......
:plain :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 @@ ...@@ -22,7 +22,7 @@
%span.label.label-gray %span.label.label-gray
= milestone.project.name = milestone.project.name
.col-sm-6 .col-sm-6
- if can?(current_user, :admin_group, @group) - if can?(current_user, :admin_milestones, @group)
- if milestone.closed? - 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" = 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 - else
......
...@@ -3,15 +3,22 @@ ...@@ -3,15 +3,22 @@
= render 'shared/milestones_filter' = render 'shared/milestones_filter'
.gray-content-block .gray-content-block
Only milestones from - if can?(current_user, :admin_milestones, @group)
%strong #{@group.name} .pull-right
group are listed here. %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 .milestones
%ul.content-list %ul.content-list
- if @group_milestones.blank? - if @milestones.blank?
%li %li
.nothing-here-block No milestones to show .nothing-here-block No milestones to show
- else - else
- @group_milestones.each do |milestone| - @milestones.each do |milestone|
= render 'milestone', milestone: milestone = render 'milestone', milestone: milestone
= paginate @group_milestones, theme: "gitlab" = paginate @milestones, theme: "gitlab"
- 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()));
- page_title @group_milestone.title, "Milestones" - page_title @milestone.title, "Milestones"
= render "header_title" = render "header_title"
%h4.page-title %h4.page-title
.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- if @group_milestone.closed? - if @milestone.closed?
Closed Closed
- else - else
Open Open
Milestone #{@group_milestone.title} Milestone #{@milestone.title}
.pull-right .pull-right
- if can?(current_user, :admin_group, @group) - if can?(current_user, :admin_milestones, @group)
- if @group_milestone.active? - if @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" = 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 - 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 %hr
- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active? - if @milestone.complete? && @milestone.active?
.alert.alert-success .alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now. %span All issues for this milestone are closed. You may close the milestone now.
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%th Open issues %th Open issues
%th State %th State
%th Due date %th Due date
- @group_milestone.milestones.each do |milestone| - @milestone.milestones.each do |milestone|
%tr %tr
%td %td
= link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
...@@ -47,46 +47,46 @@ ...@@ -47,46 +47,46 @@
.context .context
%p.lead %p.lead
Progress: Progress:
#{@group_milestone.closed_items_count} closed #{@milestone.closed_items_count} closed
&ndash; &ndash;
#{@group_milestone.open_items_count} open #{@milestone.open_items_count} open
= milestone_progress_bar(@group_milestone) = milestone_progress_bar(@milestone)
%ul.nav.nav-tabs %ul.nav.nav-tabs
%li.active %li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do = link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues Issues
%span.badge= @group_milestone.issue_count %span.badge= @milestone.issue_count
%li %li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests Merge Requests
%span.badge= @group_milestone.merge_requests_count %span.badge= @milestone.merge_requests_count
%li %li
= link_to '#tab-participants', 'data-toggle' => 'tab' do = link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants Participants
%span.badge= @group_milestone.participants.count %span.badge= @milestone.participants.count
.pull-right .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-content
.tab-pane.active#tab-issues .tab-pane.active#tab-issues
.row .row
.col-md-6 .col-md-6
= render 'issues', title: "Open", issues: @group_milestone.opened_issues = render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6 .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 .tab-pane#tab-merge-requests
.row .row
.col-md-6 .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 .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 .tab-pane#tab-participants
%ul.bordered-list %ul.bordered-list
- @group_milestone.participants.each do |user| - @milestone.participants.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32" = 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 ...@@ -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, 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.link href: group_url(@group), rel: "alternate", type: "text/html"
xml.id group_url(@group) 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| @events.each do |event|
event_to_atom(xml, event) event_to_atom(xml, event)
......
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 The project is now located under
<%= namespace_project_url(@project.namespace, @project) %> <%= namespace_project_url(@project.namespace, @project) %>
......
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
%input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox") %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop .zen-backdrop
- classes << ' js-gfm-input markdown-area' - 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="#") %a.zen-enter-link(tabindex="-1" href="#")
%i.fa.fa-expand = icon('expand')
Edit in fullscreen Edit in fullscreen
%a.zen-leave-link(href="#") %a.zen-leave-link(href="#")
%i.fa.fa-compress = icon('compress')
...@@ -19,4 +19,4 @@ ...@@ -19,4 +19,4 @@
- if allowed_tree_edit? - if allowed_tree_edit?
.btn-group{ role: "group" } .btn-group{ role: "group" }
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace %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
...@@ -5,21 +5,19 @@ ...@@ -5,21 +5,19 @@
%a.close{href: "#", "data-dismiss" => "modal"} × %a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Create New Directory %h3.page-title Create New Directory
.modal-body .modal-body
= form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, 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 .form-group
= label_tag :dir_name, 'Directory Name', class: 'control-label' = label_tag :dir_name, 'Directory Name', class: 'control-label'
.col-sm-10 .col-sm-10
= text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control' = text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
= render 'shared/commit_message_container', params: params, placeholder: ''
- unless @project.empty_repo? = render 'shared/new_commit_form', placeholder: "Add new directory"
.form-group
= label_tag :branch_name, 'Branch', class: 'control-label'
.col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control"
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= submit_tag "Create directory", class: 'btn btn-primary btn-create' = submit_tag "Create directory", class: 'btn btn-primary btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript :javascript
disableButtonIfAnyEmptyField($("#dir-create-form"), ".form-control", ".btn-create"); disableButtonIfAnyEmptyField($(".js-create-dir-form"), ".form-control", ".btn-create");
new NewCommitForm($('.js-create-dir-form'))
...@@ -3,16 +3,16 @@ ...@@ -3,16 +3,16 @@
.modal-content .modal-content
.modal-header .modal-header
%a.close{href: "#", "data-dismiss" => "modal"} × %a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Remove #{@blob.name} %h3.page-title Delete #{@blob.name}
%p.light
From branch
%strong= @ref
.modal-body .modal-body
= form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do = 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/commit_message_container', params: params, = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
placeholder: 'Removed this file because...'
.form-group .form-group
.col-sm-offset-2.col-sm-10 .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" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
new NewCommitForm($('.js-replace-blob-form'))
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%a.close{href: "#", "data-dismiss" => "modal"} × %a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title #{title} %h3.page-title #{title}
.modal-body .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
.dropzone-previews.blob-upload-dropzone-previews .dropzone-previews.blob-upload-dropzone-previews
%p.dz-message.light %p.dz-message.light
...@@ -13,19 +13,15 @@ ...@@ -13,19 +13,15 @@
= link_to 'click to upload', '#', class: "markdown-selector" = link_to 'click to upload', '#', class: "markdown-selector"
%br %br
.dropzone-alerts{class: "alert alert-danger data", style: "display:none"} .dropzone-alerts{class: "alert alert-danger data", style: "display:none"}
= render 'shared/commit_message_container', params: params,
placeholder: placeholder = render 'shared/new_commit_form', 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"
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all' = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript :javascript
disableButtonIfEmptyField($('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file'); disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}'); new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
new NewCommitForm($('.js-upload-blob-form'))
...@@ -13,15 +13,9 @@ ...@@ -13,15 +13,9 @@
%i.fa.fa-eye %i.fa.fa-eye
= editing_preview_title(@blob.name) = 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 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}" = render 'shared/new_commit_form', 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"
= hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'content', '', id: "file-content"
...@@ -30,3 +24,4 @@ ...@@ -30,3 +24,4 @@
:javascript :javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
new NewCommitForm($('.js-edit-blob-form'))
...@@ -2,20 +2,13 @@ ...@@ -2,20 +2,13 @@
= render "header_title" = render "header_title"
.gray-content-block.top-block .gray-content-block.top-block
Create a new file %h3.page-title
Create New File
.file-editor .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 'projects/blob/editor', ref: @ref
= render 'shared/commit_message_container', params: params, = render 'shared/new_commit_form', placeholder: "Add new file"
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"
= hidden_field_tag 'content', '', id: 'file-content' = hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref, = render 'projects/commit_button', ref: @ref,
...@@ -23,3 +16,4 @@ ...@@ -23,3 +16,4 @@
:javascript :javascript
blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null) blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
new NewCommitForm($('.js-new-blob-form'))
...@@ -10,6 +10,4 @@ ...@@ -10,6 +10,4 @@
= render 'projects/blob/remove' = render 'projects/blob/remove'
- title = "Replace #{@blob.name}" - title = "Replace #{@blob.name}"
= render 'projects/blob/upload', title: title, placeholder: title, = 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
button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id),
method: :put
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
= render "commit_box" = render "commit_box"
= render "ci_menu" if @ci_commit = render "ci_menu" if @ci_commit
= render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form", view: params[:view] = render "projects/notes/notes_with_form"
- if params[:view] == 'parallel' - if diff_view == 'parallel'
- fluid_layout true - fluid_layout true
- diff_files = safe_diff_files(diffs) - diff_files = safe_diff_files(diffs)
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
-# Skipp all non non-supported blobs -# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?') - return unless blob.respond_to?('text?')
- if blob.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 = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else - else
= render "projects/diffs/text_file", diff_file: diff_file, index: i = render "projects/diffs/text_file", diff_file: diff_file, index: i
...@@ -42,4 +42,3 @@ ...@@ -42,4 +42,3 @@
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
- else - else
.nothing-here-block No preview for this file type .nothing-here-block No preview for this file type
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= render 'shared/show_aside' = render 'shared/show_aside'
.gray-content-block.second-block .gray-content-block.second-block.oneline-block
.row .row
.col-md-9 .col-md-9
.votes-holder.pull-right .votes-holder.pull-right
......
...@@ -29,8 +29,6 @@ ...@@ -29,8 +29,6 @@
.issue-info .issue-info
= "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe = "#{issue.to_reference} 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 - if issue.milestone
&nbsp; &nbsp;
%span %span
......
...@@ -14,8 +14,10 @@ ...@@ -14,8 +14,10 @@
#votes= render 'votes/votes_block', votable: @merge_request #votes= render 'votes/votes_block', votable: @merge_request
= render "projects/merge_requests/show/participants" = render "projects/merge_requests/show/participants"
.col-md-3 .col-md-3
%span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'} .input-group.cross-project-reference
= cross_project_reference(@project, @merge_request) %span.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
= clipboard_button
.row .row
%section.col-md-9 %section.col-md-9
......
...@@ -34,8 +34,6 @@ ...@@ -34,8 +34,6 @@
.merge-request-info .merge-request-info
= "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe = "##{merge_request.iid} 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? - if merge_request.milestone_id?
&nbsp; &nbsp;
%span %span
......
...@@ -23,9 +23,7 @@ ...@@ -23,9 +23,7 @@
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
.hint = render 'projects/notes/hints'
.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' }.
.clearfix .clearfix
.error-alert .error-alert
.col-md-6 .col-md-6
...@@ -45,7 +43,7 @@ ...@@ -45,7 +43,7 @@
:javascript :javascript
$( ".datepicker" ).datepicker({ $(".datepicker").datepicker({
dateFormat: "yy-mm-dd", dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
= 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| = 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 = hidden_field_tag :line_type
= note_target_fields(@note) = note_target_fields(@note)
= f.hidden_field :commit_id = f.hidden_field :commit_id
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.note-header .note-header
- if note_editable?(note) - if note_editable?(note)
.note-actions .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') = 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 = 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 @@ ...@@ -35,30 +35,12 @@
- if note.updated_by && note.updated_by != note.author - if note.updated_by && note.updated_by != note.author
by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)} 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-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text .note-text
= preserve do = preserve do
= markdown(note.note, {no_header_anchors: true}) = markdown(note.note, {no_header_anchors: true})
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
- if note.attachment.url - if note.attachment.url
.note-attachment .note-attachment
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.js-main-target-form .js-main-target-form
- if can? current_user, :create_note, @project - if can? current_user, :create_note, @project
= render "projects/notes/form", view: params[:view] = render "projects/notes/form", view: diff_view
:javascript :javascript
window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment