Commit f1a85747 authored by Tomasz Maczukin's avatar Tomasz Maczukin

Merge branch 'master' into dev-master

* master: (98 commits)
  Enable Style/EmptyLines cop, remove redundant ones
  Update CHANGELOG
  Cache results from jQuery selectors to retrieve namespace name
  Fix import button when import fail due the namespace already been taken
  Fix snippets comments not displayed
  Fix emoji paths in relative root configurations
  Exclude requesters from Project#members, Group#members and User#members
  Upgrade Thin from 1.6.1 to 1.7.0.
  Many squashed commits
  Cache autocomplete results
  Upgrade Sidekiq from 4.1.2 to 4.1.4.
  Upgrade seed-fu from 2.3.5 to 2.3.6
  use has_many relationship with events
  Support creating a todo on issuables via API
  Expose target, filter by state as string
  Add todos API documentation and changelog
  Improve the request / withdraw access button
  Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
  Groundwork for Kerberos SPNEGO (EE feature)
  Update CHANGELOG 8.9.5 for runners related fixes
  ...
parents 3a6ebb1f fc3402b7
require: rubocop-rspec require:
- rubocop-rspec
- ./rubocop/rubocop
AllCops: AllCops:
TargetRubyVersion: 2.1 TargetRubyVersion: 2.1
...@@ -191,7 +193,7 @@ Style/EmptyLineBetweenDefs: ...@@ -191,7 +193,7 @@ Style/EmptyLineBetweenDefs:
# Don't use several empty lines in a row. # Don't use several empty lines in a row.
Style/EmptyLines: Style/EmptyLines:
Enabled: false Enabled: true
# Keep blank lines around access modifiers. # Keep blank lines around access modifiers.
Style/EmptyLinesAroundAccessModifier: Style/EmptyLinesAroundAccessModifier:
...@@ -532,11 +534,11 @@ Style/SingleLineMethods: ...@@ -532,11 +534,11 @@ Style/SingleLineMethods:
# Use spaces after colons. # Use spaces after colons.
Style/SpaceAfterColon: Style/SpaceAfterColon:
Enabled: false Enabled: true
# Use spaces after commas. # Use spaces after commas.
Style/SpaceAfterComma: Style/SpaceAfterComma:
Enabled: false Enabled: true
# Do not put a space between a method name and the opening parenthesis in a # Do not put a space between a method name and the opening parenthesis in a
# method definition. # method definition.
...@@ -679,7 +681,7 @@ Style/UnlessElse: ...@@ -679,7 +681,7 @@ Style/UnlessElse:
# Checks for %W when interpolation is not needed. # Checks for %W when interpolation is not needed.
Style/UnneededCapitalW: Style/UnneededCapitalW:
Enabled: false Enabled: true
# TODO: Enable UnneededInterpolation Cop. # TODO: Enable UnneededInterpolation Cop.
# Checks for strings that are just an interpolated expression. # Checks for strings that are just an interpolated expression.
......
...@@ -3,8 +3,12 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,8 +3,12 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.10.0 (unreleased) v 8.10.0 (unreleased)
- Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Fix commit builds API, return all builds for all pipelines for given commit. !4849
- Replace Haml with Hamlit to make view rendering faster. !3666 - Replace Haml with Hamlit to make view rendering faster. !3666
- Refactor repository paths handling to allow multiple git mount points
- Add Application Setting to configure default Repository Path for new projects
- Wrap code blocks on Activies and Todos page. !4783 (winniehell) - Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Align flash messages with left side of page content !4959 (winniehell)
- Display last commit of deleted branch in push events !4699 (winniehell) - Display last commit of deleted branch in push events !4699 (winniehell)
- Apply the trusted_proxies config to the rack request object for use with rack_attack
- Add Sidekiq queue duration to transaction metrics. - Add Sidekiq queue duration to transaction metrics.
- Let Workhorse serve format-patch diffs - Let Workhorse serve format-patch diffs
- Make images fit to the size of the viewport !4810 - Make images fit to the size of the viewport !4810
...@@ -14,17 +18,42 @@ v 8.10.0 (unreleased) ...@@ -14,17 +18,42 @@ v 8.10.0 (unreleased)
- Exclude email check from the standard health check - Exclude email check from the standard health check
- Fix changing issue state columns in milestone view - Fix changing issue state columns in milestone view
- Add notification settings dropdown for groups - Add notification settings dropdown for groups
- Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Todos !3188 (Robert Schilling)
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- PipelinesFinder uses git cache data - PipelinesFinder uses git cache data
- Check for conflicts with existing Project's wiki path when creating a new project. - Check for conflicts with existing Project's wiki path when creating a new project.
- Don't instantiate a git tree on Projects show default view
- Remove unused front-end variable -> default_issues_tracker - Remove unused front-end variable -> default_issues_tracker
- Better caching of git calls on ProjectsController#show.
- Add API endpoint for a group issues !4520 (mahcsig) - Add API endpoint for a group issues !4520 (mahcsig)
- Add Bugzilla integration !4930 (iamtjg) - Add Bugzilla integration !4930 (iamtjg)
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
- Add basic system information like memory and disk usage to the admin panel - Add basic system information like memory and disk usage to the admin panel
v 8.9.4 (unreleased) v 8.9.4 (unreleased)
- Ensure references to private repos aren't shown to logged-out users - Ensure references to private repos aren't shown to logged-out users
v 8.9.5 (unreleased)
- Improve the request / withdraw access button. !4860
- Fix assigning shared runners as admins. !4961
- Show "locked" label for locked runners on runners admin. !4961
- Fixes issues importing events in Import/Export. Import/Export version bumped to 0.1.1
- Fix import button disabled when import process fail due to the namespace already been taken.
v 8.9.4
- Fix privilege escalation issue with OAuth external users.
- Ensure references to private repos aren't shown to logged-out users.
- Fixed search field blur not removing focus. !4704
- Resolve "Sub nav isn't showing on file view". !4890
- Fixes middle click and double request when navigating through the file browser. !4891
- Fixed URL on label button when filtering. !4897
- Fixed commit avatar alignment. !4933
- Do not show build retry link when build is active. !4967
- Fix restore Rake task warning message output. !4980
- Handle external issues in IssueReferenceFilter. !4988
- Expiry date on pinned nav cookie. !5009
- Updated breakpoint for sidebar pinning. !5019
v 8.9.3 v 8.9.3
- Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963
...@@ -41,6 +70,7 @@ v 8.9.3 ...@@ -41,6 +70,7 @@ v 8.9.3
- Fix missing avatar on system notes. !4954 - Fix missing avatar on system notes. !4954
- Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973
- Use update_columns to by_pass all the dirty code on active_record. !4985 - Use update_columns to by_pass all the dirty code on active_record. !4985
- Fix restore Rake task warning message output !4980
v 8.9.2 v 8.9.2
- Fix visibility of snippets when searching. - Fix visibility of snippets when searching.
...@@ -91,7 +121,6 @@ v 8.9.1 ...@@ -91,7 +121,6 @@ v 8.9.1
- Remove duplicate 'New Page' button on edit wiki page - Remove duplicate 'New Page' button on edit wiki page
v 8.9.0 v 8.9.0
v 8.9.0 (unreleased)
- Fix group visibility form layout in application settings - Fix group visibility form layout in application settings
- Fix builds API response not including commit data - Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined - Fix error when CI job variables key specified but not defined
...@@ -238,12 +267,17 @@ v 8.9.0 (unreleased) ...@@ -238,12 +267,17 @@ v 8.9.0 (unreleased)
- Filter parameters for request_uri value on instrumented transactions. - Filter parameters for request_uri value on instrumented transactions.
- Remove duplicated keys add UNIQUE index to keys fingerprint column - Remove duplicated keys add UNIQUE index to keys fingerprint column
- ExtractsPath get ref_names from repository cache, if not there access git. - ExtractsPath get ref_names from repository cache, if not there access git.
- Show a flash warning about the error detail of XHR requests which failed with status code 404 and 500
- Cache user todo counts from TodoService - Cache user todo counts from TodoService
- Ensure Todos counters doesn't count Todos for projects pending delete - Ensure Todos counters doesn't count Todos for projects pending delete
- Add left/right arrows horizontal navigation - Add left/right arrows horizontal navigation
- Add tooltip to pin/unpin navbar - Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation - Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.7
- Fix privilege escalation issue with OAuth external users.
- Ensure references to private repos aren't shown to logged-out users.
v 8.8.6 v 8.8.6
- Fix visibility of snippets when searching. - Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951 - Update omniauth-saml to 1.6.0 !4951
...@@ -378,6 +412,10 @@ v 8.8.0 ...@@ -378,6 +412,10 @@ v 8.8.0
- When creating a .gitignore file a dropdown with templates will be provided - When creating a .gitignore file a dropdown with templates will be provided
- Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562
v 8.7.9
- Fix privilege escalation issue with OAuth external users.
- Ensure references to private repos aren't shown to logged-out users.
v 8.7.8 v 8.7.8
- Fix visibility of snippets when searching. - Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951 - Update omniauth-saml to 1.6.0 !4951
......
...@@ -251,7 +251,6 @@ group :development do ...@@ -251,7 +251,6 @@ group :development do
gem 'brakeman', '~> 3.3.0', require: false gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0' gem 'rerun', '~> 0.11.0'
gem 'bullet', require: false gem 'bullet', require: false
gem 'rblineprof', platform: :mri, require: false gem 'rblineprof', platform: :mri, require: false
...@@ -265,7 +264,7 @@ group :development do ...@@ -265,7 +264,7 @@ group :development do
gem "sdoc", '~> 0.3.20' gem "sdoc", '~> 0.3.20'
# thin instead webrick # thin instead webrick
gem 'thin', '~> 1.6.1' gem 'thin', '~> 1.7.0'
end end
group :development, :test do group :development, :test do
...@@ -303,7 +302,6 @@ group :development, :test do ...@@ -303,7 +302,6 @@ group :development, :test do
gem 'rubocop', '~> 0.40.0', require: false gem 'rubocop', '~> 0.40.0', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.11.0', require: false gem 'simplecov', '~> 0.11.0', require: false
gem 'flog', require: false gem 'flog', require: false
gem 'flay', require: false gem 'flay', require: false
...@@ -350,3 +348,4 @@ gem 'health_check', '~> 1.5.1' ...@@ -350,3 +348,4 @@ gem 'health_check', '~> 1.5.1'
# System information # System information
gem 'vmstat', '~> 2.1.0' gem 'vmstat', '~> 2.1.0'
gem 'sys-filesystem', '~> 1.1.6'
...@@ -141,12 +141,6 @@ GEM ...@@ -141,12 +141,6 @@ GEM
colorize (0.7.7) colorize (0.7.7)
concurrent-ruby (1.0.2) concurrent-ruby (1.0.2)
connection_pool (2.2.0) connection_pool (2.2.0)
coveralls (0.8.13)
json (~> 1.8)
simplecov (~> 0.11.0)
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
tins (~> 1.6.0)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
creole (0.5.0) creole (0.5.0)
...@@ -505,8 +499,6 @@ GEM ...@@ -505,8 +499,6 @@ GEM
pry-rails (0.3.4) pry-rails (0.3.4)
pry (>= 0.9.10) pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.3)
railties (>= 3.1, < 5.0)
rack (1.6.4) rack (1.6.4)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
...@@ -640,8 +632,8 @@ GEM ...@@ -640,8 +632,8 @@ GEM
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.4.22) sass (3.4.22)
sass-rails (5.0.4) sass-rails (5.0.5)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 6)
sass (~> 3.1) sass (~> 3.1)
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
...@@ -655,9 +647,9 @@ GEM ...@@ -655,9 +647,9 @@ GEM
sdoc (0.3.20) sdoc (0.3.20)
json (>= 1.1.3) json (>= 1.1.3)
rdoc (~> 3.10) rdoc (~> 3.10)
seed-fu (2.3.5) seed-fu (2.3.6)
activerecord (>= 3.1, < 4.3) activerecord (>= 3.1)
activesupport (>= 3.1, < 4.3) activesupport (>= 3.1)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
sentry-raven (1.1.0) sentry-raven (1.1.0)
...@@ -668,10 +660,11 @@ GEM ...@@ -668,10 +660,11 @@ GEM
rack rack
shoulda-matchers (2.8.0) shoulda-matchers (2.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (4.1.2) sidekiq (4.1.4)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0) connection_pool (~> 2.2, >= 2.2.0)
redis (~> 3.2, >= 3.2.1) redis (~> 3.2, >= 3.2.1)
sinatra (>= 1.4.7)
sidekiq-cron (0.4.0) sidekiq-cron (0.4.0)
redis-namespace (>= 1.5.2) redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24) rufus-scheduler (>= 2.0.24)
...@@ -682,8 +675,8 @@ GEM ...@@ -682,8 +675,8 @@ GEM
json (~> 1.8) json (~> 1.8)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.0) simplecov-html (0.10.0)
sinatra (1.4.6) sinatra (1.4.7)
rack (~> 1.4) rack (~> 1.5)
rack-protection (~> 1.4) rack-protection (~> 1.4)
tilt (>= 1.3, < 3) tilt (>= 1.3, < 3)
six (0.2.0) six (0.2.0)
...@@ -706,10 +699,10 @@ GEM ...@@ -706,10 +699,10 @@ GEM
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2) spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (3.6.0) sprockets (3.6.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.0.4) sprockets-rails (3.1.1)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
...@@ -721,6 +714,8 @@ GEM ...@@ -721,6 +714,8 @@ GEM
activerecord (>= 4.1, < 5.1) activerecord (>= 4.1, < 5.1)
state_machines-activemodel (>= 0.3.0) state_machines-activemodel (>= 0.3.0)
stringex (2.5.2) stringex (2.5.2)
sys-filesystem (1.1.6)
ffi
systemu (2.6.5) systemu (2.6.5)
task_list (1.0.2) task_list (1.0.2)
html-pipeline html-pipeline
...@@ -729,14 +724,12 @@ GEM ...@@ -729,14 +724,12 @@ GEM
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.7) temple (0.7.7)
term-ansicolor (1.3.2)
tins (~> 1.0)
test_after_commit (0.4.2) test_after_commit (0.4.2)
activerecord (>= 3.2) activerecord (>= 3.2)
thin (1.6.4) thin (1.7.0)
daemons (~> 1.0, >= 1.0.9) daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4) eventmachine (~> 1.0, >= 1.0.4)
rack (~> 1.0) rack (>= 1, < 3)
thor (0.19.1) thor (0.19.1)
thread_safe (0.3.5) thread_safe (0.3.5)
tilt (2.0.5) tilt (2.0.5)
...@@ -751,7 +744,6 @@ GEM ...@@ -751,7 +744,6 @@ GEM
mime-types mime-types
multi_json (~> 1.7) multi_json (~> 1.7)
twitter-stream (~> 0.1) twitter-stream (~> 0.1)
tins (1.6.0)
turbolinks (2.5.3) turbolinks (2.5.3)
coffee-rails coffee-rails
twitter-stream (0.1.16) twitter-stream (0.1.16)
...@@ -841,7 +833,6 @@ DEPENDENCIES ...@@ -841,7 +833,6 @@ DEPENDENCIES
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
connection_pool (~> 2.0) connection_pool (~> 2.0)
coveralls (~> 0.8.2)
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
database_cleaner (~> 1.4.0) database_cleaner (~> 1.4.0)
...@@ -928,7 +919,6 @@ DEPENDENCIES ...@@ -928,7 +919,6 @@ DEPENDENCIES
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0) premailer-rails (~> 1.9.0)
pry-rails pry-rails
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.1) rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
...@@ -976,11 +966,12 @@ DEPENDENCIES ...@@ -976,11 +966,12 @@ DEPENDENCIES
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.6.0) sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6)
task_list (~> 1.0.2) task_list (~> 1.0.2)
teaspoon (~> 1.1.0) teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0) teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2) test_after_commit (~> 0.4.2)
thin (~> 1.6.1) thin (~> 1.7.0)
tinder (~> 1.10.0) tinder (~> 1.10.0)
turbolinks (~> 2.5.0) turbolinks (~> 2.5.0)
u2f (~> 0.2.1) u2f (~> 0.2.1)
......
...@@ -185,6 +185,15 @@ $ -> ...@@ -185,6 +185,15 @@ $ ->
else else
buttons.enable() buttons.enable()
$(document).ajaxError (e, xhrObj, xhrSetting, xhrErrorText) ->
if xhrObj.status is 401
new Flash 'You need to be logged in.', 'alert'
else if xhrObj.status in [ 404, 500 ]
new Flash 'Something went wrong on our end.', 'alert'
# Show/Hide the profile menu when hovering the account box # Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(@).toggleClass('hover') $('.account-box').hover -> $(@).toggleClass('hover')
...@@ -260,8 +269,8 @@ $ -> ...@@ -260,8 +269,8 @@ $ ->
new Aside() new Aside()
# Sidenav pinning # Sidenav pinning
if $window.width() < 1280 and $.cookie('pin_nav') is 'true' if $window.width() < 1024 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false', { path: '/' }) $.cookie('pin_nav', 'false', { path: '/', expires: 365 * 10 })
$('.page-with-sidebar') $('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded') .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned') .removeClass('page-sidebar-pinned')
...@@ -292,7 +301,7 @@ $ -> ...@@ -292,7 +301,7 @@ $ ->
.toggleClass('header-collapsed header-expanded') .toggleClass('header-collapsed header-expanded')
# Save settings # Save settings
$.cookie 'pin_nav', doPinNav, { path: '/' } $.cookie 'pin_nav', doPinNav, { path: '/', expires: 365 * 10 }
if $.cookie('pin_nav') is 'true' or doPinNav if $.cookie('pin_nav') is 'true' or doPinNav
tooltipText = 'Unpin navigation' tooltipText = 'Unpin navigation'
......
...@@ -4,11 +4,19 @@ class @Flash ...@@ -4,11 +4,19 @@ class @Flash
@flash.html("") @flash.html("")
innerDiv = $('<div/>', innerDiv = $('<div/>',
class: "flash-#{type}", class: "flash-#{type}"
text: message
) )
innerDiv.appendTo(".flash-container") innerDiv.appendTo(".flash-container")
textDiv = $("<div/>",
class: "flash-text",
text: message
)
textDiv.appendTo(innerDiv)
if @flash.parent().hasClass('content-wrapper')
textDiv.addClass('container-fluid container-limited')
@flash.click -> $(@).fadeOut() @flash.click -> $(@).fadeOut()
@flash.show() @flash.show()
......
...@@ -4,7 +4,7 @@ window.GitLab ?= {} ...@@ -4,7 +4,7 @@ window.GitLab ?= {}
GitLab.GfmAutoComplete = GitLab.GfmAutoComplete =
dataLoading: false dataLoading: false
dataLoaded: false dataLoaded: false
cachedData: {}
dataSource: '' dataSource: ''
# Emoji # Emoji
...@@ -55,7 +55,7 @@ GitLab.GfmAutoComplete = ...@@ -55,7 +55,7 @@ GitLab.GfmAutoComplete =
@setupAtWho() @setupAtWho()
if @dataSource if @dataSource
if !@dataLoading if not @dataLoading and not @cachedData
@dataLoading = true @dataLoading = true
# We should wait until initializations are done # We should wait until initializations are done
...@@ -70,6 +70,8 @@ GitLab.GfmAutoComplete = ...@@ -70,6 +70,8 @@ GitLab.GfmAutoComplete =
@loadData(data) @loadData(data)
, 1000) , 1000)
if @cachedData?
@loadData(@cachedData)
setupAtWho: -> setupAtWho: ->
# Emoji # Emoji
...@@ -205,6 +207,7 @@ GitLab.GfmAutoComplete = ...@@ -205,6 +207,7 @@ GitLab.GfmAutoComplete =
$.getJSON(dataSource) $.getJSON(dataSource)
loadData: (data) -> loadData: (data) ->
@cachedData = data
@dataLoaded = true @dataLoaded = true
# load members # load members
......
...@@ -7,13 +7,16 @@ class @ImporterStatus ...@@ -7,13 +7,16 @@ class @ImporterStatus
$('.js-add-to-import') $('.js-add-to-import')
.off 'click' .off 'click'
.on 'click', (e) => .on 'click', (e) =>
new_namespace = null
$btn = $(e.currentTarget) $btn = $(e.currentTarget)
$tr = $btn.closest('tr') $tr = $btn.closest('tr')
$target_field = $tr.find('.import-target')
$namespace_input = $target_field.find('input')
id = $tr.attr('id').replace('repo_', '') id = $tr.attr('id').replace('repo_', '')
if $tr.find('.import-target input').length > 0 new_namespace = null
new_namespace = $tr.find('.import-target input').prop('value')
$tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}") if $namespace_input.length > 0
new_namespace = $namespace_input.prop('value')
$target_field.empty().append("#{new_namespace}/#{$target_field.data('project_name')}")
$btn $btn
.disable() .disable()
......
...@@ -9,12 +9,12 @@ class @Shortcuts ...@@ -9,12 +9,12 @@ class @Shortcuts
onToggleHelp: (e) => onToggleHelp: (e) =>
e.preventDefault() e.preventDefault()
@toggleHelp(@enabledHelp) Shortcuts.toggleHelp(@enabledHelp)
toggleMarkdownPreview: (e) => toggleMarkdownPreview: (e) ->
$(document).triggerHandler('markdown-preview:toggle', [e]) $(document).triggerHandler('markdown-preview:toggle', [e])
toggleHelp: (location) -> @toggleHelp: (location) ->
$modal = $('#modal-shortcuts') $modal = $('#modal-shortcuts')
if $modal.length if $modal.length
......
...@@ -5,9 +5,15 @@ class @TreeView ...@@ -5,9 +5,15 @@ class @TreeView
# Code browser tree slider # Code browser tree slider
# Make the entire tree-item row clickable, but not if clicking another link (like a commit message) # Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
$(".tree-content-holder .tree-item").on 'click', (e) -> $(".tree-content-holder .tree-item").on 'click', (e) ->
if (e.target.nodeName != "A") $clickedEl = $(e.target)
path = $('.tree-item-file-name a', this).attr('href') path = $('.tree-item-file-name a', this).attr('href')
Turbolinks.visit(path)
if not $clickedEl.is('a') and not $clickedEl.is('.str-truncated')
if e.metaKey or e.which is 2
e.preventDefault()
window.open path, '_blank'
else
Turbolinks.visit path
# Show the "Loading commit data" for only the first element # Show the "Loading commit data" for only the first element
$('span.log_loading:first').removeClass('hide') $('span.log_loading:first').removeClass('hide')
......
...@@ -16,4 +16,11 @@ ...@@ -16,4 +16,11 @@
@extend .alert-danger; @extend .alert-danger;
margin: 0; margin: 0;
} }
.flash-notice, .flash-alert {
.container-fluid.flash-text {
background: transparent;
}
}
} }
...@@ -125,7 +125,8 @@ ...@@ -125,7 +125,8 @@
border: 0; border: 0;
outline: 0; outline: 0;
&:hover { &:hover,
&:focus {
color: $gl-link-color; color: $gl-link-color;
} }
} }
...@@ -71,6 +71,10 @@ ...@@ -71,6 +71,10 @@
display: none; display: none;
} }
.group-right-buttons {
display: none;
}
.container .title { .container .title {
padding-left: 15px !important; padding-left: 15px !important;
} }
......
...@@ -7,7 +7,7 @@ $gutter_collapsed_width: 62px; ...@@ -7,7 +7,7 @@ $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 258px; $gutter_inner_width: 258px;
$sidebar-transition-duration: .15s; $sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1280px; $sidebar-breakpoint: 1024px;
/* /*
* UI elements * UI elements
......
...@@ -83,11 +83,7 @@ ...@@ -83,11 +83,7 @@
position: relative; position: relative;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-left: 20px; padding-left: 46px;
.commit-info-block {
padding-left: 44px;
}
} }
&:not(:last-child) { &:not(:last-child) {
...@@ -102,9 +98,7 @@ ...@@ -102,9 +98,7 @@
.avatar { .avatar {
position: absolute; margin-left: -46px;
top: 10px;
left: 16px;
} }
.item-title { .item-title {
......
...@@ -41,14 +41,17 @@ ...@@ -41,14 +41,17 @@
} }
.groups-cover-block { .groups-cover-block {
.container-fluid { .container-fluid {
position: relative; position: relative;
} }
.access-request-button { .group-right-buttons {
position: absolute;
right: 16px;
.btn {
@include btn-gray; @include btn-gray;
margin-right: 10px; padding: 3px 10px;
text-transform: none; background-color: $background-color;
}
} }
} }
...@@ -266,18 +266,6 @@ ...@@ -266,18 +266,6 @@
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
top: 0; top: 0;
} }
.access-request-button {
position: absolute;
right: 0;
bottom: 61px;
@media (max-width: $screen-md-max) {
position: relative;
bottom: 0;
margin-right: 10px;
}
}
} }
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
......
...@@ -101,7 +101,8 @@ ...@@ -101,7 +101,8 @@
margin: 0; margin: 0;
.commit { .commit {
padding: 0 0 0 55px; padding-top: 0;
padding-bottom: 0;
.commit-row-title { .commit-row-title {
.commit-row-message { .commit-row-message {
......
...@@ -109,6 +109,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -109,6 +109,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_packet_size, :metrics_packet_size,
:send_user_confirmation_email, :send_user_confirmation_email,
:container_registry_token_expire_delay, :container_registry_token_expire_delay,
:repository_storage,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [], import_sources: [],
disabled_oauth_sign_in_sources: [] disabled_oauth_sign_in_sources: []
......
...@@ -10,6 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -10,6 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController
def show def show
@members = @group.members.order("access_level DESC").page(params[:members_page]) @members = @group.members.order("access_level DESC").page(params[:members_page])
@requesters = @group.requesters
@projects = @group.projects.page(params[:projects_page]) @projects = @group.projects.page(params[:projects_page])
end end
......
...@@ -22,7 +22,6 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -22,7 +22,6 @@ class Admin::HooksController < Admin::ApplicationController
redirect_to admin_hooks_path redirect_to admin_hooks_path
end end
def test def test
@hook = SystemHook.find(params[:hook_id]) @hook = SystemHook.find(params[:hook_id])
data = { data = {
......
...@@ -20,7 +20,8 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -20,7 +20,8 @@ class Admin::ProjectsController < Admin::ApplicationController
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page]) @group_members = @group.members.order("access_level DESC").page(params[:group_members_page])
end end
@project_members = @project.project_members.page(params[:project_members_page]) @project_members = @project.members.page(params[:project_members_page])
@requesters = @project.requesters
end end
def transfer def transfer
......
...@@ -4,8 +4,6 @@ class Admin::RunnerProjectsController < Admin::ApplicationController ...@@ -4,8 +4,6 @@ class Admin::RunnerProjectsController < Admin::ApplicationController
def create def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id]) @runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) if @runner.is_shared? || @runner.locked?
runner_project = @runner.assign_to(@project, current_user) runner_project = @runner.assign_to(@project, current_user)
if runner_project.persisted? if runner_project.persisted?
......
class Admin::SystemInfoController < Admin::ApplicationController class Admin::SystemInfoController < Admin::ApplicationController
EXCLUDED_MOUNT_OPTIONS = [
'nobrowse',
'read-only',
'ro'
]
EXCLUDED_MOUNT_TYPES = [
'autofs',
'binfmt_misc',
'cgroup',
'debugfs',
'devfs',
'devpts',
'devtmpfs',
'efivarfs',
'fuse.gvfsd-fuse',
'fuseblk',
'fusectl',
'hugetlbfs',
'mqueue',
'proc',
'pstore',
'securityfs',
'sysfs',
'tmpfs',
'tracefs',
'vfat'
]
def show def show
system_info = Vmstat.snapshot system_info = Vmstat.snapshot
mounts = Sys::Filesystem.mounts
@disks = []
mounts.each do |mount|
mount_options = mount.options.split(',')
next if (EXCLUDED_MOUNT_OPTIONS & mount_options).any?
next if (EXCLUDED_MOUNT_TYPES & [mount.mount_type]).any?
begin
disk = Sys::Filesystem.stat(mount.mount_point)
@disks.push({
bytes_total: disk.bytes_total,
bytes_used: disk.bytes_used,
disk_name: mount.name,
mount_path: disk.path
})
rescue Sys::Filesystem::Error
end
end
@cpus = system_info.cpus.length @cpus = system_info.cpus.length
@mem_used = system_info.memory.active_bytes @mem_used = system_info.memory.active_bytes
@mem_total = system_info.memory.total_bytes @mem_total = system_info.memory.total_bytes
@disk_used = system_info.disks[0].used_bytes
@disk_total = system_info.disks[0].total_bytes
end end
end end
...@@ -25,7 +25,7 @@ module Ci ...@@ -25,7 +25,7 @@ module Ci
return render_404 unless @project return render_404 unless @project
image = Ci::ImageForBuildService.new.execute(@project, params) image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" send_file image.path, filename: image.name, disposition: 'inline', type: "image/svg+xml"
end end
protected protected
......
...@@ -10,7 +10,7 @@ module MembershipActions ...@@ -10,7 +10,7 @@ module MembershipActions
end end
def approve_access_request def approve_access_request
@member = membershipable.members.request.find(params[:id]) @member = membershipable.requesters.find(params[:id])
return render_403 unless can?(current_user, action_member_permission(:update, @member), @member) return render_403 unless can?(current_user, action_member_permission(:update, @member), @member)
...@@ -20,7 +20,8 @@ module MembershipActions ...@@ -20,7 +20,8 @@ module MembershipActions
end end
def leave def leave
@member = membershipable.members.find_by(user_id: current_user) @member = membershipable.members.find_by(user_id: current_user) ||
membershipable.requesters.find_by(user_id: current_user)
Members::DestroyService.new(@member, current_user).execute Members::DestroyService.new(@member, current_user).execute
source_type = @member.real_source_type.humanize(capitalize: false) source_type = @member.real_source_type.humanize(capitalize: false)
......
class ConfirmationsController < Devise::ConfirmationsController class ConfirmationsController < Devise::ConfirmationsController
def almost_there def almost_there
flash[:notice] = nil flash[:notice] = nil
render layout: "devise_empty" render layout: "devise_empty"
......
...@@ -7,7 +7,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -7,7 +7,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
def index def index
@project = @group.projects.find(params[:project_id]) if params[:project_id] @project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = @group.group_members @members = @group.group_members
@members = @members.non_pending unless can?(current_user, :admin_group, @group) @members = @members.non_invite unless can?(current_user, :admin_group, @group)
if params[:search].present? if params[:search].present?
users = @group.users.search(params[:search]).to_a users = @group.users.search(params[:search]).to_a
...@@ -15,6 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -15,6 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
@members = @members.order('access_level DESC').page(params[:page]).per(50) @members = @members.order('access_level DESC').page(params[:page]).per(50)
@requesters = @group.requesters if can?(current_user, :admin_group, @group)
@group_member = @group.group_members.new @group_member = @group.group_members.new
end end
...@@ -34,7 +35,8 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -34,7 +35,8 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def destroy def destroy
@group_member = @group.group_members.find(params[:id]) @group_member = @group.members.find_by(id: params[:id]) ||
@group.requesters.find_by(id: params[:id])
Members::DestroyService.new(@group_member, current_user).execute Members::DestroyService.new(@group_member, current_user).execute
......
class Import::BaseController < ApplicationController class Import::BaseController < ApplicationController
private private
def get_or_create_namespace def get_or_create_namespace
......
...@@ -5,7 +5,6 @@ class Import::FogbugzController < Import::BaseController ...@@ -5,7 +5,6 @@ class Import::FogbugzController < Import::BaseController
rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized
def new def new
end end
def callback def callback
...@@ -22,7 +21,6 @@ class Import::FogbugzController < Import::BaseController ...@@ -22,7 +21,6 @@ class Import::FogbugzController < Import::BaseController
end end
def new_user_map def new_user_map
end end
def create_user_map def create_user_map
......
class Import::GithubController < Import::BaseController class Import::GithubController < Import::BaseController
before_action :verify_github_import_enabled before_action :verify_github_import_enabled
before_action :github_auth, except: :callback before_action :github_auth, only: [:status, :jobs, :create]
rescue_from Octokit::Unauthorized, with: :github_unauthorized rescue_from Octokit::Unauthorized, with: :github_unauthorized
helper_method :logged_in_with_github?
def new
if logged_in_with_github?
go_to_github_for_permissions
elsif session[:github_access_token]
redirect_to status_import_github_url
end
end
def callback def callback
session[:github_access_token] = client.get_token(params[:code]) session[:github_access_token] = client.get_token(params[:code])
redirect_to status_import_github_url redirect_to status_import_github_url
end end
def personal_access_token
session[:github_access_token] = params[:personal_access_token]
redirect_to status_import_github_url
end
def status def status
@repos = client.repos @repos = client.repos
@already_added_projects = current_user.created_projects.where(import_type: "github") @already_added_projects = current_user.created_projects.where(import_type: "github")
...@@ -57,10 +72,14 @@ class Import::GithubController < Import::BaseController ...@@ -57,10 +72,14 @@ class Import::GithubController < Import::BaseController
end end
def github_unauthorized def github_unauthorized
go_to_github_for_permissions session[:github_access_token] = nil
redirect_to new_import_github_url,
alert: 'Access denied to your GitHub account.'
end end
private def logged_in_with_github?
current_user.identities.exists?(provider: 'github')
end
def access_params def access_params
{ github_access_token: session[:github_access_token] } { github_access_token: session[:github_access_token] }
......
...@@ -44,5 +44,4 @@ class Import::GitoriousController < Import::BaseController ...@@ -44,5 +44,4 @@ class Import::GitoriousController < Import::BaseController
def verify_gitorious_import_enabled def verify_gitorious_import_enabled
render_404 unless gitorious_import_enabled? render_404 unless gitorious_import_enabled?
end end
end end
...@@ -3,7 +3,6 @@ class Import::GoogleCodeController < Import::BaseController ...@@ -3,7 +3,6 @@ class Import::GoogleCodeController < Import::BaseController
before_action :user_map, only: [:new_user_map, :create_user_map] before_action :user_map, only: [:new_user_map, :create_user_map]
def new def new
end end
def callback def callback
...@@ -34,7 +33,6 @@ class Import::GoogleCodeController < Import::BaseController ...@@ -34,7 +33,6 @@ class Import::GoogleCodeController < Import::BaseController
end end
def new_user_map def new_user_map
end end
def create_user_map def create_user_map
......
...@@ -5,7 +5,6 @@ class InvitesController < ApplicationController ...@@ -5,7 +5,6 @@ class InvitesController < ApplicationController
respond_to :html respond_to :html
def show def show
end end
def accept def accept
......
# This file should be identical in GitLab Community Edition and Enterprise Edition
class Projects::GitHttpController < Projects::ApplicationController class Projects::GitHttpController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
attr_reader :user attr_reader :user
# Git clients will not know what authenticity token to send along # Git clients will not know what authenticity token to send along
...@@ -40,9 +45,12 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -40,9 +45,12 @@ class Projects::GitHttpController < Projects::ApplicationController
private private
def authenticate_user def authenticate_user
return if project && project.public? && upload_pack? if project && project.public? && upload_pack?
return # Allow access
end
authenticate_or_request_with_http_basic do |login, password| if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && upload_pack? if auth_result.type == :ci && upload_pack?
...@@ -53,8 +61,31 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -53,8 +61,31 @@ class Projects::GitHttpController < Projects::ApplicationController
@user = auth_result.user @user = auth_result.user
end end
ci? || user if ci? || user
return # Allow access
end
elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user
if user
send_final_spnego_response
return # Allow access
end
end
send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401
end end
def basic_auth_provided?
has_basic_credentials?(request)
end
def send_challenges
challenges = []
challenges << 'Basic realm="GitLab"' if allow_basic_auth?
challenges << spnego_challenge if allow_kerberos_spnego_auth?
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end end
def ensure_project_found! def ensure_project_found!
...@@ -120,7 +151,7 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -120,7 +151,7 @@ class Projects::GitHttpController < Projects::ApplicationController
end end
def render_not_found def render_not_found
render text: 'Not Found', status: :not_found render plain: 'Not Found', status: :not_found
end end
def ci? def ci?
......
...@@ -76,7 +76,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -76,7 +76,6 @@ class Projects::IssuesController < Projects::ApplicationController
render json: @issue.to_json(include: [:milestone, :labels]) render json: @issue.to_json(include: [:milestone, :labels])
end end
end end
end end
def create def create
......
...@@ -7,7 +7,6 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -7,7 +7,6 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
def show def show
@url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json)) @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
@commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s") @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
......
...@@ -6,7 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -6,7 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def index def index
@project_members = @project.project_members @project_members = @project.project_members
@project_members = @project_members.non_pending unless can?(current_user, :admin_project, @project) @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
if params[:search].present? if params[:search].present?
users = @project.users.search(params[:search]).to_a users = @project.users.search(params[:search]).to_a
...@@ -19,7 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -19,7 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
if @group if @group
@group_members = @group.group_members @group_members = @group.group_members
@group_members = @group_members.non_pending unless can?(current_user, :admin_group, @group) @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
if params[:search].present? if params[:search].present?
users = @group.users.search(params[:search]).to_a users = @group.users.search(params[:search]).to_a
...@@ -29,6 +29,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -29,6 +29,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_members = @group_members.order('access_level DESC') @group_members = @group_members.order('access_level DESC')
end end
@requesters = @project.requesters if can?(current_user, :admin_project, @project)
@project_member = @project.project_members.new @project_member = @project.project_members.new
@project_group_links = @project.project_group_links @project_group_links = @project.project_group_links
end end
...@@ -48,7 +50,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -48,7 +50,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end end
def destroy def destroy
@project_member = @project.project_members.find(params[:id]) @project_member = @project.members.find_by(id: params[:id]) ||
@project.requesters.find_by(id: params[:id])
Members::DestroyService.new(@project_member, current_user).execute Members::DestroyService.new(@project_member, current_user).execute
......
...@@ -6,8 +6,7 @@ class Projects::RunnerProjectsController < Projects::ApplicationController ...@@ -6,8 +6,7 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id]) @runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) if @runner.is_shared? || @runner.locked? return head(403) unless can?(current_user, :assign_runner, @runner)
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project) path = runners_path(project)
runner_project = @runner.assign_to(project, current_user) runner_project = @runner.assign_to(project, current_user)
......
...@@ -54,7 +54,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -54,7 +54,7 @@ class Projects::SnippetsController < Projects::ApplicationController
def show def show
@note = @project.notes.new(noteable: @snippet) @note = @project.notes.new(noteable: @snippet)
@notes = @snippet.notes.fresh @notes = Banzai::NoteRenderer.render(@snippet.notes.fresh, @project, current_user)
@noteable = @snippet @noteable = @snippet
end end
......
...@@ -124,5 +124,4 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -124,5 +124,4 @@ class Projects::WikisController < Projects::ApplicationController
def wiki_params def wiki_params
params[:wiki].slice(:title, :content, :format, :message) params[:wiki].slice(:title, :content, :format, :message)
end end
end end
...@@ -4,7 +4,8 @@ class ProjectsController < Projects::ApplicationController ...@@ -4,7 +4,8 @@ class ProjectsController < Projects::ApplicationController
before_action :authenticate_user!, except: [:show, :activity, :refs] before_action :authenticate_user!, except: [:show, :activity, :refs]
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :tree, only: [:show], if: :project_view_files?
# Authorize # Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
...@@ -303,6 +304,10 @@ class ProjectsController < Projects::ApplicationController ...@@ -303,6 +304,10 @@ class ProjectsController < Projects::ApplicationController
project.repository_exists? && !project.empty_repo? project.repository_exists? && !project.empty_repo?
end end
def project_view_files?
current_user && current_user.project_view == 'files'
end
# Override extract_ref from ExtractsPath, which returns the branch and file path # Override extract_ref from ExtractsPath, which returns the branch and file path
# for the blob/tree, which in this case is just the root of the default branch. # for the blob/tree, which in this case is just the root of the default branch.
# This way we avoid to access the repository.ref_names. # This way we avoid to access the repository.ref_names.
......
...@@ -25,6 +25,7 @@ class TodosFinder ...@@ -25,6 +25,7 @@ class TodosFinder
def execute def execute
items = current_user.todos items = current_user.todos
items = by_action_id(items) items = by_action_id(items)
items = by_action(items)
items = by_author(items) items = by_author(items)
items = by_project(items) items = by_project(items)
items = by_state(items) items = by_state(items)
...@@ -43,6 +44,18 @@ class TodosFinder ...@@ -43,6 +44,18 @@ class TodosFinder
params[:action_id] params[:action_id]
end end
def to_action_id
Todo::ACTION_NAMES.key(action.to_sym)
end
def action?
action.present? && to_action_id
end
def action
params[:action]
end
def author? def author?
params[:author_id].present? params[:author_id].present?
end end
...@@ -96,6 +109,14 @@ class TodosFinder ...@@ -96,6 +109,14 @@ class TodosFinder
params[:type] params[:type]
end end
def by_action(items)
if action?
items = items.where(action: to_action_id)
end
items
end
def by_action_id(items) def by_action_id(items)
if action_id? if action_id?
items = items.where(action: action_id) items = items.where(action: action_id)
......
...@@ -78,4 +78,12 @@ module ApplicationSettingsHelper ...@@ -78,4 +78,12 @@ module ApplicationSettingsHelper
end end
end end
end end
def repository_storage_options_for_select
options = Gitlab.config.repositories.storages.map do |name, path|
["#{name} - #{path}", name]
end
options_for_select(options, @application_setting.repository_storage)
end
end end
...@@ -69,7 +69,7 @@ module DropdownsHelper ...@@ -69,7 +69,7 @@ module DropdownsHelper
def dropdown_filter(placeholder, search_id: nil) def dropdown_filter(placeholder, search_id: nil)
content_tag :div, class: "dropdown-input" do content_tag :div, class: "dropdown-input" do
filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off'
filter_output << icon('search', class: "dropdown-input-search") filter_output << icon('search', class: "dropdown-input-search")
filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button") filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button")
......
module EmailsHelper module EmailsHelper
# Google Actions # Google Actions
# https://developers.google.com/gmail/markup/reference/go-to-action # https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url) def email_action(url)
......
module IssuablesHelper module IssuablesHelper
def sidebar_gutter_toggle_icon def sidebar_gutter_toggle_icon
sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right') sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right')
end end
......
module KerberosSpnegoHelper
def allow_basic_auth?
true # different behavior in GitLab Enterprise Edition
end
def allow_kerberos_spnego_auth?
false # different behavior in GitLab Enterprise Edition
end
end
...@@ -34,10 +34,7 @@ module LabelsHelper ...@@ -34,10 +34,7 @@ module LabelsHelper
# Returns a String # Returns a String
def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block) def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block)
project ||= @project || label.project project ||= @project || label.project
link = send("namespace_project_#{type.to_s.pluralize}_path", link = label_filter_path(project, label, type: type)
project.namespace,
project,
label_name: [label.name])
if block_given? if block_given?
link_to link, class: css_class, &block link_to link, class: css_class, &block
...@@ -46,6 +43,13 @@ module LabelsHelper ...@@ -46,6 +43,13 @@ module LabelsHelper
end end
end end
def label_filter_path(project, label, type: issue)
send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
project,
label_name: [label.name])
end
def project_label_names def project_label_names
@project.labels.pluck(:title) @project.labels.pluck(:title)
end end
......
...@@ -12,6 +12,17 @@ module MembersHelper ...@@ -12,6 +12,17 @@ module MembersHelper
can?(current_user, action_member_permission(:admin, member), member.source) can?(current_user, action_member_permission(:admin, member), member.source)
end end
def can_see_request_access_button?(source)
source_parent = source.respond_to?(:group) && source.group
return false if source_parent && source.group.members.exists?(user_id: current_user.id)
return false if source_parent && source.group.requesters.exists?(user_id: current_user.id)
return false if source.members.exists?(user_id: current_user.id)
return true if source.requesters.exists?(user_id: current_user.id)
true
end
def remove_member_message(member, user: nil) def remove_member_message(member, user: nil)
user = current_user if defined?(current_user) user = current_user if defined?(current_user)
......
...@@ -69,4 +69,14 @@ module NotesHelper ...@@ -69,4 +69,14 @@ module NotesHelper
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply' data: data, title: 'Add a reply'
end end
def note_max_access_for_user(note)
@max_access_by_user_id ||= Hash.new do |hash, key|
project = key[:project]
hash[key] = project.team.human_max_access(key[:user_id])
end
full_key = { project: note.project, user_id: note.author_id }
@max_access_by_user_id[full_key]
end
end end
...@@ -52,7 +52,7 @@ module PageLayoutHelper ...@@ -52,7 +52,7 @@ module PageLayoutHelper
raise ArgumentError, 'cannot provide more than two attributes' if map.length > 2 raise ArgumentError, 'cannot provide more than two attributes' if map.length > 2
@page_card_attributes ||= {} @page_card_attributes ||= {}
@page_card_attributes = map.reject { |_,v| v.blank? } if map.present? @page_card_attributes = map.reject { |_, v| v.blank? } if map.present?
@page_card_attributes @page_card_attributes
end end
......
...@@ -15,7 +15,7 @@ module ProjectsHelper ...@@ -15,7 +15,7 @@ module ProjectsHelper
def link_to_member_avatar(author, opts = {}) def link_to_member_avatar(author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts) opts = default_opts.merge(opts)
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar]
end end
def link_to_member(project, author, opts = {}, &block) def link_to_member(project, author, opts = {}, &block)
...@@ -27,7 +27,7 @@ module ProjectsHelper ...@@ -27,7 +27,7 @@ module ProjectsHelper
author_html = "" author_html = ""
# Build avatar image tag # Build avatar image tag
author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar]
# Build name span tag # Build name span tag
if opts[:by_username] if opts[:by_username]
...@@ -327,9 +327,9 @@ module ProjectsHelper ...@@ -327,9 +327,9 @@ module ProjectsHelper
end end
end end
def sanitize_repo_path(message) def sanitize_repo_path(project, message)
return '' unless message.present? return '' unless message.present?
message.strip.gsub(Gitlab.config.gitlab_shell.repos_path.chomp('/'), "[REPOS PATH]") message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
end end
end end
module SearchHelper module SearchHelper
def search_autocomplete_opts(term) def search_autocomplete_opts(term)
return unless current_user return unless current_user
......
class Ability class Ability
class << self class << self
# rubocop: disable Metrics/CyclomaticComplexity
def allowed(user, subject) def allowed(user, subject)
return anonymous_abilities(user, subject) if user.nil? return anonymous_abilities(user, subject) if user.nil?
return [] unless user.is_a?(User) return [] unless user.is_a?(User)
...@@ -19,6 +20,7 @@ class Ability ...@@ -19,6 +20,7 @@ class Ability
when ProjectMember then project_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject)
when User then user_abilities when User then user_abilities
when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
when Ci::Runner then runner_abilities(user, subject)
else [] else []
end.concat(global_abilities(user)) end.concat(global_abilities(user))
end end
...@@ -512,6 +514,18 @@ class Ability ...@@ -512,6 +514,18 @@ class Ability
rules rules
end end
def runner_abilities(user, runner)
if user.is_admin?
[:assign_runner]
elsif runner.is_shared? || runner.locked?
[]
elsif user.ci_authorized_runners.include?(runner)
[:assign_runner]
else
[]
end
end
def user_abilities def user_abilities
[:read_user] [:read_user]
end end
......
...@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { only_integer: true, greater_than: 0 } numericality: { only_integer: true, greater_than: 0 }
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
...@@ -134,6 +138,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -134,6 +138,7 @@ class ApplicationSetting < ActiveRecord::Base
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false, send_user_confirmation_email: false,
container_registry_token_expire_delay: 5, container_registry_token_expire_delay: 5,
repository_storage: 'default',
) )
end end
......
...@@ -90,7 +90,7 @@ module Ci ...@@ -90,7 +90,7 @@ module Ci
end end
def retryable? def retryable?
project.builds_enabled? && commands.present? project.builds_enabled? && commands.present? && complete?
end end
def retried? def retried?
......
...@@ -58,7 +58,6 @@ module Issuable ...@@ -58,7 +58,6 @@ module Issuable
scope :references_project, -> { references(:project) } scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) } scope :non_archived, -> { join_project.where(projects: { archived: false }) }
delegate :name, delegate :name,
:email, :email,
to: :author, to: :author,
......
...@@ -6,15 +6,16 @@ class Group < Namespace ...@@ -6,15 +6,16 @@ class Group < Namespace
include AccessRequestable include AccessRequestable
include Referable include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember'
alias_method :members, :group_members alias_method :members, :group_members
has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members has_many :users, through: :group_members
has_many :owners, has_many :owners,
-> { where(members: { requested_at: nil, access_level: Gitlab::Access::OWNER }) }, -> { where(members: { access_level: Gitlab::Access::OWNER }) },
through: :group_members, through: :group_members,
source: :user source: :user
has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :project_group_links, dependent: :destroy has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project has_many :shared_projects, through: :project_group_links, source: :project
has_many :notification_settings, dependent: :destroy, as: :source has_many :notification_settings, dependent: :destroy, as: :source
......
...@@ -19,6 +19,8 @@ class Issue < ActiveRecord::Base ...@@ -19,6 +19,8 @@ class Issue < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :moved_to, class_name: 'Issue' belongs_to :moved_to, class_name: 'Issue'
has_many :events, as: :target, dependent: :destroy
validates :project, presence: true validates :project, presence: true
scope :cared, ->(user) { where(assignee_id: user) } scope :cared, ->(user) { where(assignee_id: user) }
......
...@@ -30,8 +30,6 @@ class Member < ActiveRecord::Base ...@@ -30,8 +30,6 @@ class Member < ActiveRecord::Base
scope :invite, -> { where.not(invite_token: nil) } scope :invite, -> { where.not(invite_token: nil) }
scope :non_invite, -> { where(invite_token: nil) } scope :non_invite, -> { where(invite_token: nil) }
scope :request, -> { where.not(requested_at: nil) } scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) }
scope :non_pending, -> { non_request.non_invite }
scope :has_access, -> { where('access_level > 0') } scope :has_access, -> { where('access_level > 0') }
scope :guests, -> { where(access_level: GUEST) } scope :guests, -> { where(access_level: GUEST) }
......
...@@ -15,7 +15,6 @@ class ProjectMember < Member ...@@ -15,7 +15,6 @@ class ProjectMember < Member
before_destroy :delete_member_todos before_destroy :delete_member_todos
class << self class << self
# Add users to project teams with passed access option # Add users to project teams with passed access option
# #
# access can be an integer representing a access code # access can be an integer representing a access code
......
...@@ -12,6 +12,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -12,6 +12,8 @@ class MergeRequest < ActiveRecord::Base
has_one :merge_request_diff, dependent: :destroy has_one :merge_request_diff, dependent: :destroy
has_many :events, as: :target, dependent: :destroy
serialize :merge_params, Hash serialize :merge_params, Hash
after_create :create_merge_request_diff, unless: :importing after_create :create_merge_request_diff, unless: :importing
......
...@@ -17,6 +17,7 @@ class Milestone < ActiveRecord::Base ...@@ -17,6 +17,7 @@ class Milestone < ActiveRecord::Base
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests has_many :merge_requests
has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
has_many :events, as: :target, dependent: :destroy
scope :active, -> { with_state(:active) } scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
......
...@@ -21,8 +21,10 @@ class Namespace < ActiveRecord::Base ...@@ -21,8 +21,10 @@ class Namespace < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
after_create :ensure_dir_exist
after_update :move_dir, if: :path_changed? after_update :move_dir, if: :path_changed?
# Save the storage paths before the projects are destroyed to use them on after destroy
before_destroy(prepend: true) { @old_repository_storage_paths = repository_storage_paths }
after_destroy :rm_dir after_destroy :rm_dir
scope :root, -> { where('type IS NULL') } scope :root, -> { where('type IS NULL') }
...@@ -87,34 +89,23 @@ class Namespace < ActiveRecord::Base ...@@ -87,34 +89,23 @@ class Namespace < ActiveRecord::Base
owner_name owner_name
end end
def ensure_dir_exist def move_dir
gitlab_shell.add_namespace(path) if any_project_has_container_registry_tags?
end raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry')
def rm_dir
# Move namespace directory into trash.
# We will remove it later async
new_path = "#{path}+#{id}+deleted"
if gitlab_shell.mv_namespace(path, new_path)
message = "Namespace directory \"#{path}\" moved to \"#{new_path}\""
Gitlab::AppLogger.info message
# Remove namespace directroy async with delay so
# GitLab has time to remove all projects first
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, new_path)
end
end end
def move_dir # Move the namespace directory in all storages paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it # Ensure old directory exists before moving it
gitlab_shell.add_namespace(path_was) gitlab_shell.add_namespace(repository_storage_path, path_was)
if any_project_has_container_registry_tags? unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry') # if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Exception.new('namespace directory cannot be moved')
end
end end
if gitlab_shell.mv_namespace(path_was, path)
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
# If repositories moved successfully we need to # If repositories moved successfully we need to
...@@ -128,11 +119,6 @@ class Namespace < ActiveRecord::Base ...@@ -128,11 +119,6 @@ class Namespace < ActiveRecord::Base
# us information about failing some of tasks # us information about failing some of tasks
false false
end end
else
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Exception.new('namespace directory cannot be moved')
end
end end
def any_project_has_container_registry_tags? def any_project_has_container_registry_tags?
...@@ -152,4 +138,33 @@ class Namespace < ActiveRecord::Base ...@@ -152,4 +138,33 @@ class Namespace < ActiveRecord::Base
def find_fork_of(project) def find_fork_of(project)
projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
end end
private
def repository_storage_paths
# We need to get the storage paths for all the projects, even the ones that are
# pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT.
Project.unscoped do
projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path)
end
end
def rm_dir
# Remove the namespace directory in all storages paths used by member projects
@old_repository_storage_paths.each do |repository_storage_path|
# Move namespace directory into trash.
# We will remove it later async
new_path = "#{path}+#{id}+deleted"
if gitlab_shell.mv_namespace(repository_storage_path, path, new_path)
message = "Namespace directory \"#{path}\" moved to \"#{new_path}\""
Gitlab::AppLogger.info message
# Remove namespace directroy async with delay so
# GitLab has time to remove all projects first
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
end
end
end
end end
...@@ -54,7 +54,7 @@ module Network ...@@ -54,7 +54,7 @@ module Network
@map = {} @map = {}
@reserved = {} @reserved = {}
@commits.each_with_index do |c,i| @commits.each_with_index do |c, i|
c.time = i c.time = i
days[i] = c.committed_date days[i] = c.committed_date
@map[c.id] = c @map[c.id] = c
...@@ -116,7 +116,7 @@ module Network ...@@ -116,7 +116,7 @@ module Network
end end
def commits_sort_by_ref def commits_sort_by_ref
@commits.sort do |a,b| @commits.sort do |a, b|
if include_ref?(a) if include_ref?(a)
-1 -1
elsif include_ref?(b) elsif include_ref?(b)
......
...@@ -21,6 +21,7 @@ class Note < ActiveRecord::Base ...@@ -21,6 +21,7 @@ class Note < ActiveRecord::Base
belongs_to :updated_by, class_name: "User" belongs_to :updated_by, class_name: "User"
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
has_many :events, as: :target, dependent: :destroy
delegate :gfm_reference, :local_reference, to: :noteable delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
......
...@@ -24,8 +24,12 @@ class Project < ActiveRecord::Base ...@@ -24,8 +24,12 @@ class Project < ActiveRecord::Base
default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
after_create :ensure_dir_exist
after_save :ensure_dir_exist, if: :namespace_id_changed?
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
after_create :set_last_activity_at after_create :set_last_activity_at
def set_last_activity_at def set_last_activity_at
...@@ -104,9 +108,13 @@ class Project < ActiveRecord::Base ...@@ -104,9 +108,13 @@ class Project < ActiveRecord::Base
has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet'
has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy has_many :protected_branches, dependent: :destroy
has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember'
alias_method :members, :project_members alias_method :members, :project_members
has_many :users, -> { where(members: { requested_at: nil }) }, through: :project_members has_many :users, through: :project_members
has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
...@@ -165,6 +173,9 @@ class Project < ActiveRecord::Base ...@@ -165,6 +173,9 @@ class Project < ActiveRecord::Base
validate :visibility_level_allowed_by_group validate :visibility_level_allowed_by_group
validate :visibility_level_allowed_as_fork validate :visibility_level_allowed_as_fork
validate :check_wiki_path_conflict validate :check_wiki_path_conflict
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
add_authentication_token_field :runners_token add_authentication_token_field :runners_token
before_save :ensure_runners_token before_save :ensure_runners_token
...@@ -376,6 +387,10 @@ class Project < ActiveRecord::Base ...@@ -376,6 +387,10 @@ class Project < ActiveRecord::Base
end end
end end
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]
end
def team def team
@team ||= ProjectTeam.new(self) @team ||= ProjectTeam.new(self)
end end
...@@ -842,12 +857,12 @@ class Project < ActiveRecord::Base ...@@ -842,12 +857,12 @@ class Project < ActiveRecord::Base
raise Exception.new('Project cannot be renamed, because tags are present in its container registry') raise Exception.new('Project cannot be renamed, because tags are present in its container registry')
end end
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users. # If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository # However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions # So we basically we mute exceptions in next actions
begin begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace) send_move_instructions(old_path_with_namespace)
reset_events_cache reset_events_cache
...@@ -988,7 +1003,7 @@ class Project < ActiveRecord::Base ...@@ -988,7 +1003,7 @@ class Project < ActiveRecord::Base
def create_repository def create_repository
# Forked import is handled asynchronously # Forked import is handled asynchronously
unless forked? unless forked?
if gitlab_shell.add_repository(path_with_namespace) if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
repository.after_create repository.after_create
true true
else else
...@@ -1140,4 +1155,8 @@ class Project < ActiveRecord::Base ...@@ -1140,4 +1155,8 @@ class Project < ActiveRecord::Base
_, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) _, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
status.zero? status.zero?
end end
def ensure_dir_exist
gitlab_shell.add_namespace(repository_storage_path, namespace.path)
end
end end
class BugzillaService < IssueTrackerService class BugzillaService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title def title
...@@ -21,5 +20,4 @@ class BugzillaService < IssueTrackerService ...@@ -21,5 +20,4 @@ class BugzillaService < IssueTrackerService
def to_param def to_param
'bugzilla' 'bugzilla'
end end
end end
class CustomIssueTrackerService < IssueTrackerService class CustomIssueTrackerService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title def title
...@@ -31,5 +30,4 @@ class CustomIssueTrackerService < IssueTrackerService ...@@ -31,5 +30,4 @@ class CustomIssueTrackerService < IssueTrackerService
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' } { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' }
] ]
end end
end end
class DroneCiService < CiService class DroneCiService < CiService
prop_accessor :drone_url, :token, :enable_ssl_verification prop_accessor :drone_url, :token, :enable_ssl_verification
validates :drone_url, presence: true, url: true, if: :activated? validates :drone_url, presence: true, url: true, if: :activated?
......
...@@ -106,7 +106,7 @@ class HipchatService < Service ...@@ -106,7 +106,7 @@ class HipchatService < Service
else else
message << "pushed to #{ref_type} <a href=\""\ message << "pushed to #{ref_type} <a href=\""\
"#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> " "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> " message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/, '')}</a> "
message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)" message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
push[:commits].take(MAX_COMMITS).each do |commit| push[:commits].take(MAX_COMMITS).each do |commit|
......
class IssueTrackerService < Service class IssueTrackerService < Service
validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
default_value_for :category, 'issue_tracker' default_value_for :category, 'issue_tracker'
......
...@@ -124,7 +124,7 @@ class JiraService < IssueTrackerService ...@@ -124,7 +124,7 @@ class JiraService < IssueTrackerService
def build_api_url_from_project_url def build_api_url_from_project_url
server = URI(project_url) server = URI(project_url)
default_ports = [["http",80],["https",443]].include?([server.scheme,server.port]) default_ports = [["http", 80], ["https", 443]].include?([server.scheme, server.port])
server_url = "#{server.scheme}://#{server.host}" server_url = "#{server.scheme}://#{server.host}"
server_url.concat(":#{server.port}") unless default_ports server_url.concat(":#{server.port}") unless default_ports
"#{server_url}/rest/api/#{DEFAULT_API_VERSION}" "#{server_url}/rest/api/#{DEFAULT_API_VERSION}"
...@@ -190,7 +190,6 @@ class JiraService < IssueTrackerService ...@@ -190,7 +190,6 @@ class JiraService < IssueTrackerService
end end
end end
def auth def auth
require 'base64' require 'base64'
Base64.urlsafe_encode64("#{self.username}:#{self.password}") Base64.urlsafe_encode64("#{self.username}:#{self.password}")
......
class RedmineService < IssueTrackerService class RedmineService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def title def title
......
...@@ -22,12 +22,12 @@ class ProjectTeam ...@@ -22,12 +22,12 @@ class ProjectTeam
end end
def find_member(user_id) def find_member(user_id)
member = project.members.non_request.find_by(user_id: user_id) member = project.members.find_by(user_id: user_id)
# If user is not in project members # If user is not in project members
# we should check for group membership # we should check for group membership
if group && !member if group && !member
member = group.members.non_request.find_by(user_id: user_id) member = group.members.find_by(user_id: user_id)
end end
member member
...@@ -137,10 +137,10 @@ class ProjectTeam ...@@ -137,10 +137,10 @@ class ProjectTeam
def max_member_access(user_id) def max_member_access(user_id)
access = [] access = []
access += project.members.non_request.where(user_id: user_id).has_access.pluck(:access_level) access += project.members.where(user_id: user_id).has_access.pluck(:access_level)
if group if group
access += group.members.non_request.where(user_id: user_id).has_access.pluck(:access_level) access += group.members.where(user_id: user_id).has_access.pluck(:access_level)
end end
if project.invited_groups.any? && project.allowed_to_share_with_group? if project.invited_groups.any? && project.allowed_to_share_with_group?
...@@ -168,14 +168,14 @@ class ProjectTeam ...@@ -168,14 +168,14 @@ class ProjectTeam
end end
def fetch_members(level = nil) def fetch_members(level = nil)
project_members = project.members.non_request project_members = project.members
group_members = group ? group.members.non_request : [] group_members = group ? group.members : []
invited_members = [] invited_members = []
if project.invited_groups.any? && project.allowed_to_share_with_group? if project.invited_groups.any? && project.allowed_to_share_with_group?
project.project_group_links.each do |group_link| project.project_group_links.each do |group_link|
invited_group = group_link.group invited_group = group_link.group
im = invited_group.members.non_request im = invited_group.members
if level if level
int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize] int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize]
......
...@@ -159,7 +159,7 @@ class ProjectWiki ...@@ -159,7 +159,7 @@ class ProjectWiki
private private
def init_repo(path_with_namespace) def init_repo(path_with_namespace)
gitlab_shell.add_repository(path_with_namespace) gitlab_shell.add_repository(project.repository_storage_path, path_with_namespace)
end end
def commit_details(action, message = nil, title = nil) def commit_details(action, message = nil, title = nil)
...@@ -173,7 +173,7 @@ class ProjectWiki ...@@ -173,7 +173,7 @@ class ProjectWiki
end end
def path_to_repo def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") @path_to_repo ||= File.join(project.repository_storage_path, "#{path_with_namespace}.git")
end end
def update_project_activity def update_project_activity
......
...@@ -39,7 +39,7 @@ class Repository ...@@ -39,7 +39,7 @@ class Repository
# Return absolute path to repository # Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.expand_path( @path_to_repo ||= File.expand_path(
File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git") File.join(@project.repository_storage_path, path_with_namespace + ".git")
) )
end end
...@@ -246,24 +246,26 @@ class Repository ...@@ -246,24 +246,26 @@ class Repository
end end
end end
# Keys for data that can be affected for any commit push.
def cache_keys def cache_keys
%i(size branch_names tag_names branch_count tag_count commit_count %i(size commit_count
readme version contribution_guide changelog readme version contribution_guide changelog
license_blob license_key gitignore) license_blob license_key gitignore)
end end
# Keys for data on branch/tag operations.
def cache_keys_for_branches_and_tags
%i(branch_names tag_names branch_count tag_count)
end
def build_cache def build_cache
cache_keys.each do |key| (cache_keys + cache_keys_for_branches_and_tags).each do |key|
unless cache.exist?(key) unless cache.exist?(key)
send(key) send(key)
end end
end end
end end
def expire_gitignore
cache.expire(:gitignore)
end
def expire_tags_cache def expire_tags_cache
cache.expire(:tag_names) cache.expire(:tag_names)
@tags = nil @tags = nil
...@@ -286,8 +288,6 @@ class Repository ...@@ -286,8 +288,6 @@ class Repository
# This ensures this particular cache is flushed after the first commit to a # This ensures this particular cache is flushed after the first commit to a
# new repository. # new repository.
expire_emptiness_caches if empty? expire_emptiness_caches if empty?
expire_branch_count_cache
expire_tag_count_cache
end end
def expire_branch_cache(branch_name = nil) def expire_branch_cache(branch_name = nil)
...@@ -875,7 +875,6 @@ class Repository ...@@ -875,7 +875,6 @@ class Repository
merge_base(ancestor_id, descendant_id) == ancestor_id merge_base(ancestor_id, descendant_id) == ancestor_id
end end
def search_files(query, ref) def search_files(query, ref)
offset = 2 offset = 2
args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
......
...@@ -4,6 +4,13 @@ class Todo < ActiveRecord::Base ...@@ -4,6 +4,13 @@ class Todo < ActiveRecord::Base
BUILD_FAILED = 3 BUILD_FAILED = 3
MARKED = 4 MARKED = 4
ACTION_NAMES = {
ASSIGNED => :assigned,
MENTIONED => :mentioned,
BUILD_FAILED => :build_failed,
MARKED => :marked
}
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :note belongs_to :note
belongs_to :project belongs_to :project
...@@ -34,6 +41,10 @@ class Todo < ActiveRecord::Base ...@@ -34,6 +41,10 @@ class Todo < ActiveRecord::Base
action == BUILD_FAILED action == BUILD_FAILED
end end
def action_name
ACTION_NAMES[action]
end
def body def body
if note.present? if note.present?
note.note note.note
......
...@@ -764,7 +764,7 @@ class User < ActiveRecord::Base ...@@ -764,7 +764,7 @@ class User < ActiveRecord::Base
unless email_domains.blank? unless email_domains.blank?
match_found = email_domains.any? do |domain| match_found = email_domains.any? do |domain|
escaped = Regexp.escape(domain).gsub('\*','.*?') escaped = Regexp.escape(domain).gsub('\*', '.*?')
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
email_domain = Mail::Address.new(self.email).domain email_domain = Mail::Address.new(self.email).domain
email_domain =~ regexp email_domain =~ regexp
...@@ -852,7 +852,6 @@ class User < ActiveRecord::Base ...@@ -852,7 +852,6 @@ class User < ActiveRecord::Base
projects.select(:id), projects.select(:id),
groups.joins(:shared_projects).select(:project_id)] groups.joins(:shared_projects).select(:project_id)]
if min_access_level if min_access_level
scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } } scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) } relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
......
...@@ -2,7 +2,6 @@ require_relative 'base_service' ...@@ -2,7 +2,6 @@ require_relative 'base_service'
class CreateReleaseService < BaseService class CreateReleaseService < BaseService
def execute(tag_name, release_description) def execute(tag_name, release_description)
repository = project.repository repository = project.repository
existing_tag = repository.find_tag(tag_name) existing_tag = repository.find_tag(tag_name)
......
module Issues module Issues
class BaseService < ::IssuableBaseService class BaseService < ::IssuableBaseService
def hook_data(issue, action) def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user) issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.build(issue) issue_url = Gitlab::UrlBuilder.build(issue)
......
module MergeRequests module MergeRequests
class BaseService < ::IssuableBaseService class BaseService < ::IssuableBaseService
def create_note(merge_request) def create_note(merge_request)
SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, merge_request.state, nil) SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end end
......
...@@ -40,6 +40,5 @@ module MergeRequests ...@@ -40,6 +40,5 @@ module MergeRequests
error("Can't cancel the automatic merge", 406) error("Can't cancel the automatic merge", 406)
end end
end end
end end
end end
module Milestones module Milestones
class DestroyService < Milestones::BaseService class DestroyService < Milestones::BaseService
def execute(milestone) def execute(milestone)
Milestone.transaction do Milestone.transaction do
update_params = { milestone: nil } update_params = { milestone: nil }
......
...@@ -51,13 +51,13 @@ module Projects ...@@ -51,13 +51,13 @@ module Projects
return true if params[:skip_repo] == true return true if params[:skip_repo] == true
# There is a possibility project does not have repository or wiki # There is a possibility project does not have repository or wiki
return true unless gitlab_shell.exists?(path + '.git') return true unless gitlab_shell.exists?(project.repository_storage_path, path + '.git')
new_path = removal_path(path) new_path = removal_path(path)
if gitlab_shell.mv_repository(path, new_path) if gitlab_shell.mv_repository(project.repository_storage_path, path, new_path)
log_info("Repository \"#{path}\" moved to \"#{new_path}\"") log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
GitlabShellWorker.perform_in(5.minutes, :remove_repository, new_path) GitlabShellWorker.perform_in(5.minutes, :remove_repository, project.repository_storage_path, new_path)
else else
false false
end end
......
module Projects module Projects
class DownloadService < BaseService class DownloadService < BaseService
WHITELIST = [ WHITELIST = [
/^[^.]+\.fogbugz.com$/ /^[^.]+\.fogbugz.com$/
] ]
......
...@@ -24,7 +24,7 @@ module Projects ...@@ -24,7 +24,7 @@ module Projects
def execute def execute
raise LeaseTaken unless try_obtain_lease raise LeaseTaken unless try_obtain_lease
GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace) GitlabShellOneShotWorker.perform_async(:gc, @project.repository_storage_path, @project.path_with_namespace)
ensure ensure
Gitlab::Metrics.measure(:reset_pushes_since_gc) do Gitlab::Metrics.measure(:reset_pushes_since_gc) do
@project.update_column(:pushes_since_gc, 0) @project.update_column(:pushes_since_gc, 0)
......
module Projects module Projects
module ImportExport module ImportExport
class ExportService < BaseService class ExportService < BaseService
def execute(_options = {}) def execute(_options = {})
@shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.path_with_namespace, 'work')) @shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.path_with_namespace, 'work'))
save_all save_all
......
...@@ -42,7 +42,7 @@ module Projects ...@@ -42,7 +42,7 @@ module Projects
def import_repository def import_repository
begin begin
gitlab_shell.import_repository(project.path_with_namespace, project.import_url) gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url)
rescue Gitlab::Shell::Error => e rescue Gitlab::Shell::Error => e
raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
end end
......
...@@ -50,12 +50,12 @@ module Projects ...@@ -50,12 +50,12 @@ module Projects
project.send_move_instructions(old_path) project.send_move_instructions(old_path)
# Move main repository # Move main repository
unless gitlab_shell.mv_repository(old_path, new_path) unless gitlab_shell.mv_repository(project.repository_storage_path, old_path, new_path)
raise TransferError.new('Cannot move project') raise TransferError.new('Cannot move project')
end end
# Move wiki repo also if present # Move wiki repo also if present
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki") gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki")
# clear project cached events # clear project cached events
project.reset_events_cache project.reset_events_cache
......
...@@ -293,7 +293,6 @@ class SystemNoteService ...@@ -293,7 +293,6 @@ class SystemNoteService
end end
end end
def self.cross_reference?(note_text) def self.cross_reference?(note_text)
note_text.start_with?(cross_reference_note_prefix) note_text.start_with?(cross_reference_note_prefix)
end end
......
...@@ -2,7 +2,6 @@ require_relative 'base_service' ...@@ -2,7 +2,6 @@ require_relative 'base_service'
class UpdateReleaseService < BaseService class UpdateReleaseService < BaseService
def execute(tag_name, release_description) def execute(tag_name, release_description)
repository = project.repository repository = project.repository
existing_tag = repository.find_tag(tag_name) existing_tag = repository.find_tag(tag_name)
......
module WikiPages module WikiPages
class BaseService < ::BaseService class BaseService < ::BaseService
def hook_data(page, action) def hook_data(page, action)
hook_data = { hook_data = {
object_kind: page.class.name.underscore, object_kind: page.class.name.underscore,
......
...@@ -4,7 +4,7 @@ class LfsObjectUploader < CarrierWave::Uploader::Base ...@@ -4,7 +4,7 @@ class LfsObjectUploader < CarrierWave::Uploader::Base
storage :file storage :file
def store_dir def store_dir
"#{Gitlab.config.lfs.storage_path}/#{model.oid[0,2]}/#{model.oid[2,2]}" "#{Gitlab.config.lfs.storage_path}/#{model.oid[0, 2]}/#{model.oid[2, 2]}"
end end
def cache_dir def cache_dir
......
...@@ -310,6 +310,15 @@ ...@@ -310,6 +310,15 @@
.col-sm-10 .col-sm-10
= f.text_field :sentry_dsn, class: 'form-control' = f.text_field :sentry_dsn, class: 'form-control'
%fieldset
%legend Repository Storage
.form-group
= f.label :repository_storage, 'Storage path for new projects', class: 'control-label col-sm-2'
.col-sm-10
= f.select :repository_storage, repository_storage_options_for_select, {}, class: 'form-control'
.help-block
You can manage the repository storage paths in your gitlab.yml configuration file
%fieldset %fieldset
%legend Repository Checks %legend Repository Checks
.form-group .form-group
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- page_title "Background Jobs" - page_title "Background Jobs"
= render 'admin/background_jobs/head' = render 'admin/background_jobs/head'
%div{ class: (container_class) } %div{ class: container_class }
%h3.page-title Background Jobs %h3.page-title Background Jobs
%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing %p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
......
- @no_container = true - @no_container = true
= render "admin/dashboard/head" = render "admin/dashboard/head"
%div{ class: (container_class) } %div{ class: container_class }
.top-area .top-area
%ul.nav-links %ul.nav-links
......
- @no_container = true - @no_container = true
= render "admin/dashboard/head" = render "admin/dashboard/head"
%div{ class: (container_class) } %div{ class: container_class }
.admin-dashboard.prepend-top-default .admin-dashboard.prepend-top-default
.row .row
.col-md-4 .col-md-4
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment