Commit 2c27325e authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into artifacts-from-ref-and-build-name

* upstream/master: (117 commits)
  spec and fix for sanitize method
  remove fix validatable import url migration
  Fix typo in spec
  Update Gemfile.lock after versions were added in !5078
  Add more comments to regex
  Add changelog item
  Add blockquote fence syntax to Markdown
  Fix typo and explain the precedence of STDERR and STDOUT
  Add changelog entry for !4399
  Add reminder to not paste private SSH keys
  Make subnavigation a bit darker color
  Update ui_guide.md with button capitalize rule
  Re-use queries in reference parsers
  Use btn-danger for delete button.
  Update New Snippet buttons.
  Run bundle install.
  Add min attribute to project_limit field on user's form
  Wrong gitlab-shell version
  API: Expose shared projects in a group
  Memoize MR merged/closed events retrieval
  ...
parents ef833a22 7303de91
...@@ -284,7 +284,7 @@ Style/IfWithSemicolon: ...@@ -284,7 +284,7 @@ Style/IfWithSemicolon:
# Checks that conditional statements do not have an identical line at the # Checks that conditional statements do not have an identical line at the
# end of each branch, which can validly be moved out of the conditional. # end of each branch, which can validly be moved out of the conditional.
Style/IdenticalConditionalBranches: Style/IdenticalConditionalBranches:
Enabled: false Enabled: true
# Checks the indentation of the first line of the right-hand-side of a # Checks the indentation of the first line of the right-hand-side of a
# multi-line assignment. # multi-line assignment.
......
...@@ -8,6 +8,7 @@ v 8.10.0 (unreleased) ...@@ -8,6 +8,7 @@ v 8.10.0 (unreleased)
- Wrap code blocks on Activies and Todos page. !4783 (winniehell) - Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Align flash messages with left side of page content !4959 (winniehell) - Align flash messages with left side of page content !4959 (winniehell)
- Display last commit of deleted branch in push events !4699 (winniehell) - Display last commit of deleted branch in push events !4699 (winniehell)
- Escape file extension when parsing search results !5141 (winniehell)
- Apply the trusted_proxies config to the rack request object for use with rack_attack - Apply the trusted_proxies config to the rack request object for use with rack_attack
- Add Sidekiq queue duration to transaction metrics. - Add Sidekiq queue duration to transaction metrics.
- Add a new column `artifacts_size` to table `ci_builds` !4964 - Add a new column `artifacts_size` to table `ci_builds` !4964
...@@ -17,15 +18,22 @@ v 8.10.0 (unreleased) ...@@ -17,15 +18,22 @@ v 8.10.0 (unreleased)
- Fix MR-auto-close text added to description. !4836 - Fix MR-auto-close text added to description. !4836
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates. - Add Spring EmojiOne updates.
- Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion
- Fix pagination when sorting by columns with lots of ties (like priority) - Fix pagination when sorting by columns with lots of ties (like priority)
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
- Updated project header design - Updated project header design
- Exclude email check from the standard health check - Exclude email check from the standard health check
- Updated layout for Projects, Groups, Users on Admin area !4424
- Fix changing issue state columns in milestone view - Fix changing issue state columns in milestone view
- Add notification settings dropdown for groups - Add notification settings dropdown for groups
- Wildcards for protected branches. !4665
- Allow importing from Github using Personal Access Tokens. (Eric K Idema) - Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Todos !3188 (Robert Schilling) - API: Todos !3188 (Robert Schilling)
- API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
- Add "Enabled Git access protocols" to Application Settings - Add "Enabled Git access protocols" to Application Settings
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Only show New Snippet button to users that can create snippets.
- PipelinesFinder uses git cache data - PipelinesFinder uses git cache data
- Throttle the update of `project.pushes_since_gc` to 1 minute. - Throttle the update of `project.pushes_since_gc` to 1 minute.
- Check for conflicts with existing Project's wiki path when creating a new project. - Check for conflicts with existing Project's wiki path when creating a new project.
...@@ -34,6 +42,7 @@ v 8.10.0 (unreleased) ...@@ -34,6 +42,7 @@ v 8.10.0 (unreleased)
- Bump Rinku to 2.0.0 - Bump Rinku to 2.0.0
- Remove unused front-end variable -> default_issues_tracker - Remove unused front-end variable -> default_issues_tracker
- Better caching of git calls on ProjectsController#show. - Better caching of git calls on ProjectsController#show.
- Avoid to retrieve MR closes_issues as much as possible.
- Add API endpoint for a group issues !4520 (mahcsig) - Add API endpoint for a group issues !4520 (mahcsig)
- Add Bugzilla integration !4930 (iamtjg) - Add Bugzilla integration !4930 (iamtjg)
- Instrument Rinku usage - Instrument Rinku usage
...@@ -41,6 +50,8 @@ v 8.10.0 (unreleased) ...@@ -41,6 +50,8 @@ v 8.10.0 (unreleased)
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
- Set import_url validation to be more strict - Set import_url validation to be more strict
- Memoize MR merged/closed events retrieval
- Don't render discussion notes when requesting diff tab through AJAX
- Add basic system information like memory and disk usage to the admin panel - Add basic system information like memory and disk usage to the admin panel
- Don't garbage collect commits that have related DB records like comments - Don't garbage collect commits that have related DB records like comments
- More descriptive message for git hooks and file locks - More descriptive message for git hooks and file locks
...@@ -48,6 +59,10 @@ v 8.10.0 (unreleased) ...@@ -48,6 +59,10 @@ v 8.10.0 (unreleased)
- Allow '?', or '&' for label names - Allow '?', or '&' for label names
- Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
- Add date when user joined the team on the member page - Add date when user joined the team on the member page
- Fix 404 redirect after validation fails importing a GitLab project
- Added setting to set new users by default as external !4545 (Dravere)
- Add min value for project limit field on user's form !3622 (jastkand)
- Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt)
v 8.9.5 v 8.9.5
- Add more debug info to import/export and memory killer. !5108 - Add more debug info to import/export and memory killer. !5108
...@@ -92,7 +107,7 @@ v 8.9.3 ...@@ -92,7 +107,7 @@ v 8.9.3
- Removed fade when filtering results. !4932 - Removed fade when filtering results. !4932
- Fix missing avatar on system notes. !4954 - Fix missing avatar on system notes. !4954
- Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973
- Use update_columns to by_pass all the dirty code on active_record. !4985 - Use update_columns to bypass all the dirty code on active_record. !4985
- Fix restore Rake task warning message output !4980 - Fix restore Rake task warning message output !4980
v 8.9.2 v 8.9.2
......
source "https://rubygems.org" source 'https://rubygems.org'
gem 'rails', '4.2.6' gem 'rails', '4.2.6'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
...@@ -11,11 +11,11 @@ gem 'responders', '~> 2.0' ...@@ -11,11 +11,11 @@ gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.6.0' gem 'sprockets', '~> 3.6.0'
# Default values for AR models # Default values for AR models
gem "default_value_for", "~> 3.0.0" gem 'default_value_for', '~> 3.0.0'
# Supported DBs # Supported DBs
gem "mysql2", '~> 0.3.16', group: :mysql gem 'mysql2', '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres gem 'pg', '~> 0.18.2', group: :postgres
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.0' gem 'devise', '~> 4.0'
...@@ -28,7 +28,7 @@ gem 'omniauth-cas3', '~> 1.1.2' ...@@ -28,7 +28,7 @@ gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 3.0.0' gem 'omniauth-facebook', '~> 3.0.0'
gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.6.0' gem 'omniauth-saml', '~> 1.6.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
...@@ -48,16 +48,16 @@ gem 'attr_encrypted', '~> 3.0.0' ...@@ -48,16 +48,16 @@ gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
# Browser detection # Browser detection
gem "browser", '~> 2.2' gem 'browser', '~> 2.2'
# 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", '~> 10.2' gem 'gitlab_git', '~> 10.2'
# 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
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap'
# Git Wiki # Git Wiki
# Required manually in config/initializers/gollum.rb to control load order # Required manually in config/initializers/gollum.rb to control load order
...@@ -65,7 +65,7 @@ gem 'gollum-lib', '~> 4.1.0', require: false ...@@ -65,7 +65,7 @@ gem 'gollum-lib', '~> 4.1.0', require: false
gem 'gollum-rugged_adapter', '~> 0.4.2', require: false gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
# Language detection # Language detection
gem "github-linguist", "~> 4.7.0", require: "linguist" gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API # API
gem 'grape', '~> 0.13.0' gem 'grape', '~> 0.13.0'
...@@ -73,13 +73,13 @@ gem 'grape-entity', '~> 0.4.2' ...@@ -73,13 +73,13 @@ gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination # Pagination
gem "kaminari", "~> 0.17.0" gem 'kaminari', '~> 0.17.0'
# HAML # HAML
gem 'hamlit', '~> 2.5' gem 'hamlit', '~> 2.5'
# Files attachments # Files attachments
gem "carrierwave", '~> 0.10.0' gem 'carrierwave', '~> 0.10.0'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
...@@ -94,13 +94,13 @@ gem 'fog-openstack', '~> 0.1' ...@@ -94,13 +94,13 @@ gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1' gem 'fog-rackspace', '~> 0.1.1'
# for aws storage # for aws storage
gem "unf", '~> 0.1.4' gem 'unf', '~> 0.1.4'
# Authorization # Authorization
gem "six", '~> 0.2.0' gem 'six', '~> 0.2.0'
# Seed data # Seed data
gem "seed-fu", '~> 2.3.5' gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
...@@ -124,29 +124,29 @@ gem 'diffy', '~> 3.0.3' ...@@ -124,29 +124,29 @@ gem 'diffy', '~> 3.0.3'
# Application server # Application server
group :unicorn do group :unicorn do
gem "unicorn", '~> 4.9.0' gem 'unicorn', '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2' gem 'unicorn-worker-killer', '~> 0.4.2'
end end
# State machine # State machine
gem "state_machines-activerecord", '~> 0.4.0' gem 'state_machines-activerecord', '~> 0.4.0'
# Run events after state machine commits # Run events after state machine commits
gem 'after_commit_queue' gem 'after_commit_queue', '~> 1.3.0'
# Issue tags # Issue tags
gem 'acts-as-taggable-on', '~> 3.4' gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs # Background jobs
gem 'sinatra', '~> 1.4.4', require: nil gem 'sinatra', '~> 1.4.4', require: false
gem 'sidekiq', '~> 4.0' gem 'sidekiq', '~> 4.0'
gem 'sidekiq-cron', '~> 0.4.0' gem 'sidekiq-cron', '~> 0.4.0'
gem 'redis-namespace' gem 'redis-namespace', '~> 1.5.2'
# HTTP requests # HTTP requests
gem "httparty", '~> 0.13.3' gem 'httparty', '~> 0.13.3'
# Colored output to console # Colored output to console
gem "rainbow", '~> 2.1.0' gem 'rainbow', '~> 2.1.0'
# GitLab settings # GitLab settings
gem 'settingslogic', '~> 2.0.9' gem 'settingslogic', '~> 2.0.9'
...@@ -156,7 +156,7 @@ gem 'settingslogic', '~> 2.0.9' ...@@ -156,7 +156,7 @@ gem 'settingslogic', '~> 2.0.9'
gem 'version_sorter', '~> 2.0.0' gem 'version_sorter', '~> 2.0.0'
# Cache # Cache
gem "redis-rails", '~> 4.0.0' gem 'redis-rails', '~> 4.0.0'
# Redis # Redis
gem 'redis', '~> 3.2' gem 'redis', '~> 3.2'
...@@ -169,13 +169,13 @@ gem 'tinder', '~> 1.10.0' ...@@ -169,13 +169,13 @@ gem 'tinder', '~> 1.10.0'
gem 'hipchat', '~> 1.5.0' gem 'hipchat', '~> 1.5.0'
# Flowdock integration # Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 1.0.1" gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
# Gemnasium integration # Gemnasium integration
gem "gemnasium-gitlab-service", "~> 0.2" gem 'gemnasium-gitlab-service', '~> 0.2'
# Slack integration # Slack integration
gem "slack-notifier", "~> 1.2.0" gem 'slack-notifier', '~> 1.2.0'
# Asana integration # Asana integration
gem 'asana', '~> 0.4.0' gem 'asana', '~> 0.4.0'
...@@ -187,20 +187,20 @@ gem 'ruby-fogbugz', '~> 0.2.1' ...@@ -187,20 +187,20 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'd3_rails', '~> 3.5.0' gem 'd3_rails', '~> 3.5.0'
# underscore-rails # underscore-rails
gem "underscore-rails", "~> 1.8.0" gem 'underscore-rails', '~> 1.8.0'
# Sanitize user input # Sanitize user input
gem "sanitize", '~> 2.0' gem 'sanitize', '~> 2.0'
gem 'babosa', '~> 1.0.2' gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input # Sanitizes SVG input
gem "loofah", "~> 2.0.3" gem 'loofah', '~> 2.0.3'
# Working with license # Working with license
gem 'licensee', '~> 8.0.0' gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing # Protect against bruteforcing
gem "rack-attack", '~> 4.3.1' gem 'rack-attack', '~> 4.3.1'
# Ace editor # Ace editor
gem 'ace-rails-ap', '~> 4.0.2' gem 'ace-rails-ap', '~> 4.0.2'
...@@ -214,9 +214,9 @@ gem 'charlock_holmes', '~> 0.7.3' ...@@ -214,9 +214,9 @@ gem 'charlock_holmes', '~> 0.7.3'
# Parse duration # Parse duration
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
gem "sass-rails", '~> 5.0.0' gem 'sass-rails', '~> 5.0.0'
gem "coffee-rails", '~> 4.1.0' gem 'coffee-rails', '~> 4.1.0'
gem "uglifier", '~> 2.7.2' gem 'uglifier', '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0' gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.1.0' gem 'jquery-turbolinks', '~> 2.1.0'
...@@ -247,13 +247,13 @@ group :metrics do ...@@ -247,13 +247,13 @@ group :metrics do
end end
group :development do group :development do
gem "foreman" gem 'foreman', '~> 0.78.0'
gem 'brakeman', '~> 3.3.0', require: false gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
gem 'rerun', '~> 0.11.0' gem 'rerun', '~> 0.11.0'
gem 'bullet', require: false gem 'bullet', '~> 5.0.0', require: false
gem 'rblineprof', platform: :mri, require: false gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'web-console', '~> 2.0' gem 'web-console', '~> 2.0'
# Better errors handler # Better errors handler
...@@ -261,15 +261,15 @@ group :development do ...@@ -261,15 +261,15 @@ group :development do
gem 'binding_of_caller', '~> 0.7.2' gem 'binding_of_caller', '~> 0.7.2'
# Docs generator # Docs generator
gem "sdoc", '~> 0.3.20' gem 'sdoc', '~> 0.3.20'
# thin instead webrick # thin instead webrick
gem 'thin', '~> 1.7.0' gem 'thin', '~> 1.7.0'
end end
group :development, :test do group :development, :test do
gem 'byebug', platform: :mri gem 'byebug', '~> 8.2.1', platform: :mri
gem 'pry-rails' gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0' gem 'fuubar', '~> 2.0.0'
...@@ -277,7 +277,7 @@ group :development, :test do ...@@ -277,7 +277,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.4.0' gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0' gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.5.0' gem 'rspec-rails', '~> 3.5.0'
gem 'rspec-retry' gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2' gem 'spinach-rerun-reporter', '~> 0.0.2'
...@@ -303,14 +303,14 @@ group :development, :test do ...@@ -303,14 +303,14 @@ group :development, :test do
gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'simplecov', '~> 0.11.0', require: false gem 'simplecov', '~> 0.11.0', require: false
gem 'flog', require: false gem 'flog', '~> 4.3.2', require: false
gem 'flay', require: false gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', require: false gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
gem "license_finder", require: false gem 'license_finder', '~> 2.1.0', require: false
gem 'knapsack' gem 'knapsack', '~> 1.11.0'
end end
group :test do group :test do
...@@ -318,30 +318,30 @@ group :test do ...@@ -318,30 +318,30 @@ group :test do
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0' gem 'webmock', '~> 1.21.0'
gem 'test_after_commit', '~> 0.4.2' gem 'test_after_commit', '~> 0.4.2'
gem 'sham_rack' gem 'sham_rack', '~> 1.3.6'
end end
group :production do group :production do
gem "gitlab_meta", '7.0' gem 'gitlab_meta', '7.0'
end end
gem "newrelic_rpm", '~> 3.14' gem 'newrelic_rpm', '~> 3.14'
gem 'octokit', '~> 4.3.0' gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.8" gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
## CI ## CI
gem 'activerecord-session_store', '~> 1.0.0' gem 'activerecord-session_store', '~> 1.0.0'
gem "nested_form", '~> 0.3.2' gem 'nested_form', '~> 0.3.2'
# OAuth # OAuth
gem 'oauth2', '~> 1.0.0' gem 'oauth2', '~> 1.2.0'
# Soft deletion # Soft deletion
gem "paranoia", "~> 2.0" gem 'paranoia', '~> 2.0'
# Health check # Health check
gem 'health_check', '~> 1.5.1' gem 'health_check', '~> 1.5.1'
......
...@@ -353,7 +353,7 @@ GEM ...@@ -353,7 +353,7 @@ GEM
jquery-ui-rails (5.0.5) jquery-ui-rails (5.0.5)
railties (>= 3.2.16) railties (>= 3.2.16)
json (1.8.3) json (1.8.3)
jwt (1.5.2) jwt (1.5.4)
kaminari (0.17.0) kaminari (0.17.0)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
...@@ -393,7 +393,7 @@ GEM ...@@ -393,7 +393,7 @@ GEM
mini_portile2 (2.1.0) mini_portile2 (2.1.0)
minitest (5.7.0) minitest (5.7.0)
mousetrap-rails (1.4.6) mousetrap-rails (1.4.6)
multi_json (1.11.2) multi_json (1.12.1)
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)
...@@ -406,12 +406,12 @@ GEM ...@@ -406,12 +406,12 @@ GEM
pkg-config (~> 1.1.7) pkg-config (~> 1.1.7)
numerizer (0.1.1) numerizer (0.1.1)
oauth (0.4.7) oauth (0.4.7)
oauth2 (1.0.0) oauth2 (1.2.0)
faraday (>= 0.8, < 0.10) faraday (>= 0.8, < 0.10)
jwt (~> 1.0) jwt (~> 1.0)
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (~> 1.2) rack (>= 1.2, < 3)
octokit (4.3.0) octokit (4.3.0)
sawyer (~> 0.7.0, >= 0.5.3) sawyer (~> 0.7.0, >= 0.5.3)
omniauth (1.3.1) omniauth (1.3.1)
...@@ -439,7 +439,7 @@ GEM ...@@ -439,7 +439,7 @@ GEM
omniauth-gitlab (1.0.1) omniauth-gitlab (1.0.1)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
omniauth-google-oauth2 (0.2.10) omniauth-google-oauth2 (0.4.1)
addressable (~> 2.3) addressable (~> 2.3)
jwt (~> 1.0) jwt (~> 1.0)
multi_json (~> 1.3) multi_json (~> 1.3)
...@@ -806,7 +806,7 @@ DEPENDENCIES ...@@ -806,7 +806,7 @@ DEPENDENCIES
activerecord-session_store (~> 1.0.0) activerecord-session_store (~> 1.0.0)
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8) addressable (~> 2.3.8)
after_commit_queue after_commit_queue (~> 1.3.0)
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
asana (~> 0.4.0) asana (~> 0.4.0)
...@@ -815,15 +815,15 @@ DEPENDENCIES ...@@ -815,15 +815,15 @@ DEPENDENCIES
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
babosa (~> 1.0.2) babosa (~> 1.0.2)
base32 (~> 0.3.0) base32 (~> 0.3.0)
benchmark-ips benchmark-ips (~> 2.3.0)
better_errors (~> 1.0.1) better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0) brakeman (~> 3.3.0)
browser (~> 2.2) browser (~> 2.2)
bullet bullet (~> 5.0.0)
bundler-audit bundler-audit (~> 0.5.0)
byebug byebug (~> 8.2.1)
capybara (~> 2.6.2) capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0) carrierwave (~> 0.10.0)
...@@ -844,8 +844,8 @@ DEPENDENCIES ...@@ -844,8 +844,8 @@ DEPENDENCIES
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
factory_girl_rails (~> 4.6.0) factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay flay (~> 2.6.1)
flog flog (~> 4.3.2)
fog-aws (~> 0.9) fog-aws (~> 0.9)
fog-azure (~> 0.0) fog-azure (~> 0.0)
fog-core (~> 1.40) fog-core (~> 1.40)
...@@ -854,7 +854,7 @@ DEPENDENCIES ...@@ -854,7 +854,7 @@ DEPENDENCIES
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1) fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.6.1) font-awesome-rails (~> 4.6.1)
foreman foreman (~> 0.78.0)
fuubar (~> 2.0.0) fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
gemojione (~> 2.6) gemojione (~> 2.6)
...@@ -881,9 +881,9 @@ DEPENDENCIES ...@@ -881,9 +881,9 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0) jquery-ui-rails (~> 5.0.0)
jwt jwt
kaminari (~> 0.17.0) kaminari (~> 0.17.0)
knapsack knapsack (~> 1.11.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder license_finder (~> 2.1.0)
licensee (~> 8.0.0) licensee (~> 8.0.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.8) mail_room (~> 0.8)
...@@ -895,7 +895,7 @@ DEPENDENCIES ...@@ -895,7 +895,7 @@ DEPENDENCIES
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.14) newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.0.0) oauth2 (~> 1.2.0)
octokit (~> 4.3.0) octokit (~> 4.3.0)
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
...@@ -905,7 +905,7 @@ DEPENDENCIES ...@@ -905,7 +905,7 @@ DEPENDENCIES
omniauth-facebook (~> 3.0.0) omniauth-facebook (~> 3.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0) omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.6.0) omniauth-saml (~> 1.6.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
...@@ -916,19 +916,19 @@ DEPENDENCIES ...@@ -916,19 +916,19 @@ DEPENDENCIES
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0) premailer-rails (~> 1.9.0)
pry-rails pry-rails (~> 0.3.4)
rack-attack (~> 4.3.1) rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rails (= 4.2.6) rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0) rainbow (~> 2.1.0)
rblineprof rblineprof (~> 0.3.6)
rdoc (~> 3.6) rdoc (~> 3.6)
recaptcha (~> 3.0) recaptcha (~> 3.0)
redcarpet (~> 3.3.3) redcarpet (~> 3.3.3)
redis (~> 3.2) redis (~> 3.2)
redis-namespace redis-namespace (~> 1.5.2)
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.3.0) request_store (~> 1.3.0)
rerun (~> 0.11.0) rerun (~> 0.11.0)
...@@ -936,7 +936,7 @@ DEPENDENCIES ...@@ -936,7 +936,7 @@ DEPENDENCIES
rouge (~> 1.11) rouge (~> 1.11)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0) rspec-rails (~> 3.5.0)
rspec-retry rspec-retry (~> 0.4.5)
rubocop (~> 0.40.0) rubocop (~> 0.40.0)
rubocop-rspec (~> 1.5.0) rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
...@@ -948,7 +948,7 @@ DEPENDENCIES ...@@ -948,7 +948,7 @@ DEPENDENCIES
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
sentry-raven (~> 1.1.0) sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0) sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0) sidekiq-cron (~> 0.4.0)
......
...@@ -54,7 +54,6 @@ ...@@ -54,7 +54,6 @@
#= require_directory ./u2f #= require_directory ./u2f
#= require_directory . #= require_directory .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
#= require cropper
#= require u2f #= require u2f
window.slugify = (text) -> window.slugify = (text) ->
......
...@@ -127,11 +127,10 @@ class Dispatcher ...@@ -127,11 +127,10 @@ class Dispatcher
when 'groups' when 'groups'
new UsersSelect() new UsersSelect()
when 'projects' when 'projects'
new NamespaceSelect() new NamespaceSelects()
when 'dashboard', 'root' when 'dashboard', 'root'
shortcut_handler = new ShortcutsDashboardNavigation() shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles' when 'profiles'
new Profile()
new NotificationsForm() new NotificationsForm()
new NotificationsDropdown() new NotificationsDropdown()
when 'projects' when 'projects'
......
...@@ -56,6 +56,7 @@ class GitLabDropdownFilter ...@@ -56,6 +56,7 @@ class GitLabDropdownFilter
return BLUR_KEYCODES.indexOf(keyCode) >= 0 return BLUR_KEYCODES.indexOf(keyCode) >= 0
filter: (search_text) -> filter: (search_text) ->
@options.onFilter(search_text) if @options.onFilter
data = @options.data() data = @options.data()
if data? and not @options.filterByText if data? and not @options.filterByText
...@@ -195,6 +196,7 @@ class GitLabDropdown ...@@ -195,6 +196,7 @@ class GitLabDropdown
@filter = new GitLabDropdownFilter @filterInput, @filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur filterInputBlur: @filterInputBlur
filterByText: @options.filterByText filterByText: @options.filterByText
onFilter: @options.onFilter
remote: @options.filterRemote remote: @options.filterRemote
query: @options.data query: @options.data
keys: searchFields keys: searchFields
...@@ -530,7 +532,7 @@ class GitLabDropdown ...@@ -530,7 +532,7 @@ class GitLabDropdown
if $el.length if $el.length
e.preventDefault() e.preventDefault()
e.stopImmediatePropagation() e.stopImmediatePropagation()
$(selector, @dropdown)[0].click() $el.first().trigger('click')
addArrowKeyEvent: -> addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40] ARROW_KEY_CODES = [38, 40]
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
w.gl.utils.isInGroupsPage = -> w.gl.utils.isInGroupsPage = ->
return $('body').data('page').split(':')[0] is 'groups' return gl.utils.getPagePath() is 'groups'
w.gl.utils.isInProjectPage = -> w.gl.utils.isInProjectPage = ->
return $('body').data('page').split(':')[0] is 'projects' return gl.utils.getPagePath() is 'projects'
w.gl.utils.getProjectSlug = -> w.gl.utils.getProjectSlug = ->
...@@ -40,6 +40,9 @@ ...@@ -40,6 +40,9 @@
e.stopImmediatePropagation() e.stopImmediatePropagation()
return false return false
gl.utils.getPagePath = ->
return $('body').data('page').split(':')[0]
jQuery.timefor = (time, suffix, expiredLabel) -> jQuery.timefor = (time, suffix, expiredLabel) ->
......
class @NamespaceSelect class @NamespaceSelect
constructor: -> constructor: (opts) ->
namespaceFormatResult = (namespace) -> {
markup = "<div class='namespace-result'>" @dropdown
markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" } = opts
markup += "<span class='namespace-path'>" + namespace.path + "</span>"
markup += "</div>" showAny = true
markup fieldName = 'namespace_id'
formatSelection = (namespace) -> if @dropdown.attr 'data-field-name'
namespace.kind + ": " + namespace.path fieldName = @dropdown.data 'fieldName'
$('.ajax-namespace-select').each (i, select) -> if @dropdown.attr 'data-show-any'
$(select).select2 showAny = @dropdown.data 'showAny'
placeholder: "Search for namespace"
multiple: $(select).hasClass('multiselect') @dropdown.glDropdown(
minimumInputLength: 0 filterable: true
query: (query) -> selectable: true
Api.namespaces query.term, (namespaces) -> filterRemote: true
data = { results: namespaces } search:
query.callback(data) fields: ['path']
fieldName: fieldName
dropdownCssClass: "ajax-namespace-dropdown" toggleLabel: (selected) ->
formatResult: namespaceFormatResult return if not selected.id? then selected.text else "#{selected.kind}: #{selected.path}"
formatSelection: formatSelection data: (term, dataCallback) ->
Api.namespaces term, (namespaces) ->
if showAny
anyNamespace =
text: 'Any namespace'
id: null
namespaces.unshift(anyNamespace)
namespaces.splice 1, 0, 'divider'
dataCallback(namespaces)
text: (namespace) ->
return if not namespace.id? then namespace.text else "#{namespace.kind}: #{namespace.path}"
renderRow: @renderRow
clicked: @onSelectItem
)
onSelectItem: (item, el, e) =>
e.preventDefault()
class @NamespaceSelects
constructor: (opts = {}) ->
{
@$dropdowns = $('.js-namespace-select')
} = opts
@$dropdowns.each (i, dropdown) ->
$dropdown = $(dropdown)
new NamespaceSelect(
dropdown: $dropdown
)
...@@ -240,12 +240,16 @@ class @Notes ...@@ -240,12 +240,16 @@ class @Notes
@note_ids.push(note.id) @note_ids.push(note.id)
form = $("#new-discussion-note-form-#{note.discussion_id}") form = $("#new-discussion-note-form-#{note.discussion_id}")
if note.original_discussion_id? and form.length is 0
form = $("#new-discussion-note-form-#{note.original_discussion_id}")
row = form.closest("tr") row = form.closest("tr")
note_html = $(note.html) note_html = $(note.html)
note_html.syntaxHighlight() note_html.syntaxHighlight()
# is this the first note of discussion? # is this the first note of discussion?
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']") discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
if note.original_discussion_id? and discussionContainer.length is 0
discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']")
if discussionContainer.length is 0 if discussionContainer.length is 0
# insert the note and the reply button after the temp row # insert the note and the reply button after the temp row
row.after note.discussion_html row.after note.discussion_html
...@@ -318,6 +322,7 @@ class @Notes ...@@ -318,6 +322,7 @@ class @Notes
form.addClass "js-main-target-form" form.addClass "js-main-target-form"
form.find("#note_line_code").remove() form.find("#note_line_code").remove()
form.find("#note_position").remove()
form.find("#note_type").remove() form.find("#note_type").remove()
### ###
...@@ -335,10 +340,12 @@ class @Notes ...@@ -335,10 +340,12 @@ class @Notes
new Autosave textarea, [ new Autosave textarea, [
"Note" "Note"
form.find("#note_commit_id").val()
form.find("#note_line_code").val()
form.find("#note_noteable_type").val() form.find("#note_noteable_type").val()
form.find("#note_noteable_id").val() form.find("#note_noteable_id").val()
form.find("#note_commit_id").val()
form.find("#note_type").val()
form.find("#note_line_code").val()
form.find("#note_position").val()
] ]
### ###
...@@ -512,10 +519,12 @@ class @Notes ...@@ -512,10 +519,12 @@ class @Notes
setupDiscussionNoteForm: (dataHolder, form) => setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target # setup note target
form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}" form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
form.attr "data-line-code", dataHolder.data("lineCode")
form.find("#note_type").val dataHolder.data("noteType") form.find("#note_type").val dataHolder.data("noteType")
form.find("#line_type").val dataHolder.data("lineType") form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId") form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode") form.find("#note_line_code").val dataHolder.data("lineCode")
form.find("#note_position").val dataHolder.attr("data-position")
form.find("#note_noteable_type").val dataHolder.data("noteableType") form.find("#note_noteable_type").val dataHolder.data("noteableType")
form.find("#note_noteable_id").val dataHolder.data("noteableId") form.find("#note_noteable_id").val dataHolder.data("noteableId")
form.find('.js-note-discard') form.find('.js-note-discard')
......
...@@ -78,3 +78,6 @@ $ -> ...@@ -78,3 +78,6 @@ $ ->
if comment && comment.length > 1 && $title.val() == '' if comment && comment.length > 1 && $title.val() == ''
$title.val(comment[1]).change() $title.val(comment[1]).change()
if gl.utils.getPagePath() == 'profiles'
new Profile()
class @ProtectedBranchSelect
constructor: (currentProject) ->
$('.dropdown-footer').hide();
@dropdown = $('.js-protected-branch-select').glDropdown(
data: @getProtectedBranches
filterable: true
remote: false
search:
fields: ['title']
selectable: true
toggleLabel: (selected) -> if (selected and 'id' of selected) then selected.title else 'Protected Branch'
fieldName: 'protected_branch[name]'
text: (protected_branch) -> _.escape(protected_branch.title)
id: (protected_branch) -> _.escape(protected_branch.id)
onFilter: @toggleCreateNewButton
clicked: () -> $('.protect-branch-btn').attr('disabled', false)
)
$('.create-new-protected-branch').on 'click', (event) =>
# Refresh the dropdown's data, which ends up calling `getProtectedBranches`
@dropdown.data('glDropdown').remote.execute()
@dropdown.data('glDropdown').selectRowAtIndex(event, 0)
getProtectedBranches: (term, callback) =>
if @selectedBranch
callback(gon.open_branches.concat(@selectedBranch))
else
callback(gon.open_branches)
toggleCreateNewButton: (branchName) =>
@selectedBranch = { title: branchName, id: branchName, text: branchName }
if branchName is ''
$('.protected-branch-select-footer-list').addClass('hidden')
$('.dropdown-footer').hide();
else
$('.create-new-protected-branch').text("Create Protected Branch: #{branchName}")
$('.protected-branch-select-footer-list').removeClass('hidden')
$('.dropdown-footer').show();
...@@ -11,7 +11,8 @@ $ -> ...@@ -11,7 +11,8 @@ $ ->
dataType: "json" dataType: "json"
data: data:
id: id id: id
developers_can_push: checked protected_branch:
developers_can_push: checked
success: -> success: ->
row = $(e.target) row = $(e.target)
......
.blank-state-welcome {
text-align: center;
border-bottom: 1px solid $border-color;
.blank-state-text {
margin-bottom: 0;
}
}
.blank-state { .blank-state {
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
...@@ -6,7 +15,15 @@ ...@@ -6,7 +15,15 @@
.blank-state-no-icon { .blank-state-no-icon {
padding-top: 40px; padding-top: 40px;
padding-bottom: 40px; padding-bottom: 40px;
}
.blank-state-icon {
padding-bottom: 20px;
path {
fill: $gray-darkest;
}
} }
.blank-state-title { .blank-state-title {
...@@ -21,3 +38,7 @@ ...@@ -21,3 +38,7 @@
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
font-size: 15px; font-size: 15px;
} }
.blank-state-welcome-title {
font-size: 24px;
}
...@@ -68,6 +68,10 @@ ...@@ -68,6 +68,10 @@
color: $dropdown-toggle-hover-icon-color; color: $dropdown-toggle-hover-icon-color;
} }
} }
&.large {
width: 200px;
}
} }
.dropdown-menu, .dropdown-menu,
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
} }
a:not(.btn) { a:not(.btn) {
color: $gl-dark-link-color; color: $gl-text-color;
} }
.left-options { .left-options {
......
...@@ -175,6 +175,12 @@ ul.content-list { ...@@ -175,6 +175,12 @@ ul.content-list {
.panel > .content-list > li { .panel > .content-list > li {
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
&.commit {
@media (min-width: $screen-sm-min) {
padding-left: 46px + $gl-padding;
}
}
} }
ul.controls { ul.controls {
......
...@@ -77,10 +77,10 @@ ...@@ -77,10 +77,10 @@
&.sub-nav { &.sub-nav {
text-align: center; text-align: center;
background-color: $background-color; background-color: $dark-background-color;
.container-fluid { .container-fluid {
background-color: $background-color; background-color: $dark-background-color;
margin-bottom: 0; margin-bottom: 0;
} }
...@@ -134,6 +134,11 @@ ...@@ -134,6 +134,11 @@
margin-bottom: 0; margin-bottom: 0;
border-bottom: none; border-bottom: none;
&.wide {
width: 100%;
display: block;
}
li a { li a {
padding: 16px 10px 11px; padding: 16px 10px 11px;
} }
...@@ -164,6 +169,7 @@ ...@@ -164,6 +169,7 @@
> .btn { > .btn {
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
display: inline-block; display: inline-block;
vertical-align: top;
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
......
...@@ -12,10 +12,11 @@ $sidebar-breakpoint: 1024px; ...@@ -12,10 +12,11 @@ $sidebar-breakpoint: 1024px;
/* /*
* UI elements * UI elements
*/ */
$border-color: #e5e5e5; $border-color: #e5e5e5;
$focus-border-color: #3aabf0; $focus-border-color: #3aabf0;
$table-border-color: #f0f0f0; $table-border-color: #f0f0f0;
$background-color: #fafafa; $background-color: #fafafa;
$dark-background-color: #f7f7f7;
/* /*
* Text * Text
...@@ -153,9 +154,6 @@ $warning-message-bg: #fbf2d9; ...@@ -153,9 +154,6 @@ $warning-message-bg: #fbf2d9;
$warning-message-color: #9e8e60; $warning-message-color: #9e8e60;
$warning-message-border: #f0e2bb; $warning-message-border: #f0e2bb;
/* header */
$light-grey-header: #faf9f9;
/* tanuki logo colors */ /* tanuki logo colors */
$tanuki-red: #e24329; $tanuki-red: #e24329;
$tanuki-orange: #fc6d26; $tanuki-orange: #fc6d26;
......
...@@ -71,3 +71,36 @@ ...@@ -71,3 +71,36 @@
@extend .broadcast-message; @extend .broadcast-message;
margin-bottom: 20px; margin-bottom: 20px;
} }
// Users List
.users-list {
.user-row {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.user-details {
flex: 1 1 auto;
}
.user-name {
display: inline-block;
font-weight: bold;
}
.controls {
> .btn, > .dropdown {
margin-left: 5px;
}
}
.dropdown {
.btn-block {
margin-bottom: 0;
line-height: inherit;
}
}
}
...@@ -434,13 +434,3 @@ ...@@ -434,13 +434,3 @@
} }
} }
} }
.discussion {
.diff-content {
.diff-line-num {
&:before {
content: attr(data-linenumber);
}
}
}
}
...@@ -38,6 +38,39 @@ ...@@ -38,6 +38,39 @@
margin-right: 15px; margin-right: 15px;
} }
} }
&.group-admin {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
.group-avatar, .group-details, .group-controls {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.group-details {
flex: 1 1 auto;
flex-direction: column;
min-width: 0;
}
.group-controls {
align-items: center;
a {
margin-left: 5px;
}
}
}
}
.ldap-group-links {
.form-actions {
margin-bottom: $gl-padding;
}
} }
.groups-cover-block { .groups-cover-block {
......
...@@ -475,10 +475,6 @@ pre.light-well { ...@@ -475,10 +475,6 @@ pre.light-well {
a:hover { a:hover {
text-decoration: none; text-decoration: none;
} }
> span {
margin-left: 10px;
}
} }
} }
......
...@@ -208,7 +208,7 @@ ...@@ -208,7 +208,7 @@
margin-top: 5px; margin-top: 5px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
width: 160px; width: 180px;
margin-top: 0; margin-top: 0;
} }
} }
......
...@@ -87,6 +87,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -87,6 +87,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:version_check_enabled, :version_check_enabled,
:admin_notification_email, :admin_notification_email,
:user_oauth_applications, :user_oauth_applications,
:user_default_external,
:shared_runners_enabled, :shared_runners_enabled,
:shared_runners_text, :shared_runners_text,
:max_artifacts_size, :max_artifacts_size,
......
...@@ -5,11 +5,12 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -5,11 +5,12 @@ class Admin::ProjectsController < Admin::ApplicationController
def index def index
@projects = Project.all @projects = Project.all
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.with_push if params[:with_push].present? @projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present? @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present?
@projects = @projects.non_archived unless params[:with_archived].present? @projects = @projects.non_archived unless params[:archived].present?
@projects = @projects.personal(current_user) if params[:personal].present?
@projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]) @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
......
...@@ -27,10 +27,7 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -27,10 +27,7 @@ class Import::GitlabProjectsController < Import::BaseController
notice: "Project '#{@project.name}' is being imported." notice: "Project '#{@project.name}' is being imported."
) )
else else
redirect_to( redirect_back_or_default(options: { alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" })
new_import_gitlab_project_path,
alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}"
)
end end
end end
......
...@@ -57,7 +57,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -57,7 +57,7 @@ class Projects::BlobController < Projects::ApplicationController
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1] diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines) diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
@diff_lines = Gitlab::Diff::Highlight.new(diff_lines).highlight @diff_lines = Gitlab::Diff::Highlight.new(diff_lines, repository: @repository).highlight
render layout: false render layout: false
end end
......
...@@ -121,7 +121,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -121,7 +121,6 @@ class Projects::CommitController < Projects::ApplicationController
opts[:ignore_whitespace_change] = true if params[:format] == 'diff' opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
@diffs = commit.diffs(opts) @diffs = commit.diffs(opts)
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count @notes_count = commit.notes.count
@statuses = CommitStatus.where(pipeline: pipelines) @statuses = CommitStatus.where(pipeline: pipelines)
......
...@@ -14,14 +14,22 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -14,14 +14,22 @@ class Projects::CompareController < Projects::ApplicationController
def show def show
compare = CompareService.new. compare = CompareService.new.
execute(@project, @head_ref, @project, @base_ref, diff_options) execute(@project, @head_ref, @project, @start_ref, diff_options)
if compare if compare
@commits = Commit.decorate(compare.commits, @project) @commits = Commit.decorate(compare.commits, @project)
@start_commit = @project.commit(@start_ref)
@commit = @project.commit(@head_ref) @commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@base_ref, @head_ref) @base_commit = @project.merge_base_commit(@start_ref, @head_ref)
@diffs = compare.diffs(diff_options) @diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit] @diff_refs = Gitlab::Diff::DiffRefs.new(
base_sha: @base_commit.try(:sha),
start_sha: @start_commit.try(:sha),
head_sha: @commit.try(:sha)
)
@diff_notes_disabled = true @diff_notes_disabled = true
@grouped_diff_notes = {} @grouped_diff_notes = {}
end end
...@@ -35,12 +43,12 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -35,12 +43,12 @@ class Projects::CompareController < Projects::ApplicationController
private private
def assign_ref_vars def assign_ref_vars
@base_ref = Addressable::URI.unescape(params[:from]) @start_ref = Addressable::URI.unescape(params[:from])
@ref = @head_ref = Addressable::URI.unescape(params[:to]) @ref = @head_ref = Addressable::URI.unescape(params[:to])
end end
def merge_request def merge_request
@merge_request ||= @project.merge_requests.opened. @merge_request ||= @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref) find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
end end
end end
...@@ -9,7 +9,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -9,7 +9,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
:ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip
] ]
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
before_action :define_show_vars, only: [:show, :diffs, :commits, :builds] before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check] before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
...@@ -53,19 +52,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -53,19 +52,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def show def show
@note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
group(:commit_id).count
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: @merge_request }
format.json do
render json: @merge_request
end
format.patch do format.patch do
headers.store(*Gitlab::Workhorse.send_git_patch(@project.repository, return render_404 unless @merge_request.diff_refs
@merge_request.diff_base_commit.id,
@merge_request.last_commit.id)) send_git_patch @project.repository, @merge_request.diff_refs
headers['Content-Disposition'] = 'inline'
head :ok
end end
format.diff do format.diff do
return render_404 unless @merge_request.diff_refs return render_404 unless @merge_request.diff_refs
...@@ -77,18 +76,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -77,18 +76,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs def diffs
apply_diff_view_cookie! apply_diff_view_cookie!
@commit = @merge_request.last_commit @merge_request_diff = @merge_request.merge_request_diff
@base_commit = @merge_request.diff_base_commit
# MRs created before 8.4 don't have a diff_base_commit, @commit = @merge_request.diff_head_commit
# but we need it for the "View file @ ..." link by deleted files @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
@base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
@comments_target = { @comments_target = {
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
noteable_id: @merge_request.id noteable_id: @merge_request.id
} }
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render( Banzai::NoteRenderer.render(
...@@ -109,7 +107,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -109,7 +107,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def commits def commits
respond_to do |format| respond_to do |format|
format.html { render 'show' } format.html { render 'show' }
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } } format.json do
# Get commits from repository
# or from cache if already merged
@commits = @merge_request.commits
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
render json: { html: view_to_html_string('projects/merge_requests/show/_commits') }
end
end end
end end
...@@ -134,7 +140,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -134,7 +140,7 @@ 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.reverse @commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_commit @commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit @base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true @diff_notes_disabled = true
...@@ -212,7 +218,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -212,7 +218,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return return
end end
if params[:sha] != @merge_request.source_sha if params[:sha] != @merge_request.diff_head_sha
@status = :sha_mismatch @status = :sha_mismatch
return return
end end
...@@ -274,16 +280,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -274,16 +280,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
status ||= "preparing" status ||= "preparing"
else else
ci_service = @merge_request.source_project.ci_service ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage) if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch) coverage = ci_service.commit_coverage(merge_request.diff_head_sha, merge_request.source_branch)
end end
end end
response = { response = {
title: merge_request.title, title: merge_request.title,
sha: merge_request.last_commit_short_sha, sha: merge_request.diff_head_commit.short_id,
status: status, status: status,
coverage: coverage coverage: coverage
} }
...@@ -308,10 +314,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -308,10 +314,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
alias_method :issuable, :merge_request alias_method :issuable, :merge_request
alias_method :awardable, :merge_request alias_method :awardable, :merge_request
def closes_issues
@closes_issues ||= @merge_request.closes_issues
end
def authorize_update_merge_request! def authorize_update_merge_request!
return render_404 unless can?(current_user, :update_merge_request, @merge_request) return render_404 unless can?(current_user, :update_merge_request, @merge_request)
end end
...@@ -340,14 +342,33 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -340,14 +342,33 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def define_show_vars def define_show_vars
@noteable = @merge_request
@commits_count = @merge_request.commits.count
@pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses if @pipeline
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
if request.format == :html || action_name == 'show'
define_show_html_vars
end
end
# Discussion tab data is only required on html requests
def define_show_html_vars
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @note = @project.notes.new(noteable: @noteable)
@discussions = @merge_request.mr_and_commit_notes. @discussions = @noteable.mr_and_commit_notes.
inc_author_project_award_emoji. inc_author_project_award_emoji.
fresh. fresh.
discussions discussions
# This is not executed lazily
@notes = Banzai::NoteRenderer.render( @notes = Banzai::NoteRenderer.render(
@discussions.flatten, @discussions.flatten,
@project, @project,
...@@ -356,28 +377,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -356,28 +377,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@project_wiki, @project_wiki,
@ref @ref
) )
@noteable = @merge_request
# Get commits from repository
# or from cache if already merged
@commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff
@pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses if @pipeline
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
end end
def define_widget_vars def define_widget_vars
@pipeline = @merge_request.pipeline @pipeline = @merge_request.pipeline
@pipelines = [@pipeline].compact @pipelines = [@pipeline].compact
closes_issues
end end
def invalid_mr def invalid_mr
......
...@@ -128,7 +128,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -128,7 +128,7 @@ class Projects::NotesController < Projects::ApplicationController
elsif note.valid? elsif note.valid?
Banzai::NoteRenderer.render([note], @project, current_user) Banzai::NoteRenderer.render([note], @project, current_user)
{ attrs = {
valid: true, valid: true,
id: note.id, id: note.id,
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
...@@ -138,6 +138,23 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -138,6 +138,23 @@ class Projects::NotesController < Projects::ApplicationController
discussion_html: note_to_discussion_html(note), discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note) discussion_with_diff_html: note_to_discussion_with_diff_html(note)
} }
# The discussion_id is used to add the comment to the correct discussion
# element on the merge request page. Among other things, the discussion_id
# contains the sha of head commit of the merge request.
# When new commits are pushed into the merge request after the initial
# load of the merge request page, the discussion elements will still have
# the old discussion_ids, with the old head commit sha. The new comment,
# however, will have the new discussion_id with the new commit sha.
# To ensure that these new comments will still end up in the correct
# discussion element, we also send the original discussion_id, with the
# old commit sha, along, and fall back on this value when no discussion
# element with the new discussion_id could be found.
if note.new_diff_note? && note.position != note.original_position
attrs[:original_discussion_id] = note.original_discussion_id
end
attrs
else else
{ {
valid: false, valid: false,
...@@ -154,7 +171,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -154,7 +171,7 @@ class Projects::NotesController < Projects::ApplicationController
def note_params def note_params
params.require(:note).permit( params.require(:note).permit(
:note, :noteable, :noteable_id, :noteable_type, :project_id, :note, :noteable, :noteable_id, :noteable_type, :project_id,
:attachment, :line_code, :commit_id, :type :attachment, :line_code, :commit_id, :type, :position
) )
end end
......
...@@ -2,12 +2,14 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -2,12 +2,14 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :load_protected_branch, only: [:show, :update, :destroy]
layout "project_settings" layout "project_settings"
def index def index
@branches = @project.protected_branches.to_a @protected_branches = @project.protected_branches.order(:name).page(params[:page])
@protected_branch = @project.protected_branches.new @protected_branch = @project.protected_branches.new
gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } })
end end
def create def create
...@@ -16,26 +18,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -16,26 +18,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
@project) @project)
end end
def update def show
protected_branch = @project.protected_branches.find(params[:id]) @matching_branches = @protected_branch.matching(@project.repository.branches)
end
if protected_branch &&
protected_branch.update_attributes(
developers_can_push: params[:developers_can_push]
)
def update
if @protected_branch && @protected_branch.update_attributes(protected_branch_params)
respond_to do |format| respond_to do |format|
format.json { render json: protected_branch, status: :ok } format.json { render json: @protected_branch, status: :ok }
end end
else else
respond_to do |format| respond_to do |format|
format.json { render json: protected_branch.errors, status: :unprocessable_entity } format.json { render json: @protected_branch.errors, status: :unprocessable_entity }
end end
end end
end end
def destroy def destroy
@project.protected_branches.find(params[:id]).destroy @protected_branch.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to namespace_project_protected_branches_path } format.html { redirect_to namespace_project_protected_branches_path }
...@@ -45,6 +45,10 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -45,6 +45,10 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
private private
def load_protected_branch
@protected_branch = @project.protected_branches.find(params[:id])
end
def protected_branch_params def protected_branch_params
params.require(:protected_branch).permit(:name, :developers_can_push) params.require(:protected_branch).permit(:name, :developers_can_push)
end end
......
...@@ -53,11 +53,11 @@ class ProjectsController < Projects::ApplicationController ...@@ -53,11 +53,11 @@ class ProjectsController < Projects::ApplicationController
notice: "Project '#{@project.name}' was successfully updated." notice: "Project '#{@project.name}' was successfully updated."
) )
end end
format.js
else else
format.html { render 'edit' } format.html { render 'edit' }
format.js
end end
format.js
end end
end end
......
...@@ -31,7 +31,7 @@ module AppearancesHelper ...@@ -31,7 +31,7 @@ module AppearancesHelper
end end
end end
def navbar_icon(icon_name) def navbar_icon(icon_name, size: 16)
render "shared/icons/#{icon_name}.svg" render "shared/icons/#{icon_name}.svg", size: size
end end
end end
...@@ -306,4 +306,15 @@ module ApplicationHelper ...@@ -306,4 +306,15 @@ module ApplicationHelper
def truncate_first_line(message, length = 50) def truncate_first_line(message, length = 50)
truncate(message.each_line.first.chomp, length: length) if message truncate(message.each_line.first.chomp, length: length) if message
end end
# While similarly named to Rails's `link_to_if`, this method behaves quite differently.
# If `condition` is truthy, a link will be returned with the result of the block
# as its body. If `condition` is falsy, only the result of the block will be returned.
def conditional_link_to(condition, options, html_options = {}, &block)
if condition
link_to options, html_options, &block
else
capture(&block)
end
end
end end
...@@ -30,12 +30,8 @@ module DiffHelper ...@@ -30,12 +30,8 @@ module DiffHelper
options options
end end
def safe_diff_files(diffs, diff_refs) def safe_diff_files(diffs, diff_refs: nil, repository: nil)
diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) } diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
end
def generate_line_code(file_path, line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end end
def unfold_bottom_class(bottom) def unfold_bottom_class(bottom)
...@@ -93,6 +89,8 @@ module DiffHelper ...@@ -93,6 +89,8 @@ module DiffHelper
end end
def commit_for_diff(diff_file) def commit_for_diff(diff_file)
return diff_file.content_commit if diff_file.content_commit
if diff_file.deleted_file if diff_file.deleted_file
@base_commit || @commit.parent || @commit @base_commit || @commit.parent || @commit
else else
...@@ -100,10 +98,11 @@ module DiffHelper ...@@ -100,10 +98,11 @@ module DiffHelper
end end
end end
def diff_file_html_data(project, diff_commit, diff_file) def diff_file_html_data(project, diff_file)
commit = commit_for_diff(diff_file)
{ {
blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
tree_join(diff_commit.id, diff_file.file_path)) tree_join(commit.id, diff_file.file_path))
} }
end end
......
...@@ -39,7 +39,7 @@ module DropdownsHelper ...@@ -39,7 +39,7 @@ module DropdownsHelper
end end
end end
def dropdown_toggle(toggle_text, data_attr, options) def dropdown_toggle(toggle_text, data_attr, options = {})
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text") output = content_tag(:span, toggle_text, class: "dropdown-toggle-text")
output << icon('chevron-down') output << icon('chevron-down')
......
...@@ -27,7 +27,7 @@ module MergeRequestsHelper ...@@ -27,7 +27,7 @@ module MergeRequestsHelper
end end
def ci_build_details_path(merge_request) def ci_build_details_path(merge_request)
build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) build_url = merge_request.source_project.ci_service.build_page(merge_request.diff_head_sha, merge_request.source_branch)
return nil unless build_url return nil unless build_url
parsed_url = URI.parse(build_url) parsed_url = URI.parse(build_url)
...@@ -55,6 +55,10 @@ module MergeRequestsHelper ...@@ -55,6 +55,10 @@ module MergeRequestsHelper
end.sort.to_sentence end.sort.to_sentence
end end
def mr_closes_issues
@mr_closes_issues ||= @merge_request.closes_issues
end
def mr_change_branches_path(merge_request) def mr_change_branches_path(merge_request)
new_namespace_project_merge_request_path( new_namespace_project_merge_request_path(
@project.namespace, @project, @project.namespace, @project,
......
...@@ -24,23 +24,55 @@ module NotesHelper ...@@ -24,23 +24,55 @@ module NotesHelper
}.to_json }.to_json
end end
def link_to_new_diff_note(line_code, line_type = nil) def link_to_new_diff_note(line_code, position, line_type = nil)
discussion_id = LegacyDiffNote.build_discussion_id( use_legacy_diff_note = @use_legacy_diff_notes
@comments_target[:noteable_type], # If the controller doesn't force the use of legacy diff notes, we
@comments_target[:noteable_id] || @comments_target[:commit_id], # determine this on a line-by-line basis by seeing if there already exist
line_code # active legacy diff notes at this line, in which case newly created notes
) # will use the legacy technology as well.
# We do this because the discussion_id values of legacy and "new" diff
# notes, which are used to group notes on the merge request discussion tab,
# are incompatible.
# If we didn't, diff notes that would show for the same line on the changes
# tab, would show in different discussions on the discussion tab.
use_legacy_diff_note ||= begin
line_diff_notes = @grouped_diff_notes[line_code]
line_diff_notes && line_diff_notes.any?(&:legacy_diff_note?)
end
data = { data = {
noteable_type: @comments_target[:noteable_type], noteable_type: @comments_target[:noteable_type],
noteable_id: @comments_target[:noteable_id], noteable_id: @comments_target[:noteable_id],
commit_id: @comments_target[:commit_id], commit_id: @comments_target[:commit_id],
line_type: line_type, line_type: line_type,
line_code: line_code, line_code: line_code
note_type: LegacyDiffNote.name,
discussion_id: discussion_id
} }
if use_legacy_diff_note
discussion_id = LegacyDiffNote.build_discussion_id(
@comments_target[:noteable_type],
@comments_target[:noteable_id] || @comments_target[:commit_id],
line_code
)
data.merge!(
note_type: LegacyDiffNote.name,
discussion_id: discussion_id
)
else
discussion_id = DiffNote.build_discussion_id(
@comments_target[:noteable_type],
@comments_target[:noteable_id] || @comments_target[:commit_id],
position
)
data.merge!(
position: position.to_json,
note_type: DiffNote.name,
discussion_id: discussion_id
)
end
button_tag(class: 'btn add-diff-note js-add-diff-note-button', button_tag(class: 'btn add-diff-note js-add-diff-note-button',
data: data, data: data,
title: 'Add a comment to this line') do title: 'Add a comment to this line') do
...@@ -60,14 +92,15 @@ module NotesHelper ...@@ -60,14 +92,15 @@ module NotesHelper
} }
if note.diff_note? if note.diff_note?
data.merge!( data[:note_type] = note.type
line_code: note.line_code,
note_type: LegacyDiffNote.name data.merge!(note.diff_attributes)
)
end end
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', content_tag(:div, class: "discussion-reply-holder") do
data: data, title: 'Add a reply' button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply'
end
end end
def note_max_access_for_user(note) def note_max_access_for_user(note)
...@@ -79,4 +112,14 @@ module NotesHelper ...@@ -79,4 +112,14 @@ module NotesHelper
full_key = { project: note.project, user_id: note.author_id } full_key = { project: note.project, user_id: note.author_id }
@max_access_by_user_id[full_key] @max_access_by_user_id[full_key]
end end
def diff_note_path(note)
return unless note.diff_note?
if note.for_merge_request? && note.active?
diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
elsif note.for_commit?
namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
end
end
end end
...@@ -23,4 +23,11 @@ module TimeHelper ...@@ -23,4 +23,11 @@ module TimeHelper
def date_from_to(from, to) def date_from_to(from, to)
"#{from.to_s(:short)} - #{to.to_s(:short)}" "#{from.to_s(:short)} - #{to.to_s(:short)}"
end end
def duration_in_numbers(finished_at, started_at)
diff_in_seconds = finished_at.to_i - started_at.to_i
time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S"
Time.at(diff_in_seconds).utc.strftime(time_format)
end
end end
# Helpers to send Git blobs, diffs or archives through Workhorse. # Helpers to send Git blobs, diffs, patches or archives through Workhorse.
# Workhorse will also serve files when using `send_file`. # Workhorse will also serve files when using `send_file`.
module WorkhorseHelper module WorkhorseHelper
# Send a Git blob through Workhorse # Send a Git blob through Workhorse
...@@ -16,6 +16,13 @@ module WorkhorseHelper ...@@ -16,6 +16,13 @@ module WorkhorseHelper
head :ok head :ok
end end
# Send a Git patch through Workhorse
def send_git_patch(repository, diff_refs)
headers.store(*Gitlab::Workhorse.send_git_patch(repository, diff_refs))
headers['Content-Disposition'] = 'inline'
head :ok
end
# Archive a Git repository and send it through Workhorse # Archive a Git repository and send it through Workhorse
def send_git_archive(repository, ref:, format:) def send_git_archive(repository, ref:, format:)
headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format)) headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
......
...@@ -29,8 +29,7 @@ module Emails ...@@ -29,8 +29,7 @@ module Emails
# used in notify layout # used in notify layout
@target_url = @message.target_url @target_url = @message.target_url
@project = Project.find(project_id) @project = Project.find(project_id)
@diff_notes_disabled = true
add_project_headers add_project_headers
headers['X-GitLab-Author'] = @message.author_username headers['X-GitLab-Author'] = @message.author_username
......
...@@ -142,6 +142,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -142,6 +142,7 @@ class ApplicationSetting < ActiveRecord::Base
send_user_confirmation_email: false, send_user_confirmation_email: false,
container_registry_token_expire_delay: 5, container_registry_token_expire_delay: 5,
repository_storage: 'default', repository_storage: 'default',
user_default_external: false,
) )
end end
......
...@@ -13,6 +13,7 @@ module Ci ...@@ -13,6 +13,7 @@ module Ci
scope :ignore_failures, ->() { where(allow_failure: false) } scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts, ->() { where.not(artifacts_file: nil) } scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader
...@@ -25,10 +26,6 @@ module Ci ...@@ -25,10 +26,6 @@ module Ci
after_create :execute_hooks after_create :execute_hooks
class << self class << self
def last_month
where('created_at > ?', Date.today - 1.month)
end
def first_pending def first_pending
pending.unstarted.order('created_at ASC').first pending.unstarted.order('created_at ASC').first
end end
......
...@@ -214,6 +214,13 @@ class Commit ...@@ -214,6 +214,13 @@ class Commit
@raw.short_id(7) @raw.short_id(7)
end end
def diff_refs
Gitlab::Diff::DiffRefs.new(
base_sha: self.parent_id || self.sha,
head_sha: self.sha
)
end
def pipelines def pipelines
@pipeline ||= project.pipelines.where(sha: sha) @pipeline ||= project.pipelines.where(sha: sha)
end end
......
...@@ -23,7 +23,7 @@ class CommitRange ...@@ -23,7 +23,7 @@ class CommitRange
attr_reader :commit_from, :notation, :commit_to attr_reader :commit_from, :notation, :commit_to
attr_reader :ref_from, :ref_to attr_reader :ref_from, :ref_to
# Optional Project model # The Project model
attr_accessor :project attr_accessor :project
# The beginning and ending refs can be named or SHAs, and # The beginning and ending refs can be named or SHAs, and
...@@ -56,7 +56,7 @@ class CommitRange ...@@ -56,7 +56,7 @@ class CommitRange
# Initialize a CommitRange # Initialize a CommitRange
# #
# range_string - The String commit range. # range_string - The String commit range.
# project - An optional Project model. # project - The Project model.
# #
# Raises ArgumentError if `range_string` does not match `PATTERN`. # Raises ArgumentError if `range_string` does not match `PATTERN`.
def initialize(range_string, project) def initialize(range_string, project)
......
module NoteOnDiff
extend ActiveSupport::Concern
NUMBER_OF_TRUNCATED_DIFF_LINES = 16
included do
delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
end
def diff_note?
true
end
def diff_file
raise NotImplementedError
end
def diff_line
raise NotImplementedError
end
def for_line?(line)
raise NotImplementedError
end
def diff_attributes
raise NotImplementedError
end
def can_be_award_emoji?
false
end
# Returns an array of at most 16 highlighted lines above a diff note
def truncated_diff_lines
prev_lines = []
highlighted_diff_lines.each do |line|
if line.meta?
prev_lines.clear
else
prev_lines << line
break if for_line?(line)
prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
end
end
prev_lines
end
end
...@@ -11,6 +11,8 @@ class Deployment < ActiveRecord::Base ...@@ -11,6 +11,8 @@ class Deployment < ActiveRecord::Base
delegate :name, to: :environment, prefix: true delegate :name, to: :environment, prefix: true
after_save :keep_around_commit
def commit def commit
project.commit(sha) project.commit(sha)
end end
...@@ -26,4 +28,8 @@ class Deployment < ActiveRecord::Base ...@@ -26,4 +28,8 @@ class Deployment < ActiveRecord::Base
def last? def last?
self == environment.last_deployment self == environment.last_deployment
end end
def keep_around_commit
project.repository.keep_around(self.sha)
end
end end
class DiffNote < Note
include NoteOnDiff
serialize :original_position, Gitlab::Diff::Position
serialize :position, Gitlab::Diff::Position
validates :original_position, presence: true
validates :position, presence: true
validates :diff_line, presence: true
validates :line_code, presence: true, line_code: true
validates :noteable_type, inclusion: { in: ['Commit', 'MergeRequest'] }
validate :positions_complete
validate :verify_supported
before_validation :set_original_position, :update_position, on: :create
before_validation :set_line_code
after_save :keep_around_commits
class << self
def build_discussion_id(noteable_type, noteable_id, position)
[super(noteable_type, noteable_id), *position.key].join("-")
end
end
def new_diff_note?
true
end
def diff_attributes
{ position: position.to_json }
end
def discussion_id
@discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, position)
end
def original_discussion_id
@original_discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, original_position)
end
def position=(new_position)
if new_position.is_a?(String)
new_position = JSON.parse(new_position) rescue nil
end
if new_position.is_a?(Hash)
new_position = new_position.with_indifferent_access
new_position = Gitlab::Diff::Position.new(new_position)
end
super(new_position)
end
def diff_file
@diff_file ||= self.original_position.diff_file(self.project.repository)
end
def diff_line
@diff_line ||= diff_file.line_for_position(self.original_position) if diff_file
end
def for_line?(line)
diff_file.position(line) == self.original_position
end
def active?(diff_refs = nil)
return false unless supported?
return true if for_commit?
diff_refs ||= self.noteable.diff_refs
self.position.diff_refs == diff_refs
end
private
def supported?
!self.for_merge_request? || self.noteable.support_new_diff_notes?
end
def set_original_position
self.original_position = self.position.dup
end
def set_line_code
self.line_code = self.position.line_code(self.project.repository)
end
def update_position
return unless supported?
return if for_commit?
return if active?
Notes::DiffPositionUpdateService.new(
self.project,
nil,
old_diff_refs: self.position.diff_refs,
new_diff_refs: self.noteable.diff_refs,
paths: self.position.paths
).execute(self)
end
def verify_supported
return if supported?
errors.add(:noteable, "doesn't support new-style diff notes")
end
def positions_complete
return if self.original_position.complete? && self.position.complete?
errors.add(:position, "is invalid")
end
def keep_around_commits
project.repository.keep_around(self.original_position.base_sha)
project.repository.keep_around(self.original_position.start_sha)
project.repository.keep_around(self.original_position.head_sha)
if self.position != self.original_position
project.repository.keep_around(self.position.base_sha)
project.repository.keep_around(self.position.start_sha)
project.repository.keep_around(self.position.head_sha)
end
end
end
class LegacyDiffNote < Note class LegacyDiffNote < Note
include NoteOnDiff
serialize :st_diff serialize :st_diff
validates :line_code, presence: true, line_code: true validates :line_code, presence: true, line_code: true
...@@ -11,12 +13,12 @@ class LegacyDiffNote < Note ...@@ -11,12 +13,12 @@ class LegacyDiffNote < Note
end end
end end
def diff_note? def legacy_diff_note?
true true
end end
def legacy_diff_note? def diff_attributes
true { line_code: line_code }
end end
def discussion_id def discussion_id
...@@ -27,61 +29,20 @@ class LegacyDiffNote < Note ...@@ -27,61 +29,20 @@ class LegacyDiffNote < Note
line_code.split('_')[0] if line_code line_code.split('_')[0] if line_code
end end
def diff_old_line
line_code.split('_')[1].to_i if line_code
end
def diff_new_line
line_code.split('_')[2].to_i if line_code
end
def diff def diff
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map) @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
end end
def diff_file_path def diff_file
diff.new_path.presence || diff.old_path @diff_file ||= Gitlab::Diff::File.new(diff, repository: self.project.repository) if diff
end
def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end end
def diff_line def diff_line
@diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code } @diff_line ||= diff_file.line_for_line_code(self.line_code)
end end
def diff_line_text def for_line?(line)
diff_line.try(:text) !line.meta? && diff_file.line_code(line) == self.line_code
end
def diff_line_type
diff_line.try(:type)
end
def highlighted_diff_lines
Gitlab::Diff::Highlight.new(diff_lines).highlight
end
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
highlighted_diff_lines.each do |line|
if line.type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines << line
break if generate_line_code(line) == self.line_code
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
end
prev_lines
end end
# Check if this note is part of an "active" discussion # Check if this note is part of an "active" discussion
...@@ -102,7 +63,7 @@ class LegacyDiffNote < Note ...@@ -102,7 +63,7 @@ class LegacyDiffNote < Note
if noteable_diff if noteable_diff
parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line) parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
@active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text } @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line.text }
else else
@active = false @active = false
end end
...@@ -110,10 +71,6 @@ class LegacyDiffNote < Note ...@@ -110,10 +71,6 @@ class LegacyDiffNote < Note
@active @active
end end
def award_emoji_supported?
false
end
private private
def find_diff def find_diff
...@@ -149,10 +106,6 @@ class LegacyDiffNote < Note ...@@ -149,10 +106,6 @@ class LegacyDiffNote < Note
self.class.where(attributes).last.try(:diff) self.class.where(attributes).last.try(:diff)
end end
def generate_line_code(line)
Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos)
end
# Find the diff on noteable that matches our own # Find the diff on noteable that matches our own
def find_noteable_diff def find_noteable_diff
diffs = noteable.diffs(Commit.max_diff_options) diffs = noteable.diffs(Commit.max_diff_options)
......
...@@ -16,7 +16,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -16,7 +16,7 @@ class MergeRequest < ActiveRecord::Base
serialize :merge_params, Hash serialize :merge_params, Hash
after_create :create_merge_request_diff, unless: :importing after_create :create_merge_request_diff, unless: :importing?
after_update :update_merge_request_diff after_update :update_merge_request_diff
delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil
...@@ -29,10 +29,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -29,10 +29,6 @@ class MergeRequest < ActiveRecord::Base
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_commits, :compare attr_accessor :can_be_created, :compare_commits, :compare
# Temporary fields to store target_sha, and base_sha to
# compare when importing pull requests from GitHub
attr_accessor :base_target_sha, :head_source_sha
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
transition [:reopened, :opened] => :closed transition [:reopened, :opened] => :closed
...@@ -89,12 +85,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -89,12 +85,7 @@ class MergeRequest < ActiveRecord::Base
state :cannot_be_merged state :cannot_be_merged
around_transition do |merge_request, transition, block| around_transition do |merge_request, transition, block|
merge_request.record_timestamps = false Gitlab::Timeless.timeless(merge_request, &block)
begin
block.call
ensure
merge_request.record_timestamps = true
end
end end
end end
...@@ -169,10 +160,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -169,10 +160,6 @@ 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 def first_commit
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end end
...@@ -182,15 +169,86 @@ class MergeRequest < ActiveRecord::Base ...@@ -182,15 +169,86 @@ class MergeRequest < ActiveRecord::Base
end end
def diff_base_commit def diff_base_commit
if merge_request_diff if persisted?
merge_request_diff.base_commit merge_request_diff.base_commit
elsif source_sha elsif diff_start_commit && diff_head_commit
self.target_project.merge_base_commit(self.source_sha, self.target_branch) self.target_project.merge_base_commit(diff_start_sha, diff_head_sha)
end
end
# MRs created before 8.4 don't store a MergeRequestDiff#base_commit_sha,
# but we need to get a commit for the "View file @ ..." link by deleted files,
# so we find the likely one if we can't get the actual one.
# This will not be the actual base commit if the target branch was merged into
# the source branch after the merge request was created, but it is good enough
# for the specific purpose of linking to a commit.
# It is not good enough for use in `Gitlab::Git::DiffRefs`, which needs the
# true base commit, so we can't simply have `#diff_base_commit` fall back on
# this method.
def likely_diff_base_commit
first_commit.parent || first_commit
end
def diff_start_commit
if persisted?
merge_request_diff.start_commit
else
target_branch_head
end
end
def diff_head_commit
if persisted?
merge_request_diff.head_commit
else
source_branch_head
end end
end end
def last_commit_short_sha def diff_start_sha
last_commit.short_id diff_start_commit.try(:sha)
end
def diff_base_sha
diff_base_commit.try(:sha)
end
def diff_head_sha
diff_head_commit.try(:sha)
end
# When importing a pull request from GitHub, the old and new branches may no
# longer actually exist by those names, but we need to recreate the merge
# request diff with the right source and target shas.
# We use these attributes to force these to the intended values.
attr_writer :target_branch_sha, :source_branch_sha
def source_branch_head
source_branch_ref = @source_branch_sha || source_branch
source_project.repository.commit(source_branch) if source_branch_ref
end
def target_branch_head
target_branch_ref = @target_branch_sha || target_branch
target_project.repository.commit(target_branch) if target_branch_ref
end
def target_branch_sha
target_branch_head.try(:sha)
end
def source_branch_sha
source_branch_head.try(:sha)
end
def diff_refs
return unless diff_start_commit || diff_base_commit
Gitlab::Diff::DiffRefs.new(
base_sha: diff_base_sha,
start_sha: diff_start_sha,
head_sha: diff_head_sha
)
end end
def validate_branches def validate_branches
...@@ -227,21 +285,30 @@ class MergeRequest < ActiveRecord::Base ...@@ -227,21 +285,30 @@ class MergeRequest < ActiveRecord::Base
def update_merge_request_diff def update_merge_request_diff
if source_branch_changed? || target_branch_changed? if source_branch_changed? || target_branch_changed?
reload_code reload_diff
end end
end end
def reload_code def reload_diff
if merge_request_diff && open? return unless merge_request_diff && open?
merge_request_diff.reload_content
end old_diff_refs = self.diff_refs
merge_request_diff.reload_content
new_diff_refs = self.diff_refs
update_diff_notes_positions(
old_diff_refs: old_diff_refs,
new_diff_refs: new_diff_refs
)
end end
def check_if_can_be_merged def check_if_can_be_merged
return unless unchecked? return unless unchecked?
can_be_merged = can_be_merged =
!broken? && project.repository.can_be_merged?(source_sha, target_branch) !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
if can_be_merged if can_be_merged
mark_as_mergeable mark_as_mergeable
...@@ -251,11 +318,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -251,11 +318,11 @@ class MergeRequest < ActiveRecord::Base
end end
def merge_event def merge_event
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last @merge_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
end end
def closed_event def closed_event
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
...@@ -293,7 +360,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -293,7 +360,7 @@ class MergeRequest < ActiveRecord::Base
!source_project.protected_branch?(source_branch) && !source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) && !source_project.root_ref?(source_branch) &&
Ability.abilities.allowed?(current_user, :push_code, source_project) && Ability.abilities.allowed?(current_user, :push_code, source_project) &&
last_commit == source_project.commit(source_branch) diff_head_commit == source_branch_head
end end
def should_remove_source_branch? def should_remove_source_branch?
...@@ -331,8 +398,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -331,8 +398,8 @@ class MergeRequest < ActiveRecord::Base
work_in_progress: work_in_progress? work_in_progress: work_in_progress?
} }
if last_commit if diff_head_commit
attrs.merge!(last_commit: last_commit.hook_attrs) attrs.merge!(last_commit: diff_head_commit.hook_attrs)
end end
attributes.merge!(attrs) attributes.merge!(attrs)
...@@ -510,22 +577,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -510,22 +577,6 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def target_sha
return @base_target_sha if defined?(@base_target_sha)
target_project.repository.commit(target_branch).try(:sha)
end
def source_sha
return @head_source_sha if defined?(@head_source_sha)
last_commit.try(:sha) || source_tip.try(:sha)
end
def source_tip
source_branch && source_project.repository.commit(source_branch)
end
def fetch_ref def fetch_ref
target_project.repository.fetch_ref( target_project.repository.fetch_ref(
source_project.repository.path_to_repo, source_project.repository.path_to_repo,
...@@ -558,10 +609,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -558,10 +609,10 @@ class MergeRequest < ActiveRecord::Base
def diverged_commits_count def diverged_commits_count
cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits") cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")
if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha if cache.blank? || cache[:source_sha] != source_branch_sha || cache[:target_sha] != target_branch_sha
cache = { cache = {
source_sha: source_sha, source_sha: source_branch_sha,
target_sha: target_sha, target_sha: target_branch_sha,
diverged_commits_count: compute_diverged_commits_count diverged_commits_count: compute_diverged_commits_count
} }
Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache) Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
...@@ -571,9 +622,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -571,9 +622,9 @@ class MergeRequest < ActiveRecord::Base
end end
def compute_diverged_commits_count def compute_diverged_commits_count
return 0 unless source_sha && target_sha return 0 unless source_branch_sha && target_branch_sha
Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_branch_sha, target_branch_sha).size
end end
private :compute_diverged_commits_count private :compute_diverged_commits_count
...@@ -582,13 +633,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -582,13 +633,7 @@ class MergeRequest < ActiveRecord::Base
end end
def pipeline def pipeline
@pipeline ||= source_project.pipeline(last_commit.id, source_branch) if last_commit && source_project @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
end
def diff_refs
return nil unless diff_base_commit
[diff_base_commit, last_commit]
end end
def merge_commit def merge_commit
...@@ -603,6 +648,36 @@ class MergeRequest < ActiveRecord::Base ...@@ -603,6 +648,36 @@ class MergeRequest < ActiveRecord::Base
merge_commit merge_commit
end end
def support_new_diff_notes?
diff_refs && diff_refs.complete?
end
def update_diff_notes_positions(old_diff_refs:, new_diff_refs:)
return unless support_new_diff_notes?
return if new_diff_refs == old_diff_refs
active_diff_notes = self.notes.diff_notes.select do |note|
note.new_diff_note? && note.active?(old_diff_refs)
end
return if active_diff_notes.empty?
paths = active_diff_notes.flat_map { |n| n.diff_file.paths }.uniq
service = Notes::DiffPositionUpdateService.new(
self.project,
nil,
old_diff_refs: old_diff_refs,
new_diff_refs: new_diff_refs,
paths: paths
)
active_diff_notes.each do |note|
service.execute(note)
Gitlab::Timeless.timeless(note, &:save)
end
end
def keep_around_commit def keep_around_commit
project.repository.keep_around(self.merge_commit_sha) project.repository.keep_around(self.merge_commit_sha)
end end
......
...@@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request belongs_to :merge_request
delegate :head_source_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
state :collected state :collected
...@@ -24,7 +24,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -24,7 +24,7 @@ class MergeRequestDiff < ActiveRecord::Base
serialize :st_diffs serialize :st_diffs
after_create :reload_content, unless: :importing? after_create :reload_content, unless: :importing?
after_save :keep_around_commit after_save :keep_around_commits, unless: :importing?
def reload_content def reload_content
reload_commits reload_commits
...@@ -39,9 +39,9 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -39,9 +39,9 @@ class MergeRequestDiff < ActiveRecord::Base
if options[:ignore_whitespace_change] if options[:ignore_whitespace_change]
@diffs_no_whitespace ||= begin @diffs_no_whitespace ||= begin
compare = Gitlab::Git::Compare.new( compare = Gitlab::Git::Compare.new(
self.repository.raw_repository, repository.raw_repository,
self.base, self.start_commit_sha || self.target_branch_sha,
self.head, self.head_commit_sha || self.source_branch_sha,
) )
compare.diffs(options) compare.diffs(options)
end end
...@@ -63,37 +63,39 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -63,37 +63,39 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def base_commit def base_commit
return nil unless self.base_commit_sha return unless self.base_commit_sha
merge_request.target_project.commit(self.base_commit_sha) project.commit(self.base_commit_sha)
end end
def last_commit_short_sha def start_commit
@last_commit_short_sha ||= last_commit.short_id return unless self.start_commit_sha
end
def dump_commits(commits) project.commit(self.start_commit_sha)
commits.map(&:to_hash)
end end
def load_commits(array) def head_commit
array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } return last_commit unless self.head_commit_sha
end
def dump_diffs(diffs) project.commit(self.head_commit_sha)
if diffs.respond_to?(:map)
diffs.map(&:to_hash)
end
end end
def load_diffs(raw, options) def compare
if raw.respond_to?(:each) @compare ||=
Gitlab::Git::DiffCollection.new(raw, options) begin
else # Update ref for merge request
Gitlab::Git::DiffCollection.new([]) merge_request.fetch_ref
end
Gitlab::Git::Compare.new(
repository.raw_repository,
self.target_branch_sha,
self.source_branch_sha
)
end
end end
private
# Collect array of Git::Commit objects # Collect array of Git::Commit objects
# between target and source branches # between target and source branches
def unmerged_commits def unmerged_commits
...@@ -106,6 +108,14 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -106,6 +108,14 @@ class MergeRequestDiff < ActiveRecord::Base
commits commits
end end
def dump_commits(commits)
commits.map(&:to_hash)
end
def load_commits(array)
array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) }
end
# Reload all commits related to current merge request from repo # Reload all commits related to current merge request from repo
# and save it as array of hashes in st_commits db field # and save it as array of hashes in st_commits db field
def reload_commits def reload_commits
...@@ -120,6 +130,26 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -120,6 +130,26 @@ class MergeRequestDiff < ActiveRecord::Base
update_columns_serialized(new_attributes) update_columns_serialized(new_attributes)
end end
# Collect array of Git::Diff objects
# between target and source branches
def unmerged_diffs
compare.diffs(Commit.max_diff_options)
end
def dump_diffs(diffs)
if diffs.respond_to?(:map)
diffs.map(&:to_hash)
end
end
def load_diffs(raw, options)
if raw.respond_to?(:each)
Gitlab::Git::DiffCollection.new(raw, options)
else
Gitlab::Git::DiffCollection.new([])
end
end
# Reload diffs between branches related to current merge request from repo # Reload diffs between branches related to current merge request from repo
# and save it as array of hashes in st_diffs db field # and save it as array of hashes in st_diffs db field
def reload_diffs def reload_diffs
...@@ -147,59 +177,33 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -147,59 +177,33 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:st_diffs] = new_diffs new_attributes[:st_diffs] = new_diffs
base_commit_sha = self.repository.merge_base(self.head, self.base) new_attributes[:start_commit_sha] = self.target_branch_sha
new_attributes[:base_commit_sha] = base_commit_sha new_attributes[:head_commit_sha] = self.source_branch_sha
new_attributes[:base_commit_sha] = branch_base_sha
self.repository.keep_around(base_commit_sha)
update_columns_serialized(new_attributes) update_columns_serialized(new_attributes)
end
# Collect array of Git::Diff objects keep_around_commits
# between target and source branches
def unmerged_diffs
compare.diffs(Commit.max_diff_options)
end end
def repository def project
merge_request.target_project.repository merge_request.target_project
end end
def source_sha def repository
return head_source_sha if head_source_sha.present? project.repository
source_commit = merge_request.source_project.commit(source_branch)
source_commit.try(:sha)
end end
def target_sha def branch_base_commit
merge_request.target_sha return unless self.source_branch_sha && self.target_branch_sha
end
def base project.merge_base_commit(self.source_branch_sha, self.target_branch_sha)
self.target_sha || self.target_branch
end end
def head def branch_base_sha
self.source_sha branch_base_commit.try(:sha)
end end
def compare
@compare ||=
begin
# Update ref for merge request
merge_request.fetch_ref
Gitlab::Git::Compare.new(
self.repository.raw_repository,
self.base,
self.head
)
end
end
private
# #
# #save or #update_attributes providing changes on serialized attributes do a lot of # #save or #update_attributes providing changes on serialized attributes do a lot of
# serialization and deserialization calls resulting in bad performance. # serialization and deserialization calls resulting in bad performance.
...@@ -223,7 +227,9 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -223,7 +227,9 @@ class MergeRequestDiff < ActiveRecord::Base
reload reload
end end
def keep_around_commit def keep_around_commits
self.repository.keep_around(self.base_commit_sha) repository.keep_around(target_branch_sha)
repository.keep_around(source_branch_sha)
repository.keep_around(branch_base_sha)
end end
end end
...@@ -56,7 +56,7 @@ class Note < ActiveRecord::Base ...@@ -56,7 +56,7 @@ class Note < ActiveRecord::Base
scope :inc_author, ->{ includes(:author) } scope :inc_author, ->{ includes(:author) }
scope :inc_author_project_award_emoji, ->{ includes(:project, :author, :award_emoji) } scope :inc_author_project_award_emoji, ->{ includes(:project, :author, :award_emoji) }
scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') } scope :diff_notes, ->{ where(type: ['LegacyDiffNote', 'DiffNote']) }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) } scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do scope :with_associations, -> do
...@@ -82,7 +82,7 @@ class Note < ActiveRecord::Base ...@@ -82,7 +82,7 @@ class Note < ActiveRecord::Base
end end
def grouped_diff_notes def grouped_diff_notes
legacy_diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code) diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end end
# Searches for notes matching the given query. # Searches for notes matching the given query.
...@@ -115,6 +115,10 @@ class Note < ActiveRecord::Base ...@@ -115,6 +115,10 @@ class Note < ActiveRecord::Base
false false
end end
def new_diff_note?
false
end
def active? def active?
true true
end end
...@@ -193,7 +197,7 @@ class Note < ActiveRecord::Base ...@@ -193,7 +197,7 @@ class Note < ActiveRecord::Base
end end
def award_emoji? def award_emoji?
award_emoji_supported? && contains_emoji_only? can_be_award_emoji? && contains_emoji_only?
end end
def emoji_awardable? def emoji_awardable?
...@@ -204,7 +208,7 @@ class Note < ActiveRecord::Base ...@@ -204,7 +208,7 @@ class Note < ActiveRecord::Base
self.line_code = nil if self.line_code.blank? self.line_code = nil if self.line_code.blank?
end end
def award_emoji_supported? def can_be_award_emoji?
noteable.is_a?(Awardable) noteable.is_a?(Awardable)
end end
......
...@@ -5,6 +5,7 @@ class NotificationSetting < ActiveRecord::Base ...@@ -5,6 +5,7 @@ class NotificationSetting < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :source, polymorphic: true belongs_to :source, polymorphic: true
belongs_to :project, foreign_key: 'source_id'
validates :user, presence: true validates :user, presence: true
validates :level, presence: true validates :level, presence: true
...@@ -13,7 +14,13 @@ class NotificationSetting < ActiveRecord::Base ...@@ -13,7 +14,13 @@ class NotificationSetting < ActiveRecord::Base
allow_nil: true } allow_nil: true }
scope :for_groups, -> { where(source_type: 'Namespace') } scope :for_groups, -> { where(source_type: 'Namespace') }
scope :for_projects, -> { where(source_type: 'Project') }
# Exclude projects not included by the Project model's default scope (those that are
# pending delete).
#
scope :for_projects, -> do
includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil })
end
EMAIL_EVENTS = [ EMAIL_EVENTS = [
:new_note, :new_note,
......
...@@ -425,8 +425,8 @@ class Project < ActiveRecord::Base ...@@ -425,8 +425,8 @@ class Project < ActiveRecord::Base
container_registry_repository.tags.any? container_registry_repository.tags.any?
end end
def commit(id = 'HEAD') def commit(ref = 'HEAD')
repository.commit(id) repository.commit(ref)
end end
def builds_for(build_name, ref = 'HEAD') def builds_for(build_name, ref = 'HEAD')
...@@ -810,18 +810,12 @@ class Project < ActiveRecord::Base ...@@ -810,18 +810,12 @@ class Project < ActiveRecord::Base
@repo_exists = false @repo_exists = false
end end
# Branches that are not _exactly_ matched by a protected branch.
def open_branches def open_branches
# We're using a Set here as checking values in a large Set is faster than exact_protected_branch_names = protected_branches.reject(&:wildcard?).map(&:name)
# checking values in a large Array. branch_names = repository.branches.map(&:name)
protected_set = Set.new(protected_branch_names) non_open_branch_names = Set.new(exact_protected_branch_names).intersection(Set.new(branch_names))
repository.branches.reject { |branch| non_open_branch_names.include? branch.name }
repository.branches.reject do |branch|
protected_set.include?(branch.name)
end
end
def protected_branch_names
@protected_branch_names ||= protected_branches.pluck(:name)
end end
def root_ref?(branch) def root_ref?(branch)
...@@ -838,11 +832,12 @@ class Project < ActiveRecord::Base ...@@ -838,11 +832,12 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system # Check if current branch name is marked as protected in the system
def protected_branch?(branch_name) def protected_branch?(branch_name)
protected_branch_names.include?(branch_name) @protected_branches ||= self.protected_branches.to_a
ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present?
end end
def developers_can_push_to_protected_branch?(branch_name) def developers_can_push_to_protected_branch?(branch_name)
protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push } protected_branches.matching(branch_name).any?(&:developers_can_push)
end end
def forked? def forked?
......
...@@ -112,15 +112,7 @@ class IrkerService < Service ...@@ -112,15 +112,7 @@ class IrkerService < Service
# Authorize both irc://domain.com/#chan and irc://domain.com/chan # Authorize both irc://domain.com/#chan and irc://domain.com/chan
if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil?
# Do not authorize irc://domain.com/ uri.to_s
if uri.fragment.nil? && uri.path.length > 1
uri.to_s
else
# Authorize irc://domain.com/smthg#chan
# The irker daemon will deal with it by concatenating smthg and
# chan, thus sending messages on #smthgchan
uri.to_s
end
end end
end end
end end
...@@ -144,7 +144,7 @@ class JiraService < IssueTrackerService ...@@ -144,7 +144,7 @@ class JiraService < IssueTrackerService
commit_id = if entity.is_a?(Commit) commit_id = if entity.is_a?(Commit)
entity.id entity.id
elsif entity.is_a?(MergeRequest) elsif entity.is_a?(MergeRequest)
entity.last_commit.id entity.diff_head_sha
end end
commit_url = build_entity_url(:commit, commit_id) commit_url = build_entity_url(:commit, commit_id)
......
...@@ -8,4 +8,51 @@ class ProtectedBranch < ActiveRecord::Base ...@@ -8,4 +8,51 @@ class ProtectedBranch < ActiveRecord::Base
def commit def commit
project.commit(self.name) project.commit(self.name)
end end
# Returns all protected branches that match the given branch name.
# This realizes all records from the scope built up so far, and does
# _not_ return a relation.
#
# This method optionally takes in a list of `protected_branches` to search
# through, to avoid calling out to the database.
def self.matching(branch_name, protected_branches: nil)
(protected_branches || all).select { |protected_branch| protected_branch.matches?(branch_name) }
end
# Returns all branches (among the given list of branches [`Gitlab::Git::Branch`])
# that match the current protected branch.
def matching(branches)
branches.select { |branch| self.matches?(branch.name) }
end
# Checks if the protected branch matches the given branch name.
def matches?(branch_name)
return false if self.name.blank?
exact_match?(branch_name) || wildcard_match?(branch_name)
end
# Checks if this protected branch contains a wildcard
def wildcard?
self.name && self.name.include?('*')
end
protected
def exact_match?(branch_name)
self.name == branch_name
end
def wildcard_match?(branch_name)
wildcard_regex === branch_name
end
def wildcard_regex
@wildcard_regex ||= begin
name = self.name.gsub('*', 'STAR_DONT_ESCAPE')
quoted_name = Regexp.quote(name)
regex_string = quoted_name.gsub('STAR_DONT_ESCAPE', '.*?')
/\A#{regex_string}\z/
end
end
end end
...@@ -78,9 +78,9 @@ class Repository ...@@ -78,9 +78,9 @@ class Repository
end end
end end
def commit(id = 'HEAD') def commit(ref = 'HEAD')
return nil unless exists? return nil unless exists?
commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Gitlab::Git::Commit.find(raw_repository, ref)
commit = ::Commit.new(commit, @project) if commit commit = ::Commit.new(commit, @project) if commit
commit commit
rescue Rugged::OdbError rescue Rugged::OdbError
...@@ -653,16 +653,6 @@ class Repository ...@@ -653,16 +653,6 @@ class Repository
end end
end end
def blob_for_diff(commit, diff)
blob_at(commit.id, diff.file_path)
end
def prev_blob_for_diff(commit, diff)
if commit.parent_id
blob_at(commit.parent_id, diff.old_path)
end
end
def refs_contains_sha(ref_type, sha) def refs_contains_sha(ref_type, sha)
args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
names = Gitlab::Popen.popen(args, path_to_repo).first names = Gitlab::Popen.popen(args, path_to_repo).first
...@@ -911,7 +901,7 @@ class Repository ...@@ -911,7 +901,7 @@ class Repository
if line =~ /^.*:.*:\d+:/ if line =~ /^.*:.*:\d+:/
ref, filename, startline = line.split(':') ref, filename, startline = line.split(':')
startline = startline.to_i - index startline = startline.to_i - index
extname = File.extname(filename) extname = Regexp.escape(File.extname(filename))
basename = filename.sub(/#{extname}$/, '') basename = filename.sub(/#{extname}$/, '')
break break
end end
......
class SentNotification < ActiveRecord::Base class SentNotification < ActiveRecord::Base
serialize :position, Gitlab::Diff::Position
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true
belongs_to :recipient, class_name: "User" belongs_to :recipient, class_name: "User"
...@@ -7,7 +9,7 @@ class SentNotification < ActiveRecord::Base ...@@ -7,7 +9,7 @@ class SentNotification < ActiveRecord::Base
validates :reply_key, uniqueness: true validates :reply_key, uniqueness: true
validates :noteable_id, presence: true, unless: :for_commit? validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit? validates :commit_id, presence: true, if: :for_commit?
validates :line_code, line_code: true, allow_blank: true validate :note_valid
after_save :keep_around_commit after_save :keep_around_commit
...@@ -20,7 +22,7 @@ class SentNotification < ActiveRecord::Base ...@@ -20,7 +22,7 @@ class SentNotification < ActiveRecord::Base
find_by(reply_key: reply_key) find_by(reply_key: reply_key)
end end
def record(noteable, recipient_id, reply_key, params = {}) def record(noteable, recipient_id, reply_key, attrs = {})
return unless reply_key return unless reply_key
noteable_id = nil noteable_id = nil
...@@ -31,7 +33,7 @@ class SentNotification < ActiveRecord::Base ...@@ -31,7 +33,7 @@ class SentNotification < ActiveRecord::Base
noteable_id = noteable.id noteable_id = noteable.id
end end
params.reverse_merge!( attrs.reverse_merge!(
project: noteable.project, project: noteable.project,
noteable_type: noteable.class.name, noteable_type: noteable.class.name,
noteable_id: noteable_id, noteable_id: noteable_id,
...@@ -40,13 +42,17 @@ class SentNotification < ActiveRecord::Base ...@@ -40,13 +42,17 @@ class SentNotification < ActiveRecord::Base
reply_key: reply_key reply_key: reply_key
) )
create(params) create(attrs)
end end
def record_note(note, recipient_id, reply_key, params = {}) def record_note(note, recipient_id, reply_key, attrs = {})
params[:line_code] = note.line_code if note.diff_note?
attrs[:note_type] = note.type
attrs.merge!(note.diff_attributes)
end
record(note.noteable, recipient_id, reply_key, params) record(note.noteable, recipient_id, reply_key, attrs)
end end
end end
...@@ -70,8 +76,33 @@ class SentNotification < ActiveRecord::Base ...@@ -70,8 +76,33 @@ class SentNotification < ActiveRecord::Base
self.reply_key self.reply_key
end end
def note_attributes
{
project: self.project,
author: self.recipient,
type: self.note_type,
noteable_type: self.noteable_type,
noteable_id: self.noteable_id,
commit_id: self.commit_id,
line_code: self.line_code,
position: self.position.to_json
}
end
def create_note(note)
Notes::CreateService.new(
self.project,
self.recipient,
self.note_attributes.merge(note: note)
).execute
end
private private
def note_valid
Note.new(note_attributes.merge(note: "Test")).valid?
end
def keep_around_commit def keep_around_commit
project.repository.keep_around(self.commit_id) project.repository.keep_around(self.commit_id)
end end
......
...@@ -15,7 +15,7 @@ class User < ActiveRecord::Base ...@@ -15,7 +15,7 @@ class User < ActiveRecord::Base
add_authentication_token_field :authentication_token add_authentication_token_field :authentication_token
default_value_for :admin, false default_value_for :admin, false
default_value_for :external, false default_value_for(:external) { current_application_settings.user_default_external }
default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_group, gitlab_config.default_can_create_group
default_value_for :can_create_team, false default_value_for :can_create_team, false
default_value_for :hide_no_ssh_key, false default_value_for :hide_no_ssh_key, false
......
...@@ -34,7 +34,7 @@ module MergeRequests ...@@ -34,7 +34,7 @@ module MergeRequests
committer: committer committer: committer
} }
commit_id = repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options) commit_id = repository.merge(current_user, merge_request.diff_head_sha, merge_request.target_branch, options)
merge_request.update(merge_commit_sha: commit_id) merge_request.update(merge_commit_sha: commit_id)
rescue GitHooksService::PreReceiveError => e rescue GitHooksService::PreReceiveError => e
merge_request.update(merge_error: e.message) merge_request.update(merge_error: e.message)
......
...@@ -12,7 +12,7 @@ module MergeRequests ...@@ -12,7 +12,7 @@ module MergeRequests
merge_request.merge_when_build_succeeds = true merge_request.merge_when_build_succeeds = true
merge_request.merge_user = @current_user merge_request.merge_user = @current_user
SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.last_commit) SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit)
end end
merge_request.save merge_request.save
......
...@@ -34,10 +34,10 @@ module MergeRequests ...@@ -34,10 +34,10 @@ module MergeRequests
def close_merge_requests def close_merge_requests
commit_ids = @commits.map(&:id) commit_ids = @commits.map(&:id)
merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a
merge_requests = merge_requests.select(&:last_commit) merge_requests = merge_requests.select(&:diff_head_commit)
merge_requests = merge_requests.select do |merge_request| merge_requests = merge_requests.select do |merge_request|
commit_ids.include?(merge_request.last_commit.id) commit_ids.include?(merge_request.diff_head_sha)
end end
merge_requests.uniq.select(&:source_project).each do |merge_request| merge_requests.uniq.select(&:source_project).each do |merge_request|
...@@ -60,20 +60,15 @@ module MergeRequests ...@@ -60,20 +60,15 @@ module MergeRequests
merge_requests.each do |merge_request| merge_requests.each do |merge_request|
if merge_request.source_branch == @branch_name || force_push? if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_code merge_request.reload_diff
merge_request.mark_as_unchecked
else else
mr_commit_ids = merge_request.commits.map(&:id) mr_commit_ids = merge_request.commits.map(&:id)
push_commit_ids = @commits.map(&:id) push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids matches = mr_commit_ids & push_commit_ids
merge_request.reload_diff if matches.any?
if matches.any?
merge_request.reload_code
merge_request.mark_as_unchecked
else
merge_request.mark_as_unchecked
end
end end
merge_request.mark_as_unchecked
end end
end end
...@@ -94,12 +89,10 @@ module MergeRequests ...@@ -94,12 +89,10 @@ module MergeRequests
merge_request = merge_requests_for_source_branch.first merge_request = merge_requests_for_source_branch.first
return unless merge_request return unless merge_request
last_commit = merge_request.last_commit
begin begin
# Since any number of commits could have been made to the restored branch, # Since any number of commits could have been made to the restored branch,
# find the common root to see what has been added. # find the common root to see what has been added.
common_ref = @project.repository.merge_base(last_commit.id, @newrev) common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @newrev)
# If the a commit no longer exists in this repo, gitlab_git throws # 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 # 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 @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref
......
...@@ -6,7 +6,7 @@ module MergeRequests ...@@ -6,7 +6,7 @@ module MergeRequests
create_note(merge_request) create_note(merge_request)
notification_service.reopen_mr(merge_request, current_user) notification_service.reopen_mr(merge_request, current_user)
execute_hooks(merge_request, 'reopen') execute_hooks(merge_request, 'reopen')
merge_request.reload_code merge_request.reload_diff
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
end end
......
module Notes
class DiffPositionUpdateService < BaseService
def execute(note)
new_position = tracer.trace(note.position)
# Don't update the position if the type doesn't match, since that means
# the diff line commented on was changed, and the comment is now outdated
old_position = note.position
if new_position &&
new_position != old_position &&
new_position.type == old_position.type
note.position = new_position
end
note
end
private
def tracer
@tracer ||= Gitlab::Diff::PositionTracer.new(
repository: project.repository,
old_diff_refs: params[:old_diff_refs],
new_diff_refs: params[:new_diff_refs],
paths: params[:paths]
)
end
end
end
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
%tr %tr
%td %td
- if user - if user
= link_to user.name, [:admin, user] = link_to user.name, user
.light.small .light.small
Joined #{time_ago_with_tooltip(user.created_at)} Joined #{time_ago_with_tooltip(user.created_at)}
- else - else
(removed) (removed)
%td %td
- if reporter - if reporter
= link_to reporter.name, [:admin, reporter] = link_to reporter.name, reporter
- else - else
(removed) (removed)
.light.small .light.small
......
...@@ -100,6 +100,13 @@ ...@@ -100,6 +100,13 @@
= f.label :user_oauth_applications do = f.label :user_oauth_applications do
= f.check_box :user_oauth_applications = f.check_box :user_oauth_applications
Allow users to register any application to use GitLab as an OAuth provider Allow users to register any application to use GitLab as an OAuth provider
.form-group
= f.label :user_default_external, 'New users set to external', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :user_default_external do
= f.check_box :user_default_external
Newly registered users will by default be external
%fieldset %fieldset
%legend Sign-in Restrictions %legend Sign-in Restrictions
......
- css_class = '' unless local_assigns[:css_class] - css_class = '' unless local_assigns[:css_class]
- css_class += ' no-description' if group.description.blank?
%li.group-row{ class: css_class } %li.group-row.group-admin{ class: css_class }
.controls.hidden-xs .group-avatar
= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm' = image_tag group_icon(group), class: 'avatar hidden-xs'
= link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove' .group-details
.title
= link_to [:admin, group], class: 'group-name' do
= group.name
.group-stats
%span>= pluralize(number_with_delimiter(group.projects.count), 'project')
,
%span= pluralize(number_with_delimiter(group.users.count), 'member')
.stats - if group.description.present?
%span .description
= icon('bookmark') = markdown(group.description, pipeline: :description)
= number_with_delimiter(group.projects.count) .group-controls.hidden-xs
= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
%span = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove'
= icon('users')
= number_with_delimiter(group.users.count)
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
= image_tag group_icon(group), class: 'avatar s40 hidden-xs'
.title
= link_to [:admin, group], class: 'group-name' do
= group.name
- if group.description.present?
.description
= markdown(group.description, pipeline: :description)
...@@ -3,41 +3,32 @@ ...@@ -3,41 +3,32 @@
= render "admin/dashboard/head" = render "admin/dashboard/head"
%div{ class: container_class } %div{ class: container_class }
%h3.page-title
Groups (#{number_with_delimiter(@groups.total_count)})
%p.light
Group allows you to keep projects organized.
Use groups for uniting related projects.
.top-area .top-area
.nav-search .prepend-top-default.append-bottom-default
= form_tag admin_groups_path, method: :get, class: 'form-inline' do = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f|
= hidden_field_tag :sort, @sort = hidden_field_tag :sort, @sort
= text_field_tag :name, params[:name], class: "form-control" .search-holder
= button_tag "Search", class: "btn submit btn-primary" - project_name = params[:name].present? ? params[:name] : nil
.search-field-holder
.nav-controls = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name'
.dropdown.inline = icon("search", class: "search-icon")
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} .dropdown
%span.light - toggle_text = @sort.present? ? sort_options_hash[@sort] : sort_title_recently_created
- if @sort.present? = dropdown_toggle(toggle_text, { toggle: 'dropdown' })
= sort_options_hash[@sort] %ul.dropdown-menu.dropdown-menu-align-right
- else %li.dropdown-header
= sort_title_recently_created Sort by
%b.caret %li
%ul.dropdown-menu = link_to admin_groups_path(sort: sort_value_recently_created, name: project_name) do
%li = sort_title_recently_created
= link_to admin_groups_path(sort: sort_value_recently_created) do = link_to admin_groups_path(sort: sort_value_oldest_created, name: project_name) do
= sort_title_recently_created = sort_title_oldest_created
= link_to admin_groups_path(sort: sort_value_oldest_created) do = link_to admin_groups_path(sort: sort_value_recently_updated, name: project_name) do
= sort_title_oldest_created = sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_recently_updated) do = link_to admin_groups_path(sort: sort_value_oldest_updated, name: project_name) do
= sort_title_recently_updated = sort_title_oldest_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated) do = link_to new_admin_group_path, class: "btn btn-new" do
= sort_title_oldest_updated New Group
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
%ul.content-list %ul.content-list
= render @groups = render @groups
......
- @no_container = true - @no_container = true
- page_title "Projects" - page_title "Projects"
= render 'shared/show_aside' - params[:visibility_level] ||= []
= render "admin/dashboard/head" = render "admin/dashboard/head"
%div{ class: container_class } %div{ class: container_class }
.row.prepend-top-default .top-area
%aside.col-md-3 .prepend-top-default
.panel.admin-filter = form_tag admin_namespaces_projects_path, method: :get do |f|
= form_tag admin_namespaces_projects_path, method: :get, class: '' do .search-holder
.form-group .search-field-holder
= label_tag :name, 'Name:' = search_field_tag :name, params[:name], class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false, placeholder: 'Search by name'
= text_field_tag :name, params[:name], class: "form-control"
- if params[:visibility_level].present?
= hidden_field_tag 'visibility_level', params[:visibility_level]
- if params[:sort].present?
= hidden_field_tag 'sort', params[:sort]
- if params[:personal].present?
= hidden_field_tag 'visibility_level', 'true'
- if params[:archived].present?
= hidden_field_tag 'archived', 'true'
= icon("search", class: "search-icon")
.dropdown
- toggle_text = 'Search for Namespace'
- if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.path}"
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
= dropdown_content
= dropdown_loading
= button_tag "Search", class: "btn btn-primary btn-search"
%ul.nav-links
- opts = params[:visibility_level].present? ? {} : { page: admin_namespaces_projects_path }
= nav_link(opts) do
= link_to admin_namespaces_projects_path do
All
.form-group = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s ? 'active' : '' }) do
= label_tag :namespace_id, "Namespace" = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
= namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' Private
= nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s ? 'active' : '' }) do
= link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
Internal
= nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s ? 'active' : '' }) do
= link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
Public
.form-group .nav-controls
%strong Activity = render 'shared/projects/dropdown'
.checkbox = link_to new_project_path, class: 'btn btn-new' do
= label_tag :with_push do New Project
= check_box_tag :with_push, 1, params[:with_push]
%span Projects with push events
.checkbox
= label_tag :abandoned do
= check_box_tag :abandoned, 1, params[:abandoned]
%span No activity over 6 month
.checkbox
= label_tag :with_archived do
= check_box_tag :with_archived, 1, params[:with_archived]
%span Show archived projects
%fieldset .projects-list-holder
%strong Visibility level: - if @projects.any?
.visibility-levels %ul.projects-list.content-list
- Project.visibility_levels.each do |label, level| - @projects.each_with_index do |project|
.checkbox %li.project-row
%label .controls.pull-right
= check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) - if project.archived
%span.descr %span.label.label-warning archived
= visibility_level_icon(level) %span.label.label-gray
= label = repository_size(project)
%fieldset = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
%strong Problems = link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
.checkbox .title
= label_tag :last_repository_check_failed do = link_to [:admin, project.namespace.becomes(Namespace), project] do
= check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed] .dash-project-avatar
%span Last repository check failed = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace
= project.namespace.human_name
\/
%span.project-name.filter-title
= project.name
= hidden_field_tag :sort, params[:sort] - if project.description.present?
= button_tag "Search", class: "btn submit btn-primary" .description
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" = markdown(project.description, pipeline: :description)
%section.col-md-9 = paginate @projects, theme: 'gitlab'
.panel.panel-default - else
.panel-heading .nothing-here-block No projects found
Projects (#{@projects.total_count})
.controls
.dropdown.inline
%button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
= link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do
= sort_title_oldest_created
= link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do
= sort_title_recently_updated
= link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
= link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do
= sort_title_largest_repo
= link_to 'New Project', new_project_path, class: "btn btn-sm btn-success"
%ul.well-list
- @projects.each do |project|
%li
.list-item-name
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level)
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
.pull-right
- if project.archived
%span.label.label-warning archived
%span.label.label-gray
= repository_size(project)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove"
- if @projects.blank?
.nothing-here-block 0 projects matches
= paginate @projects, theme: "gitlab"
...@@ -99,7 +99,13 @@ ...@@ -99,7 +99,13 @@
.form-group .form-group
= f.label :new_namespace_id, "Namespace", class: 'control-label' = f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10 .col-sm-10
= namespace_select_tag :new_namespace_id, selected: params[:namespace_id], class: 'input-large' .dropdown
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id', show_any: 'false' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
= dropdown_content
= dropdown_loading
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
%legend Access %legend Access
.form-group .form-group
= f.label :projects_limit, class: 'control-label' = f.label :projects_limit, class: 'control-label'
.col-sm-10= f.number_field :projects_limit, class: 'form-control' .col-sm-10= f.number_field :projects_limit, min: 0, class: 'form-control'
.form-group .form-group
= f.label :can_create_group, class: 'control-label' = f.label :can_create_group, class: 'control-label'
......
%li.user-row
.user-avatar
= image_tag avatar_icon(user), class: "avatar", alt: ''
.user-details
.user-name
= link_to user.name, [:admin, user]
- if user.blocked?
%span.label.label-danger blocked
- if user.admin?
%span.label.label-success Admin
- if user.external?
%span.label.label-default External
- if user == current_user
%span It's you!
.user-email
= mail_to user.email, user.email
.controls.pull-right
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn'
- unless user == current_user
.dropdown.inline
%a.dropdown-new.btn.btn-default#project-settings-button{href: '#', data: { toggle: 'dropdown' } }
= icon('cog')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li.dropdown-header
Settings
%li
- if user.ldap_blocked?
%span.small Cannot unblock LDAP blocked users
- elsif user.blocked?
= link_to 'Unblock', unblock_admin_user_path(user), method: :put
- else
= link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put
- if user.access_locked?
%li
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
- if user.can_be_removed?
%li.divider
%li
= link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
class: 'btn btn-remove btn-block',
method: :delete
- @no_container = true - @no_container = true
- page_title "Users" - page_title "Users"
= render 'shared/show_aside'
= render "admin/dashboard/head" = render "admin/dashboard/head"
%div{ class: container_class } %div{ class: container_class }
.admin-filter .top-area
%ul.nav-links .prepend-top-default
%li{class: "#{'active' unless params[:filter]}"} = form_tag admin_users_path, method: :get do
= link_to admin_users_path do - if params[:filter].present?
Active = hidden_field_tag "filter", h(params[:filter])
%small.badge= number_with_delimiter(User.active.count) .search-holder
%li{class: "#{'active' if params[:filter] == "admins"}"} .search-field-holder
= link_to admin_users_path(filter: "admins") do = search_field_tag :name, params[:name], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false
Admins = icon("search", class: "search-icon")
%small.badge= number_with_delimiter(User.admins.count) .dropdown
%li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"} - toggle_text = if @sort.present? then sort_options_hash[@sort] else sort_title_name end
= link_to admin_users_path(filter: 'two_factor_enabled') do = dropdown_toggle(toggle_text, { toggle: 'dropdown' })
2FA Enabled %ul.dropdown-menu.dropdown-menu-align-right
%small.badge= number_with_delimiter(User.with_two_factor.count) %li.dropdown-header
%li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"} Sort by
= link_to admin_users_path(filter: 'two_factor_disabled') do %li
2FA Disabled = link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do
%small.badge= number_with_delimiter(User.without_two_factor.count) = sort_title_name
%li.filter-external{class: "#{'active' if params[:filter] == 'external'}"} = link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do
= link_to admin_users_path(filter: 'external') do = sort_title_recently_signin
External = link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do
%small.badge= number_with_delimiter(User.external.count) = sort_title_oldest_signin
%li{class: "#{'active' if params[:filter] == "blocked"}"} = link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do
= link_to admin_users_path(filter: "blocked") do = sort_title_recently_created
Blocked = link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do
%small.badge= number_with_delimiter(User.blocked.count) = sort_title_oldest_created
%li{class: "#{'active' if params[:filter] == "wop"}"} = link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do
= link_to admin_users_path(filter: "wop") do = sort_title_recently_updated
Without projects = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
%small.badge= number_with_delimiter(User.without_projects.count) = sort_title_oldest_updated
= link_to 'New User', new_admin_user_path, class: 'btn btn-new btn-search'
.row-content-block.second-block .nav-block
.pull-right %ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs
.dropdown.inline .fade-left
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} = nav_link(html_options: { class: ('active' unless params[:filter]) }) do
%span.light = link_to admin_users_path do
- if @sort.present? Active
= sort_options_hash[@sort] %small.badge= number_with_delimiter(User.active.count)
- else = nav_link(html_options: { class: ('active' if params[:filter] == 'admins') }) do
= sort_title_name = link_to admin_users_path(filter: "admins") do
%b.caret Admins
%ul.dropdown-menu %small.badge= number_with_delimiter(User.admins.count)
%li = nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_enabled'} filter-two-factor-enabled" }) do
= link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do = link_to admin_users_path(filter: 'two_factor_enabled') do
= sort_title_name 2FA Enabled
= link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do %small.badge= number_with_delimiter(User.with_two_factor.count)
= sort_title_recently_signin = nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_disabled'} filter-two-factor-disabled" }) do
= link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do = link_to admin_users_path(filter: 'two_factor_disabled') do
= sort_title_oldest_signin 2FA Disabled
= link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do %small.badge= number_with_delimiter(User.without_two_factor.count)
= sort_title_recently_created = nav_link(html_options: { class: ('active' if params[:filter] == 'external') }) do
= link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do = link_to admin_users_path(filter: 'external') do
= sort_title_oldest_created External
= link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do %small.badge= number_with_delimiter(User.external.count)
= sort_title_recently_updated = nav_link(html_options: { class: ('active' if params[:filter] == 'blocked') }) do
= link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do = link_to admin_users_path(filter: "blocked") do
= sort_title_oldest_updated Blocked
%small.badge= number_with_delimiter(User.blocked.count)
= nav_link(html_options: { class: ('active' if params[:filter] == 'wop') }) do
= link_to admin_users_path(filter: "wop") do
Without projects
%small.badge= number_with_delimiter(User.without_projects.count)
.fade-right
= link_to 'New User', new_admin_user_path, class: "btn btn-new" %ul.users-list.content-list
= form_tag admin_users_path, method: :get, class: 'form-inline' do - if @users.empty?
.form-group %li
= search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false .nothing-here-block No users found.
= hidden_field_tag "filter", params[:filter] - else
= button_tag class: 'btn btn-primary' do = render partial: 'admin/users/user', collection: @users
%i.fa.fa-search
.panel.panel-default
%ul.well-list
- @users.each do |user|
%li
.list-item-name
- if user.blocked?
= icon("lock", class: "cred")
- else
= icon("user", class: "cgreen")
= link_to user.name, [:admin, user]
- if user.admin?
%strong.cred (Admin)
- if user.external?
%strong.cred (External)
- if user == current_user
%span.cred It's you!
.pull-right
%span.light
%i.fa.fa-envelope
= mail_to user.email, user.email, class: 'light'
&nbsp;
.pull-right
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn-grouped btn btn-xs'
- unless user == current_user
- if user.ldap_blocked?
= link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn-grouped btn btn-xs btn-success disabled' do
%i.fa.fa-lock
Unblock
- elsif user.blocked?
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success'
- else
= link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: 'btn-grouped btn btn-xs btn-warning'
- if user.access_locked?
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
- if user.can_be_removed?
= link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: 'btn-grouped btn btn-xs btn-remove'
= paginate @users, theme: "gitlab" = paginate @users, theme: "gitlab"
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
- if current_user - if current_user
.pull-right .pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
= icon('plus')
New Snippet New Snippet
.oneline .oneline
......
- if @note.legacy_diff_note? - if @note.diff_note?
%p.details %p.details
New comment on diff for New comment on diff for
= link_to @note.diff_file_path, @target_url = link_to @note.diff_file.file_path, @target_url
\: \:
= render 'note_message' = render 'note_message'
...@@ -72,12 +72,11 @@ ...@@ -72,12 +72,11 @@
The diff for this file was not included because it is too large. The diff for this file was not included because it is too large.
- else - else
%hr %hr
- diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last - blob = diff_file.blob
- blob = @message.project.repository.blob_for_diff(diff_commit, diff_file)
- if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob) - if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob)
%table.code.white %table.code.white
- diff_file.highlighted_diff_lines.each do |line| - diff_file.highlighted_diff_lines.each do |line|
= render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true} = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
- else - else
No preview for this file type No preview for this file type
%br %br
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/cropper.js')
= page_specific_javascript_tag('profile/application.js')
- page_title "Account" - page_title "Account"
= render 'profiles/head'
- if current_user.ldap_user? - if current_user.ldap_user?
.alert.alert-info .alert.alert-info
......
- page_title "Audit Log" - page_title "Audit Log"
= render 'profiles/head'
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
- page_title "Emails" - page_title "Emails"
= render 'profiles/head'
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.form-group .form-group
= f.label :key, class: 'label-light' = f.label :key, class: 'label-light'
= f.text_area :key, class: "form-control", rows: 8, required: true = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the SSH key. Paste the public part, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'."
.form-group .form-group
= f.label :title, class: 'label-light' = f.label :title, class: 'label-light'
= f.text_field :title, class: "form-control", required: true = f.text_field :title, class: "form-control", required: true
......
- page_title @key.title, "SSH Keys" - page_title @key.title, "SSH Keys"
= render 'profiles/head'
= render "key_details" = render "key_details"
- page_title "Notifications" - page_title "Notifications"
= render 'profiles/head'
%div %div
- if @user.errors.any? - if @user.errors.any?
......
- page_title "Personal Access Tokens" - page_title "Personal Access Tokens"
= render 'profiles/head'
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
- page_title 'Preferences' - page_title 'Preferences'
= render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
= render 'profiles/head'
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| = form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
= form_errors(@user) = form_errors(@user)
......
- page_title 'Two-Factor Authentication', 'Account' - page_title 'Two-Factor Authentication', 'Account'
- header_title "Two-Factor Authentication", profile_two_factor_auth_path - header_title "Two-Factor Authentication", profile_two_factor_auth_path
= render 'profiles/head'
.row.prepend-top-default .row.prepend-top-default
.col-lg-3 .col-lg-3
......
...@@ -4,12 +4,10 @@ ...@@ -4,12 +4,10 @@
%ul.nav-links.no-bottom.js-edit-mode %ul.nav-links.no-bottom.js-edit-mode
%li.active %li.active
= link_to '#editor' do = link_to '#editor' do
= icon('edit')
Edit File Edit File
%li %li
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
= icon('eye')
= editing_preview_title(@blob.name) = editing_preview_title(@blob.name)
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
%td %td
- if pipeline.started_at && pipeline.finished_at - if pipeline.started_at && pipeline.finished_at
%p.duration %p.duration
#{duration_in_words(pipeline.finished_at, pipeline.started_at)} = duration_in_numbers(pipeline.finished_at, pipeline.started_at)
%td %td
.controls.hidden-xs.pull-right .controls.hidden-xs.pull-right
......
...@@ -7,8 +7,7 @@ ...@@ -7,8 +7,7 @@
= render "ci_menu" = render "ci_menu"
- else - else
%div.block-connector %div.block-connector
= render "projects/diffs/diffs", diffs: @diffs, project: @project, = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @commit.diff_refs
diff_refs: @diff_refs
= render "projects/notes/notes_with_form" = render "projects/notes/notes_with_form"
- if can_collaborate_with_project? - if can_collaborate_with_project?
- %w(revert cherry-pick).each do |type| - %w(revert cherry-pick).each do |type|
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if diff_view == 'parallel' - if diff_view == 'parallel'
- fluid_layout true - fluid_layout true
- diff_files = safe_diff_files(diffs, diff_refs) - diff_files = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository)
.content-block.oneline-block.files-changed .content-block.oneline-block.files-changed
.inline-parallel-buttons .inline-parallel-buttons
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.files .files
- diff_files.each_with_index do |diff_file, index| - diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file) - diff_commit = commit_for_diff(diff_file)
- blob = project.repository.blob_for_diff(diff_commit, diff_file) - blob = diff_file.blob(diff_commit)
- next unless blob - next unless blob
- blob.load_all_data!(project.repository) unless blob.only_display_raw? - blob.load_all_data!(project.repository) unless blob.only_display_raw?
......
.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)} .diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_file)}
.file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"} .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- if diff_file.diff.submodule? = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{i}"
%span
= icon('archive fw')
%span
= submodule_link(blob, @commit.id, project.repository)
- else
= blob_icon blob.mode, blob.name
= link_to "#diff-#{i}" do
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
= old_path
&rarr;
= new_path
- else
%span
= diff_file.new_path
- if diff_file.deleted_file
deleted
- if diff_file.mode_changed?
%small
= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}"
- unless diff_file.submodule?
.file-actions.hidden-xs .file-actions.hidden-xs
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do
...@@ -42,17 +21,23 @@ ...@@ -42,17 +21,23 @@
- return unless blob.respond_to?(:text?) - return unless blob.respond_to?(:text?)
- if diff_file.too_large? - if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large. .nothing-here-block This diff could not be displayed because it is too large.
- elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif blob_text_viewable?(blob)
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- elsif blob.only_display_raw? - elsif blob.only_display_raw?
.nothing-here-block This file is too large to display. .nothing-here-block This file is too large to display.
- elsif blob_text_viewable?(blob)
- if !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif diff_file.diff_lines.length > 0
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- else
- if diff_file.mode_changed?
.nothing-here-block File mode changed
- elsif diff_file.renamed_file
.nothing-here-block File moved
- elsif blob.image? - elsif blob.image?
- old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file) - old_blob = diff_file.old_blob(diff_commit)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob, index: i
- else - else
.nothing-here-block No preview for this file type .nothing-here-block No preview for this file type
- if defined?(blob) && blob && diff_file.submodule?
%span
= icon('archive fw')
%span
= submodule_link(blob, diff_commit.id, project.repository)
- else
= conditional_link_to url.present?, url do
= blob_icon diff_file.b_mode, diff_file.file_path
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
%strong
= old_path
&rarr;
%strong
= new_path
- else
%strong
= diff_file.new_path
- if diff_file.deleted_file
deleted
- if diff_file.mode_changed?
%small
= "#{diff_file.a_mode}#{diff_file.b_mode}"
- diff = diff_file.diff - diff = diff_file.diff
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path)) - file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))
// diff_refs will be nil for orphaned commits (e.g. first commit in repo) // diff_refs will be nil for orphaned commits (e.g. first commit in repo)
- if diff_refs - if diff_file.old_ref
- old_commit_id = diff_refs.first.id - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))
- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file - if diff.renamed_file || diff.new_file || diff.deleted_file
.image .image
...@@ -16,7 +15,7 @@ ...@@ -16,7 +15,7 @@
%div.two-up.view %div.two-up.view
%span.wrap %span.wrap
.frame.deleted .frame.deleted
%a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))} %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))}
%img{src: old_file_raw_path} %img{src: old_file_raw_path}
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}" %span.meta-filesize= "#{number_to_human_size old_file.size}"
...@@ -28,7 +27,7 @@ ...@@ -28,7 +27,7 @@
%span.meta-height %span.meta-height
%span.wrap %span.wrap
.frame.added .frame.added
%a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))} %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))}
%img{src: file_raw_path} %img{src: file_raw_path}
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}" %span.meta-filesize= "#{number_to_human_size file.size}"
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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