Merge branch 'ce-to-ee' into 'master'

Recent changes from CE master

cc @job

See merge request !370
This commit is contained in:
Dmitriy Zaporozhets
2015-04-16 11:34:06 +00:00
275 changed files with 4399 additions and 1105 deletions
+1 -1
View File
@@ -652,7 +652,7 @@ Style/SymbolProc:
Style/Tab:
Description: 'No hard tabs.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
Enabled: false
Enabled: true
Style/TrailingBlankLines:
Description: 'Checks trailing blank lines and final newline.'
+44 -2
View File
@@ -1,23 +1,42 @@
Please view this file on the master branch, on stable branches it's out of date.
v 7.10.0 (unreleased)
- Allow users to be invited by email to join a group or project.
- Don't crash when project repository doesn't exist.
- Add config var to block auto-created LDAP users.
- Don't use HTML ellipsis in EmailsOnPush subject truncated commit message.
- Set EmailsOnPush reply-to address to committer email when enabled.
- Fix broken file browsing with a submodule that contains a relative link (Stan Hu)
- Fix persistent XSS vulnerability around profile website URLs.
- Fix project import URL regex to prevent arbitary local repos from being imported.
- Fix directory traversal vulnerability around uploads routes.
- Fix directory traversal vulnerability around help pages.
- Don't leak existence of project via search autocomplete.
- Don't leak existence of group or project via search.
- Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu)
- Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu)
- Add ability to configure Reply-To address in gitlab.yml (Stan Hu)
- Move current user to the top of the list in assignee/author filters (Stan Hu)
- Fix broken side-by-side diff view on merge request page (Stan Hu)
- Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
- Allow HTML tags in Markdown input
- Fix code unfold not working on Compare commits page (Stan Hu)
- Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
- Fix "Import projects from" button to show the correct instructions (Stan Hu)
- Fix dots in Wiki slugs causing errors (Stan Hu)
- Make maximum attachment size configurable via Application Settings (Stan Hu)
- Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
- Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
- Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu)
- Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu)
- enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger)
- extend the commit calendar to show the actual commits made on a date (Hannes Rosenögger)
- Fix a link in the patch update guide
- Add a service to support external wikis (Hannes Rosenögger)
- Omit the "email patches" link and fix plain diff view for merge commits
- List new commits for newly pushed branch in activity view.
- Add sidetiq gem dependency to match EE
- Add changelog, license and contribution guide links to project sidebar.
- Add changelog, license and contribution guide links to project tab bar.
- Improve diff UI
- Fix alignment of navbar toggle button (Cody Mize)
- Fix checkbox rendering for nested task lists
@@ -59,8 +78,31 @@ v 7.9.0 (unreleased)
- Fix "Hello @username." references not working by no longer allowing usernames to end in period.
- Archive repositories in background worker.
- Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
- Project labels are now available over the API under the "tag_list" field (Cristian Medina)
- Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz)
- Fix and improve help rendering (Sullivan Sénéchal)
- Fix final line in EmailsOnPush email diff being rendered as error.
- Authometic setup GitLab CI project for forks if origin project has GitLab CI enabled
- Prevent duplicate Buildkite service creation.
- Fix git over ssh errors 'fatal: protocol error: bad line length character'
- Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled
- Bust group page project list cache when namespace name or path changes.
- Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
- Allow user to choose a public email to show on public profile
- Remove truncation from issue titles on milestone page (Jason Blanchard)
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
- Fix merge request comments on files with multiple commits
v 7.9.3
- Contains no changes
- Add icons to Add dropdown items.
- Allow admin to create public deploy keys that are accessible to any project.
- Warn when gitlab-shell version doesn't match requirement.
- Skip email confirmation when set by admin or via LDAP.
- Only allow users to reference groups, projects, issues, MRs, commits they have access to.
v 7.9.3
- Contains no changes
v 7.9.2
- Contains no changes
+1 -1
View File
@@ -71,7 +71,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Fork the project on GitLab Cloud
1. Create a feature branch
1. Write [tests](README.md#run-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. 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 -1
View File
@@ -1 +1 @@
2.6.0
2.6.2
+4 -4
View File
@@ -39,7 +39,7 @@ gem "browser"
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.1.3'
gem "gitlab_git", '~> 7.1.6'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack'
@@ -95,7 +95,7 @@ gem 'html-pipeline-gitlab', '~> 0.1'
gem "github-markup"
# Required markup gems by github-markdown
gem 'redcarpet', '~> 3.1.2'
gem 'redcarpet', '~> 3.2.3'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.12'
@@ -123,7 +123,6 @@ gem 'slim'
gem 'sinatra', require: nil
gem 'sidekiq', '~> 3.3'
gem 'sidetiq', '0.6.3'
gem 'sidekiq-limit_fetch'
# HTTP requests
gem "httparty"
@@ -210,7 +209,6 @@ group :development do
gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1'
gem 'rack-mini-profiler', require: false
gem "byebug"
# Better errors handler
gem 'better_errors'
@@ -259,6 +257,8 @@ group :development, :test do
gem "spring", '~> 1.3.1'
gem "spring-commands-rspec", '1.0.4'
gem "spring-commands-spinach", '1.0.0'
gem "byebug"
end
group :test do
+5 -8
View File
@@ -188,7 +188,7 @@ GEM
dotenv (>= 0.7)
thor (>= 0.13.6)
formatador (0.2.4)
gemnasium-gitlab-service (0.2.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
gemojione (2.0.0)
json
@@ -212,7 +212,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
gitlab_git (7.1.3)
gitlab_git (7.1.6)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -457,7 +457,7 @@ GEM
ffi (>= 0.5.0)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.1.2)
redcarpet (3.2.3)
redis (3.1.0)
redis-actionpack (4.0.0)
actionpack (~> 4)
@@ -547,8 +547,6 @@ GEM
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sidekiq-limit_fetch (2.4.1)
sidekiq (>= 2.6.5, < 4.0)
sidetiq (0.6.3)
celluloid (>= 0.14.1)
ice_cube (= 0.11.1)
@@ -705,7 +703,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.rc2)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
gitlab_git (~> 7.1.3)
gitlab_git (~> 7.1.6)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
@@ -758,7 +756,7 @@ DEPENDENCIES
rb-fsevent
rb-inotify
rdoc (~> 3.6)
redcarpet (~> 3.1.2)
redcarpet (~> 3.2.3)
redis-rails
request_store
rspec-rails (= 2.99)
@@ -772,7 +770,6 @@ DEPENDENCIES
settingslogic
shoulda-matchers (~> 2.7.0)
sidekiq (~> 3.3)
sidekiq-limit_fetch
sidetiq (= 0.6.3)
simplecov
sinatra
@@ -10,6 +10,7 @@ class @DropzoneInput
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
@@ -76,7 +77,7 @@ class @DropzoneInput
dictDefaultMessage: ""
clickable: true
paramName: "file"
maxFilesize: 10
maxFilesize: max_file_size
uploadMultiple: false
headers:
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
@@ -108,9 +109,10 @@ class @DropzoneInput
return
error: (temp, errorMessage) ->
checkIfMsgExists = $(".error-alert").children().length
errorAlert = $(form).find('.error-alert')
checkIfMsgExists = errorAlert.children().length
if checkIfMsgExists is 0
$(".error-alert").append divAlert
errorAlert.append divAlert
$(".div-dropzone-alert").append btnAlert + errorMessage
return
@@ -221,9 +223,10 @@ class @DropzoneInput
"display": "none"
showError = (message) ->
checkIfMsgExists = $(".error-alert").children().length
errorAlert = $(form).find('.error-alert')
checkIfMsgExists = errorAlert.children().length
if checkIfMsgExists is 0
$(".error-alert").append divAlert
errorAlert.append divAlert
$(".div-dropzone-alert").append btnAlert + message
closeAlertMessage = ->
@@ -237,4 +240,4 @@ class @DropzoneInput
formatLink: (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
text
text
+4
View File
@@ -18,3 +18,7 @@ class @Issue
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
@@ -26,6 +26,10 @@ class @MergeRequest
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
# Local jQuery finder
$: (selector) ->
@@ -160,4 +164,3 @@ class @MergeRequest
else
setTimeout(merge_request.mergeInProgress, 3000)
dataType: 'json'
@@ -3,12 +3,12 @@
class @ShortcutsDashboardNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-activity'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-projects'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-activity'))
Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-projects'))
@findAndollowLink: (selector) ->
@findAndFollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
@@ -3,18 +3,18 @@
class @ShortcutsNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndollowLink('.shortcuts-project'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndollowLink('.shortcuts-snippets'))
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndollowLink: (selector) ->
@findAndFollowLink: (selector) ->
link = $(selector).attr('href')
if link
window.location = link
+31 -13
View File
@@ -9,6 +9,8 @@ class @UsersSelect
@groupId = $(select).data('group-id')
showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user')
firstUser = $(select).data('first-user')
$(select).select2
placeholder: "Search for a user"
@@ -19,25 +21,41 @@ class @UsersSelect
data = { results: users }
if query.term.length == 0
anyUser = {
name: 'Any',
avatar: null,
username: 'none',
id: null
}
nullUser = {
name: 'Unassigned',
avatar: null,
username: 'none',
id: 0
}
if firstUser
# Move current user to the front of the list
for obj, index in data.results
if obj.username == firstUser
data.results.splice(index, 1)
data.results.unshift(obj)
break
if showNullUser
nullUser = {
name: 'Unassigned',
avatar: null,
username: 'none',
id: 0
}
data.results.unshift(nullUser)
if showAnyUser
anyUser = {
name: 'Any',
avatar: null,
username: 'none',
id: null
}
data.results.unshift(anyUser)
if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
emailUser = {
name: "Invite \"#{query.term}\"",
avatar: null,
username: query.term,
id: query.term
}
data.results.unshift(emailUser)
query.callback(data)
initSelection: (element, callback) =>
+16
View File
@@ -119,6 +119,22 @@
li {
line-height: 1.5;
}
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
&:before {
margin-right: 4px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f0c6";
}
&:hover:before {
text-decoration: none;
}
}
}
@mixin str-truncated($max_width: 82%) {
+2 -1
View File
@@ -45,7 +45,8 @@
padding: 12px 0px;
border-bottom: 1px solid #eee;
.event-title {
@include str-truncated(72%);
max-width: 70%;
@include str-truncated(calc(100% - 174px));
font-weight: 500;
font-size: 14px;
.author_name {
+2 -14
View File
@@ -62,20 +62,8 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
a[href*="/uploads/"] {
&:before {
margin-right: 4px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f0c6";
}
&:hover:before {
text-decoration: none;
}
hr {
margin: 10px 0;
}
}
}
+17 -7
View File
@@ -15,7 +15,7 @@
}
.project-home-panel {
margin-bottom: 15px;
margin-bottom: 20px;
position: relative;
padding-left: 85px;
@@ -38,6 +38,11 @@
font-size: 45px;
line-height: 1.6;
}
.avatar, .identicon {
@include border-radius(4px);
box-shadow: 0 1px 2px #ddd;
}
}
.project-home-dropdown {
@@ -61,13 +66,12 @@
.project-home-desc {
float: left;
color: #666;
color: $gray;
}
}
.visibility-level-label {
color: #555;
font-weight: bold;
color: $gray;
i {
color: inherit;
}
@@ -115,6 +119,10 @@
background: #FAFAFA;
width: 100%;
}
.input-group-addon {
background: #FAFAFA;
}
}
.project-visibility-level-holder {
@@ -133,7 +141,7 @@
.option-descr {
margin-left: 24px;
color: #666;
color: $gray;
}
}
}
@@ -212,8 +220,10 @@ ul.nav.nav-projects-tabs {
white-space: normal;
text-align: left;
padding: 10px 15px;
background-color: #F9F9F9;
border-color: #DDD;
&.dropdown-toggle {
text-align: center;
}
&:hover {
background-color: #eee;
@@ -39,6 +39,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:sign_in_text,
:home_page_url,
:help_text,
:max_attachment_size,
restricted_visibility_levels: []
)
end
@@ -0,0 +1,49 @@
class Admin::DeployKeysController < Admin::ApplicationController
before_filter :deploy_keys, only: [:index]
before_filter :deploy_key, only: [:show, :destroy]
def index
end
def show
end
def new
@deploy_key = deploy_keys.new
end
def create
@deploy_key = deploy_keys.new(deploy_key_params)
if @deploy_key.save
redirect_to admin_deploy_keys_path
else
render "new"
end
end
def destroy
deploy_key.destroy
respond_to do |format|
format.html { redirect_to admin_deploy_keys_path }
format.json { head :ok }
end
end
protected
def deploy_key
@deploy_key ||= deploy_keys.find(params[:id])
end
def deploy_keys
@deploy_keys ||= DeployKey.are_public
end
def deploy_key_params
params.require(:deploy_key).permit(:key, :title)
end
end
+1 -1
View File
@@ -41,7 +41,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def members_update
@group.add_users(params[:user_ids].split(','), params[:access_level])
@group.add_users(params[:user_ids].split(','), params[:access_level], current_user)
redirect_to [:admin, @group], notice: 'Users were successfully added.'
end
+1 -1
View File
@@ -72,8 +72,8 @@ class Admin::UsersController < Admin::ApplicationController
end
respond_to do |format|
user.skip_reconfirmation!
if user.update_attributes(user_params_with_pass)
user.confirm!
format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' }
format.json { head :ok }
else
+3 -2
View File
@@ -126,7 +126,7 @@ class ApplicationController < ActionController::Base
def repository
@repository ||= project.repository
rescue Grit::NoSuchPathError(e)
rescue Grit::NoSuchPathError => e
log_exception(e)
nil
end
@@ -153,7 +153,7 @@ class ApplicationController < ActionController::Base
end
def method_missing(method_sym, *arguments, &block)
if method_sym.to_s =~ /^authorize_(.*)!$/
if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
authorize_project!($1.to_sym)
else
super
@@ -203,6 +203,7 @@ class ApplicationController < ActionController::Base
gon.api_version = API::API.version
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size;
if current_user
gon.current_user_id = current_user.id
+1 -1
View File
@@ -22,7 +22,7 @@ class AuditEventsController < ApplicationController
end
def authorize_admin_group!
render_404 unless can?(current_user, :manage_group, group)
render_404 unless can?(current_user, :admin_group, group)
end
def determine_layout
+2 -2
View File
@@ -4,11 +4,11 @@ class ConfirmationsController < Devise::ConfirmationsController
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
after_sign_in_path_for(resource)
else
sign_in(resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
after_sign_in_path_for(resource)
else
new_session_path(resource_name)
end
@@ -13,7 +13,7 @@ class Groups::ApplicationController < ApplicationController
end
def authorize_admin_group!
unless can?(current_user, :manage_group, group)
unless can?(current_user, :admin_group, group)
return render_404
end
end
@@ -11,6 +11,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = @group.group_members
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
if params[:search].present?
users = @group.users.search(params[:search]).to_a
@@ -25,7 +26,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
access_level = params[:access_level]
user_ids = params[:user_ids].split(',')
@group.add_users(user_ids, access_level)
@group.add_users(user_ids, access_level, current_user)
group_members = @group.group_members.where(user_id: user_ids)
group_members.each do |group_member|
@@ -52,7 +53,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
log_audit_event(@group_member, action: :destroy)
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
end
else
@@ -60,6 +61,20 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
end
def resend_invite
redirect_path = group_group_members_path(@group)
@group_member = @group.group_members.find(params[:id])
if @group_member.invite?
@group_member.resend_invite
redirect_to redirect_path, notice: 'The invitation was successfully resent.'
else
redirect_to redirect_path, alert: 'The invitation has already been accepted.'
end
end
def leave
@group_member = @group.group_members.where(user_id: current_user.id).first
@@ -67,7 +82,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@group_member.destroy
log_audit_event(@group_member, action: :destroy)
redirect_to(dashboard_groups_path, info: "You left #{group.name} group.")
redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.")
else
return render_403
end
@@ -51,6 +51,6 @@ class Groups::MilestonesController < ApplicationController
end
def authorize_group_milestone!
return render_404 unless can?(current_user, :manage_group, group)
return render_404 unless can?(current_user, :admin_group, group)
end
end
+32 -1
View File
@@ -3,7 +3,7 @@ class HelpController < ApplicationController
end
def show
@filepath = params[:filepath]
@filepath = clean_path_info(params[:filepath])
@format = params[:format]
respond_to do |format|
@@ -36,4 +36,35 @@ class HelpController < ApplicationController
def ui
end
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
# Taken from ActionDispatch::FileHandler
# Cleans up the path, to prevent directory traversal outside the doc folder.
def clean_path_info(path_info)
parts = path_info.split(PATH_SEPS)
clean = []
# Walk over each part of the path
parts.each do |part|
# Turn `one//two` or `one/./two` into `one/two`.
next if part.empty? || part == '.'
if part == '..'
# Turn `one/two/../` into `one`
clean.pop
else
# Add simple folder names to the clean path.
clean << part
end
end
# If the path was an absolute path (i.e. `/` or `/one/two`),
# add `/` to the front of the clean path.
clean.unshift '/' if parts.empty? || parts.first.empty?
# Join all the clean path parts by the path separator.
::File.join(*clean)
end
end
+1 -1
View File
@@ -8,7 +8,7 @@ class Import::BaseController < ApplicationController
namespace.add_owner(current_user)
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
namespace = Namespace.find_by_path_or_name(@target_namespace)
unless namespace.owner == current_user
unless current_user.can?(:create_projects, namespace)
@already_been_taken = true
return false
end
@@ -0,0 +1,111 @@
class Import::GoogleCodeController < Import::BaseController
before_filter :user_map, only: [:new_user_map, :create_user_map]
def new
end
def callback
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
return redirect_to :back, alert: "You need to upload a Google Takeout archive."
end
begin
dump = JSON.parse(dump_file.read)
rescue
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
end
session[:google_code_dump] = dump
if params[:create_user_map] == "1"
redirect_to new_user_map_import_google_code_path
else
redirect_to status_import_google_code_path
end
end
def new_user_map
end
def create_user_map
user_map_json = params[:user_map]
user_map_json = "{}" if user_map_json.blank?
begin
user_map = JSON.parse(user_map_json)
rescue
flash.now[:alert] = "The entered user map is not a valid JSON user map."
render "new_user_map" and return
end
unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
flash.now[:alert] = "The entered user map is not a valid JSON user map."
render "new_user_map" and return
end
session[:google_code_user_map] = user_map
flash[:notice] = "The user map has been saved. Continue by selecting the projects you want to import."
redirect_to status_import_google_code_path
end
def status
unless client.valid?
return redirect_to new_import_google_path
end
@repos = client.repos
@already_added_projects = current_user.created_projects.where(import_type: "google_code")
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
end
def jobs
jobs = current_user.created_projects.where(import_type: "google_code").to_json(only: [:id, :import_status])
render json: jobs
end
def create
@repo_id = params[:repo_id]
repo = client.repo(@repo_id)
@target_namespace = current_user.namespace
@project_name = repo.name
namespace = @target_namespace
user_map = session[:google_code_user_map]
@project = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, namespace, current_user, user_map).execute
end
private
def client
@client ||= Gitlab::GoogleCodeImport::Client.new(session[:google_code_dump])
end
def user_map
@user_map ||= begin
user_map = client.user_map
stored_user_map = session[:google_code_user_map]
user_map.update(stored_user_map) if stored_user_map
Hash[user_map.sort]
end
end
end
+83
View File
@@ -0,0 +1,83 @@
class InvitesController < ApplicationController
before_filter :member
skip_before_filter :authenticate_user!, only: :decline
respond_to :html
layout 'navless'
def show
end
def accept
if member.accept_invite!(current_user)
label, path = source_info(member.source)
redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
else
redirect_to :back, alert: "The invitation could not be accepted."
end
end
def decline
if member.decline_invite!
label, _ = source_info(member.source)
path =
if current_user
dashboard_path
else
new_user_session_path
end
redirect_to path, notice: "You have declined the invitation to join #{label}."
else
redirect_to :back, alert: "The invitation could not be declined."
end
end
private
def member
return @member if defined?(@member)
@token = params[:id]
@member = Member.find_by_invite_token(@token)
unless @member
render_404 and return
end
@member
end
def authenticate_user!
return if current_user
notice = "To accept this invitation, sign in"
notice << " or create an account" if current_application_settings.signup_enabled?
notice << "."
store_location_for :user, request.fullpath
redirect_to new_user_session_path, notice: notice
end
def source_info(source)
case source
when Project
project = member.source
label = "project #{project.name_with_namespace}"
path = namespace_project_path(project.namespace, project)
when Group
group = member.source
label = "group #{group.name}"
path = group_path(group)
else
label = "who knows what"
path = dashboard_path
end
[label, path]
end
end
@@ -3,6 +3,7 @@ class Profiles::EmailsController < ApplicationController
def index
@primary = current_user.email
@public_email = current_user.public_email
@emails = current_user.emails
end
@@ -19,7 +20,8 @@ class Profiles::EmailsController < ApplicationController
@email.destroy
current_user.set_notification_email
current_user.save if current_user.notification_email_changed?
current_user.set_public_email
current_user.save if current_user.notification_email_changed? or current_user.public_email_changed?
respond_to do |format|
format.html { redirect_to profile_emails_url }
+4 -3
View File
@@ -67,9 +67,10 @@ class ProfilesController < ApplicationController
def user_params
params.require(:user).permit(
:email, :password, :password_confirmation, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id,
:avatar, :hide_no_ssh_key, :hide_no_password, :location
:email, :password, :password_confirmation, :bio, :name,
:username, :skype, :linkedin, :twitter, :website_url,
:color_scheme_id, :theme_id, :avatar, :hide_no_ssh_key,
:hide_no_password, :location, :public_email
)
end
end
@@ -8,7 +8,14 @@ class Projects::DeployKeysController < Projects::ApplicationController
def index
@enabled_keys = @project.deploy_keys
@available_keys = available_keys - @enabled_keys
@available_keys = accessible_keys - @enabled_keys
@available_project_keys = current_user.project_deploy_keys - @enabled_keys
@available_public_keys = DeployKey.are_public - @enabled_keys
# Public keys that are already used by another accessible project are already
# in @available_project_keys.
@available_public_keys -= @available_project_keys
end
def show
@@ -34,36 +41,27 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
end
def destroy
@key = @project.deploy_keys.find(params[:id])
@key.destroy
log_audit_event(@key.title, action: :destroy)
respond_to do |format|
format.html { redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) }
format.js { render nothing: true }
end
end
def enable
@project.deploy_keys << available_keys.find(params[:id])
@key = accessible_keys.find(params[:id])
@project.deploy_keys << @key
log_audit_event(@key.title, action: :create)
redirect_to namespace_project_deploy_keys_path(@project.namespace,
@project)
end
def disable
@key = accessible_keys.find(params[:id])
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
log_audit_event(@key.title, action: :destroy)
redirect_to namespace_project_deploy_keys_path(@project.namespace,
@project)
redirect_to :back
end
protected
def available_keys
@available_keys ||= current_user.accessible_deploy_keys
def accessible_keys
@accessible_keys ||= current_user.accessible_deploy_keys
end
def deploy_key_params
@@ -6,6 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def index
@project_members = @project.project_members
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
if params[:search].present?
users = @project.users.search(params[:search]).to_a
@@ -17,6 +18,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group = @project.group
if @group
@group_members = @group.group_members
@group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
if params[:search].present?
users = @group.users.search(params[:search]).to_a
@@ -35,9 +37,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def create
users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:access_level]]
members = @project.project_members.where(user_id: users.pluck(:id))
@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.each do |member|
log_audit_event(member, action: :create)
@@ -47,7 +48,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def update
@project_member = @project.project_members.find_by(user_id: member)
@project_member = @project.project_members.find(params[:id])
old_access_level = @project_member.human_access
if @project_member.update_attributes(member_params)
@@ -56,19 +57,32 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def destroy
@project_member = @project.project_members.find_by(user_id: member)
@project_member = @project.project_members.find(params[:id])
@project_member.destroy
log_audit_event(@project_member, action: :destroy)
respond_to do |format|
format.html do
redirect_to namespace_project_project_members_path(@project.namespace,
@project)
redirect_to namespace_project_project_members_path(@project.namespace, @project)
end
format.js { render nothing: true }
end
end
def resend_invite
redirect_path = namespace_project_project_members_path(@project.namespace, @project)
@project_member = @project.project_members.find(params[:id])
if @project_member.invite?
@project_member.resend_invite
redirect_to redirect_path, notice: 'The invitation was successfully resent.'
else
redirect_to redirect_path, alert: 'The invitation has already been accepted.'
end
end
def leave
@project_member = @project.project_members.find_by(user_id: current_user)
@project_member.destroy
@@ -82,7 +96,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def apply_import
giver = Project.find(params[:source_project_id])
status = @project.team.import(giver)
status = @project.team.import(giver, current_user)
notice = status ? "Successfully imported" : "Import failed"
redirect_to(namespace_project_project_members_path(project.namespace, project),
@@ -91,10 +105,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
protected
def member
@member ||= User.find_by(username: params[:id])
end
def member_params
params.require(:project_member).permit(:user_id, :access_level)
end
@@ -55,5 +55,10 @@ class Projects::RefsController < Projects::ApplicationController
commit: last_commit
}
end
respond_to do |format|
format.html { render_404 }
format.js
end
end
end
+5 -1
View File
@@ -5,6 +5,7 @@ class Projects::WikisController < Projects::ApplicationController
before_filter :authorize_write_wiki!, only: [:edit, :create, :history]
before_filter :authorize_admin_wiki!, only: :destroy
before_filter :load_project_wiki
include WikiHelper
def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE)
@@ -45,7 +46,10 @@ class Projects::WikisController < Projects::ApplicationController
return render('empty') unless can?(current_user, :write_wiki, @project)
if @page.update(content, format, message)
redirect_to [@project.namespace.becomes(Namespace), @project, @page], notice: 'Wiki was successfully updated.'
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
)
else
render 'edit'
end
+17 -5
View File
@@ -3,15 +3,22 @@ class SearchController < ApplicationController
def show
return if params[:search].nil? || params[:search].blank?
@project = Project.find_by(id: params[:project_id]) if params[:project_id].present?
@group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project)
end
if params[:group_id].present?
@group = Group.find_by(id: params[:group_id])
@group = nil unless can?(current_user, :read_group, @group)
end
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
@search_results =
if @project
return access_denied! unless can?(current_user, :download_code, @project)
unless %w(blobs notes issues merge_requests wiki_blobs).
include?(@scope)
@scope = 'blobs'
@@ -35,7 +42,12 @@ class SearchController < ApplicationController
def autocomplete
term = params[:term]
@project = Project.find(params[:project_id]) if params[:project_id].present?
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :read_project, @project)
end
@ref = params[:project_ref] if params[:project_ref].present?
render json: search_autocomplete_opts(term).to_json
+7 -1
View File
@@ -26,6 +26,12 @@ class SessionsController < Devise::SessionsController
end
def create
super
super do |resource|
# User has successfully signed in, so clear any unused reset tokens
if resource.reset_password_token.present?
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
end
end
end
end
+24 -22
View File
@@ -125,7 +125,7 @@ module ApplicationHelper
# If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/)
@ref =~ /\A[0-9a-zA-Z]{6,52}\z/)
options << ['Commit', [@ref]]
end
@@ -236,33 +236,35 @@ module ApplicationHelper
Gitlab::MarkdownHelper.gitlab_markdown?(filename)
end
def link_to(name = nil, options = nil, html_options = nil, &block)
begin
uri = URI(options)
host = uri.host
absolute_uri = uri.absolute?
rescue URI::InvalidURIError, ArgumentError
host = nil
absolute_uri = nil
end
# Add 'nofollow' only to external links
if host && host != Gitlab.config.gitlab.host && absolute_uri
if html_options
if html_options[:rel]
html_options[:rel] << ' nofollow'
else
html_options.merge!(rel: 'nofollow')
end
else
html_options = Hash.new
html_options[:rel] = 'nofollow'
# Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to
# external links
def link_to(name = nil, options = nil, html_options = {})
if options.kind_of?(String)
if !options.start_with?('#', '/')
html_options = add_nofollow(options, html_options)
end
end
super
end
# Add `"rel=nofollow"` to external links
#
# link - String link to check
# html_options - Hash of `html_options` passed to `link_to`
#
# Returns `html_options`, adding `rel: nofollow` for external links
def add_nofollow(link, html_options = {})
uri = URI(link)
if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host
rel = html_options.fetch(:rel, '')
html_options[:rel] = (rel + ' nofollow').strip
end
html_options
end
def escaped_autolink(text)
auto_link ERB::Util.html_escape(text), link: :urls
end
+4 -3
View File
@@ -134,12 +134,13 @@ module CommitsHelper
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
user = commit.send(options[:source])
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
user = User.find_for_commit(source_email, source_name)
person_name = user.nil? ? source_name : user.name
person_email = user.nil? ? source_email : user.email
person_name = user.try(:name) || source_name
person_email = user.try(:email) || source_email
text =
if options[:avatar]
+1 -1
View File
@@ -101,7 +101,7 @@ module DiffHelper
end
def line_comments
@line_comments ||= @line_notes.group_by(&:line_code)
@line_comments ||= @line_notes.select(&:active?).group_by(&:line_code)
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
+1 -30
View File
@@ -1,6 +1,3 @@
require 'html/pipeline'
require 'html/pipeline/gitlab'
module EmailsHelper
# Google Actions
@@ -33,35 +30,9 @@ module EmailsHelper
end
end
def add_email_highlight_css
Rugments::Themes::Github.render(scope: '.highlight')
end
def color_email_diff(diffcontent)
formatter = Rugments::Formatters::HTML.new(cssclass: 'highlight')
formatter = Rugments::Formatters::HTML.new(cssclass: "highlight", inline_theme: :github)
lexer = Rugments::Lexers::Diff.new
raw formatter.format(lexer.lex(diffcontent))
end
def replace_image_links_with_base64(text, project)
# Used pipelines in GitLab:
# GitlabEmailImageFilter - replaces images that have been uploaded as attachments with inline images in emails.
#
# see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
filters = [
HTML::Pipeline::Gitlab::GitlabEmailImageFilter
]
context = {
base_url: File.join(Gitlab.config.gitlab.url, project.path_with_namespace, 'uploads'),
upload_path: File.join(Rails.root, 'public', 'uploads', project.path_with_namespace),
}
pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
result = pipeline.call(text, context)
text = result[:output].to_html(save_with: 0)
text.html_safe
end
end
+23 -19
View File
@@ -13,7 +13,7 @@ module GitlabMarkdownHelper
def link_to_gfm(body, url, html_options = {})
return "" if body.blank?
escaped_body = if body =~ /^\<img/
escaped_body = if body =~ /\A\<img/
body
else
escape_once(body)
@@ -31,24 +31,28 @@ module GitlabMarkdownHelper
def markdown(text, options={})
unless @markdown && options == @options
@options = options
gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
user_color_scheme_class,
{
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
with_toc_data: true,
safe_links_only: true
}.merge(options))
@markdown = Redcarpet::Markdown.new(gitlab_renderer,
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
no_intra_emphasis: true,
tables: true,
fenced_code_blocks: true,
autolink: true,
strikethrough: true,
lax_spacing: true,
space_after_headers: true,
superscript: true)
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch
rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, {
with_toc_data: true,
safe_links_only: true,
# Handled further down the line by HTML::Pipeline::SanitizationFilter
escape_html: false
}.merge(options))
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
@markdown = Redcarpet::Markdown.new(rend,
no_intra_emphasis: true,
tables: true,
fenced_code_blocks: true,
autolink: true,
strikethrough: true,
lax_spacing: true,
space_after_headers: true,
superscript: true
)
end
@markdown.render(text).html_safe
end
@@ -135,7 +139,7 @@ module GitlabMarkdownHelper
@project.path_with_namespace,
path_with_ref(file_path),
file_path
].compact.join("/").gsub(/^\/*|\/*$/, '') + id
].compact.join("/").gsub(/\A\/*|\/*\z/, '') + id
end
def sanitize_slashes(path)
+6 -2
View File
@@ -1,6 +1,10 @@
module GroupsHelper
def remove_user_from_group_message(group, user)
"Are you sure you want to remove \"#{user.name}\" from \"#{group.name}\"?"
def remove_user_from_group_message(group, member)
if member.user
"Are you sure you want to remove \"#{member.user.name}\" from \"#{group.name}\"?"
else
"Are you sure you want to revoke the invitation for \"#{member.invite_email}\" to join \"#{group.name}\"?"
end
end
def leave_group_message(group)
+14 -2
View File
@@ -1,6 +1,10 @@
module ProjectsHelper
def remove_from_project_team_message(project, user)
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
def remove_from_project_team_message(project, member)
if member.user
"You are going to remove #{member.user.name} from #{project.name} project team. Are you sure?"
else
"You are going to revoke the invitation for #{member.invite_email} to join #{project.name} project team. Are you sure?"
end
end
def link_to_project(project)
@@ -127,6 +131,14 @@ module ProjectsHelper
html + count_html
end
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
@project
else
deploy_key.projects.find { |project| can?(current_user, :read_project, project) }
end
end
private
def get_project_nav_tabs(project, current_user)
+4
View File
@@ -9,12 +9,16 @@ module SelectsHelper
null_user = opts[:null_user] || false
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
html = {
class: css_class,
'data-placeholder' => placeholder,
'data-null-user' => null_user,
'data-any-user' => any_user,
'data-email-user' => email_user,
'data-first-user' => first_user
}
unless opts[:scope] == :all
+14 -7
View File
@@ -44,7 +44,7 @@ module SubmoduleHelper
def relative_self_url?(url)
# (./)?(../repo.git) || (./)?(../../project/repo.git) )
url =~ /^((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*\.git\Z/ || url =~ /^((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*\.git\Z/
url =~ /\A((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*\.git\z/ || url =~ /\A((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*\.git\z/
end
def standard_links(host, namespace, project, commit)
@@ -53,15 +53,22 @@ module SubmoduleHelper
end
def relative_self_links(url, commit)
if url.scan(/(\.\.\/)/).size == 2
base = url[/([^\/]*\/[^\/]*)\.git/, 1]
else
base = [ @project.group.path, '/', url[/([^\/]*)\.git/, 1] ].join('')
# Map relative links to a namespace and project
# For example:
# ../bar.git -> same namespace, repo bar
# ../foo/bar.git -> namespace foo, repo bar
# ../../foo/bar/baz.git -> namespace bar, repo baz
components = url.split('/')
base = components.pop.gsub(/.git$/, '')
namespace = components.pop.gsub(/^\.\.$/, '')
if namespace.empty?
namespace = @project.group.path
end
[
namespace_project_path(base.namespace, base),
namespace_project_tree_path(base.namespace, base, commit)
namespace_project_path(namespace, base),
namespace_project_tree_path(namespace, base, commit)
]
end
end
+22
View File
@@ -0,0 +1,22 @@
module WikiHelper
# Rails v4.1.9+ escapes all model IDs, converting slashes into %2F. The
# only way around this is to implement our own path generators.
def namespace_project_wiki_path(namespace, project, wiki_page, *args)
slug =
case wiki_page
when Symbol
wiki_page
else
wiki_page.slug
end
namespace_project_path(namespace, project) + "/wikis/#{slug}"
end
def edit_namespace_project_wiki_path(namespace, project, wiki_page, *args)
namespace_project_wiki_path(namespace, project, wiki_page) + '/edit'
end
def history_namespace_project_wiki_path(namespace, project, wiki_page, *args)
namespace_project_wiki_path(namespace, project, wiki_page) + '/history'
end
end
+42 -1
View File
@@ -3,9 +3,50 @@ module Emails
def group_access_granted_email(group_member_id)
@group_member = GroupMember.find(group_member_id)
@group = @group_member.group
@target_url = group_url(@group)
mail(to: @group_member.user.email,
@current_user = @group_member.user
mail(to: @group_member.user.notification_email,
subject: subject("Access to group was granted"))
end
def group_member_invited_email(group_member_id, token)
@group_member = GroupMember.find group_member_id
@group = @group_member.group
@token = token
@target_url = group_url(@group)
@current_user = @group_member.user
mail(to: @group_member.invite_email,
subject: "Invitation to join group #{@group.name}")
end
def group_invite_accepted_email(group_member_id)
@group_member = GroupMember.find group_member_id
return if @group_member.created_by.nil?
@group = @group_member.group
@target_url = group_url(@group)
@current_user = @group_member.created_by
mail(to: @group_member.created_by.notification_email,
subject: subject("Invitation accepted"))
end
def group_invite_declined_email(group_id, invite_email, access_level, created_by_id)
return if created_by_id.nil?
@group = Group.find(group_id)
@current_user = @created_by = User.find(created_by_id)
@access_level = access_level
@invite_email = invite_email
@target_url = group_url(@group)
mail(to: @created_by.notification_email,
subject: subject("Invitation declined"))
end
end
end
+3 -3
View File
@@ -1,7 +1,7 @@
module Emails
module Profile
def new_user_email(user_id, token = nil)
@user = User.find(user_id)
@current_user = @user = User.find(user_id)
@target_url = user_url(@user)
@token = token
mail(to: @user.notification_email, subject: subject("Account was created for you"))
@@ -9,13 +9,13 @@ module Emails
def new_email_email(email_id)
@email = Email.find(email_id)
@user = @email.user
@current_user = @user = @email.user
mail(to: @user.notification_email, subject: subject("Email was added to your account"))
end
def new_ssh_key_email(key_id)
@key = Key.find(key_id)
@user = @key.user
@current_user = @user = @key.user
@target_url = user_url(@user)
mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
end
+58 -8
View File
@@ -1,15 +1,57 @@
module Emails
module Projects
def project_access_granted_email(user_project_id)
@project_member = ProjectMember.find user_project_id
def project_access_granted_email(project_member_id)
@project_member = ProjectMember.find project_member_id
@project = @project_member.project
@target_url = namespace_project_url(@project.namespace, @project)
mail(to: @project_member.user.email,
@current_user = @project_member.user
mail(to: @project_member.user.notification_email,
subject: subject("Access to project was granted"))
end
def project_member_invited_email(project_member_id, token)
@project_member = ProjectMember.find project_member_id
@project = @project_member.project
@token = token
@target_url = namespace_project_url(@project.namespace, @project)
@current_user = @project_member.user
mail(to: @project_member.invite_email,
subject: "Invitation to join project #{@project.name_with_namespace}")
end
def project_invite_accepted_email(project_member_id)
@project_member = ProjectMember.find project_member_id
return if @project_member.created_by.nil?
@project = @project_member.project
@target_url = namespace_project_url(@project.namespace, @project)
@current_user = @project_member.created_by
mail(to: @project_member.created_by.notification_email,
subject: subject("Invitation accepted"))
end
def project_invite_declined_email(project_id, invite_email, access_level, created_by_id)
return if created_by_id.nil?
@project = Project.find(project_id)
@current_user = @created_by = User.find(created_by_id)
@access_level = access_level
@invite_email = invite_email
@target_url = namespace_project_url(@project.namespace, @project)
mail(to: @created_by.notification_email,
subject: subject("Invitation declined"))
end
def project_was_moved_email(project_id, user_id)
@user = User.find user_id
@current_user = @user = User.find user_id
@project = Project.find project_id
@target_url = namespace_project_url(@project.namespace, @project)
mail(to: @user.notification_email,
@@ -28,7 +70,7 @@ module Emails
end
@project = Project.find(project_id)
@author = User.find(author_id)
@current_user = @author = User.find(author_id)
@reverse_compare = reverse_compare
@compare = compare
@ref_name = Gitlab::Git.ref_name(ref)
@@ -83,9 +125,17 @@ module Emails
@disable_footer = true
mail(from: sender(author_id, send_from_committer_email),
to: recipient,
subject: @subject)
reply_to =
if send_from_committer_email && can_send_from_user_email?(@author)
@author.email
else
Gitlab.config.gitlab.email_reply_to
end
mail(from: sender(author_id, send_from_committer_email),
reply_to: reply_to,
to: recipient,
subject: @subject)
end
end
end
+23 -13
View File
@@ -14,13 +14,16 @@ class Notify < ActionMailer::Base
add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper
attr_accessor :current_user
helper_method :current_user, :can?
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
default from: Proc.new { default_sender_address.format }
default reply_to: "noreply@#{Gitlab.config.gitlab.host}"
default reply_to: Gitlab.config.gitlab.email_reply_to
# Just send email with 2 seconds delay
def self.delay
@@ -58,20 +61,24 @@ class Notify < ActionMailer::Base
address
end
def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain)
end
# Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same.
def sender(sender_id, send_from_user_email = false)
if sender = User.find(sender_id)
address = default_sender_address
address.display_name = sender.name
return unless sender = User.find(sender_id)
address = default_sender_address
address.display_name = sender.name
sender_domain = sender.email.split("@").last
if send_from_user_email && self.class.allowed_email_domains.include?(sender_domain)
address.address = sender.email
end
address.format
if send_from_user_email && can_send_from_user_email?(sender)
address.address = sender.email
end
address.format
end
# Look up a User by their ID and return their email address
@@ -80,9 +87,8 @@ class Notify < ActionMailer::Base
#
# Returns a String containing the User's email address.
def recipient(recipient_id)
if recipient = User.find(recipient_id)
recipient.notification_email
end
@current_user = User.find(recipient_id)
@current_user.notification_email
end
# Set the References header field
@@ -155,4 +161,8 @@ class Notify < ActionMailer::Base
mail(headers, &block)
end
def can?
Ability.abilities.allowed?(user, action, subject)
end
end
+6 -6
View File
@@ -198,11 +198,11 @@ class Ability
])
end
# Only group owner and administrators can manage group
# Only group owner and administrators can admin group
if group.has_owner?(user) || user.admin?
rules.push(*[
:manage_group,
:manage_namespace
:admin_group,
:admin_namespace
])
end
@@ -212,11 +212,11 @@ class Ability
def namespace_abilities(user, namespace)
rules = []
# Only namespace owner and administrators can manage it
# Only namespace owner and administrators can admin it
if namespace.owner == user || user.admin?
rules.push(*[
:create_projects,
:manage_namespace
:admin_namespace
])
end
@@ -254,7 +254,7 @@ class Ability
rules = []
target_user = subject.user
group = subject.group
can_manage = group_abilities(user, group).include?(:manage_group)
can_manage = group_abilities(user, group).include?(:admin_group)
if can_manage && (user != target_user)
rules << :modify_group_member
rules << :destroy_group_member
+4 -2
View File
@@ -17,6 +17,7 @@
# twitter_sharing_enabled :boolean default(TRUE)
# help_text :text
# restricted_visibility_levels :text
# max_attachment_size :integer default(10)
#
class ApplicationSetting < ActiveRecord::Base
@@ -24,7 +25,7 @@ class ApplicationSetting < ActiveRecord::Base
validates :home_page_url,
allow_blank: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" },
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
if: :home_page_url_column_exist
validates_each :restricted_visibility_levels do |record, attr, value|
@@ -50,7 +51,8 @@ class ApplicationSetting < ActiveRecord::Base
twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels']
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size']
)
end
+12 -4
View File
@@ -77,7 +77,7 @@ class Commit
title_end = title.index("\n")
if (!title_end && title.length > 100) || (title_end && title_end > 100)
title[0..79] << "&hellip;".html_safe
title[0..79] << ""
else
title.split("\n", 2).first
end
@@ -90,7 +90,7 @@ class Commit
title_end = safe_message.index("\n")
@description ||=
if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
"&hellip;".html_safe << safe_message[80..-1]
"" << safe_message[80..-1]
else
safe_message.split("\n", 2)[1].try(:chomp)
end
@@ -117,8 +117,8 @@ class Commit
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
def closes_issues(project)
Gitlab::ClosingIssueExtractor.closed_by_message_in_project(safe_message, project)
def closes_issues(project, current_user = self.committer)
Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
end
# Mentionable override.
@@ -126,6 +126,14 @@ class Commit
"commit #{id}"
end
def author
User.find_for_commit(author_email, author_name)
end
def committer
User.find_for_commit(committer_email, committer_name)
end
def method_missing(m, *args, &block)
@raw.send(m, *args, &block)
end
+4 -4
View File
@@ -118,16 +118,16 @@ module Issuable
end
# Return all users participating on the discussion
def participants
def participants(current_user = self.author)
users = []
users << author
users << assignee if is_assigned?
mentions = []
mentions << self.mentioned_users
mentions << self.mentioned_users(current_user)
notes.each do |note|
users << note.author
mentions << note.mentioned_users
mentions << note.mentioned_users(current_user)
end
users.concat(mentions.reduce([], :|)).uniq
@@ -140,7 +140,7 @@ module Issuable
return subscription.subscribed
end
participants.include?(user)
participants(user).include?(user)
end
def toggle_subscription(user)
+12 -26
View File
@@ -42,36 +42,22 @@ module Mentionable
Note.cross_reference_exists?(target, local_reference)
end
def mentioned_users
users = []
return users if mentionable_text.blank?
has_project = self.respond_to? :project
matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/)
matches.each do |match|
identifier = match.delete "@"
if identifier == "all"
users.push(*project.team.members.flatten)
elsif namespace = Namespace.find_by(path: identifier)
if namespace.is_a?(Group)
users.push(*namespace.users)
else
users << namespace.owner
end
end
end
users.uniq
def mentioned_users(current_user = nil)
return [] if mentionable_text.blank?
ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
ext.analyze(mentionable_text)
ext.users.uniq
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def references(p = project, text = mentionable_text)
def references(p = project, current_user = self.author, text = mentionable_text)
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new
ext.analyze(text, p)
(ext.issues_for(p) +
ext.merge_requests_for(p) +
ext.commits_for(p)
).uniq - [local_reference]
ext = Gitlab::ReferenceExtractor.new(p, current_user)
ext.analyze(text)
(ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
@@ -97,7 +83,7 @@ module Mentionable
# Only proceed if the saved changes actually include a chance to an attr_mentionable field.
return unless mentionable_changed
preexisting = references(p, original)
preexisting = references(p, self.author, original)
create_cross_references!(p, a, preexisting)
end
end
+18
View File
@@ -7,6 +7,7 @@
# created_at :datetime
# updated_at :datetime
# key :text
# public :boolean default(FALSE)
# title :string(255)
# type :string(255)
# fingerprint :string(255)
@@ -17,4 +18,21 @@ class DeployKey < Key
has_many :projects, through: :deploy_keys_projects
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
scope :are_public, -> { where(public: true) }
def private?
!public?
end
def orphaned?
self.deploy_keys_projects.length == 0
end
def almost_orphaned?
self.deploy_keys_projects.length == 1
end
def destroyed_when_orphaned?
self.private?
end
end
+3 -1
View File
@@ -22,6 +22,8 @@ class DeployKeysProject < ActiveRecord::Base
private
def destroy_orphaned_deploy_key
self.deploy_key.destroy if self.deploy_key.deploy_keys_projects.length == 0
return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned?
self.deploy_key.destroy
end
end
+5 -1
View File
@@ -183,7 +183,11 @@ class Event < ActiveRecord::Base
elsif commented?
"commented on"
elsif created_project?
"created"
if project.import?
"imported"
else
"created"
end
else
"opened"
end
+7 -8
View File
@@ -50,19 +50,18 @@ class Group < Namespace
@owners ||= group_members.owners.map(&:user)
end
def add_users(user_ids, access_level)
user_ids.compact.each do |user_id|
user = self.group_members.find_or_initialize_by(user_id: user_id)
user.update_attributes(access_level: access_level)
def add_users(user_ids, access_level, current_user = nil)
user_ids.each do |user_id|
Member.add_user(self.group_members, user_id, access_level, current_user)
end
end
def add_user(user, access_level)
self.group_members.create(user_id: user.id, access_level: access_level)
def add_user(user, access_level, current_user = nil)
add_users([user], access_level, current_user)
end
def add_owner(user)
self.add_user(user, Gitlab::Access::OWNER)
def add_owner(user, current_user = nil)
self.add_user(user, Gitlab::Access::OWNER, current_user)
end
def has_owner?(user)
+1 -1
View File
@@ -28,7 +28,7 @@ class WebHook < ActiveRecord::Base
default_timeout Gitlab.config.gitlab.webhook_timeout
validates :url, presence: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
def execute(data)
parsed_url = URI.parse(url)
+1
View File
@@ -15,4 +15,5 @@ class Identity < ActiveRecord::Base
belongs_to :user
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
validates :user_id, uniqueness: { scope: :provider }
end
+2 -14
View File
@@ -16,7 +16,6 @@ require 'digest/md5'
class Key < ActiveRecord::Base
include Sortable
include Gitlab::Popen
belongs_to :user
@@ -81,20 +80,9 @@ class Key < ActiveRecord::Base
def generate_fingerprint
self.fingerprint = nil
return unless key.present?
cmd_status = 0
cmd_output = ''
Tempfile.open('gitlab_key_file') do |file|
file.puts key
file.rewind
cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp')
end
return unless self.key.present?
if cmd_status.zero?
cmd_output.gsub /(\h{2}:)+\h{2}/ do |match|
self.fingerprint = match
end
end
self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint
end
end
+138 -2
View File
@@ -11,6 +11,10 @@
# type :string(255)
# created_at :datetime
# updated_at :datetime
# created_by_id :integer
# invite_email :string
# invite_token :string
# invite_accepted_at :datetime
#
class Member < ActiveRecord::Base
@@ -18,19 +22,151 @@ class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
attr_accessor :raw_invite_token
belongs_to :created_by, class_name: "User"
belongs_to :user
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :user, presence: true, unless: :invite?
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source",
allow_nil: true }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :invite_email, presence: { if: :invite? },
email: { strict_mode: true, allow_nil: true },
uniqueness: { scope: [:source_type, :source_id], allow_nil: true }
scope :invite, -> { where(user_id: nil) }
scope :non_invite, -> { where("user_id IS NOT NULL") }
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
scope :developers, -> { where(access_level: DEVELOPER) }
scope :masters, -> { where(access_level: MASTER) }
scope :owners, -> { where(access_level: OWNER) }
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
after_create :send_invite, if: :invite?
after_create :post_create_hook, unless: :invite?
after_update :post_update_hook, unless: :invite?
after_destroy :post_destroy_hook, unless: :invite?
delegate :name, :username, :email, to: :user, prefix: true
class << self
def find_by_invite_token(invite_token)
invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
find_by(invite_token: invite_token)
end
# This method is used to find users that have been entered into the "Add members" field.
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
def user_for_id(user_id)
return user_id if user_id.is_a?(User)
user = User.find_by(id: user_id)
user ||= User.find_by(email: user_id)
user ||= user_id
user
end
def add_user(members, user_id, access_level, current_user = nil)
user = user_for_id(user_id)
# `user` can be either a User object or an email to be invited
if user.is_a?(User)
member = members.find_or_initialize_by(user_id: user.id)
else
member = members.build
member.invite_email = user
end
member.created_by ||= current_user
member.access_level = access_level
member.save
end
end
def invite?
self.invite_token.present?
end
def accept_invite!(new_user)
return false unless invite?
self.invite_token = nil
self.invite_accepted_at = Time.now.utc
self.user = new_user
saved = self.save
after_accept_invite if saved
saved
end
def decline_invite!
return false unless invite?
destroyed = self.destroy
after_decline_invite if destroyed
destroyed
end
def generate_invite_token
raw, enc = Devise.token_generator.generate(self.class, :invite_token)
@raw_invite_token = raw
self.invite_token = enc
end
def generate_invite_token!
generate_invite_token && save(validate: false)
end
def resend_invite
return unless invite?
generate_invite_token! unless @raw_invite_token
send_invite
end
private
def send_invite
# override in subclass
end
def post_create_hook
system_hook_service.execute_hooks_for(self, :create)
end
def post_update_hook
# override in subclass
end
def post_destroy_hook
system_hook_service.execute_hooks_for(self, :destroy)
end
def after_accept_invite
post_create_hook
end
def after_decline_invite
# override in subclass
end
def system_hook_service
SystemHooksService.new
end
def notification_service
NotificationService.new
end
end
+22 -15
View File
@@ -28,10 +28,6 @@ class GroupMember < Member
scope :with_user, ->(user) { where(user_id: user.id) }
scope :with_ldap_dn, -> { joins(user: :identities).where("identities.provider LIKE ?", 'ldap%') }
after_create :post_create_hook
after_update :notify_update
after_destroy :post_destroy_hook
def self.access_level_roles
Gitlab::Access.options_with_owner
end
@@ -44,26 +40,37 @@ class GroupMember < Member
access_level
end
def post_create_hook
notification_service.new_group_member(self)
system_hook_service.execute_hooks_for(self, :create)
private
def send_invite
notification_service.invite_group_member(self, @raw_invite_token)
super
end
def notify_update
def post_create_hook
notification_service.new_group_member(self)
super
end
def post_update_hook
if access_level_changed?
notification_service.update_group_member(self)
end
super
end
def post_destroy_hook
system_hook_service.execute_hooks_for(self, :destroy)
def after_accept_invite
notification_service.accept_group_invite(self)
super
end
def system_hook_service
SystemHooksService.new
end
def after_decline_invite
notification_service.decline_group_invite(self)
def notification_service
NotificationService.new
super
end
end
+40 -24
View File
@@ -27,10 +27,6 @@ class ProjectMember < Member
validates_format_of :source_type, with: /\AProject\z/
default_scope { where(source_type: SOURCE_TYPE) }
after_create :post_create_hook
after_update :post_update_hook
after_destroy :post_destroy_hook
scope :in_project, ->(project) { where(source_id: project.id) }
scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) }
scope :with_user, ->(user) { where(user_id: user.id) }
@@ -55,7 +51,7 @@ class ProjectMember < Member
# :master
# )
#
def add_users_into_projects(project_ids, user_ids, access)
def add_users_into_projects(project_ids, user_ids, access, current_user = nil)
access_level = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
@@ -64,12 +60,14 @@ class ProjectMember < Member
raise "Non valid access"
end
users = user_ids.map { |user_id| Member.user_for_id(user_id) }
ProjectMember.transaction do
project_ids.each do |project_id|
user_ids.each do |user_id|
member = ProjectMember.new(access_level: access_level, user_id: user_id)
member.source_id = project_id
member.save
project = Project.find(project_id)
users.each do |user|
Member.add_user(project.project_members, user, access_level, current_user)
end
end
end
@@ -82,6 +80,7 @@ class ProjectMember < Member
def truncate_teams(project_ids)
ProjectMember.transaction do
members = ProjectMember.where(source_id: project_ids)
members.each do |member|
member.destroy
end
@@ -109,41 +108,58 @@ class ProjectMember < Member
access_level
end
def project
source
end
def owner?
project.owner == user
end
private
def send_invite
notification_service.invite_project_member(self, @raw_invite_token)
super
end
def post_create_hook
unless owner?
event_service.join_project(self.project, self.user)
notification_service.new_project_member(self)
end
system_hook_service.execute_hooks_for(self, :create)
super
end
def post_update_hook
notification_service.update_project_member(self) if self.access_level_changed?
if access_level_changed?
notification_service.update_project_member(self)
end
super
end
def post_destroy_hook
event_service.leave_project(self.project, self.user)
system_hook_service.execute_hooks_for(self, :destroy)
super
end
def after_accept_invite
notification_service.accept_project_invite(self)
super
end
def after_decline_invite
notification_service.decline_project_invite(self)
super
end
def event_service
EventCreateService.new
end
def notification_service
NotificationService.new
end
def system_hook_service
SystemHooksService.new
end
def project
source
end
end
+7 -5
View File
@@ -260,11 +260,11 @@ class MergeRequest < ActiveRecord::Base
end
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
issues = commits.flat_map { |c| c.closes_issues(project) }
issues.push(*Gitlab::ClosingIssueExtractor.
closed_by_message_in_project(description, project))
issues = commits.flat_map { |c| c.closes_issues(project, current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description))
issues.uniq.sort_by(&:id)
else
[]
@@ -364,6 +364,8 @@ class MergeRequest < ActiveRecord::Base
end
def locked_long_ago?
locked_at && locked_at < (Time.now - 1.day)
return false unless locked?
locked_at.nil? || locked_at < (Time.now - 1.day)
end
end
+2 -2
View File
@@ -61,8 +61,8 @@ class Namespace < ActiveRecord::Base
def clean_path(path)
path.gsub!(/@.*\z/, "")
path.gsub!(/\.git\z/, "")
path.gsub!(/\A-/, "")
path.gsub!(/.\z/, "")
path.gsub!(/\A-+/, "")
path.gsub!(/\.+\z/, "")
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
counter = 0
+13 -3
View File
@@ -22,6 +22,7 @@ require 'file_size_validator'
class Note < ActiveRecord::Base
include Mentionable
include Gitlab::CurrentSettings
default_value_for :system, false
@@ -36,7 +37,8 @@ class Note < ActiveRecord::Base
validates :note, :project, presence: true
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
validates :attachment, file_size: { maximum: 10.megabytes.to_i }
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
@@ -326,6 +328,10 @@ class Note < ActiveRecord::Base
end
end
def max_attachment_size
current_application_settings.max_attachment_size.megabytes.to_i
end
def commit_author
@commit_author ||=
project.team.users.find_by(email: noteable.author_email) ||
@@ -353,7 +359,7 @@ class Note < ActiveRecord::Base
def set_diff
# First lets find notes with same diff
# before iterating over all mr diffs
diff = Note.where(noteable_id: self.noteable_id, noteable_type: self.noteable_type, line_code: self.line_code).last.try(:diff)
diff = diff_for_line_code unless for_merge_request?
diff ||= find_diff
self.st_diff = diff.to_hash if diff
@@ -363,6 +369,10 @@ class Note < ActiveRecord::Base
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
end
def diff_for_line_code
Note.where(noteable_id: noteable_id, noteable_type: noteable_type, line_code: line_code).last.try(:diff)
end
# Check if such line of code exists in merge request diff
# If exists - its active discussion
# If not - its outdated diff
@@ -456,7 +466,7 @@ class Note < ActiveRecord::Base
prev_match_line = line
else
prev_lines << line
break if generate_line_code(line) == self.line_code
prev_lines.shift if prev_lines.length >= max_number_of_lines
+11 -2
View File
@@ -27,6 +27,7 @@
# import_type :string(255)
# import_source :string(255)
# avatar :string(255)
# import_data :text
#
require 'carrierwave/orm/activerecord'
@@ -50,6 +51,8 @@ class Project < ActiveRecord::Base
default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets
serialize :import_data, JSON
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
def set_last_activity_at
@@ -83,7 +86,7 @@ class Project < ActiveRecord::Base
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :jenkins_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy
has_one :buildkite_service, dependent: :destroy
has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
@@ -143,7 +146,7 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
format: { with: URI::regexp(%w(ssh git http https)), message: 'should be a valid url' },
format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
@@ -191,6 +194,7 @@ class Project < ActiveRecord::Base
state :failed
after_transition any => :started, do: :add_import_job
after_transition any => :finished, do: :clear_import_data
end
class << self
@@ -268,6 +272,11 @@ class Project < ActiveRecord::Base
RepositoryImportWorker.perform_in(2.seconds, id)
end
def clear_import_data
self.import_data = nil
self.save
end
def import?
import_url.present?
end
@@ -25,7 +25,7 @@ class BambooService < CiService
validates :bamboo_url,
presence: true,
format: { with: URI::regexp },
format: { with: /\A#{URI.regexp}\z/ },
if: :activated?
validates :build_key, presence: true, if: :activated?
validates :username,
@@ -20,9 +20,7 @@
require "addressable/uri"
# Buildbox renamed to Buildkite, but for backwards compatability with the STI
# of Services, the class name is kept as "Buildbox"
class BuildboxService < CiService
class BuildkiteService < CiService
ENDPOINT = "https://buildkite.com"
prop_accessor :project_url, :token
@@ -18,7 +18,7 @@ class ExternalWikiService < Service
prop_accessor :external_wiki_url
validates :external_wiki_url,
presence: true,
format: { with: URI::regexp },
format: { with: /\A#{URI.regexp}\z/ },
if: :activated?
def title
@@ -18,6 +18,8 @@
#
class GitlabCiService < CiService
API_PREFIX = "api/v1"
prop_accessor :project_url, :token
validates :project_url, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
@@ -59,6 +61,26 @@ class GitlabCiService < CiService
end
end
def fork_registration(new_project, private_token)
params = {
id: new_project.id,
name_with_namespace: new_project.name_with_namespace,
web_url: new_project.web_url,
default_branch: new_project.default_branch,
ssh_url_to_repo: new_project.ssh_url_to_repo
}
HTTParty.post(
fork_registration_path,
body: {
project_id: project.id,
project_token: token,
private_token: private_token,
data: params },
verify: false
)
end
def commit_coverage(sha, ref)
response = get_ci_build(sha, ref)
@@ -97,4 +119,10 @@ class GitlabCiService < CiService
{ type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' }
]
end
private
def fork_registration_path
project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks")
end
end
+1 -1
View File
@@ -148,7 +148,7 @@ class IrkerService < Service
def consider_uri(uri)
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
if uri.is_a?(URI) && uri.scheme[/^ircs?$/] && !uri.path.nil?
if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil?
# Do not authorize irc://domain.com/
if uri.fragment.nil? && uri.path.length > 1
uri.to_s
@@ -25,7 +25,7 @@ class TeamcityService < CiService
validates :teamcity_url,
presence: true,
format: { with: URI::regexp }, if: :activated?
format: { with: /\A#{URI.regexp}\z/ }, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username,
presence: true,
+15 -16
View File
@@ -12,12 +12,12 @@ class ProjectTeam
# @team << [@users, :master]
#
def <<(args)
users = args.first
users, access, current_user = *args
if users.respond_to?(:each)
add_users(users, args.second)
add_users(users, access, current_user)
else
add_user(users, args.second)
add_user(users, access, current_user)
end
end
@@ -43,23 +43,21 @@ class ProjectTeam
member
end
def add_user(user, access)
add_users_ids([user.id], access)
end
def add_users(users, access)
add_users_ids(users.map(&:id), access)
end
def add_users_ids(user_ids, access)
def add_users(users, access, current_user = nil)
return false if group_member_lock
ProjectMember.add_users_into_projects(
[project.id],
user_ids,
access
users,
access,
current_user
)
end
def add_user(user, access, current_user = nil)
add_users([user], access, current_user)
end
# Remove all users from project team
def truncate
ProjectMember.truncate_team(project)
@@ -89,7 +87,7 @@ class ProjectTeam
@masters ||= fetch_members(:masters)
end
def import(source_project)
def import(source_project, current_user = nil)
target_project = project
source_members = source_project.project_members.to_a
@@ -97,13 +95,14 @@ class ProjectTeam
source_members.reject! do |member|
# Skip if user already present in team
target_user_ids.include?(member.user_id)
!member.invite? && target_user_ids.include?(member.user_id)
end
source_members.map! do |member|
new_member = member.dup
new_member.id = nil
new_member.source = target_project
new_member.created_by = current_user
new_member
end
+2 -2
View File
@@ -199,7 +199,7 @@ class Repository
def changelog
cache.fetch(:changelog) do
tree(:head).blobs.find do |file|
file.name =~ /^(changelog|history)/i
file.name =~ /\A(changelog|history)/i
end
end
end
@@ -207,7 +207,7 @@ class Repository
def license
cache.fetch(:license) do
tree(:head).blobs.find do |file|
file.name =~ /^license/i
file.name =~ /\Alicense/i
end
end
end
+16 -16
View File
@@ -122,26 +122,26 @@ class Service < ActiveRecord::Base
def self.available_services_names
%w(
gitlab_ci
campfire
hipchat
pivotaltracker
flowdock
assembla
asana
emails_on_push
gemnasium
slack
jenkins
pushover
buildbox
assembla
bamboo
teamcity
jira
redmine
buildkite
campfire
custom_issue_tracker
irker
emails_on_push
external_wiki
flowdock
gemnasium
gitlab_ci
hipchat
irker
jenkins
jira
pivotaltracker
pushover
redmine
slack
teamcity
)
end
+20 -3
View File
@@ -49,6 +49,7 @@
# password_automatically_set :boolean default(FALSE)
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# public_email :string(255) default(""), not null
#
require 'carrierwave/orm/activerecord'
@@ -123,6 +124,7 @@ class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
validates :notification_email, presence: true, email: { strict_mode: true }
validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
@@ -142,6 +144,7 @@ class User < ActiveRecord::Base
before_validation :generate_password, on: :create
before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
before_save :ensure_authentication_token
after_save :ensure_namespace_correct
@@ -442,8 +445,16 @@ class User < ActiveRecord::Base
@ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end
def project_deploy_keys
DeployKey.in_projects(self.authorized_projects.pluck(:id))
end
def accessible_deploy_keys
DeployKey.in_projects(self.authorized_projects.pluck(:id)).uniq
@accessible_deploy_keys ||= begin
key_ids = project_deploy_keys.pluck(:id)
key_ids.push(*DeployKey.are_public.pluck(:id))
DeployKey.where(id: key_ids)
end
end
def created_by
@@ -463,6 +474,12 @@ class User < ActiveRecord::Base
end
end
def set_public_email
if self.public_email.blank? || !self.all_emails.include?(self.public_email)
self.public_email = ''
end
end
def set_projects_limit
connection_default_value_defined = new_record? && !projects_limit_changed?
return unless self.projects_limit.nil? || connection_default_value_defined
@@ -514,13 +531,13 @@ class User < ActiveRecord::Base
end
def full_website_url
return "http://#{website_url}" if website_url !~ /^https?:\/\//
return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//
website_url
end
def short_website_url
website_url.gsub(/https?:\/\//, '')
website_url.sub(/\Ahttps?:\/\//, '')
end
def all_ssh_keys
+1 -3
View File
@@ -13,9 +13,7 @@ class CreateTagService < BaseService
return error('Tag already exists')
end
if message
message.gsub!(/^\s+|\s+$/, '')
end
message.strip! if message
repository.add_tag(tag_name, ref, message)
new_tag = repository.find_tag(tag_name)
+3 -3
View File
@@ -70,7 +70,7 @@ class GitPushService
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
issues_to_close = commit.closes_issues(project)
issues_to_close = commit.closes_issues(project, user)
# Load commit author only if needed.
# For push with 1k commits it prevents 900+ requests in database
@@ -91,7 +91,7 @@ class GitPushService
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit
# being pushed to a different branch).
refs = commit.references(project) - issues_to_close
refs = commit.references(project, user) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) }
if refs.present?
@@ -131,6 +131,6 @@ class GitPushService
end
def commit_user(commit)
User.find_for_commit(commit.author_email, commit.author_name) || user
commit.author || user
end
end
+38 -14
View File
@@ -123,30 +123,30 @@ class NotificationService
return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
target = note.noteable
if target.respond_to?(:participants)
recipients = target.participants
else
recipients = note.mentioned_users
end
recipients = []
if note.commit_id.present?
opts.merge!(commit_id: note.commit_id)
recipients << note.commit_author
else
opts.merge!(noteable_id: note.noteable_id)
end
# Get users who left comment in thread
recipients = recipients.concat(User.where(id: Note.where(opts).pluck(:author_id)))
# Add all users participating in the thread (author, assignee, comment authors)
participants =
if target.respond_to?(:participants)
target.participants
elsif target.is_a?(Commit)
author_ids = Note.for_commit_id(target.id).pluck(:author_id).uniq
User.where(id: author_ids)
else
note.mentioned_users
end
recipients = recipients.concat(participants)
# Merge project watchers
recipients = recipients.concat(project_watchers(note.project)).compact.uniq
# Reject mention users unless mentioned in comment
# Reject users with Mention notification level, except those mentioned in _this_ note.
recipients = reject_mention_users(recipients - note.mentioned_users, note.project)
recipients = recipients + note.mentioned_users
@@ -168,6 +168,18 @@ class NotificationService
end
end
def invite_project_member(project_member, token)
mailer.project_member_invited_email(project_member.id, token)
end
def accept_project_invite(project_member)
mailer.project_invite_accepted_email(project_member.id)
end
def decline_project_invite(project_member)
mailer.project_invite_declined_email(project_member.project.id, project_member.invite_email, project_member.access_level, project_member.created_by_id)
end
def new_project_member(project_member)
mailer.project_access_granted_email(project_member.id)
end
@@ -176,6 +188,18 @@ class NotificationService
mailer.project_access_granted_email(project_member.id)
end
def invite_group_member(group_member, token)
mailer.group_member_invited_email(group_member.id, token)
end
def accept_group_invite(group_member)
mailer.group_invite_accepted_email(group_member.id)
end
def decline_group_invite(group_member)
mailer.group_invite_declined_email(group_member.group.id, group_member.invite_email, group_member.access_level, group_member.created_by_id)
end
def new_group_member(group_member)
mailer.group_access_granted_email(group_member.id)
end
+1 -1
View File
@@ -83,7 +83,7 @@ module Projects
system_hook_service.execute_hooks_for(@project, :create)
unless @project.group
@project.team << [current_user, :master]
@project.team << [current_user, :master, current_user]
end
@project.update_column(:last_activity_at, @project.created_at)
+7 -1
View File
@@ -38,14 +38,20 @@ module Projects
#First save the DB entries as they can be rolled back if the repo fork fails
project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
if project.save
project.team << [@current_user, :master]
project.team << [@current_user, :master, @current_user]
end
#Now fork the repo
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
raise 'forking failed in gitlab-shell'
end
project.ensure_satellite_exists
end
if @from_project.gitlab_ci?
ForkRegistrationWorker.perform_async(@from_project.id, project.id, @current_user.private_token)
end
rescue => ex
project.errors.add(:base, 'Fork transaction failed.')
project.destroy
+7 -12
View File
@@ -1,10 +1,5 @@
module Projects
class ParticipantsService < BaseService
def initialize(project, user)
@project = project
@user = user
end
def execute(note_type, note_id)
participating =
if note_type && note_id
@@ -12,7 +7,7 @@ module Projects
else
[]
end
project_members = sorted(@project.team.members)
project_members = sorted(project.team.members)
participants = all_members + groups + project_members + participating
participants.uniq
end
@@ -20,11 +15,11 @@ module Projects
def participants_in(type, id)
users = case type
when "Issue"
issue = @project.issues.find_by_iid(id)
issue ? issue.participants : []
issue = project.issues.find_by_iid(id)
issue ? issue.participants(current_user) : []
when "MergeRequest"
merge_request = @project.merge_requests.find_by_iid(id)
merge_request ? merge_request.participants : []
merge_request = project.merge_requests.find_by_iid(id)
merge_request ? merge_request.participants(current_user) : []
when "Commit"
author_ids = Note.for_commit_id(id).pluck(:author_id).uniq
User.where(id: author_ids)
@@ -41,14 +36,14 @@ module Projects
end
def groups
@user.authorized_groups.sort_by(&:path).map do |group|
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
{ username: group.path, name: "#{group.name} (#{count})" }
end
end
def all_members
count = @project.team.members.flatten.count
count = project.team.members.flatten.count
[{ username: "all", name: "All Project and Group Members (#{count})" }]
end
end
+7 -1
View File
@@ -5,7 +5,7 @@ module Projects
end
def execute
return nil unless @file
return nil unless @file and @file.size <= max_attachment_size
uploader = FileUploader.new(@project)
uploader.store!(@file)
@@ -18,5 +18,11 @@ module Projects
'is_image' => uploader.image?
}
end
private
def max_attachment_size
current_application_settings.max_attachment_size.megabytes.to_i
end
end
end
@@ -65,5 +65,10 @@
.col-sm-10
= f.text_area :help_text, class: 'form-control', rows: 4
.help-block Markdown enabled
.form-group
= f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :max_attachment_size, class: 'form-control'
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
+6 -4
View File
@@ -1,13 +1,15 @@
= form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f|
- if application.errors.any?
.alert.alert-danger{"data-alert" => ""}
%p Whoops! Check your form for possible errors
= content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
- application.errors.full_messages.each do |msg|
%p= msg
= content_tag :div, class: 'form-group' do
= f.label :name, class: 'col-sm-2 control-label'
.col-sm-10
= f.text_field :name, class: 'form-control'
= doorkeeper_errors_for application, :name
= content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do
= content_tag :div, class: 'form-group' do
= f.label :redirect_uri, class: 'col-sm-2 control-label'
.col-sm-10
= f.text_area :redirect_uri, class: 'form-control'
@@ -41,4 +41,4 @@
.panel.panel-default
%iframe{src: sidekiq_path, width: '100%', height: 900, style: "border: none"}
%iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"}
@@ -0,0 +1,27 @@
.panel.panel-default
.panel-heading
Public deploy keys (#{@deploy_keys.count})
.panel-head-actions
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- if @deploy_keys.any?
%table.table
%thead.panel-heading
%tr
%th Title
%th Fingerprint
%th Added at
%th
%tbody
- @deploy_keys.each do |deploy_key|
%tr
%td
= link_to admin_deploy_key_path(deploy_key) do
%strong= deploy_key.title
%td
%span
(#{deploy_key.fingerprint})
%td
%span.cgray
added #{time_ago_with_tooltip(deploy_key.created_at)}
%td
= link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
+26
View File
@@ -0,0 +1,26 @@
%h3.page-title New public deploy key
%hr
%div
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f|
-if @deploy_key.errors.any?
.alert.alert-danger
%ul
- @deploy_key.errors.full_messages.each do |msg|
%li= msg
.form-group
= f.label :title, class: "control-label"
.col-sm-10= f.text_field :title, class: 'form-control'
.form-group
= f.label :key, class: "control-label"
.col-sm-10
%p.light
Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh", "README")
= f.text_area :key, class: "form-control thin_area", rows: 5
.form-actions
= f.submit 'Create', class: "btn-create btn"
= link_to "Cancel", admin_deploy_keys_path, class: "btn btn-cancel"
@@ -0,0 +1,34 @@
.row
.col-md-4
.panel.panel-default
.panel-heading
Deploy Key
%ul.well-list
%li
%span.light Title:
%strong= @deploy_key.title
%li
%span.light Created on:
%strong= @deploy_key.created_at.stamp("Aug 21, 2011")
.panel.panel-default
.panel-heading Projects (#{@deploy_key.deploy_keys_projects.count})
- if @deploy_key.deploy_keys_projects.any?
%ul.well-list
- @deploy_key.projects.each do |project|
%li
%span
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
.pull-right
= link_to disable_namespace_project_deploy_key_path(project.namespace, project, @deploy_key), data: { confirm: "Are you sure?" }, method: :put, class: "btn-xs btn btn-remove", title: 'Remove deploy key from project' do
%i.fa.fa-times.fa-inverse
.col-md-8
%p
%span.light Fingerprint:
%strong= @deploy_key.fingerprint
%pre.well-pre
= @deploy_key.key
.pull-right
= link_to 'Remove', admin_deploy_key_path(@deploy_key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
+10 -5
View File
@@ -87,7 +87,7 @@
= form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, { multiple: true, skip_ldap: @group.ldap_synced? })
= users_select_tag(:user_ids, multiple: true, email_user: true, skip_ldap: @group.ldap_synced?)
%div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
@@ -101,13 +101,18 @@
%ul.well-list.group-users-list
- @members.each do |member|
- user = member.user
%li{class: dom_class(member), id: dom_id(user)}
%li{class: dom_class(member), id: (dom_id(user) if user)}
.list-item-name
%strong
= link_to user.name, admin_user_path(user)
- if user
%strong
= link_to user.name, admin_user_path(user)
- else
%strong
= member.invite_email
(invited)
%span.pull-right.light
= member.human_access
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
+10 -5
View File
@@ -42,11 +42,11 @@
%li
%span.light http:
%strong
= link_to @project.http_url_to_repo
= link_to @project.http_url_to_repo, project_path(@project)
%li
%span.light ssh:
%strong
= link_to @project.ssh_url_to_repo
= link_to @project.ssh_url_to_repo, project_path(@project)
- if @project.repository.exists?
%li
%span.light fs:
@@ -124,14 +124,19 @@
- user = project_member.user
%li.project_member
.list-item-name
%strong
= link_to user.name, admin_user_path(user)
- if user
%strong
= link_to user.name, admin_user_path(user)
- else
%strong
= project_member.invite_email
(invited)
.pull-right
- if project_member.owner?
%span.light Owner
- else
%span.light= project_member.human_access
= link_to namespace_project_project_member_path(@project.namespace, @project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
= link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_from_project_team_message(@project, project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
%i.fa.fa-times
.panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
+2 -2
View File
@@ -182,7 +182,7 @@
.pull-right
%span.light= group_member.human_access
- unless group_member.owner?
= link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
= link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-times.fa-inverse
- else
.nothing-here-block This user has no groups.
@@ -221,7 +221,7 @@
%span.light= member.human_access
- if member.respond_to? :project
= link_to namespace_project_project_member_path(project.namespace, project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do
= link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_from_project_team_message(project, member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do
%i.fa.fa-times
#ssh-keys.tab-pane
= render 'profiles/keys/key_table', admin: true
+1 -1
View File
@@ -17,7 +17,7 @@
- group = group_member.group
%li
.pull-right
- if can?(current_user, :manage_group, group)
- if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
%i.fa.fa-cogs
Settings
@@ -19,5 +19,5 @@
%i.fa.fa-angle-left
- else
%h3 You dont have starred projects yet
%h3 You don't have starred projects yet
%p.slead Visit project page and press on star icon and it will appear on this page.
@@ -22,5 +22,6 @@
.clearfix.prepend-top-20
%p
%span.light Did not receive confirmation email?
= link_to "Send again", new_confirmation_path(resource_name)
%span.light Didn't receive a confirmation email?
= succeed '.' do
= link_to "Request a new one", new_confirmation_path(resource_name)

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