Commit de0acf3c authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into reference-pipeline-and-caching

parents 9c2214f2 d25b50fd
...@@ -25,7 +25,6 @@ config/initializers/rack_attack.rb ...@@ -25,7 +25,6 @@ config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb config/initializers/smtp_settings.rb
config/resque.yml config/resque.yml
config/unicorn.rb config/unicorn.rb
config/mail_room.yml
config/secrets.yml config/secrets.yml
coverage/* coverage/*
db/*.sqlite3 db/*.sqlite3
......
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.2.0 (unreleased)
- Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu)
- Improved performance of replacing references in comments
- Fix duplicate repositories in GitHub import page (Stan Hu)
- Redirect to a default path if HTTP_REFERER is not set (Stan Hu)
- Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL
- Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw)
- Improved performance of sorting milestone issues
- Allow users to select the Files view as default project view (Cristian Bica)
v 8.1.0 (unreleased) v 8.1.0 (unreleased)
- Send an email to admin email when a user is reported for spam (Jonathan Rochkind)
- Show notifications button when user is member of group rather than project (Grzegorz Bizon)
- Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge.
- Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu)
- Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu)
- Speed up load times of issue detail pages by roughly 1.5x
- If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg)
- Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu)
- Make diff file view easier to use on mobile screens (Stan Hu) - Make diff file view easier to use on mobile screens (Stan Hu)
- Improved performance of finding users by username or Email address
- Fix bug where merge request comments created by API would not trigger notifications (Stan Hu)
- Add support for creating directories from Files page (Stan Hu) - Add support for creating directories from Files page (Stan Hu)
- Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu)
- Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu)
...@@ -17,6 +38,8 @@ v 8.1.0 (unreleased) ...@@ -17,6 +38,8 @@ v 8.1.0 (unreleased)
- Fix cases where Markdown did not render links in activity feed (Stan Hu) - Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg) - Add first and last to pagination (Zeger-Jan van de Weg)
- Added Commit Status API - Added Commit Status API
- Added Builds View
- Added when to .gitlab-ci.yml
- Show CI status on commit page - Show CI status on commit page
- Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds
- Show CI status on Your projects page and Starred projects page - Show CI status on Your projects page and Starred projects page
...@@ -40,7 +63,7 @@ v 8.1.0 (unreleased) ...@@ -40,7 +63,7 @@ v 8.1.0 (unreleased)
- Move CI web hooks page to project settings area - Move CI web hooks page to project settings area
- Fix User Identities API. It now allows you to properly create or update user's identities. - Fix User Identities API. It now allows you to properly create or update user's identities.
- Add user preference to change layout width (Peter Göbel) - Add user preference to change layout width (Peter Göbel)
- Use commit status in merge request widget as preffered source of CI status - Use commit status in merge request widget as preferred source of CI status
- Integrate CI commit and build pages into project pages - Integrate CI commit and build pages into project pages
- Move CI services page to project settings area - Move CI services page to project settings area
- Add "Quick Submit" behavior to input fields throughout the application. Use - Add "Quick Submit" behavior to input fields throughout the application. Use
...@@ -48,6 +71,7 @@ v 8.1.0 (unreleased) ...@@ -48,6 +71,7 @@ v 8.1.0 (unreleased)
- Fix position of hamburger in header for smaller screens (Han Loong Liauw) - Fix position of hamburger in header for smaller screens (Han Loong Liauw)
- Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
- Persist filters when sorting on admin user page (Jerry Lukins) - Persist filters when sorting on admin user page (Jerry Lukins)
- Allow dashboard and group issues/MRs to be filtered by label
- Add spellcheck=false to certain input fields - Add spellcheck=false to certain input fields
- Invalidate stored service password if the endpoint URL is changed - Invalidate stored service password if the endpoint URL is changed
- Project names are not fully shown if group name is too big, even on group page view - Project names are not fully shown if group name is too big, even on group page view
...@@ -56,6 +80,14 @@ v 8.1.0 (unreleased) ...@@ -56,6 +80,14 @@ v 8.1.0 (unreleased)
- Only render 404 page from /public - Only render 404 page from /public
- Hide passwords from services API (Alex Lossent) - Hide passwords from services API (Alex Lossent)
- Fix: Images cannot show when projects' path was changed - Fix: Images cannot show when projects' path was changed
- Let gitlab-git-http-server generate and serve 'git archive' downloads
- Optimize query when filtering on issuables (Zeger-Jan van de Weg)
- Fix padding of outdated discussion item.
v 8.0.5
- Correct lookup-by-email for LDAP logins
- Fix loading spinner sometimes not being hidden on Merge Request tab switches
- Animate the logo on hover
v 8.0.4 v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......
source "https://rubygems.org" source "https://rubygems.org"
def darwin_only(require_as)
RUBY_PLATFORM.include?('darwin') && require_as
end
def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
gem 'rails', '4.1.12' gem 'rails', '4.1.12'
# Specify a sprockets version due to security issue # Specify a sprockets version due to security issue
...@@ -47,7 +39,7 @@ gem "browser", '~> 1.0.0' ...@@ -47,7 +39,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.18' gem "gitlab_git", '~> 7.2.19'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -102,7 +94,7 @@ gem "seed-fu", '~> 2.3.5' ...@@ -102,7 +94,7 @@ gem "seed-fu", '~> 2.3.5'
gem 'html-pipeline', '~> 1.11.0' 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.2' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9' gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
...@@ -196,7 +188,7 @@ gem 'charlock_holmes', '~> 0.6.9.4' ...@@ -196,7 +188,7 @@ gem 'charlock_holmes', '~> 0.6.9.4'
gem "sass-rails", '~> 4.0.5' gem "sass-rails", '~> 4.0.5'
gem "coffee-rails", '~> 4.1.0' gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.3.2' gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0' gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.0.1' gem 'jquery-turbolinks', '~> 2.0.1'
...@@ -224,6 +216,9 @@ group :development do ...@@ -224,6 +216,9 @@ group :development do
gem 'quiet_assets', '~> 1.0.2' gem 'quiet_assets', '~> 1.0.2'
gem 'rack-mini-profiler', '~> 0.9.0', require: false gem 'rack-mini-profiler', '~> 0.9.0', require: false
gem 'rerun', '~> 0.10.0' gem 'rerun', '~> 0.10.0'
gem 'bullet', require: false
gem 'active_record_query_trace', require: false
gem 'rack-lineprof', platform: :mri
# Better errors handler # Better errors handler
gem 'better_errors', '~> 1.0.1' gem 'better_errors', '~> 1.0.1'
...@@ -290,7 +285,7 @@ gem 'newrelic-grape' ...@@ -290,7 +285,7 @@ gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0' gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.6.0" gem "mail_room", "~> 0.6.1"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
...@@ -304,11 +299,3 @@ gem 'oauth2', '~> 1.0.0' ...@@ -304,11 +299,3 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion # Soft deletion
gem "paranoia", "~> 2.0" gem "paranoia", "~> 2.0"
group :development, :test do
gem 'guard-rspec', '~> 4.2.0'
gem 'rb-fsevent', require: darwin_only('rb-fsevent')
gem 'growl', require: darwin_only('growl')
gem 'rb-inotify', require: linux_only('rb-inotify')
end
...@@ -17,6 +17,7 @@ GEM ...@@ -17,6 +17,7 @@ GEM
activesupport (= 4.1.12) activesupport (= 4.1.12)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
active_record_query_trace (1.5)
activemodel (4.1.12) activemodel (4.1.12)
activesupport (= 4.1.12) activesupport (= 4.1.12)
builder (~> 3.1) builder (~> 3.1)
...@@ -87,6 +88,9 @@ GEM ...@@ -87,6 +88,9 @@ GEM
terminal-table (~> 1.4) terminal-table (~> 1.4)
browser (1.0.0) browser (1.0.0)
builder (3.2.2) builder (3.2.2)
bullet (4.14.9)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0)
byebug (6.0.2) byebug (6.0.2)
cal-heatmap-rails (0.0.1) cal-heatmap-rails (0.0.1)
capybara (2.4.4) capybara (2.4.4)
...@@ -134,6 +138,7 @@ GEM ...@@ -134,6 +138,7 @@ GEM
daemons (1.2.3) daemons (1.2.3)
database_cleaner (1.4.1) database_cleaner (1.4.1)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
default_value_for (3.0.1) default_value_for (3.0.1)
activerecord (>= 3.2.0, < 5.0) activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
...@@ -278,7 +283,7 @@ GEM ...@@ -278,7 +283,7 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.1.1) gitlab_emoji (0.1.1)
gemojione (~> 2.0) gemojione (~> 2.0)
gitlab_git (7.2.18) gitlab_git (7.2.19)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
...@@ -314,19 +319,6 @@ GEM ...@@ -314,19 +319,6 @@ GEM
grape-entity (0.4.8) grape-entity (0.4.8)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
growl (1.0.3)
guard (2.13.0)
formatador (>= 0.2.4)
listen (>= 2.7, <= 4.0)
lumberjack (~> 1.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-rspec (4.2.10)
guard (~> 2.1)
rspec (>= 2.14, < 4.0)
haml (4.0.7) haml (4.0.7)
tilt tilt
haml-rails (0.9.0) haml-rails (0.9.0)
...@@ -387,12 +379,11 @@ GEM ...@@ -387,12 +379,11 @@ GEM
celluloid (~> 0.16.0) celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
lumberjack (1.0.9)
macaddr (1.7.1) macaddr (1.7.1)
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.3)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
mail_room (0.6.0) mail_room (0.6.1)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -403,7 +394,6 @@ GEM ...@@ -403,7 +394,6 @@ GEM
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (2.0.0) multipart-post (2.0.0)
mysql2 (0.3.20) mysql2 (0.3.20)
nenv (0.2.0)
nested_form (0.3.2) nested_form (0.3.2)
net-ldap (0.11) net-ldap (0.11)
net-scp (1.2.1) net-scp (1.2.1)
...@@ -416,9 +406,6 @@ GEM ...@@ -416,9 +406,6 @@ GEM
newrelic_rpm (3.9.4.245) newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2) nokogiri (1.6.6.2)
mini_portile (~> 0.6.0) mini_portile (~> 0.6.0)
notiffany (0.0.7)
nenv (~> 0.1)
shellany (~> 0.0)
nprogress-rails (0.1.2.3) nprogress-rails (0.1.2.3)
oauth (0.4.7) oauth (0.4.7)
oauth2 (1.0.0) oauth2 (1.0.0)
...@@ -502,6 +489,10 @@ GEM ...@@ -502,6 +489,10 @@ GEM
rack-attack (4.3.0) rack-attack (4.3.0)
rack rack
rack-cors (0.4.0) rack-cors (0.4.0)
rack-lineprof (0.0.3)
rack (~> 1.5)
rblineprof (~> 0.3.6)
term-ansicolor (~> 1.3)
rack-mini-profiler (0.9.7) rack-mini-profiler (0.9.7)
rack (>= 1.1.3) rack (>= 1.1.3)
rack-mount (0.8.3) rack-mount (0.8.3)
...@@ -540,13 +531,15 @@ GEM ...@@ -540,13 +531,15 @@ GEM
rb-fsevent (0.9.5) rb-fsevent (0.9.5)
rb-inotify (0.9.5) rb-inotify (0.9.5)
ffi (>= 0.5.0) ffi (>= 0.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbvmomi (1.8.2) rbvmomi (1.8.2)
builder builder
nokogiri (>= 1.4.1) nokogiri (>= 1.4.1)
trollop trollop
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (3.3.2) redcarpet (3.3.3)
redis (3.2.1) redis (3.2.1)
redis-actionpack (4.0.0) redis-actionpack (4.0.0)
actionpack (~> 4) actionpack (~> 4)
...@@ -647,7 +640,6 @@ GEM ...@@ -647,7 +640,6 @@ GEM
sexp_processor (4.6.0) sexp_processor (4.6.0)
sham_rack (1.3.6) sham_rack (1.3.6)
rack rack
shellany (0.0.1)
shoulda-matchers (2.8.0) shoulda-matchers (2.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (3.3.0) sidekiq (3.3.0)
...@@ -741,7 +733,7 @@ GEM ...@@ -741,7 +733,7 @@ GEM
simple_oauth (~> 0.1.4) simple_oauth (~> 0.1.4)
tzinfo (1.2.2) tzinfo (1.2.2)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uglifier (2.3.3) uglifier (2.7.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)
underscore-rails (1.4.4) underscore-rails (1.4.4)
...@@ -755,6 +747,7 @@ GEM ...@@ -755,6 +747,7 @@ GEM
unicorn-worker-killer (0.4.3) unicorn-worker-killer (0.4.3)
get_process_mem (~> 0) get_process_mem (~> 0)
unicorn (~> 4) unicorn (~> 4)
uniform_notifier (1.9.0)
uuid (2.3.8) uuid (2.3.8)
macaddr (~> 1.0) macaddr (~> 1.0)
version_sorter (2.0.0) version_sorter (2.0.0)
...@@ -784,6 +777,7 @@ PLATFORMS ...@@ -784,6 +777,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.2.9) RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1) ace-rails-ap (~> 2.0.1)
active_record_query_trace
activerecord-deprecated_finders (~> 1.0.3) activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0) activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
...@@ -800,6 +794,7 @@ DEPENDENCIES ...@@ -800,6 +794,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
brakeman (= 3.0.1) brakeman (= 3.0.1)
browser (~> 1.0.0) browser (~> 1.0.0)
bullet
byebug byebug
cal-heatmap-rails (~> 0.0.1) cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0) capybara (~> 2.4.0)
...@@ -834,15 +829,13 @@ DEPENDENCIES ...@@ -834,15 +829,13 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-linguist (~> 3.0.1) gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1) gitlab_emoji (~> 0.1)
gitlab_git (~> 7.2.18) gitlab_git (~> 7.2.19)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.0.2) gollum-lib (~> 4.0.2)
gon (~> 5.0.0) gon (~> 5.0.0)
grape (~> 0.6.1) grape (~> 0.6.1)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
growl
guard-rspec (~> 4.2.0)
haml-rails (~> 0.9.0) haml-rails (~> 0.9.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
...@@ -854,7 +847,7 @@ DEPENDENCIES ...@@ -854,7 +847,7 @@ DEPENDENCIES
jquery-ui-rails (~> 4.2.1) jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener (~> 1.1.2)
mail_room (~> 0.6.0) mail_room (~> 0.6.1)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
...@@ -882,14 +875,13 @@ DEPENDENCIES ...@@ -882,14 +875,13 @@ DEPENDENCIES
quiet_assets (~> 1.0.2) quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.0) rack-attack (~> 4.3.0)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-lineprof
rack-mini-profiler (~> 0.9.0) rack-mini-profiler (~> 0.9.0)
rack-oauth2 (~> 1.0.5) rack-oauth2 (~> 1.0.5)
rails (= 4.1.12) rails (= 4.1.12)
raphael-rails (~> 2.1.2) raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
rdoc (~> 3.6) rdoc (~> 3.6)
redcarpet (~> 3.3.2) redcarpet (~> 3.3.3)
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.2.0) request_store (~> 1.2.0)
rerun (~> 0.10.0) rerun (~> 0.10.0)
...@@ -926,7 +918,7 @@ DEPENDENCIES ...@@ -926,7 +918,7 @@ DEPENDENCIES
thin (~> 1.6.1) thin (~> 1.6.1)
tinder (~> 1.10.0) tinder (~> 1.10.0)
turbolinks (~> 2.5.0) turbolinks (~> 2.5.0)
uglifier (~> 2.3.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.4.4) underscore-rails (~> 1.4.4)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.8.2) unicorn (~> 4.8.2)
......
...@@ -10,17 +10,17 @@ ...@@ -10,17 +10,17 @@
<g id="Fill-1-+-Group-24"> <g id="Fill-1-+-Group-24">
<g id="Group-24"> <g id="Group-24">
<g id="Group"> <g id="Group">
<path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path> <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
<path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path> <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
<path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path> <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
<path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path> <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
<path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path> <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
<path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path> <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
<path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path> <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>
\ No newline at end of file
...@@ -68,8 +68,8 @@ class @MergeRequestTabs ...@@ -68,8 +68,8 @@ class @MergeRequestTabs
scrollToElement: (container) -> scrollToElement: (container) ->
if window.location.hash if window.location.hash
top = $(container + " " + window.location.hash).offset().top $el = $("#{container} #{window.location.hash}")
$('body').scrollTo(top) $('body').scrollTo($el.offset().top) if $el.length
# Activate a tab based on the current action # Activate a tab based on the current action
activateTab: (action) -> activateTab: (action) ->
...@@ -127,7 +127,7 @@ class @MergeRequestTabs ...@@ -127,7 +127,7 @@ class @MergeRequestTabs
document.getElementById('commits').innerHTML = data.html document.getElementById('commits').innerHTML = data.html
$('.js-timeago').timeago() $('.js-timeago').timeago()
@commitsLoaded = true @commitsLoaded = true
@scrollToElement(".commits") @scrollToElement("#commits")
loadDiff: (source) -> loadDiff: (source) ->
return if @diffsLoaded return if @diffsLoaded
...@@ -137,7 +137,7 @@ class @MergeRequestTabs ...@@ -137,7 +137,7 @@ class @MergeRequestTabs
success: (data) => success: (data) =>
document.getElementById('diffs').innerHTML = data.html document.getElementById('diffs').innerHTML = data.html
@diffsLoaded = true @diffsLoaded = true
@scrollToElement(".diffs") @scrollToElement("#diffs")
# Show or hide the loading spinner # Show or hide the loading spinner
# #
......
...@@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts ...@@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity')) Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree')) Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits')) Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network')) Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs')) Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues')) Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
line-height: 36px; line-height: 36px;
} }
.content-block,
.gray-content-block { .gray-content-block {
margin: -$gl-padding; margin: -$gl-padding;
background-color: $background-color; background-color: $background-color;
...@@ -27,6 +28,10 @@ ...@@ -27,6 +28,10 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: $gl-gray; color: $gl-gray;
&.white {
background-color: white;
}
&.top-block { &.top-block {
border-top: none; border-top: none;
} }
...@@ -60,3 +65,48 @@ ...@@ -60,3 +65,48 @@
line-height: 42px; line-height: 42px;
} }
} }
.cover-block {
text-align: center;
background: #f7f8fa;
margin: -$gl-padding;
margin-bottom: 0;
padding: 44px $gl-padding;
border-bottom: 1px solid $border-color;
position: relative;
.avatar-holder {
margin-bottom: 16px;
.avatar, .identicon {
margin: 0 auto;
float: none;
}
.identicon {
@include border-radius(50%);
}
}
.cover-title {
color: $gl-header-color;
margin: 0;
font-size: 23px;
font-weight: normal;
margin: 16px 0 5px 0;
color: #4c4e54;
font-size: 23px;
line-height: 1.1;
}
.cover-desc {
padding: 0 $gl-padding;
color: $gl-text-color;
}
.cover-controls {
position: absolute;
top: 10px;
right: 10px;
}
}
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
border-bottom: 1px solid #E7E9EE; border-bottom: 1px solid #E7E9EE;
margin-bottom: 1em; margin-bottom: 1em;
&.readme-holder {
border-bottom: 0;
}
table { table {
@extend .table; @extend .table;
} }
...@@ -94,7 +98,6 @@ ...@@ -94,7 +98,6 @@
border-right: none; border-right: none;
} }
background: #fff; background: #fff;
padding: 10px $gl-padding;
} }
.lines { .lines {
pre { pre {
......
...@@ -107,7 +107,7 @@ ul.content-list { ...@@ -107,7 +107,7 @@ ul.content-list {
> li { > li {
padding: $gl-padding; padding: $gl-padding;
border-color: #f1f2f4; border-color: $table-border-color;
margin-left: -$gl-padding; margin-left: -$gl-padding;
margin-right: -$gl-padding; margin-right: -$gl-padding;
color: $gl-gray; color: $gl-gray;
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
} }
.select2-results .select2-result-label { .select2-results .select2-result-label {
padding: 16px; padding: 9px;
} }
.select2-drop{ .select2-drop{
...@@ -143,4 +143,4 @@ ...@@ -143,4 +143,4 @@
.ajax-users-dropdown { .ajax-users-dropdown {
min-width: 250px !important; min-width: 250px !important;
} }
\ No newline at end of file
...@@ -242,6 +242,9 @@ ...@@ -242,6 +242,9 @@
img { img {
width: 36px; width: 36px;
height: 36px; height: 36px;
}
#tanuki-logo, img {
float: left; float: left;
} }
...@@ -265,3 +268,13 @@ ...@@ -265,3 +268,13 @@
} }
} }
} }
.tanuki-shape {
transition: all 0.8s;
&:hover {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
.table-holder {
margin: -$gl-padding;
margin-top: 0;
margin-bottom: 0;
}
table { table {
&.table { &.table {
.dropdown-menu a { .dropdown-menu a {
...@@ -18,15 +24,17 @@ table { ...@@ -18,15 +24,17 @@ table {
tr { tr {
td, th { td, th {
padding: 8px 10px; padding: 10px $gl-padding;
line-height: 20px; line-height: 20px;
vertical-align: middle; vertical-align: middle;
} }
th { th {
font-weight: normal; font-weight: normal;
font-size: 15px; font-size: 15px;
border-bottom: 1px solid $border-color !important; border-bottom: 1px solid $border-color !important;
} }
td { td {
border-color: $table-border-color !important; border-color: $table-border-color !important;
border-bottom: 1px solid; border-bottom: 1px solid;
......
...@@ -6,13 +6,17 @@ ...@@ -6,13 +6,17 @@
.timeline-entry { .timeline-entry {
padding: $gl-padding; padding: $gl-padding;
border-color: #f1f2f4; border-color: $table-border-color;
margin-left: -$gl-padding; margin-left: -$gl-padding;
margin-right: -$gl-padding; margin-right: -$gl-padding;
color: $gl-gray; color: $gl-gray;
border-bottom: 1px solid #ECEEF1; border-bottom: 1px solid #ECEEF1;
border-right: 1px solid #ECEEF1; border-right: 1px solid #ECEEF1;
&:target {
background: $hover;
}
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
......
@mixin md-typography { @mixin md-typography {
color: $md-text-color; color: $md-text-color;
word-wrap: break-word;
a { a {
color: $md-link-color; color: $md-link-color;
...@@ -17,7 +18,6 @@ ...@@ -17,7 +18,6 @@
font-family: $monospace_font; font-family: $monospace_font;
white-space: pre; white-space: pre;
word-wrap: normal; word-wrap: normal;
padding: 1px 2px;
} }
kbd { kbd {
...@@ -73,6 +73,8 @@ ...@@ -73,6 +73,8 @@
} }
blockquote { blockquote {
color: #7f8fa4;
font-size: inherit;
padding: 8px 21px; padding: 8px 21px;
margin: 12px 0 12px; margin: 12px 0 12px;
border-left: 3px solid #e7e9ed; border-left: 3px solid #e7e9ed;
...@@ -80,7 +82,7 @@ ...@@ -80,7 +82,7 @@
blockquote p { blockquote p {
color: #7f8fa4 !important; color: #7f8fa4 !important;
font-size: 15px; font-size: inherit;
line-height: 1.5; line-height: 1.5;
} }
...@@ -101,9 +103,9 @@ ...@@ -101,9 +103,9 @@
pre { pre {
margin: 12px 0 12px 0 !important; margin: 12px 0 12px 0 !important;
background-color: #f8fafc !important; background-color: #f8fafc;
font-size: 13px !important; font-size: 13px !important;
color: #5b6169 !important; color: #5b6169;
line-height: 1.6em !important; line-height: 1.6em !important;
@include border-radius(2px); @include border-radius(2px);
} }
...@@ -112,9 +114,9 @@ ...@@ -112,9 +114,9 @@
font-weight: inherit; font-weight: inherit;
} }
ul, ol {
ul { padding: 0;
color: #5c5d5e; margin: 6px 0 6px 18px !important;
} }
li { li {
...@@ -136,6 +138,33 @@ ...@@ -136,6 +138,33 @@
text-decoration: none; text-decoration: none;
} }
} }
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
a.anchor {
// Setting `display: none` would prevent the anchor being scrolled to, so
// instead we set the height to 0 and it gets updated on hover.
height: 0;
}
&:hover > a.anchor {
$size: 16px;
position: absolute;
right: 100%;
top: 50%;
margin-top: -$size/2;
margin-right: 0px;
padding-right: 20px;
display: inline-block;
width: $size;
height: $size;
background-image: image-url("icon-link.png");
background-size: contain;
background-repeat: no-repeat;
}
}
} }
...@@ -202,53 +231,11 @@ a > code { ...@@ -202,53 +231,11 @@ a > code {
} }
/** /**
* Wiki typography * Apply Markdown typography
* *
*/ */
.wiki { .wiki {
@include md-typography; @include md-typography;
word-wrap: break-word;
padding: 7px;
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
a.anchor {
// Setting `display: none` would prevent the anchor being scrolled to, so
// instead we set the height to 0 and it gets updated on hover.
height: 0;
}
&:hover > a.anchor {
$size: 16px;
position: absolute;
right: 100%;
top: 50%;
margin-top: -$size/2;
margin-right: 0px;
padding-right: 20px;
display: inline-block;
width: $size;
height: $size;
background-image: image-url("icon-link.png");
background-size: contain;
background-repeat: no-repeat;
}
}
ul,ol {
padding: 0;
margin: 6px 0 6px 18px !important;
}
ol {
color: #5c5d5e;
}
}
.md-area {
@include md-typography;
} }
.md { .md {
...@@ -261,6 +248,7 @@ a > code { ...@@ -261,6 +248,7 @@ a > code {
*/ */
textarea.js-gfm-input { textarea.js-gfm-input {
font-family: $monospace_font; font-family: $monospace_font;
color: $gl-text-color;
} }
.md-preview { .md-preview {
......
/* https://github.com/MozMorris/tomorrow-pygments */ /* https://github.com/MozMorris/tomorrow-pygments */
pre.code.highlight.dark,
.code.dark { .code.dark {
background-color: #1d1f21; background-color: #1d1f21 !important;
color: #c5c8c6; color: #c5c8c6 !important;
pre.code, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background-color: #1d1f21 !important; background-color: #1d1f21 !important;
...@@ -23,8 +22,8 @@ pre.code.highlight.dark, ...@@ -23,8 +22,8 @@ pre.code.highlight.dark,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #ffe792; background-color: #ffe792 !important;
color: #000000; color: #000000 !important;
} }
.hll { background-color: #373b41 } .hll { background-color: #373b41 }
......
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */
pre.code.monokai,
.code.monokai { .code.monokai {
background: #272822; background-color: #272822 !important;
color: #f8f8f2; color: #f8f8f2 !important;
pre.highlight, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background:#272822 !important; background-color :#272822 !important;
color:#f8f8f2 !important; color: #f8f8f2 !important;
} }
pre.code { pre.code {
...@@ -23,8 +22,8 @@ pre.code.monokai, ...@@ -23,8 +22,8 @@ pre.code.monokai,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #ffe792; background-color: #ffe792 !important;
color: #000000; color: #000000 !important;
} }
.hll { background-color: #49483e } .hll { background-color: #49483e }
......
/* https://gist.github.com/qguv/7936275 */ /* https://gist.github.com/qguv/7936275 */
pre.code.highlight.solarized-dark,
.code.solarized-dark { .code.solarized-dark {
background-color: #002b36; background-color: #002b36 !important;
color: #93a1a1; color: #93a1a1 !important;
pre.code, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background-color: #002b36 !important; background-color: #002b36 !important;
...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark, ...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #094554; background-color: #094554 !important;
} }
/* Solarized Dark /* Solarized Dark
......
/* https://gist.github.com/qguv/7936275 */ /* https://gist.github.com/qguv/7936275 */
pre.code.highlight.solarized-light,
.code.solarized-light { .code.solarized-light {
background-color: #fdf6e3; background-color: #fdf6e3 !important;
color: #586e75; color: #586e75 !important;
pre.code, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background-color: #fdf6e3 !important; background-color: #fdf6e3 !important;
...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-light, ...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-light,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #eee8d5; background-color: #eee8d5 !important;
} }
/* Solarized Light /* Solarized Light
......
/* https://github.com/aahan/pygments-github-style */ /* https://github.com/aahan/pygments-github-style */
pre.code.highlight.white,
.code.white { .code.white {
background-color: #f8fafc;
font-size: 13px;
color: #5b6169;
line-height: 1.6em;
background-color: #f8fafc !important;
color: #5b6169 !important;
pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background-color: $background-color !important; background-color: $background-color !important;
color: $gl-gray !important; color: $gl-gray !important;
} }
pre.highlight {
background-color: #fff !important;
color: #333 !important;
}
pre.code { pre.code {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
background-color: #fff !important;
color: #333 !important;
} }
// highlight line via anchor // highlight line via anchor
...@@ -28,7 +24,7 @@ pre.code.highlight.white, ...@@ -28,7 +24,7 @@ pre.code.highlight.white,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #fafe3d; background-color: #fafe3d !important;
} }
.hll { background-color: #f8f8f8 } .hll { background-color: #f8f8f8 }
......
...@@ -6,11 +6,6 @@ ...@@ -6,11 +6,6 @@
line-height: 1.5; line-height: 1.5;
} }
.wide-table-holder {
margin-left: -$gl-padding;
margin-right: -$gl-padding;
}
.builds, .builds,
.projects-table { .projects-table {
.light { .light {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
padding: $gl-padding; padding: $gl-padding;
margin-left: -$gl-padding; margin-left: -$gl-padding;
margin-right: -$gl-padding; margin-right: -$gl-padding;
border-bottom: 1px solid #f1f2f4; border-bottom: 1px solid $table-border-color;
color: #7f8fa4; color: #7f8fa4;
&.event-inline { &.event-inline {
......
...@@ -68,3 +68,7 @@ body.modal-open { ...@@ -68,3 +68,7 @@ body.modal-open {
.modal .modal-dialog { .modal .modal-dialog {
width: 860px; width: 860px;
} }
.documentation {
padding: 7px;
}
...@@ -132,6 +132,11 @@ form.edit-issue { ...@@ -132,6 +132,11 @@ form.edit-issue {
} }
} }
.issue-closed-by-widget {
padding: 16px 0;
margin: 0px;
}
.issue-form .select2-container { .issue-form .select2-container {
width: 250px !important; width: 250px !important;
} }
...@@ -30,7 +30,6 @@ ul.notes { ...@@ -30,7 +30,6 @@ ul.notes {
.discussion-header, .discussion-header,
.note-header { .note-header {
@extend .cgray; @extend .cgray;
padding-bottom: 15px;
a:hover { a:hover {
text-decoration: none; text-decoration: none;
...@@ -75,6 +74,10 @@ ul.notes { ...@@ -75,6 +74,10 @@ ul.notes {
} }
} }
.discussion-body {
padding-top: 15px;
}
.discussion { .discussion {
overflow: hidden; overflow: hidden;
display: block; display: block;
......
...@@ -47,3 +47,9 @@ ...@@ -47,3 +47,9 @@
} }
} }
} }
.calendar-hint {
margin-top: -12px;
float: right;
font-size: 12px;
}
...@@ -457,7 +457,7 @@ pre.light-well { ...@@ -457,7 +457,7 @@ pre.light-well {
.project-row { .project-row {
padding: $gl-padding; padding: $gl-padding;
border-color: #f1f2f4; border-color: $table-border-color;
margin-left: -$gl-padding; margin-left: -$gl-padding;
margin-right: -$gl-padding; margin-right: -$gl-padding;
...@@ -511,3 +511,38 @@ pre.light-well { ...@@ -511,3 +511,38 @@ pre.light-well {
margin-top: -1px; margin-top: -1px;
} }
} }
.project-last-commit {
margin: 0 7px;
.ci-status {
margin-right: 16px;
}
.commit-row-message {
color: $gl-gray;
}
.commit_short_id {
margin-right: 5px;
color: $gl-link-color;
font-weight: 600;
}
.commit-author-link {
margin-left: 7px;
text-decoration: none;
.avatar {
float: none;
margin-right: 4px;
}
.commit-author-name {
font-weight: 600;
}
}
}
.project-show-readme .readme-holder {
border-top: 0;
}
.tree-holder { .tree-holder {
.tree-table-holder {
margin-left: -$gl-padding;
margin-right: -$gl-padding;
}
.tree_progress {
display: none;
margin: 20px;
&.loading {
display: block;
}
}
.tree-table { .tree-table {
margin-bottom: 0; margin-bottom: 0;
tr { tr {
> td, > th { > td, > th {
padding: 10px $gl-padding;
line-height: 32px; line-height: 32px;
border-color: $table-border-color !important;
} }
&:hover { &:hover {
......
...@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController ...@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user @abuse_report.reporter = current_user
if @abuse_report.save if @abuse_report.save
if current_application_settings.admin_notification_email.present?
AbuseReportMailer.delay.notify(@abuse_report.id)
end
message = "Thank you for your report. A GitLab administrator will look into it shortly." message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message redirect_to root_path, notice: message
else else
......
...@@ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -55,6 +55,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_snippet_visibility, :default_snippet_visibility,
:restricted_signup_domains_raw, :restricted_signup_domains_raw,
:version_check_enabled, :version_check_enabled,
:admin_notification_email,
:user_oauth_applications, :user_oauth_applications,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
......
...@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
BroadcastMessage.find(params[:id]).destroy BroadcastMessage.find(params[:id]).destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_back_or_default(default: { action: 'index' }) }
format.js { render nothing: true } format.js { render nothing: true }
end end
end end
......
...@@ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController
} }
@hook.execute(data, 'system_hooks') @hook.execute(data, 'system_hooks')
redirect_to :back redirect_back_or_default
end end
def hook_params def hook_params
......
...@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController
end end
def application_services_params def application_services_params
params.permit(:id, application_services_params = params.permit(:id,
service: Projects::ServicesController::ALLOWED_PARAMS) service: Projects::ServicesController::ALLOWED_PARAMS)
if application_services_params[:service].is_a?(Hash)
Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
application_services_params[:service].delete(param) if application_services_params[:service][param].blank?
end
end
application_services_params
end end
end end
...@@ -33,33 +33,33 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -33,33 +33,33 @@ class Admin::UsersController < Admin::ApplicationController
def block def block
if user.block if user.block
redirect_to :back, notice: "Successfully blocked" redirect_back_or_admin_user(notice: "Successfully blocked")
else else
redirect_to :back, alert: "Error occurred. User was not blocked" redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
end end
end end
def unblock def unblock
if user.activate if user.activate
redirect_to :back, notice: "Successfully unblocked" redirect_back_or_admin_user(notice: "Successfully unblocked")
else else
redirect_to :back, alert: "Error occurred. User was not unblocked" redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
end end
end end
def unlock def unlock
if user.unlock_access! if user.unlock_access!
redirect_to :back, alert: "Successfully unlocked" redirect_back_or_admin_user(alert: "Successfully unlocked")
else else
redirect_to :back, alert: "Error occurred. User was not unlocked" redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
end end
end end
def confirm def confirm
if user.confirm if user.confirm
redirect_to :back, notice: "Successfully confirmed" redirect_back_or_admin_user(notice: "Successfully confirmed")
else else
redirect_to :back, alert: "Error occurred. User was not confirmed" redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
end end
end end
...@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController
user.update_secondary_emails! user.update_secondary_emails!
respond_to do |format| respond_to do |format|
format.html { redirect_to :back, notice: "Successfully removed email." } format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
format.js { render nothing: true } format.js { render nothing: true }
end end
end end
...@@ -157,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -157,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController
:projects_limit, :can_create_group, :admin, :key_id :projects_limit, :can_create_group, :admin, :key_id
) )
end end
def redirect_back_or_admin_user(options = {})
redirect_back_or_default(default: default_route, options: options)
end
def default_route
[:admin, @user]
end
end end
...@@ -33,6 +33,10 @@ class ApplicationController < ActionController::Base ...@@ -33,6 +33,10 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
protected protected
# From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example # From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
...@@ -150,7 +154,7 @@ class ApplicationController < ActionController::Base ...@@ -150,7 +154,7 @@ class ApplicationController < ActionController::Base
end end
def git_not_found! def git_not_found!
render "errors/git_not_found", layout: "errors", status: 404 render html: "errors/git_not_found", layout: "errors", status: 404
end end
def method_missing(method_sym, *arguments, &block) def method_missing(method_sym, *arguments, &block)
......
module Ci module Ci
class ProjectsController < Ci::ApplicationController class ProjectsController < Ci::ApplicationController
before_action :project before_action :project, except: [:index]
before_action :authenticate_user!, except: [:build, :badge] before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:badge] before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml] before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
protect_from_forgery protect_from_forgery
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project)
end
# Project status badge # Project status badge
# Image with build status for sha or ref # Image with build status for sha or ref
def badge def badge
......
...@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController ...@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController
def status def status
@repos = client.repos @repos = client.repos
client.orgs.each do |org|
@repos += client.org_repos(org.login)
end
@already_added_projects = current_user.created_projects.where(import_type: "github") @already_added_projects = current_user.created_projects.where(import_type: "github")
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
......
...@@ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController ...@@ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file] dump_file = params[:dump_file]
unless dump_file.respond_to?(:read) unless dump_file.respond_to?(:read)
return redirect_to :back, alert: "You need to upload a Google Takeout archive." return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." })
end end
begin begin
dump = JSON.parse(dump_file.read) dump = JSON.parse(dump_file.read)
rescue rescue
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive." return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end end
client = Gitlab::GoogleCodeImport::Client.new(dump) client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid? unless client.valid?
return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive." return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end end
session[:google_code_dump] = dump session[:google_code_dump] = dump
......
...@@ -14,7 +14,7 @@ class InvitesController < ApplicationController ...@@ -14,7 +14,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}." redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
else else
redirect_to :back, alert: "The invitation could not be accepted." redirect_back_or_default(options: { alert: "The invitation could not be accepted." })
end end
end end
...@@ -31,7 +31,7 @@ class InvitesController < ApplicationController ...@@ -31,7 +31,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have declined the invitation to join #{label}." redirect_to path, notice: "You have declined the invitation to join #{label}."
else else
redirect_to :back, alert: "The invitation could not be declined." redirect_back_or_default(options: { alert: "The invitation could not be declined." })
end end
end end
......
...@@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController ...@@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
flash[:alert] = "Failed to save new settings" flash[:alert] = "Failed to save new settings"
end end
redirect_to :back redirect_back_or_default(default: profile_notifications_path)
end end
format.js format.js
......
...@@ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_back_or_default(default: { action: 'show' }) }
end end
end end
......
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :ci_project before_action :ci_project
before_action :build before_action :build, except: [:index, :cancel_all]
before_action :authorize_admin_project!, except: [:show, :status] before_action :authorize_admin_project!, except: [:index, :show, :status]
layout "project" layout "project"
def index
@scope = params[:scope]
@all_builds = project.ci_builds
@builds =
case @scope
when 'all'
@all_builds
when 'finished'
@all_builds.finished
else
@all_builds.running_or_pending
end
@builds = @builds.order('created_at DESC').page(params[:page]).per(30)
end
def cancel_all
@project.ci_builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project)
end
def show def show
@builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') @builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20) @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20)
......
...@@ -30,7 +30,7 @@ class Projects::CiServicesController < Projects::ApplicationController ...@@ -30,7 +30,7 @@ class Projects::CiServicesController < Projects::ApplicationController
message = { alert: 'We tried to test the service but error occurred' } message = { alert: 'We tried to test the service but error occurred' }
end end
redirect_to :back, message redirect_back_or_default(options: message)
end end
private private
......
...@@ -24,7 +24,7 @@ class Projects::CiWebHooksController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::CiWebHooksController < Projects::ApplicationController
def test def test
Ci::TestHookService.new.execute(hook, current_user) Ci::TestHookService.new.execute(hook, current_user)
redirect_to :back redirect_back_or_default(default: { action: 'index' })
end end
def destroy def destroy
......
...@@ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController
execute(@project, head_ref, @project, base_ref) execute(@project, head_ref, @project, base_ref)
if compare_result if compare_result
@commits = compare_result.commits @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs @diffs = compare_result.diffs
@commit = @commits.last @commit = @commits.last
@first_commit = @commits.first
@line_notes = [] @line_notes = []
end end
end end
......
...@@ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def disable def disable
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
redirect_to :back redirect_back_or_default(default: { action: 'index' })
end end
protected protected
......
...@@ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController
flash[:alert] = 'Hook execution failed. Ensure the project has commits.' flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end end
redirect_to :back redirect_back_or_default(default: { action: 'index' })
end end
def destroy def destroy
......
...@@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update # Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update] before_action :authorize_admin_issues!, only: [:bulk_update]
# Cross-reference merge requests
before_action :closed_by_merge_requests, only: [:show]
respond_to :html respond_to :html
def index def index
...@@ -57,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -57,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show def show
@participants = @issue.participants(current_user) @participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.inc_author.fresh @notes = @issue.notes.with_associations.fresh
@noteable = @issue @noteable = @issue
respond_with(@issue) respond_with(@issue)
...@@ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController
def bulk_update def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_to :back, notice: "#{result[:count]} issues updated" redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end end
def toggle_subscription def toggle_subscription
...@@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController
render nothing: true render nothing: true
end end
def closed_by_merge_requests
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
end
protected protected
def issue def issue
......
...@@ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs def diffs
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit
@comments_allowed = @reply_allowed = true @comments_allowed = @reply_allowed = true
@comments_target = { @comments_target = {
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
...@@ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project @target_project = merge_request.target_project
@source_project = merge_request.source_project @source_project = merge_request.source_project
@commits = @merge_request.compare_commits @commits = @merge_request.compare_commits
@commit = @merge_request.compare_commits.last @commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs @diffs = @merge_request.compare_diffs
@note_counts = Note.where(commit_id: @commits.map(&:id)). @note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
...@@ -259,7 +262,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -259,7 +262,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits @commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff @merge_request_diff = @merge_request.merge_request_diff
if @merge_request.locked_long_ago? if @merge_request.locked_long_ago?
@merge_request.unlock_mr @merge_request.unlock_mr
@merge_request.close @merge_request.close
......
...@@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def sort_issues def sort_issues
@issues = @milestone.issues.where(id: params['sortable_issue']) @milestone.sort_issues(params['sortable_issue'].map(&:to_i))
@issues.each do |issue|
issue.position = params['sortable_issue'].index(issue.id.to_s) + 1
issue.save
end
render json: { saved: true } render json: { saved: true }
end end
......
...@@ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.json { render_note_json(@note) } format.json { render_note_json(@note) }
format.html { redirect_to :back } format.html { redirect_back_or_default }
end end
end end
...@@ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.json { render_note_json(@note) } format.json { render_note_json(@note) }
format.html { redirect_to :back } format.html { redirect_back_or_default }
end end
end end
......
...@@ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def leave def leave
if @project.namespace == current_user.namespace if @project.namespace == current_user.namespace
return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.') message = 'You can not leave your own project. Transfer or delete the project.'
return redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
end end
@project.project_members.find_by(user_id: current_user).destroy @project.project_members.find_by(user_id: current_user).destroy
......
...@@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController
include TreeHelper include TreeHelper
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :validate_ref_id
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController
format.js format.js
end end
end end
private
def validate_ref_id
return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex
end
end end
...@@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end end
def archive def archive
begin render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute rescue => ex
rescue logger.error("#{self.class.name}: #{ex}")
return head :not_found return git_not_found!
end
if file_path
# Send file to user
response.headers["Content-Length"] = File.open(file_path).size.to_s
send_file file_path
else
redirect_to request.fullpath
end
end end
end end
...@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification] :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test] before_action :service, only: [:edit, :update, :test]
...@@ -48,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -48,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController
message = { alert: error_message } message = { alert: error_message }
end end
redirect_to :back, message redirect_back_or_default(options: message)
end end
private private
...@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params def service_params
service_params = params.require(:service).permit(ALLOWED_PARAMS) service_params = params.require(:service).permit(ALLOWED_PARAMS)
service_params.delete("password") if service_params["password"].blank? FILTER_BLANK_PARAMS.each do |param|
service_params.delete(param) if service_params[param].blank?
end
service_params service_params
end end
end end
class ProjectsController < ApplicationController class ProjectsController < ApplicationController
include ExtractsPath
prepend_before_filter :render_go_import, only: [:show] prepend_before_filter :render_go_import, only: [:show]
skip_before_action :authenticate_user!, only: [:show, :activity] skip_before_action :authenticate_user!, only: [:show, :activity]
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?
# Authorize # Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] before_action :authorize_admin_project!, only: [:edit, :update]
before_action :event_filter, only: [:show, :activity] before_action :event_filter, only: [:show, :activity]
layout :determine_layout layout :determine_layout
...@@ -56,6 +59,8 @@ class ProjectsController < ApplicationController ...@@ -56,6 +59,8 @@ class ProjectsController < ApplicationController
end end
def transfer def transfer
return access_denied! unless can?(current_user, :change_namespace, @project)
namespace = Namespace.find_by(id: params[:new_namespace_id]) namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(project, current_user).execute(namespace) ::Projects::TransferService.new(project, current_user).execute(namespace)
...@@ -64,6 +69,15 @@ class ProjectsController < ApplicationController ...@@ -64,6 +69,15 @@ class ProjectsController < ApplicationController
end end
end end
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
if @project.forked?
@project.forked_project_link.destroy
flash[:notice] = 'The fork relationship has been removed.'
end
end
def activity def activity
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController ...@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController
render 'projects/empty' render 'projects/empty'
else else
if current_user if current_user
@membership = @project.project_member_by_id(current_user.id) @membership = @project.team.find_member(current_user.id)
end end
render :show render :show
...@@ -139,6 +153,7 @@ class ProjectsController < ApplicationController ...@@ -139,6 +153,7 @@ class ProjectsController < ApplicationController
def archive def archive
return access_denied! unless can?(current_user, :archive_project, @project) return access_denied! unless can?(current_user, :archive_project, @project)
@project.archive! @project.archive!
respond_to do |format| respond_to do |format|
...@@ -148,6 +163,7 @@ class ProjectsController < ApplicationController ...@@ -148,6 +163,7 @@ class ProjectsController < ApplicationController
def unarchive def unarchive
return access_denied! unless can?(current_user, :archive_project, @project) return access_denied! unless can?(current_user, :archive_project, @project)
@project.unarchive! @project.unarchive!
respond_to do |format| respond_to do |format|
...@@ -225,4 +241,14 @@ class ProjectsController < ApplicationController ...@@ -225,4 +241,14 @@ class ProjectsController < ApplicationController
render "go_import", layout: false render "go_import", layout: false
end end
def repo_exists?
project.repository_exists? && !project.empty_repo?
end
# Override get_id 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.
def get_id
project.repository.root_ref
end
end end
...@@ -53,15 +53,36 @@ class IssuableFinder ...@@ -53,15 +53,36 @@ class IssuableFinder
end end
end end
def project?
params[:project_id].present?
end
def project def project
return @project if defined?(@project) return @project if defined?(@project)
@project = if project?
if params[:project_id].present? @project = Project.find(params[:project_id])
Project.find(params[:project_id])
else unless Ability.abilities.allowed?(current_user, :read_project, @project)
nil @project = nil
end end
else
@project = nil
end
@project
end
def projects
return @projects if defined?(@projects)
if project?
project
elsif current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects
else
ProjectsFinder.new.execute(current_user)
end
end end
def search def search
...@@ -72,7 +93,7 @@ class IssuableFinder ...@@ -72,7 +93,7 @@ class IssuableFinder
params[:milestone_title].present? params[:milestone_title].present?
end end
def no_milestones? def filter_by_no_milestone?
milestones? && params[:milestone_title] == Milestone::None.title milestones? && params[:milestone_title] == Milestone::None.title
end end
...@@ -81,12 +102,22 @@ class IssuableFinder ...@@ -81,12 +102,22 @@ class IssuableFinder
@milestones = @milestones =
if milestones? if milestones?
Milestone.where(title: params[:milestone_title]) scope = Milestone.where(project_id: projects)
scope.where(title: params[:milestone_title])
else else
nil nil
end end
end end
def labels?
params[:label_name].present?
end
def filter_by_no_label?
labels? && params[:label_name] == Label::None.title
end
def assignee? def assignee?
params[:assignee_id].present? params[:assignee_id].present?
end end
...@@ -120,19 +151,7 @@ class IssuableFinder ...@@ -120,19 +151,7 @@ class IssuableFinder
private private
def init_collection def init_collection
table_name = klass.table_name klass.all
if project
if Ability.abilities.allowed?(current_user, :read_project, project)
project.send(table_name)
else
[]
end
elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project)
else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
end
end end
def by_scope(items) def by_scope(items)
...@@ -170,7 +189,12 @@ class IssuableFinder ...@@ -170,7 +189,12 @@ class IssuableFinder
end end
def by_project(items) def by_project(items)
items = items.of_projects(project.id) if project items =
if projects
items.of_projects(projects).references(:project)
else
items.none
end
items items
end end
...@@ -185,18 +209,6 @@ class IssuableFinder ...@@ -185,18 +209,6 @@ class IssuableFinder
items.sort(params[:sort]) items.sort(params[:sort])
end end
def by_milestone(items)
if milestones?
if no_milestones?
items = items.where(milestone_id: [-1, nil])
else
items = items.where(milestone_id: milestones.try(:pluck, :id))
end
end
items
end
def by_assignee(items) def by_assignee(items)
if assignee? if assignee?
items = items.where(assignee_id: assignee.try(:id)) items = items.where(assignee_id: assignee.try(:id))
...@@ -213,20 +225,36 @@ class IssuableFinder ...@@ -213,20 +225,36 @@ class IssuableFinder
items items
end end
def by_label(items) def by_milestone(items)
if params[:label_name].present? if milestones?
if params[:label_name] == Label::None.title if filter_by_no_milestone?
item_ids = LabelLink.where(target_type: klass.name).pluck(:target_id) items = items.where(milestone_id: [-1, nil])
else
items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
if projects
items = items.where(milestones: { project_id: projects })
end
end
end
items
end
items = items.where('id NOT IN (?)', item_ids) def by_label(items)
if labels?
if filter_by_no_label?
items = items.
joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
where(label_links: { id: nil })
else else
label_names = params[:label_name].split(",") label_names = params[:label_name].split(",")
item_ids = LabelLink.joins(:label). items = items.joins(:labels).where(labels: { title: label_names })
where('labels.title in (?)', label_names).
where(target_type: klass.name).pluck(:target_id)
items = items.where(id: item_ids) if projects
items = items.where(labels: { project_id: projects })
end
end end
end end
......
...@@ -16,6 +16,6 @@ module AppearancesHelper ...@@ -16,6 +16,6 @@ module AppearancesHelper
end end
def brand_header_logo def brand_header_logo
image_tag 'logo.svg' render 'shared/logo.svg'
end end
end end
...@@ -68,13 +68,17 @@ module ApplicationHelper ...@@ -68,13 +68,17 @@ module ApplicationHelper
end end
end end
def avatar_icon(user_email = '', size = nil) def avatar_icon(user_or_email = nil, size = nil)
user = User.find_by(email: user_email) if user_or_email.is_a?(User)
user = user_or_email
else
user = User.find_by(email: user_or_email)
end
if user if user
user.avatar_url(size) || default_avatar user.avatar_url(size) || default_avatar
else else
gravatar_icon(user_email, size) gravatar_icon(user_or_email, size)
end end
end end
......
...@@ -170,7 +170,8 @@ module DiffHelper ...@@ -170,7 +170,8 @@ module DiffHelper
def commit_for_diff(diff) def commit_for_diff(diff)
if diff.deleted_file if diff.deleted_file
@merge_request ? @merge_request.commits.last : @commit.parents.first first_commit = @first_commit || @commit
first_commit.parent
else else
@commit @commit
end end
......
...@@ -25,6 +25,10 @@ module GitlabRoutingHelper ...@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end end
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
def activity_project_path(project, *args) def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args) activity_namespace_project_path(project.namespace, project, *args)
end end
......
...@@ -83,6 +83,10 @@ module IssuesHelper ...@@ -83,6 +83,10 @@ module IssuesHelper
end end
end end
def merge_requests_sentence(merge_requests)
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
end
# Required for Gitlab::Markdown::IssueReferenceFilter # Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue module_function :url_for_issue
end end
...@@ -92,11 +92,19 @@ module LabelsHelper ...@@ -92,11 +92,19 @@ module LabelsHelper
end end
end end
def project_labels_options(project) def projects_labels_options
labels = project.labels.to_a labels =
labels.unshift(Label::None) if @project
labels.unshift(Label::Any) @project.labels
options_from_collection_for_select(labels, 'name', 'title', params[:label_name]) else
Label.where(project_id: @projects)
end
grouped_labels = Labels::GroupService.new(labels).execute
grouped_labels.unshift(Label::None)
grouped_labels.unshift(Label::Any)
options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
end end
# Required for Gitlab::Markdown::LabelReferenceFilter # Required for Gitlab::Markdown::LabelReferenceFilter
......
...@@ -47,7 +47,7 @@ module MergeRequestsHelper ...@@ -47,7 +47,7 @@ module MergeRequestsHelper
end end
def issues_sentence(issues) def issues_sentence(issues)
issues.map { |i| "##{i.iid}" }.to_sentence issues.map(&:to_reference).to_sentence
end end
def mr_change_branches_path(merge_request) def mr_change_branches_path(merge_request)
......
...@@ -34,7 +34,8 @@ module PreferencesHelper ...@@ -34,7 +34,8 @@ module PreferencesHelper
def project_view_choices def project_view_choices
[ [
['Readme (default)', :readme], ['Readme (default)', :readme],
['Activity view', :activity] ['Activity view', :activity],
['Files view', :files]
] ]
end end
...@@ -46,8 +47,7 @@ module PreferencesHelper ...@@ -46,8 +47,7 @@ module PreferencesHelper
Gitlab::ColorSchemes.for_user(current_user).css_class Gitlab::ColorSchemes.for_user(current_user).css_class
end end
def prefer_readme? def default_project_view
!current_user || current_user ? current_user.project_view : 'readme'
current_user.project_view == 'readme'
end end
end end
...@@ -29,7 +29,7 @@ module ProjectsHelper ...@@ -29,7 +29,7 @@ module ProjectsHelper
author_html = "" author_html = ""
# Build avatar image tag # Build avatar image tag
author_html << image_tag(avatar_icon(author.try(:email), 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
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
...@@ -70,6 +70,10 @@ module ProjectsHelper ...@@ -70,6 +70,10 @@ module ProjectsHelper
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?" "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end end
def remove_fork_project_message(project)
"You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?"
end
def project_nav_tabs def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user) @nav_tabs ||= get_project_nav_tabs(@project, current_user)
end end
...@@ -113,6 +117,10 @@ module ProjectsHelper ...@@ -113,6 +117,10 @@ module ProjectsHelper
nav_tabs << :merge_requests nav_tabs << :merge_requests
end end
if project.gitlab_ci? && can?(current_user, :read_build, project)
nav_tabs << :builds
end
if can?(current_user, :admin_project, project) if can?(current_user, :admin_project, project)
nav_tabs << :settings nav_tabs << :settings
end end
......
...@@ -13,4 +13,17 @@ module RunnersHelper ...@@ -13,4 +13,17 @@ module RunnersHelper
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago" title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end end
end end
def runner_link(runner)
display_name = truncate(runner.display_name, length: 15)
id = "\##{runner.id}"
if current_user && current_user.admin
link_to ci_admin_runner_path(runner) do
display_name + id
end
else
display_name + id
end
end
end end
class AbuseReportMailer < BaseMailer
include Gitlab::CurrentSettings
def notify(abuse_report_id)
@abuse_report = AbuseReport.find(abuse_report_id)
mail(
to: current_application_settings.admin_notification_email,
subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
)
end
end
...@@ -41,6 +41,7 @@ class Ability ...@@ -41,6 +41,7 @@ class Ability
:read_project_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:read_build,
:download_code :download_code
] ]
...@@ -127,6 +128,7 @@ class Ability ...@@ -127,6 +128,7 @@ class Ability
:read_project_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:read_build,
:create_project, :create_project,
:create_issue, :create_issue,
:create_note :create_note
...@@ -187,7 +189,8 @@ class Ability ...@@ -187,7 +189,8 @@ class Ability
:change_visibility_level, :change_visibility_level,
:rename_project, :rename_project,
:remove_project, :remove_project,
:archive_project :archive_project,
:remove_fork_project
] ]
end end
......
...@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true, allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
validates :admin_notification_email,
allow_blank: true,
email: true
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|
......
...@@ -93,10 +93,7 @@ module Ci ...@@ -93,10 +93,7 @@ module Ci
Ci::WebHookService.new.build_end(build) Ci::WebHookService.new.build_end(build)
end end
if build.commit.should_create_next_builds?(build) build.commit.create_next_builds(build)
build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request)
end
project.execute_services(build) project.execute_services(build)
if project.coverage_enabled? if project.coverage_enabled?
......
...@@ -24,6 +24,8 @@ module Ci ...@@ -24,6 +24,8 @@ module Ci
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }
validates_presence_of :sha validates_presence_of :sha
validate :valid_commit_sha validate :valid_commit_sha
...@@ -89,19 +91,28 @@ module Ci ...@@ -89,19 +91,28 @@ module Ci
def create_builds(ref, tag, user, trigger_request = nil) def create_builds(ref, tag, user, trigger_request = nil)
return unless config_processor return unless config_processor
config_processor.stages.any? do |stage| config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
end end
end end
def create_next_builds(ref, tag, user, trigger_request) def create_next_builds(build)
return unless config_processor return unless config_processor
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage) # don't create other builds if this one is retried
latest_builds = builds.similar(build).latest
return unless latest_builds.exists?(build.id)
config_processor.stages.any? do |stage| # get list of stages after this build
unless stages.include?(stage) next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? next_stages.delete(build.stage)
end
# get status for all prior builds
prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
status = Ci::Status.get_status(prior_builds)
# create builds for next stages based
next_stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
end end
end end
...@@ -130,24 +141,7 @@ module Ci ...@@ -130,24 +141,7 @@ module Ci
return 'failed' return 'failed'
end end
@status ||= begin @status ||= Ci::Status.get_status(latest_statuses)
latest = latest_statuses
latest.reject! { |status| status.try(&:allow_failure?) }
if latest.none?
'skipped'
elsif latest.all?(&:success?)
'success'
elsif latest.all?(&:pending?)
'pending'
elsif latest.any?(&:running?) || latest.any?(&:pending?)
'running'
elsif latest.all?(&:canceled?)
'canceled'
else
'failed'
end
end
end end
def pending? def pending?
...@@ -217,16 +211,6 @@ module Ci ...@@ -217,16 +211,6 @@ module Ci
update!(committed_at: DateTime.now) update!(committed_at: DateTime.now)
end end
def should_create_next_builds?(build)
# don't create other builds if this one is retried
other_builds = builds.similar(build).latest
return false unless other_builds.include?(build)
other_builds.all? do |build|
build.success? || build.ignored?
end
end
private private
def save_yaml_error(error) def save_yaml_error(error)
......
...@@ -205,7 +205,7 @@ module Ci ...@@ -205,7 +205,7 @@ module Ci
end end
def commits def commits
gl_project.ci_commits gl_project.ci_commits.ordered
end end
def builds def builds
......
...@@ -59,7 +59,7 @@ module Ci ...@@ -59,7 +59,7 @@ module Ci
end end
def display_name def display_name
return token unless !description.blank? return short_sha unless !description.blank?
description description
end end
...@@ -95,7 +95,7 @@ module Ci ...@@ -95,7 +95,7 @@ module Ci
end end
def short_sha def short_sha
token[0...10] token[0...8] if token
end end
end end
end end
...@@ -164,6 +164,14 @@ class Commit ...@@ -164,6 +164,14 @@ class Commit
@committer ||= User.find_by_any_email(committer_email) @committer ||= User.find_by_any_email(committer_email)
end end
def parents
@parents ||= parent_ids.map { |id| project.commit(id) }
end
def parent
@parent ||= project.commit(self.parent_id) if self.parent_id
end
def notes def notes
project.notes.for_commit_id(self.id) project.notes.for_commit_id(self.id)
end end
...@@ -181,10 +189,6 @@ class Commit ...@@ -181,10 +189,6 @@ class Commit
@raw.short_id(7) @raw.short_id(7)
end end
def parents
@parents ||= Commit.decorate(super, project)
end
def ci_commit def ci_commit
project.ci_commit(sha) project.ci_commit(sha)
end end
......
...@@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base
scope :success, -> { where(status: 'success') } scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') } scope :failed, -> { where(status: 'failed') }
scope :running_or_pending, -> { where(status:[:running, :pending]) } scope :running_or_pending, -> { where(status:[:running, :pending]) }
scope :finished, -> { where(status:[:success, :failed, :canceled]) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) } scope :for_ref, ->(ref) { where(ref: ref) }
...@@ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base
end end
event :drop do event :drop do
transition running: :failed transition [:pending, :running] => :failed
end end
event :success do event :success do
......
...@@ -48,7 +48,7 @@ module Issuable ...@@ -48,7 +48,7 @@ module Issuable
attr_mentionable :title attr_mentionable :title
attr_mentionable :description, cache: true attr_mentionable :description, cache: true
participant :author, :assignee, :notes participant :author, :assignee, :notes_with_associations
end end
module ClassMethods module ClassMethods
...@@ -86,6 +86,10 @@ module Issuable ...@@ -86,6 +86,10 @@ module Issuable
assignee_id_changed? assignee_id_changed?
end end
def open?
opened? || reopened?
end
# #
# Votes # Votes
# #
...@@ -177,6 +181,10 @@ module Issuable ...@@ -177,6 +181,10 @@ module Issuable
self.class.to_s.underscore self.class.to_s.underscore
end end
def notes_with_associations
notes.includes(:author, :project)
end
private private
def filter_superceded_votes(votes, notes) def filter_superceded_votes(votes, notes)
......
...@@ -60,12 +60,12 @@ module Mentionable ...@@ -60,12 +60,12 @@ module Mentionable
end end
def mentioned_users(current_user = nil, load_lazy_references: true) def mentioned_users(current_user = nil, load_lazy_references: true)
all_references(current_user).users all_references(current_user, load_lazy_references: load_lazy_references).users
end end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def referenced_mentionables(current_user = self.author, text = nil) def referenced_mentionables(current_user = self.author, text = nil, load_lazy_references: true)
refs = all_references(current_user, text) refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits) - [local_reference] (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end end
......
...@@ -64,7 +64,7 @@ class Group < Namespace ...@@ -64,7 +64,7 @@ class Group < Namespace
end end
def owners def owners
@owners ||= group_members.owners.map(&:user) @owners ||= group_members.owners.includes(:user).map(&:user)
end end
def add_users(user_ids, access_level, current_user = nil) def add_users(user_ids, access_level, current_user = nil)
......
class GroupLabel
attr_accessor :title, :labels
alias_attribute :name, :title
def initialize(title, labels)
@title = title
@labels = labels
end
end
class GroupMilestone class GroupMilestone
attr_accessor :title, :milestones
alias_attribute :name, :title alias_attribute :name, :title
def initialize(title, milestones) def initialize(title, milestones)
...@@ -7,18 +7,10 @@ class GroupMilestone ...@@ -7,18 +7,10 @@ class GroupMilestone
@milestones = milestones @milestones = milestones
end end
def title
@title
end
def safe_title def safe_title
@title.parameterize @title.parameterize
end end
def milestones
@milestones
end
def projects def projects
milestones.map { |milestone| milestone.project } milestones.map { |milestone| milestone.project }
end end
......
...@@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base ...@@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base
def source_project def source_project
project project
end end
# From all notes on this issue, we'll select the system notes about linked
# merge requests. Of those, the MRs closing `self` are returned.
def closed_by_merge_requests(current_user = nil)
return [] unless open?
notes.system.flat_map do |note|
note.all_references(current_user).merge_requests
end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
end
end end
...@@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff after_create :create_merge_request_diff
after_update :update_merge_request_diff after_update :update_merge_request_diff
delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil delegate :commits, :diffs, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base ...@@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base
reference reference
end end
def last_commit
merge_request_diff ? merge_request_diff.last_commit : compare_commits.last
end
def first_commit
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
def last_commit_short_sha
last_commit.short_id
end
def validate_branches def validate_branches
if target_project == source_project && target_branch == source_branch if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target" errors.add :branch_conflict, "You can not use same project/branch for source and target"
...@@ -222,10 +234,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -222,10 +234,6 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end end
def open?
opened? || reopened?
end
def work_in_progress? def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i) !!(title =~ /\A\[?WIP\]?:? /i)
end end
...@@ -294,6 +302,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -294,6 +302,10 @@ class MergeRequest < ActiveRecord::Base
target_project target_project
end end
def closes_issue?(issue)
closes_issues.include?(issue)
end
# Return the set of issues that will be closed if this merge request is accepted. # Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author) def closes_issues(current_user = self.author)
if target_branch == project.default_branch if target_branch == project.default_branch
......
...@@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base
commits.first commits.first
end end
def first_commit
commits.last
end
def last_commit_short_sha def last_commit_short_sha
@last_commit_short_sha ||= last_commit.short_id @last_commit_short_sha ||= last_commit.short_id
end end
...@@ -163,7 +167,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -163,7 +167,8 @@ class MergeRequestDiff < ActiveRecord::Base
merge_request.fetch_ref merge_request.fetch_ref
# Get latest sha of branch from source project # Get latest sha of branch from source project
source_sha = merge_request.source_project.commit(source_branch).sha source_commit = merge_request.source_project.commit(source_branch)
source_sha = source_commit.try(:sha)
Gitlab::CompareResult.new( Gitlab::CompareResult.new(
Gitlab::Git::Compare.new( Gitlab::Git::Compare.new(
......
...@@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base ...@@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base
def author_id def author_id
nil nil
end end
# Sorts the issues for the given IDs.
#
# This method runs a single SQL query using a CASE statement to update the
# position of all issues in the current milestone (scoped to the list of IDs).
#
# Given the ids [10, 20, 30] this method produces a SQL query something like
# the following:
#
# UPDATE issues
# SET position = CASE
# WHEN id = 10 THEN 1
# WHEN id = 20 THEN 2
# WHEN id = 30 THEN 3
# ELSE position
# END
# WHERE id IN (10, 20, 30);
#
# This method expects that the IDs given in `ids` are already Fixnums.
def sort_issues(ids)
pairs = []
ids.each_with_index do |id, index|
pairs << id
pairs << index + 1
end
conditions = 'WHEN id = ? THEN ? ' * ids.length
issues.where(id: ids).
update_all(["position = CASE #{conditions} ELSE position END", *pairs])
end
end end
...@@ -60,6 +60,11 @@ class Note < ActiveRecord::Base ...@@ -60,6 +60,11 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) } scope :inc_author, ->{ includes(:author) }
scope :with_associations, -> do
includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }])
end
serialize :st_diff serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? } before_create :set_diff, if: ->(n) { n.line_code.present? }
......
...@@ -119,7 +119,7 @@ class Project < ActiveRecord::Base ...@@ -119,7 +119,7 @@ class Project < ActiveRecord::Base
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
has_many :starrers, through: :users_star_projects, source: :user has_many :starrers, through: :users_star_projects, source: :user
has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
......
...@@ -48,7 +48,7 @@ class BambooService < CiService ...@@ -48,7 +48,7 @@ class BambooService < CiService
end end
def reset_password def reset_password
if prop_updated?(:bamboo_url) if bamboo_url_changed? && !password_touched?
self.password = nil self.password = nil
end end
end end
......
...@@ -45,7 +45,7 @@ class TeamcityService < CiService ...@@ -45,7 +45,7 @@ class TeamcityService < CiService
end end
def reset_password def reset_password
if prop_updated?(:teamcity_url) if teamcity_url_changed? && !password_touched?
self.password = nil self.password = nil
end end
end end
......
...@@ -139,15 +139,28 @@ class ProjectTeam ...@@ -139,15 +139,28 @@ class ProjectTeam
Gitlab::Access.options.key max_member_access(user_id) Gitlab::Access.options.key max_member_access(user_id)
end end
# This method assumes project and group members are eager loaded for optimal
# performance.
def max_member_access(user_id) def max_member_access(user_id)
access = [] access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field)
project.project_members.each do |member|
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
if group if group
access << group.group_members.find_by(user_id: user_id).try(:access_field) group.group_members.each do |member|
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
end end
access.compact.max access.max
end end
private private
......
...@@ -8,6 +8,14 @@ class Repository ...@@ -8,6 +8,14 @@ class Repository
attr_accessor :raw_repository, :path_with_namespace, :project attr_accessor :raw_repository, :path_with_namespace, :project
def self.clean_old_archives
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
return unless File.directory?(repository_downloads_path)
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
def initialize(path_with_namespace, default_branch = nil, project = nil) def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace @path_with_namespace = path_with_namespace
@project = project @project = project
...@@ -269,14 +277,6 @@ class Repository ...@@ -269,14 +277,6 @@ class Repository
end end
# Remove archives older than 2 hours # Remove archives older than 2 hours
def clean_old_archives
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
return unless File.directory?(repository_downloads_path)
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
def branches_sorted_by(value) def branches_sorted_by(value)
case value case value
when 'recently_updated' when 'recently_updated'
...@@ -312,13 +312,7 @@ class Repository ...@@ -312,13 +312,7 @@ class Repository
end end
def blob_for_diff(commit, diff) def blob_for_diff(commit, diff)
file = blob_at(commit.id, diff.new_path) blob_at(commit.id, diff.file_path)
unless file
file = prev_blob_for_diff(commit, diff)
end
file
end end
def prev_blob_for_diff(commit, diff) def prev_blob_for_diff(commit, diff)
...@@ -480,6 +474,10 @@ class Repository ...@@ -480,6 +474,10 @@ class Repository
end end
end end
def merge_base(first_commit_id, second_commit_id)
rugged.merge_base(first_commit_id, second_commit_id)
end
def search_files(query, ref) def search_files(query, ref)
offset = 2 offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref}) args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
......
...@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base ...@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties after_initialize :initialize_properties
after_commit :reset_updated_properties
belongs_to :project belongs_to :project
has_one :service_hook has_one :service_hook
...@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base ...@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base
# Provide convenient accessor methods # Provide convenient accessor methods
# for each serialized property. # for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args) def self.prop_accessor(*args)
args.each do |arg| args.each do |arg|
class_eval %{ class_eval %{
...@@ -111,21 +114,39 @@ class Service < ActiveRecord::Base ...@@ -111,21 +114,39 @@ class Service < ActiveRecord::Base
end end
def #{arg}=(value) def #{arg}=(value)
updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value self.properties['#{arg}'] = value
end end
def #{arg}_changed?
#{arg}_touched? && #{arg} != #{arg}_was
end
def #{arg}_touched?
updated_properties.include?('#{arg}')
end
def #{arg}_was
updated_properties['#{arg}']
end
} }
end end
end end
# ActiveRecord does not provide a mechanism to track changes in serialized keys. # Returns a hash of the properties that have been assigned a new value since last save,
# This is why we need to perform extra query to do it mannually. # indicating their original values (attr => original value).
def prop_updated?(prop_name) # ActiveRecord does not provide a mechanism to track changes in serialized keys,
relation_name = self.type.underscore # so we need a specific implementation for service properties.
previous_value = project.send(relation_name).send(prop_name) # This allows to track changes to properties set with the accessor methods,
return false if previous_value.nil? # but not direct manipulation of properties hash.
previous_value != send(prop_name) def updated_properties
@updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new
end end
def reset_updated_properties
@updated_properties = nil
end
def async_execute(data) def async_execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
......
...@@ -68,6 +68,7 @@ class User < ActiveRecord::Base ...@@ -68,6 +68,7 @@ class User < ActiveRecord::Base
include Referable include Referable
include Sortable include Sortable
include TokenAuthenticatable include TokenAuthenticatable
include CaseSensitivity
default_value_for :admin, false default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_group, gitlab_config.default_can_create_group
...@@ -182,7 +183,7 @@ class User < ActiveRecord::Base ...@@ -182,7 +183,7 @@ class User < ActiveRecord::Base
# User's Project preference # User's Project preference
# Note: When adding an option, it MUST go on the end of the array. # Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity] enum project_view: [:readme, :activity, :files]
alias_attribute :private_token, :authentication_token alias_attribute :private_token, :authentication_token
...@@ -273,8 +274,13 @@ class User < ActiveRecord::Base ...@@ -273,8 +274,13 @@ class User < ActiveRecord::Base
end end
def by_login(login) def by_login(login)
where('lower(username) = :value OR lower(email) = :value', return nil unless login
value: login.to_s.downcase).first
if login.include?('@'.freeze)
unscoped.iwhere(email: login).take
else
unscoped.iwhere(username: login).take
end
end end
def find_by_username!(username) def find_by_username!(username)
...@@ -700,12 +706,15 @@ class User < ActiveRecord::Base ...@@ -700,12 +706,15 @@ class User < ActiveRecord::Base
end end
def toggle_star(project) def toggle_star(project)
user_star_project = users_star_projects. UsersStarProject.transaction do
where(project: project, user: self).take user_star_project = users_star_projects.
if user_star_project where(project: project, user: self).lock(true).first
user_star_project.destroy
else if user_star_project
UsersStarProject.create!(project: project, user: self) user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end end
end end
......
...@@ -7,19 +7,12 @@ class ArchiveRepositoryService ...@@ -7,19 +7,12 @@ class ArchiveRepositoryService
end end
def execute(options = {}) def execute(options = {})
project.repository.clean_old_archives RepositoryArchiveCacheWorker.perform_async
raise "No archive file path" unless file_path metadata = project.repository.archive_metadata(ref, storage_path, format)
raise "Repository or ref not found" if metadata.empty?
return file_path if archived? metadata
unless archiving?
RepositoryArchiveWorker.perform_async(project.id, ref, format)
end
archived = wait_until_archived(options[:timeout] || 5.0)
file_path if archived
end end
private private
...@@ -27,36 +20,4 @@ class ArchiveRepositoryService ...@@ -27,36 +20,4 @@ class ArchiveRepositoryService
def storage_path def storage_path
Gitlab.config.gitlab.repository_downloads_path Gitlab.config.gitlab.repository_downloads_path
end end
def file_path
@file_path ||= project.repository.archive_file_path(ref, storage_path, format)
end
def pid_file_path
@pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
end
def archived?
File.exist?(file_path)
end
def archiving?
File.exist?(pid_file_path)
end
def wait_until_archived(timeout = 5.0)
return archived? if timeout == 0.0
t1 = Time.now
begin
sleep 0.1
success = archived?
t2 = Time.now
end until success || t2 - t1 >= timeout
success
end
end end
module Ci module Ci
class CreateBuildsService class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request) def execute(commit, stage, ref, tag, user, trigger_request, status)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag) builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
case build_attrs[:when]
when 'on_success'
status == 'success'
when 'on_failure'
status == 'failed'
when 'always'
%w(success failed).include?(status)
end
end
builds_attrs.map do |build_attrs| builds_attrs.map do |build_attrs|
# don't create the same build twice # don't create the same build twice
unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
......
...@@ -49,10 +49,13 @@ class GitPushService ...@@ -49,10 +49,13 @@ class GitPushService
elsif push_to_existing_branch?(ref, oldrev) elsif push_to_existing_branch?(ref, oldrev)
# Collect data for this git push # Collect data for this git push
@push_commits = project.repository.commits_between(oldrev, newrev) @push_commits = project.repository.commits_between(oldrev, newrev)
project.update_merge_requests(oldrev, newrev, ref, @user)
process_commit_messages(ref) process_commit_messages(ref)
end end
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
project.update_merge_requests(oldrev, newrev, ref, @user)
@push_data = build_push_data(oldrev, newrev, ref) @push_data = build_push_data(oldrev, newrev, ref)
# If CI was disabled but .gitlab-ci.yml file was pushed # If CI was disabled but .gitlab-ci.yml file was pushed
...@@ -76,7 +79,7 @@ class GitPushService ...@@ -76,7 +79,7 @@ class GitPushService
authors = Hash.new do |hash, commit| authors = Hash.new do |hash, commit|
email = commit.author_email email = commit.author_email
return hash[email] if hash.has_key?(email) next hash[email] if hash.has_key?(email)
hash[email] = commit_user(commit) hash[email] = commit_user(commit)
end end
......
module Labels
class GroupService < ::BaseService
def initialize(project_labels)
@project_labels = project_labels.group_by(&:title)
end
def execute
build(@project_labels)
end
def label(title)
if title
group_label = @project_labels[title].group_by(&:title)
build(group_label).first
else
nil
end
end
private
def build(label)
label.map { |title, labels| GroupLabel.new(title, labels) }
end
end
end
...@@ -6,6 +6,7 @@ module MergeRequests ...@@ -6,6 +6,7 @@ module MergeRequests
# #
class PostMergeService < MergeRequests::BaseService class PostMergeService < MergeRequests::BaseService
def execute(merge_request) def execute(merge_request)
close_issues(merge_request)
merge_request.mark_as_merged merge_request.mark_as_merged
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
...@@ -15,6 +16,15 @@ module MergeRequests ...@@ -15,6 +16,15 @@ module MergeRequests
private private
def close_issues(merge_request)
return unless merge_request.target_branch == project.default_branch
closed_issues = merge_request.closes_issues(current_user)
closed_issues.each do |issue|
Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
end
end
def create_merge_event(merge_request, current_user) def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user) EventCreateService.new.merge_mr(merge_request, current_user)
end end
......
...@@ -6,12 +6,20 @@ module MergeRequests ...@@ -6,12 +6,20 @@ module MergeRequests
@oldrev, @newrev = oldrev, newrev @oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref) @branch_name = Gitlab::Git.ref_name(ref)
@fork_merge_requests = @project.fork_merge_requests.opened @fork_merge_requests = @project.fork_merge_requests.opened
@commits = @project.repository.commits_between(oldrev, newrev) @commits = []
# Leave a system note if a branch were deleted/added
if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
comment_mr_branch_presence_changed
comment_mr_with_commits if @commits.present?
else
@commits = @project.repository.commits_between(oldrev, newrev)
comment_mr_with_commits
close_merge_requests
end
close_merge_requests
reload_merge_requests reload_merge_requests
execute_mr_web_hooks execute_mr_web_hooks
comment_mr_with_commits
true true
end end
...@@ -31,7 +39,6 @@ module MergeRequests ...@@ -31,7 +39,6 @@ module MergeRequests
commit_ids.include?(merge_request.last_commit.id) commit_ids.include?(merge_request.last_commit.id)
end end
merge_requests.uniq.select(&:source_project).each do |merge_request| merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::PostMergeService. MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user). new(merge_request.target_project, @current_user).
...@@ -70,13 +77,38 @@ module MergeRequests ...@@ -70,13 +77,38 @@ module MergeRequests
end end
end end
# Add comment about branches being deleted or added to merge requests
def comment_mr_branch_presence_changed
presence = Gitlab::Git.blank_ref?(@oldrev) ? :add : :delete
merge_requests_for_source_branch.each do |merge_request|
last_commit = merge_request.last_commit
# Only look at changed commits in restore branch case
unless Gitlab::Git.blank_ref?(@newrev)
begin
# Since any number of commits could have been made to the restored branch,
# find the common root to see what has been added.
common_ref = @project.repository.merge_base(last_commit.id, @newrev)
# If the a commit no longer exists in this repo, gitlab_git throws
# a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52
@commits = @project.repository.commits_between(common_ref, @newrev) if common_ref
rescue
end
# Prevent system notes from seeing a blank SHA
@oldrev = nil
end
SystemNoteService.change_branch_presence(
merge_request, merge_request.project, @current_user,
:source, @branch_name, presence)
end
end
# Add comment about pushing new commits to merge requests # Add comment about pushing new commits to merge requests
def comment_mr_with_commits def comment_mr_with_commits
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a merge_requests_for_source_branch.each do |merge_request|
merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
mr_commit_ids = Set.new(merge_request.commits.map(&:id)) mr_commit_ids = Set.new(merge_request.commits.map(&:id))
new_commits, existing_commits = @commits.partition do |commit| new_commits, existing_commits = @commits.partition do |commit|
...@@ -91,14 +123,7 @@ module MergeRequests ...@@ -91,14 +123,7 @@ module MergeRequests
# Call merge request webhook with update branches # Call merge request webhook with update branches
def execute_mr_web_hooks def execute_mr_web_hooks
merge_requests = @project.origin_merge_requests.opened merge_requests_for_source_branch.each do |merge_request|
.where(source_branch: @branch_name)
.to_a
merge_requests += @fork_merge_requests.where(source_branch: @branch_name)
.to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
execute_hooks(merge_request, 'update') execute_hooks(merge_request, 'update')
end end
end end
...@@ -106,5 +131,13 @@ module MergeRequests ...@@ -106,5 +131,13 @@ module MergeRequests
def filter_merge_requests(merge_requests) def filter_merge_requests(merge_requests)
merge_requests.uniq.select(&:source_project) merge_requests.uniq.select(&:source_project)
end end
def merge_requests_for_source_branch
@source_merge_requests ||= begin
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
filter_merge_requests(merge_requests)
end
end
end end
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.
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