Commit 596f4688 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into save-artifacts_sizes

* upstream/master: (175 commits)
  Document Repository#keep_around
  Don't garbage collect commits that have related DB records like comments
  Update CHANGELOG
  Update RedCloth to 4.3.2 for CVE-2012-6684
  Fix typo in Merge Requests API documentation
  Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq
  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
  ...
parents 25dd39f8 021b6aef
...@@ -18,6 +18,7 @@ variables: ...@@ -18,6 +18,7 @@ variables:
SIMPLECOV: "true" SIMPLECOV: "true"
USE_DB: "true" USE_DB: "true"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
......
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.
......
Please view this file on the master branch, on stable branches it's out of date. 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
- 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)
- 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.
- Add a new column `artifacts_size` to table `ci_builds` !4964 - Add a new column `artifacts_size` to table `ci_builds` !4964
- 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
- Fix check for New Branch button on Issue page !4630 (winniehell) - Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix MR-auto-close text added to description. !4836 - Fix MR-auto-close text added to description. !4836
- Fix pagination when sorting by columns with lots of ties (like priority) - Fix pagination when sorting by columns with lots of ties (like priority)
- 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
- 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
- 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
- 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)
- 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
- Don't garbage collect commits that have related DB records like comments
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
- Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq
- 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.
- Security: Update RedCloth to 4.3.2 (Takuya Noguchi)
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
- Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963
- Fix rendering of commit notes. !4953
- Resolve "Pin should show up at 1280px min". !4947
- Switched mobile button icons to ellipsis and angle. !4944
- Correctly returns todo ID after creating todo. !4941
- Better debugging for memory killer middleware. !4936
- Remove duplicate new page btn from edit wiki. !4904
- Use clock_gettime for all performance timestamps. !4899
- Use memorized tags array when searching tags by name. !4859
- Fixed avatar alignment in new MR view. !4901
- Removed fade when filtering results. !4932
- Fix missing avatar on system notes. !4954
- Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973
- 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.
...@@ -65,6 +123,7 @@ v 8.9.1 ...@@ -65,6 +123,7 @@ 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
- 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
- Fix pipeline status when there are no builds in pipeline - Fix pipeline status when there are no builds in pipeline
...@@ -160,6 +219,7 @@ v 8.9.0 ...@@ -160,6 +219,7 @@ v 8.9.0
- Add Application Setting to configure Container Registry token expire delay (default 5min) - Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav - Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment - Use Knapsack only in CI environment
- Updated project creation page to match new UI #2542
- Cache project build count in sidebar nav - Cache project build count in sidebar nav
- Add milestone expire date to the right sidebar - Add milestone expire date to the right sidebar
- Manually mark a issue or merge request as a todo - Manually mark a issue or merge request as a todo
...@@ -209,12 +269,21 @@ v 8.9.0 ...@@ -209,12 +269,21 @@ v 8.9.0
- 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
- Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951
v 8.8.5 v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166 - Import GitHub repositories respecting the API rate limit !4166
- Fix todos page throwing errors when you have a project pending deletion !4300 - Fix todos page throwing errors when you have a project pending deletion !4300
...@@ -345,6 +414,14 @@ v 8.8.0 ...@@ -345,6 +414,14 @@ 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
- Fix visibility of snippets when searching.
- Update omniauth-saml to 1.6.0 !4951
v 8.7.7 v 8.7.7
- Fix import by `Any Git URL` broken if the URL contains a space - Fix import by `Any Git URL` broken if the URL contains a space
- Prevent unauthorized access to other projects build traces - Prevent unauthorized access to other projects build traces
......
...@@ -91,6 +91,7 @@ gem 'fog-core', '~> 1.40' ...@@ -91,6 +91,7 @@ gem 'fog-core', '~> 1.40'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
gem 'fog-google', '~> 0.3' gem 'fog-google', '~> 0.3'
gem 'fog-openstack', '~> 0.1' gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1'
# for aws storage # for aws storage
gem "unf", '~> 0.1.4' gem "unf", '~> 0.1.4'
...@@ -106,7 +107,7 @@ gem 'html-pipeline', '~> 1.11.0' ...@@ -106,7 +107,7 @@ gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
gem 'github-markup', '~> 1.3.1' gem 'github-markup', '~> 1.3.1'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
...@@ -250,7 +251,6 @@ group :development do ...@@ -250,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
...@@ -264,7 +264,7 @@ group :development do ...@@ -264,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
...@@ -302,7 +302,6 @@ group :development, :test do ...@@ -302,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
...@@ -346,3 +345,7 @@ gem "paranoia", "~> 2.0" ...@@ -346,3 +345,7 @@ gem "paranoia", "~> 2.0"
# Health check # Health check
gem 'health_check', '~> 1.5.1' gem 'health_check', '~> 1.5.1'
# System information
gem 'vmstat', '~> 2.1.0'
gem 'sys-filesystem', '~> 1.1.6'
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
RedCloth (4.2.9) RedCloth (4.3.2)
ace-rails-ap (4.0.2) ace-rails-ap (4.0.2)
actionmailer (4.2.6) actionmailer (4.2.6)
actionpack (= 4.2.6) actionpack (= 4.2.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)
...@@ -243,6 +237,11 @@ GEM ...@@ -243,6 +237,11 @@ GEM
fog-core (>= 1.39) fog-core (>= 1.39)
fog-json (>= 1.0) fog-json (>= 1.0)
ipaddress (>= 0.8) ipaddress (>= 0.8)
fog-rackspace (0.1.1)
fog-core (>= 1.35)
fog-json (>= 1.0)
fog-xml (>= 0.1)
ipaddress (>= 0.8)
fog-xml (0.1.2) fog-xml (0.1.2)
fog-core fog-core
nokogiri (~> 1.5, >= 1.5.11) nokogiri (~> 1.5, >= 1.5.11)
...@@ -500,8 +499,6 @@ GEM ...@@ -500,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)
...@@ -557,7 +554,7 @@ GEM ...@@ -557,7 +554,7 @@ GEM
recaptcha (3.0.0) recaptcha (3.0.0)
json json
redcarpet (3.3.3) redcarpet (3.3.3)
redis (3.3.0) redis (3.2.2)
redis-actionpack (4.0.1) redis-actionpack (4.0.1)
actionpack (~> 4) actionpack (~> 4)
redis-rack (~> 1.5.0) redis-rack (~> 1.5.0)
...@@ -635,8 +632,8 @@ GEM ...@@ -635,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)
...@@ -650,9 +647,9 @@ GEM ...@@ -650,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)
...@@ -663,10 +660,11 @@ GEM ...@@ -663,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)
...@@ -677,8 +675,8 @@ GEM ...@@ -677,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)
...@@ -701,10 +699,10 @@ GEM ...@@ -701,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)
...@@ -716,6 +714,8 @@ GEM ...@@ -716,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
...@@ -724,14 +724,12 @@ GEM ...@@ -724,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)
...@@ -746,7 +744,6 @@ GEM ...@@ -746,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)
...@@ -780,6 +777,7 @@ GEM ...@@ -780,6 +777,7 @@ GEM
coercible (~> 1.0) coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3) descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9) equalizer (~> 0.0, >= 0.0.9)
vmstat (2.1.0)
warden (1.2.6) warden (1.2.6)
rack (>= 1.0) rack (>= 1.0)
web-console (2.3.0) web-console (2.3.0)
...@@ -805,7 +803,7 @@ PLATFORMS ...@@ -805,7 +803,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.2.9) RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.0.2) ace-rails-ap (~> 4.0.2)
activerecord-session_store (~> 1.0.0) activerecord-session_store (~> 1.0.0)
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
...@@ -835,7 +833,6 @@ DEPENDENCIES ...@@ -835,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)
...@@ -857,6 +854,7 @@ DEPENDENCIES ...@@ -857,6 +854,7 @@ DEPENDENCIES
fog-google (~> 0.3) fog-google (~> 0.3)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.6.1) font-awesome-rails (~> 4.6.1)
foreman foreman
fuubar (~> 2.0.0) fuubar (~> 2.0.0)
...@@ -921,7 +919,6 @@ DEPENDENCIES ...@@ -921,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)
...@@ -969,11 +966,12 @@ DEPENDENCIES ...@@ -969,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)
...@@ -984,6 +982,7 @@ DEPENDENCIES ...@@ -984,6 +982,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0) version_sorter (~> 2.0.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
vmstat (~> 2.1.0)
web-console (~> 2.0) web-console (~> 2.0)
webmock (~> 1.21.0) webmock (~> 1.21.0)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
......
app/assets/images/auth_buttons/azure_64.png

986 Bytes | W: | H:

app/assets/images/auth_buttons/azure_64.png

695 Bytes | W: | H:

app/assets/images/auth_buttons/azure_64.png
app/assets/images/auth_buttons/azure_64.png
app/assets/images/auth_buttons/azure_64.png
app/assets/images/auth_buttons/azure_64.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/bg_fallback.png

167 Bytes | W: | H:

app/assets/images/bg_fallback.png

167 Bytes | W: | H:

app/assets/images/bg_fallback.png
app/assets/images/bg_fallback.png
app/assets/images/bg_fallback.png
app/assets/images/bg_fallback.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/emoji.png

257 KB | W: | H:

app/assets/images/emoji.png

257 KB | W: | H:

app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/emoji@2x.png

674 KB | W: | H:

app/assets/images/emoji@2x.png

673 KB | W: | H:

app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
app/assets/images/emoji@2x.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/gitlab_logo.png

5.07 KB | W: | H:

app/assets/images/gitlab_logo.png

3.53 KB | W: | H:

app/assets/images/gitlab_logo.png
app/assets/images/gitlab_logo.png
app/assets/images/gitlab_logo.png
app/assets/images/gitlab_logo.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/gitorious-logo-black.png

809 Bytes | W: | H:

app/assets/images/gitorious-logo-black.png

631 Bytes | W: | H:

app/assets/images/gitorious-logo-black.png
app/assets/images/gitorious-logo-black.png
app/assets/images/gitorious-logo-black.png
app/assets/images/gitorious-logo-black.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/gitorious-logo-blue.png

495 Bytes | W: | H:

app/assets/images/gitorious-logo-blue.png

201 Bytes | W: | H:

app/assets/images/gitorious-logo-blue.png
app/assets/images/gitorious-logo-blue.png
app/assets/images/gitorious-logo-blue.png
app/assets/images/gitorious-logo-blue.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/icon-link.png

1.1 KB | W: | H:

app/assets/images/icon-link.png

729 Bytes | W: | H:

app/assets/images/icon-link.png
app/assets/images/icon-link.png
app/assets/images/icon-link.png
app/assets/images/icon-link.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/images.png

5.71 KB | W: | H:

app/assets/images/images.png

5.67 KB | W: | H:

app/assets/images/images.png
app/assets/images/images.png
app/assets/images/images.png
app/assets/images/images.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/no_avatar.png

621 Bytes | W: | H:

app/assets/images/no_avatar.png

621 Bytes | W: | H:

app/assets/images/no_avatar.png
app/assets/images/no_avatar.png
app/assets/images/no_avatar.png
app/assets/images/no_avatar.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/no_group_avatar.png

942 Bytes | W: | H:

app/assets/images/no_group_avatar.png

939 Bytes | W: | H:

app/assets/images/no_group_avatar.png
app/assets/images/no_group_avatar.png
app/assets/images/no_group_avatar.png
app/assets/images/no_group_avatar.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/slider_handles.png

1.34 KB | W: | H:

app/assets/images/slider_handles.png

1.31 KB | W: | H:

app/assets/images/slider_handles.png
app/assets/images/slider_handles.png
app/assets/images/slider_handles.png
app/assets/images/slider_handles.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/touch-icon-ipad.png

3.41 KB | W: | H:

app/assets/images/touch-icon-ipad.png

2.41 KB | W: | H:

app/assets/images/touch-icon-ipad.png
app/assets/images/touch-icon-ipad.png
app/assets/images/touch-icon-ipad.png
app/assets/images/touch-icon-ipad.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -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')
...@@ -199,7 +208,6 @@ $ -> ...@@ -199,7 +208,6 @@ $ ->
$('.header-content .header-logo').toggle() $('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle() $('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active') $('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff # Show/hide comments on diff
$body.on "click", ".js-toggle-diff-comments", (e) -> $body.on "click", ".js-toggle-diff-comments", (e) ->
...@@ -261,8 +269,8 @@ $ -> ...@@ -261,8 +269,8 @@ $ ->
new Aside() new Aside()
# Sidenav pinning # Sidenav pinning
if $window.width() < 1440 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')
...@@ -293,7 +301,7 @@ $ -> ...@@ -293,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'
......
...@@ -84,6 +84,8 @@ class Dispatcher ...@@ -84,6 +84,8 @@ class Dispatcher
new Activities() new Activities()
when 'groups:show' when 'groups:show'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new NotificationsForm()
new NotificationsDropdown()
when 'groups:group_members:index' when 'groups:group_members:index'
new GroupMembers() new GroupMembers()
new UsersSelect() new UsersSelect()
......
...@@ -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
......
...@@ -186,6 +186,8 @@ class GitLabDropdown ...@@ -186,6 +186,8 @@ class GitLabDropdown
@fullData = data @fullData = data
@parseData @fullData @parseData @fullData
@filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
} }
# Init filterable # Init filterable
...@@ -218,6 +220,13 @@ class GitLabDropdown ...@@ -218,6 +220,13 @@ class GitLabDropdown
@dropdown.on 'keyup', (e) => @dropdown.on 'keyup', (e) =>
if e.which is 27 # Escape key if e.which is 27 # Escape key
$('.dropdown-menu-close', @dropdown).trigger 'click' $('.dropdown-menu-close', @dropdown).trigger 'click'
@dropdown.on 'blur', 'a', (e) =>
if e.relatedTarget?
$relatedTarget = $(e.relatedTarget)
$dropdownMenu = $relatedTarget.closest('.dropdown-menu')
if $dropdownMenu.length is 0
@dropdown.removeClass('open')
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
......
...@@ -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()
......
...@@ -59,13 +59,12 @@ issuable_created = false ...@@ -59,13 +59,12 @@ issuable_created = false
filterResults: (form) => filterResults: (form) =>
formData = form.serialize() formData = form.serialize()
$('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
formAction = form.attr('action') formAction = form.attr('action')
issuesUrl = formAction issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}") issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
issuesUrl += formData issuesUrl += formData
Turbolinks.visit(issuesUrl); Turbolinks.visit(issuesUrl)
initChecks: -> initChecks: ->
@issuableBulkActions = $('.bulk-update').data('bulkActions') @issuableBulkActions = $('.bulk-update').data('bulkActions')
......
...@@ -10,11 +10,35 @@ ...@@ -10,11 +10,35 @@
gl.text.selectedText = (text, textarea) -> gl.text.selectedText = (text, textarea) ->
text.substring(textarea.selectionStart, textarea.selectionEnd) text.substring(textarea.selectionStart, textarea.selectionEnd)
gl.text.insertText = (textArea, text, tag, selected, wrap) -> gl.text.lineBefore = (text, textarea) ->
split = text.substring(0, textarea.selectionStart).trim().split('\n')
split[split.length - 1]
gl.text.lineAfter = (text, textarea) ->
text.substring(textarea.selectionEnd).trim().split('\n')[0]
gl.text.blockTagText = (text, textArea, blockTag, selected) ->
lineBefore = @lineBefore(text, textArea)
lineAfter = @lineAfter(text, textArea)
if lineBefore is blockTag and lineAfter is blockTag
# To remove the block tag we have to select the line before & after
if blockTag?
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1)
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1)
selected
else
"#{blockTag}\n#{selected}\n#{blockTag}"
gl.text.insertText = (textArea, text, tag, blockTag, selected, wrap) ->
selectedSplit = selected.split('\n') selectedSplit = selected.split('\n')
startChar = if not wrap and textArea.selectionStart > 0 then '\n' else '' startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
if selectedSplit.length > 1 and not wrap if selectedSplit.length > 1 and (not wrap or blockTag?)
if blockTag?
insertText = @blockTagText(text, textArea, blockTag, selected)
else
insertText = selectedSplit.map((val) -> insertText = selectedSplit.map((val) ->
if val.indexOf(tag) is 0 if val.indexOf(tag) is 0
"#{val.replace(tag, '')}" "#{val.replace(tag, '')}"
...@@ -51,7 +75,7 @@ ...@@ -51,7 +75,7 @@
textArea.setSelectionRange pos, pos textArea.setSelectionRange pos, pos
gl.text.updateText = (textArea, tag, wrap) -> gl.text.updateText = (textArea, tag, blockTag, wrap) ->
$textArea = $(textArea) $textArea = $(textArea)
oldVal = $textArea.val() oldVal = $textArea.val()
textArea = $textArea.get(0) textArea = $textArea.get(0)
...@@ -59,7 +83,7 @@ ...@@ -59,7 +83,7 @@
selected = @selectedText(text, textArea) selected = @selectedText(text, textArea)
$textArea.focus() $textArea.focus()
@insertText(textArea, text, tag, selected, wrap) @insertText(textArea, text, tag, blockTag, selected, wrap)
gl.text.init = (form) -> gl.text.init = (form) ->
self = @ self = @
...@@ -70,6 +94,7 @@ ...@@ -70,6 +94,7 @@
self.updateText( self.updateText(
$this.closest('.md-area').find('textarea'), $this.closest('.md-area').find('textarea'),
$this.data('md-tag'), $this.data('md-tag'),
$this.data('md-block'),
not $this.data('md-prepend') not $this.data('md-prepend')
) )
......
...@@ -171,22 +171,15 @@ class @SearchAutocomplete ...@@ -171,22 +171,15 @@ class @SearchAutocomplete
} }
bindEvents: -> bindEvents: ->
$(document).on 'click', @onDocumentClick
@searchInput.on 'keydown', @onSearchInputKeyDown @searchInput.on 'keydown', @onSearchInputKeyDown
@searchInput.on 'keyup', @onSearchInputKeyUp @searchInput.on 'keyup', @onSearchInputKeyUp
@searchInput.on 'click', @onSearchInputClick @searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus @searchInput.on 'focus', @onSearchInputFocus
@searchInput.on 'blur', @onSearchInputBlur
@clearInput.on 'click', @onClearInputClick @clearInput.on 'click', @onClearInputClick
@locationBadgeEl.on 'click', => @locationBadgeEl.on 'click', =>
@searchInput.focus() @searchInput.focus()
onDocumentClick: (e) =>
# If clicking outside the search box
# And search input is not focused
# And we are not clicking inside a suggestion
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).closest('.search-form').length
@onSearchInputBlur()
enableAutocomplete: -> enableAutocomplete: ->
# No need to enable anything if user is not logged in # No need to enable anything if user is not logged in
return if !gon.current_user_id return if !gon.current_user_id
...@@ -287,8 +280,6 @@ class @SearchAutocomplete ...@@ -287,8 +280,6 @@ class @SearchAutocomplete
value: @originalState._location value: @originalState._location
) )
@dropdown.removeClass 'open'
badgePresent: -> badgePresent: ->
@locationBadgeEl.length @locationBadgeEl.length
......
...@@ -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')
......
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
margin: 0; margin: 0;
font-size: 24px; font-size: 24px;
font-weight: normal; font-weight: normal;
margin-bottom: 5px; margin-bottom: 10px;
color: #4c4e54; color: #4c4e54;
font-size: 23px; font-size: 23px;
line-height: 1.1; line-height: 1.1;
......
...@@ -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;
} }
......
...@@ -21,9 +21,8 @@ ...@@ -21,9 +21,8 @@
.fa { .fa {
position: relative; position: relative;
top: 3px; top: 5px;
font-size: 13px; font-size: 18px;
color: $btn-placeholder-gray;
} }
} }
......
.page-with-sidebar { .page-with-sidebar {
padding-top: $header-height; padding-top: $header-height;
padding-bottom: 25px;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
.sidebar-wrapper { .sidebar-wrapper {
......
...@@ -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: 1440px; $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,18 +41,17 @@ ...@@ -41,18 +41,17 @@
} }
.groups-cover-block { .groups-cover-block {
.container-fluid { .container-fluid {
position: relative; position: relative;
} }
.access-request-button { .group-right-buttons {
@include btn-gray;
position: absolute; position: absolute;
right: 16px; right: 16px;
bottom: 32px; .btn {
@include btn-gray;
padding: 3px 10px; padding: 3px 10px;
text-transform: none;
background-color: $background-color; background-color: $background-color;
} }
}
} }
...@@ -264,8 +264,15 @@ ...@@ -264,8 +264,15 @@
margin-bottom: 4px; margin-bottom: 4px;
} }
.item-title {
@media (min-width: $screen-sm-min) {
width: 49%;
}
}
.avatar { .avatar {
margin-left: 0; left: 0;
top: 2px;
} }
.commit-row-info { .commit-row-info {
......
...@@ -41,6 +41,10 @@ ul.notes { ...@@ -41,6 +41,10 @@ ul.notes {
.timeline-icon { .timeline-icon {
.avatar { .avatar {
visibility: hidden; visibility: hidden;
.discussion-body & {
visibility: visible;
}
} }
} }
} }
......
...@@ -13,10 +13,53 @@ ...@@ -13,10 +13,53 @@
.new_project, .new_project,
.edit-project { .edit-project {
fieldset.features { fieldset {
.control-label { &.features .control-label {
font-weight: normal; font-weight: normal;
} }
.form-group {
margin-bottom: 5px;
}
&> .form-group {
padding-left: 0;
}
}
.help-block {
margin-bottom: 10px;
}
.project-path {
padding-right: 0;
.form-control {
border-radius: $border-radius-base;
}
}
.input-group > div {
&:last-child {
padding-right: 0;
}
}
@media (max-width: $screen-xs-max) {
.input-group > div {
margin-bottom: 14px;
&:last-child {
margin-bottom: 0;
}
}
fieldset > .form-group:first-child {
padding-right: 0;
}
}
.input-group-addon {
&.static-namespace {
height: 35px;
border-radius: 3px;
border: 1px solid #e5e5e5;
}
&+ .select2 a {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
} }
} }
...@@ -223,18 +266,6 @@ ...@@ -223,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) {
...@@ -365,10 +396,28 @@ a.deploy-project-label { ...@@ -365,10 +396,28 @@ a.deploy-project-label {
} }
} }
.project-import .btn { .project-import {
float: left; .form-group {
margin-bottom: 10px; margin-bottom: 0;
}
.import-buttons {
padding-left: 0;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
.btn {
margin-right: 10px; margin-right: 10px;
padding: 8px 12px;
}
&> div {
margin-bottom: 14px;
padding-left: 0;
&:last-child {
margin-bottom: 0;
}
}
}
} }
.project-stats { .project-stats {
......
...@@ -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
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
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
@mem_used = system_info.memory.active_bytes
@mem_total = system_info.memory.total_bytes
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
......
...@@ -37,15 +37,12 @@ class GroupsController < Groups::ApplicationController ...@@ -37,15 +37,12 @@ class GroupsController < Groups::ApplicationController
end end
def show def show
@last_push = current_user.recent_push if current_user if current_user
@last_push = current_user.recent_push
@projects = @projects.includes(:namespace) @notification_setting = current_user.notification_settings_for(group)
@projects = @projects.sorted_by_activity end
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
@shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user) setup_projects
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -97,6 +94,16 @@ class GroupsController < Groups::ApplicationController ...@@ -97,6 +94,16 @@ class GroupsController < Groups::ApplicationController
protected protected
def setup_projects
@projects = @projects.includes(:namespace)
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
@shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
end
def authorize_create_group! def authorize_create_group!
unless can?(current_user, :create_group, nil) unless can?(current_user, :create_group, nil)
return render_404 return render_404
......
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
......
...@@ -2,11 +2,9 @@ class NotificationSettingsController < ApplicationController ...@@ -2,11 +2,9 @@ class NotificationSettingsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def create def create
project = Project.find(params[:project][:id]) return render_404 unless can_read?(resource)
return render_404 unless can?(current_user, :read_project, project) @notification_setting = current_user.notification_settings_for(resource)
@notification_setting = current_user.notification_settings_for(project)
@saved = @notification_setting.update_attributes(notification_setting_params) @saved = @notification_setting.update_attributes(notification_setting_params)
render_response render_response
...@@ -21,6 +19,22 @@ class NotificationSettingsController < ApplicationController ...@@ -21,6 +19,22 @@ class NotificationSettingsController < ApplicationController
private private
def resource
@resource ||=
if params[:project_id].present?
Project.find(params[:project_id])
elsif params[:namespace_id].present?
Group.find(params[:namespace_id])
end
end
def can_read?(resource)
ability_name = resource.class.name.downcase
ability_name = "read_#{ability_name}".to_sym
can?(current_user, ability_name, resource)
end
def render_response def render_response
render json: { render json: {
html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting), html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting),
......
# 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
......
...@@ -59,7 +59,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -59,7 +59,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: @merge_request } format.json { render json: @merge_request }
format.patch { render text: @merge_request.to_patch } format.patch do
headers.store(*Gitlab::Workhorse.send_git_patch(@project.repository,
@merge_request.diff_base_commit.id,
@merge_request.last_commit.id))
headers['Content-Disposition'] = 'inline'
head :ok
end
format.diff do format.diff do
return render_404 unless @merge_request.diff_refs return render_404 unless @merge_request.diff_refs
......
...@@ -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.
......
...@@ -29,10 +29,10 @@ class PipelinesFinder ...@@ -29,10 +29,10 @@ class PipelinesFinder
end end
def branches def branches
project.repository.branches.map(&:name) project.repository.branch_names
end end
def tags def tags
project.repository.tags.map(&:name) project.repository.tag_names
end end
end end
...@@ -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
module BlobHelper module BlobHelper
def highlighter(blob_name, blob_content, nowrap: false) def highlighter(blob_name, blob_content, repository: nil, nowrap: false)
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap) Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository)
end end
def highlight(blob_name, blob_content, nowrap: false, plain: false) def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain) Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository)
end end
def no_highlight_files def no_highlight_files
......
...@@ -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
...@@ -72,6 +72,6 @@ module NotificationsHelper ...@@ -72,6 +72,6 @@ module NotificationsHelper
# Create hidden field to send notification setting source to controller # Create hidden field to send notification setting source to controller
def hidden_setting_source_input(notification_setting) def hidden_setting_source_input(notification_setting)
return unless notification_setting.source_type return unless notification_setting.source_type
hidden_field_tag "#{notification_setting.source_type.downcase}[id]", notification_setting.source_id hidden_field_tag "#{notification_setting.source_type.downcase}_id", notification_setting.source_id
end 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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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