mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-13 22:59:19 +10:00
Merge branch 'ce-to-ee' into 'master'
Recent changes from CE master cc @job See merge request !370
This commit is contained in:
+1
-1
@@ -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.'
|
||||
|
||||
@@ -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
@@ -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 @@
|
||||
2.6.0
|
||||
2.6.2
|
||||
|
||||
@@ -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
@@ -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 + ">×</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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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%) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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] << "…".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)
|
||||
"…".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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
+1
-3
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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"} ×
|
||||
- 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"
|
||||
@@ -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"
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user