summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml2
-rw-r--r--CHANGELOG27
-rw-r--r--Gemfile104
-rw-r--r--Gemfile.lock44
-rw-r--r--app/assets/javascripts/application.js.coffee2
-rw-r--r--app/assets/javascripts/build.coffee (renamed from app/assets/javascripts/ci/build.coffee)6
-rw-r--r--app/assets/javascripts/ci/application.js.coffee12
-rw-r--r--app/assets/javascripts/ci/projects.js.coffee3
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee1
-rw-r--r--app/assets/javascripts/flash.js.coffee30
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee4
-rw-r--r--app/assets/javascripts/lib/cropper.js.coffee1
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.coffee7
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee1
-rw-r--r--app/assets/javascripts/notes.js.coffee8
-rw-r--r--app/assets/javascripts/profile/application.js.coffee2
-rw-r--r--app/assets/javascripts/profile/gl_crop.js.coffee (renamed from app/assets/javascripts/gl_crop.js.coffee)0
-rw-r--r--app/assets/javascripts/profile/profile.js.coffee (renamed from app/assets/javascripts/profile.js.coffee)3
-rw-r--r--app/assets/javascripts/protected_branch_select.js.coffee40
-rw-r--r--app/assets/javascripts/protected_branches.js.coffee3
-rw-r--r--app/assets/javascripts/single_file_diff.js.coffee54
-rw-r--r--app/assets/stylesheets/framework/blank.scss23
-rw-r--r--app/assets/stylesheets/framework/blocks.scss3
-rw-r--r--app/assets/stylesheets/framework/flash.scss22
-rw-r--r--app/assets/stylesheets/framework/lists.scss6
-rw-r--r--app/assets/stylesheets/framework/nav.scss4
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss7
-rw-r--r--app/assets/stylesheets/framework/variables.scss12
-rw-r--r--app/assets/stylesheets/pages/issues.scss4
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/concerns/diff_for_path.rb25
-rw-r--r--app/controllers/projects/artifacts_controller.rb5
-rw-r--r--app/controllers/projects/commit_controller.rb49
-rw-r--r--app/controllers/projects/compare_controller.rb46
-rw-r--r--app/controllers/projects/merge_requests_controller.rb134
-rw-r--r--app/controllers/projects/protected_branches_controller.rb26
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/helpers/appearances_helper.rb4
-rw-r--r--app/helpers/diff_helper.rb20
-rw-r--r--app/helpers/merge_requests_helper.rb8
-rw-r--r--app/helpers/time_helper.rb7
-rw-r--r--app/helpers/workhorse_helper.rb6
-rw-r--r--app/models/application_setting.rb1
-rw-r--r--app/models/ci/build.rb5
-rw-r--r--app/models/commit_range.rb4
-rw-r--r--app/models/merge_request.rb10
-rw-r--r--app/models/merge_request_diff.rb9
-rw-r--r--app/models/project.rb25
-rw-r--r--app/models/project_services/irker_service.rb10
-rw-r--r--app/models/protected_branch.rb47
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/models/sent_notification.rb13
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/compare_service.rb2
-rw-r--r--app/services/merge_requests/refresh_service.rb11
-rw-r--r--app/views/admin/abuse_reports/_abuse_report.html.haml4
-rw-r--r--app/views/admin/application_settings/_form.html.haml7
-rw-r--r--app/views/admin/users/_form.html.haml2
-rw-r--r--app/views/explore/snippets/index.html.haml1
-rw-r--r--app/views/layouts/_flash.html.haml2
-rw-r--r--app/views/profiles/_head.html.haml3
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/profiles/audit_log.html.haml1
-rw-r--r--app/views/profiles/emails/index.html.haml1
-rw-r--r--app/views/profiles/keys/_form.html.haml2
-rw-r--r--app/views/profiles/keys/show.html.haml1
-rw-r--r--app/views/profiles/notifications/show.html.haml1
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml1
-rw-r--r--app/views/profiles/preferences/show.html.haml1
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml1
-rw-r--r--app/views/projects/builds/show.html.haml2
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml2
-rw-r--r--app/views/projects/diffs/_content.html.haml29
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml26
-rw-r--r--app/views/projects/diffs/_warning.html.haml3
-rw-r--r--app/views/projects/issues/index.html.haml48
-rw-r--r--app/views/projects/merge_requests/_show.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml8
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml6
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml33
-rw-r--r--app/views/projects/protected_branches/_dropdown.html.haml17
-rw-r--r--app/views/projects/protected_branches/_matching_branch.html.haml9
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml21
-rw-r--r--app/views/projects/protected_branches/index.html.haml24
-rw-r--r--app/views/projects/protected_branches/show.html.haml25
-rw-r--r--app/views/projects/snippets/_actions.html.haml42
-rw-r--r--app/views/projects/snippets/index.html.haml6
-rw-r--r--app/views/shared/icons/_issues.svg13
-rw-r--r--app/views/shared/icons/_issues.svg.erb4
-rw-r--r--app/views/snippets/_actions.html.haml41
-rw-r--r--app/views/users/show.html.haml5
-rw-r--r--config/application.rb1
-rw-r--r--config/routes.rb17
-rw-r--r--db/migrate/20160608211215_add_user_default_external_to_application_settings.rb13
-rw-r--r--db/migrate/20160616102642_remove_duplicated_keys.rb4
-rw-r--r--db/migrate/20160620110927_fix_no_validatable_import_url.rb86
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/custom_hooks.md3
-rw-r--r--doc/api/groups.md247
-rw-r--r--doc/api/merge_requests.md18
-rw-r--r--doc/api/projects.md119
-rw-r--r--doc/ci/examples/php.md2
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--doc/development/ui_guide.md3
-rw-r--r--doc/integration/github.md2
-rw-r--r--doc/markdown/markdown.md34
-rw-r--r--doc/permissions/permissions.md3
-rw-r--r--doc/update/8.9-to-8.10.md2
-rw-r--r--doc/workflow/protected_branches.md28
-rw-r--r--doc/workflow/protected_branches/protected_branches1.pngbin155815 -> 195061 bytes
-rw-r--r--doc/workflow/protected_branches/protected_branches2.pngbin23208 -> 41179 bytes
-rw-r--r--doc/workflow/protected_branches/protected_branches3.pngbin0 -> 110160 bytes
-rw-r--r--features/dashboard/dashboard.feature1
-rw-r--r--features/project/commits/commits.feature5
-rw-r--r--features/project/merge_requests.feature7
-rw-r--r--features/steps/project/builds/artifacts.rb14
-rw-r--r--features/steps/project/commits/commits.rb19
-rw-r--r--features/steps/shared/project.rb5
-rw-r--r--lib/api/entities.rb13
-rw-r--r--lib/banzai/filter/blockquote_fence_filter.rb71
-rw-r--r--lib/banzai/pipeline/pre_process_pipeline.rb3
-rw-r--r--lib/banzai/reference_parser/base_parser.rb36
-rw-r--r--lib/banzai/reference_parser/user_parser.rb5
-rw-r--r--lib/gitlab/current_settings.rb1
-rw-r--r--lib/gitlab/diff/file.rb4
-rw-r--r--lib/gitlab/git/hook.rb9
-rw-r--r--lib/gitlab/graphs/commits.rb2
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb9
-rw-r--r--lib/gitlab/url_sanitizer.rb2
-rw-r--r--lib/gitlab/workhorse.rb12
-rw-r--r--lib/rouge/formatters/html_gitlab.rb16
-rw-r--r--spec/controllers/commit_controller_spec.rb246
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb289
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb69
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb244
-rw-r--r--spec/factories/projects.rb2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb30
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb207
-rw-r--r--spec/features/issues/filter_issues_spec.rb4
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb2
-rw-r--r--spec/features/protected_branches_spec.rb84
-rw-r--r--spec/features/search_spec.rb2
-rw-r--r--spec/fixtures/blockquote_fence_after.md115
-rw-r--r--spec/fixtures/blockquote_fence_before.md131
-rw-r--r--spec/helpers/diff_helper_spec.rb17
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml2
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb14
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb75
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb70
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb39
-rw-r--r--spec/lib/gitlab/import_export/project.json13
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb18
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb6
-rw-r--r--spec/models/merge_request_diff_spec.rb47
-rw-r--r--spec/models/merge_request_spec.rb25
-rw-r--r--spec/models/project_spec.rb64
-rw-r--r--spec/models/protected_branch_spec.rb125
-rw-r--r--spec/models/user_spec.rb21
-rw-r--r--spec/requests/api/groups_spec.rb19
-rw-r--r--spec/requests/api/projects_spec.rb40
-rw-r--r--spec/services/notification_service_spec.rb24
-rw-r--r--spec/support/capybara_helpers.rb8
-rw-r--r--spec/support/test_env.rb29
166 files changed, 3162 insertions, 1056 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index cd13f581517..3aac8401848 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -284,7 +284,7 @@ Style/IfWithSemicolon:
# 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.
Style/IdenticalConditionalBranches:
- Enabled: false
+ Enabled: true
# Checks the indentation of the first line of the right-hand-side of a
# multi-line assignment.
diff --git a/CHANGELOG b/CHANGELOG
index 2c58a312208..eccdcc988fa 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@ v 8.10.0 (unreleased)
- Replace Haml with Hamlit to make view rendering faster. !3666
- Refactor repository paths handling to allow multiple git mount points
- Add Application Setting to configure default Repository Path for new projects
+ - Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell)
- Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- Align flash messages with left side of page content !4959 (winniehell)
- Display last commit of deleted branch in push events !4699 (winniehell)
@@ -18,27 +19,35 @@ v 8.10.0 (unreleased)
- Fix MR-auto-close text added to description. !4836
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- 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)
+ - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
- Updated project header design
- 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
- Add notification settings dropdown for groups
+ - Wildcards for protected branches. !4665
- Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- 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
- Diffs will create button/diff form on demand no on server side
- Reduce size of HTML used by diff comment forms
- 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
- Throttle the update of `project.pushes_since_gc` to 1 minute.
+ - Allow expanding and collapsing files in diff view (!4990)
+ - Collapse large diffs by default (!4990)
- Check for conflicts with existing Project's wiki path when creating a new project.
- Show last push widget in upstream after push to fork
- Don't instantiate a git tree on Projects show default view
- Bump Rinku to 2.0.0
- Remove unused front-end variable -> default_issues_tracker
- 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 Bugzilla integration !4930 (iamtjg)
- Instrument Rinku usage
@@ -46,6 +55,8 @@ v 8.10.0 (unreleased)
- 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)
- 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
- Don't garbage collect commits that have related DB records like comments
- More descriptive message for git hooks and file locks
@@ -54,6 +65,14 @@ v 8.10.0 (unreleased)
- 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
- Fix 404 redirect after validation fails importing a GitLab project
+ - 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)
+ - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel)
+
+v 8.9.6 (unreleased)
+ - Fix importing of events under notes for GitLab projects
v 8.9.5
- Add more debug info to import/export and memory killer. !5108
@@ -98,7 +117,7 @@ v 8.9.3
- Removed fade when filtering results. !4932
- Fix missing avatar on system notes. !4954
- Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973
- - Use update_columns to by_pass all the dirty code on active_record. !4985
+ - Use update_columns to bypass all the dirty code on active_record. !4985
- Fix restore Rake task warning message output !4980
v 8.9.2
@@ -2602,13 +2621,13 @@ v 6.5.0
- Files API supports base64 encoded content (sponsored by O'Reilly Media)
- Added support for Go's repository retrieval (Bruno Albuquerque)
-v6.4.3
+v 6.4.3
- Don't use unicorn worker killer if PhusionPassenger is defined
-v6.4.2
+v 6.4.2
- Fixed wrong behaviour of script/upgrade.rb
-v6.4.1
+v 6.4.1
- Fixed bug with repository rename
- Fixed bug with project transfer
diff --git a/Gemfile b/Gemfile
index 99ca6608a6f..f1fef4caf76 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,4 @@
-source "https://rubygems.org"
+source 'https://rubygems.org'
gem 'rails', '4.2.6'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
@@ -11,11 +11,11 @@ gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.6.0'
# Default values for AR models
-gem "default_value_for", "~> 3.0.0"
+gem 'default_value_for', '~> 3.0.0'
# Supported DBs
-gem "mysql2", '~> 0.3.16', group: :mysql
-gem "pg", '~> 0.18.2', group: :postgres
+gem 'mysql2', '~> 0.3.16', group: :mysql
+gem 'pg', '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 4.0'
@@ -28,7 +28,7 @@ gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 3.0.0'
gem 'omniauth-github', '~> 1.1.1'
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-saml', '~> 1.6.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
@@ -48,16 +48,16 @@ gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1'
# Browser detection
-gem "browser", '~> 2.2'
+gem 'browser', '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 10.2'
+gem 'gitlab_git', '~> 10.2'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# 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
# Required manually in config/initializers/gollum.rb to control load order
@@ -65,7 +65,7 @@ gem 'gollum-lib', '~> 4.1.0', require: false
gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
# Language detection
-gem "github-linguist", "~> 4.7.0", require: "linguist"
+gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
gem 'grape', '~> 0.13.0'
@@ -73,13 +73,13 @@ gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination
-gem "kaminari", "~> 0.17.0"
+gem 'kaminari', '~> 0.17.0'
# HAML
gem 'hamlit', '~> 2.5'
# Files attachments
-gem "carrierwave", '~> 0.10.0'
+gem 'carrierwave', '~> 0.10.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
@@ -94,13 +94,13 @@ gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1'
# for aws storage
-gem "unf", '~> 0.1.4'
+gem 'unf', '~> 0.1.4'
# Authorization
-gem "six", '~> 0.2.0'
+gem 'six', '~> 0.2.0'
# Seed data
-gem "seed-fu", '~> 2.3.5'
+gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
@@ -124,29 +124,29 @@ gem 'diffy', '~> 3.0.3'
# Application server
group :unicorn do
- gem "unicorn", '~> 4.9.0'
+ gem 'unicorn', '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2'
end
# State machine
-gem "state_machines-activerecord", '~> 0.4.0'
+gem 'state_machines-activerecord', '~> 0.4.0'
# Run events after state machine commits
-gem 'after_commit_queue'
+gem 'after_commit_queue', '~> 1.3.0'
# Issue tags
gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs
-gem 'sinatra', '~> 1.4.4', require: nil
+gem 'sinatra', '~> 1.4.4', require: false
gem 'sidekiq', '~> 4.0'
gem 'sidekiq-cron', '~> 0.4.0'
-gem 'redis-namespace'
+gem 'redis-namespace', '~> 1.5.2'
# HTTP requests
-gem "httparty", '~> 0.13.3'
+gem 'httparty', '~> 0.13.3'
# Colored output to console
-gem "rainbow", '~> 2.1.0'
+gem 'rainbow', '~> 2.1.0'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
@@ -156,7 +156,7 @@ gem 'settingslogic', '~> 2.0.9'
gem 'version_sorter', '~> 2.0.0'
# Cache
-gem "redis-rails", '~> 4.0.0'
+gem 'redis-rails', '~> 4.0.0'
# Redis
gem 'redis', '~> 3.2'
@@ -169,13 +169,13 @@ gem 'tinder', '~> 1.10.0'
gem 'hipchat', '~> 1.5.0'
# Flowdock integration
-gem "gitlab-flowdock-git-hook", "~> 1.0.1"
+gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
# Gemnasium integration
-gem "gemnasium-gitlab-service", "~> 0.2"
+gem 'gemnasium-gitlab-service', '~> 0.2'
# Slack integration
-gem "slack-notifier", "~> 1.2.0"
+gem 'slack-notifier', '~> 1.2.0'
# Asana integration
gem 'asana', '~> 0.4.0'
@@ -187,20 +187,20 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'd3_rails', '~> 3.5.0'
# underscore-rails
-gem "underscore-rails", "~> 1.8.0"
+gem 'underscore-rails', '~> 1.8.0'
# Sanitize user input
-gem "sanitize", '~> 2.0'
+gem 'sanitize', '~> 2.0'
gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input
-gem "loofah", "~> 2.0.3"
+gem 'loofah', '~> 2.0.3'
# Working with license
gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing
-gem "rack-attack", '~> 4.3.1'
+gem 'rack-attack', '~> 4.3.1'
# Ace editor
gem 'ace-rails-ap', '~> 4.0.2'
@@ -214,9 +214,9 @@ gem 'charlock_holmes', '~> 0.7.3'
# Parse duration
gem 'chronic_duration', '~> 0.10.6'
-gem "sass-rails", '~> 5.0.0'
-gem "coffee-rails", '~> 4.1.0'
-gem "uglifier", '~> 2.7.2'
+gem 'sass-rails', '~> 5.0.0'
+gem 'coffee-rails', '~> 4.1.0'
+gem 'uglifier', '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.1.0'
@@ -247,13 +247,13 @@ group :metrics do
end
group :development do
- gem "foreman"
+ gem 'foreman', '~> 0.78.0'
gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0'
gem 'rerun', '~> 0.11.0'
- gem 'bullet', require: false
- gem 'rblineprof', platform: :mri, require: false
+ gem 'bullet', '~> 5.0.0', require: false
+ gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'web-console', '~> 2.0'
# Better errors handler
@@ -261,15 +261,15 @@ group :development do
gem 'binding_of_caller', '~> 0.7.2'
# Docs generator
- gem "sdoc", '~> 0.3.20'
+ gem 'sdoc', '~> 0.3.20'
# thin instead webrick
gem 'thin', '~> 1.7.0'
end
group :development, :test do
- gem 'byebug', platform: :mri
- gem 'pry-rails'
+ gem 'byebug', '~> 8.2.1', platform: :mri
+ gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
@@ -277,7 +277,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.5.0'
- gem 'rspec-retry'
+ gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
@@ -303,14 +303,14 @@ group :development, :test do
gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'simplecov', '~> 0.11.0', require: false
- gem 'flog', require: false
- gem 'flay', require: false
- gem 'bundler-audit', require: false
+ gem 'flog', '~> 4.3.2', require: false
+ gem 'flay', '~> 2.6.1', 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 'knapsack'
+ gem 'license_finder', '~> 2.1.0', require: false
+ gem 'knapsack', '~> 1.11.0'
end
group :test do
@@ -318,30 +318,30 @@ group :test do
gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0'
gem 'test_after_commit', '~> 0.4.2'
- gem 'sham_rack'
+ gem 'sham_rack', '~> 1.3.6'
end
group :production do
- gem "gitlab_meta", '7.0'
+ gem 'gitlab_meta', '7.0'
end
-gem "newrelic_rpm", '~> 3.14'
+gem 'newrelic_rpm', '~> 3.14'
gem 'octokit', '~> 4.3.0'
-gem "mail_room", "~> 0.8"
+gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8'
## CI
gem 'activerecord-session_store', '~> 1.0.0'
-gem "nested_form", '~> 0.3.2'
+gem 'nested_form', '~> 0.3.2'
# OAuth
-gem 'oauth2', '~> 1.0.0'
+gem 'oauth2', '~> 1.2.0'
# Soft deletion
-gem "paranoia", "~> 2.0"
+gem 'paranoia', '~> 2.0'
# Health check
gem 'health_check', '~> 1.5.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index af8fbedc127..721ab9ddc5d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -353,7 +353,7 @@ GEM
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
json (1.8.3)
- jwt (1.5.2)
+ jwt (1.5.4)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
@@ -393,7 +393,7 @@ GEM
mini_portile2 (2.1.0)
minitest (5.7.0)
mousetrap-rails (1.4.6)
- multi_json (1.11.2)
+ multi_json (1.12.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
mysql2 (0.3.20)
@@ -406,12 +406,12 @@ GEM
pkg-config (~> 1.1.7)
numerizer (0.1.1)
oauth (0.4.7)
- oauth2 (1.0.0)
+ oauth2 (1.2.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
- rack (~> 1.2)
+ rack (>= 1.2, < 3)
octokit (4.3.0)
sawyer (~> 0.7.0, >= 0.5.3)
omniauth (1.3.1)
@@ -439,7 +439,7 @@ GEM
omniauth-gitlab (1.0.1)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0)
- omniauth-google-oauth2 (0.2.10)
+ omniauth-google-oauth2 (0.4.1)
addressable (~> 2.3)
jwt (~> 1.0)
multi_json (~> 1.3)
@@ -806,7 +806,7 @@ DEPENDENCIES
activerecord-session_store (~> 1.0.0)
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
- after_commit_queue
+ after_commit_queue (~> 1.3.0)
akismet (~> 2.0)
allocations (~> 1.0)
asana (~> 0.4.0)
@@ -815,15 +815,15 @@ DEPENDENCIES
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
- benchmark-ips
+ benchmark-ips (~> 2.3.0)
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0)
browser (~> 2.2)
- bullet
- bundler-audit
- byebug
+ bullet (~> 5.0.0)
+ bundler-audit (~> 0.5.0)
+ byebug (~> 8.2.1)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
@@ -844,8 +844,8 @@ DEPENDENCIES
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0)
- flay
- flog
+ flay (~> 2.6.1)
+ flog (~> 4.3.2)
fog-aws (~> 0.9)
fog-azure (~> 0.0)
fog-core (~> 1.40)
@@ -854,7 +854,7 @@ DEPENDENCIES
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.6.1)
- foreman
+ foreman (~> 0.78.0)
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 2.6)
@@ -881,9 +881,9 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0)
jwt
kaminari (~> 0.17.0)
- knapsack
+ knapsack (~> 1.11.0)
letter_opener_web (~> 1.3.0)
- license_finder
+ license_finder (~> 2.1.0)
licensee (~> 8.0.0)
loofah (~> 2.0.3)
mail_room (~> 0.8)
@@ -895,7 +895,7 @@ DEPENDENCIES
net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2)
- oauth2 (~> 1.0.0)
+ oauth2 (~> 1.2.0)
octokit (~> 4.3.0)
omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1)
@@ -905,7 +905,7 @@ DEPENDENCIES
omniauth-facebook (~> 3.0.0)
omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.0)
- omniauth-google-oauth2 (~> 0.2.0)
+ omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.6.0)
omniauth-shibboleth (~> 1.2.0)
@@ -916,19 +916,19 @@ DEPENDENCIES
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
- pry-rails
+ pry-rails (~> 0.3.4)
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
- rblineprof
+ rblineprof (~> 0.3.6)
rdoc (~> 3.6)
recaptcha (~> 3.0)
redcarpet (~> 3.3.3)
redis (~> 3.2)
- redis-namespace
+ redis-namespace (~> 1.5.2)
redis-rails (~> 4.0.0)
request_store (~> 1.3.0)
rerun (~> 0.11.0)
@@ -936,7 +936,7 @@ DEPENDENCIES
rouge (~> 1.11)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0)
- rspec-retry
+ rspec-retry (~> 0.4.5)
rubocop (~> 0.40.0)
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
@@ -948,7 +948,7 @@ DEPENDENCIES
select2-rails (~> 3.5.9)
sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9)
- sham_rack
+ sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 20fe5a5cc27..c98763d6271 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -47,14 +47,12 @@
#= require date.format
#= require_directory ./behaviors
#= require_directory ./blob
-#= require_directory ./ci
#= require_directory ./commit
#= require_directory ./extensions
#= require_directory ./lib/utils
#= require_directory ./u2f
#= require_directory .
#= require fuzzaldrin-plus
-#= require cropper
#= require u2f
window.slugify = (text) ->
diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/build.coffee
index 74691b2c1b5..cf203ea43a0 100644
--- a/app/assets/javascripts/ci/build.coffee
+++ b/app/assets/javascripts/build.coffee
@@ -1,9 +1,9 @@
-class @CiBuild
+class @Build
@interval: null
@state: null
constructor: (@page_url, @build_url, @build_status, @state) ->
- clearInterval(CiBuild.interval)
+ clearInterval(Build.interval)
# Init breakpoint checker
@bp = Breakpoints.get()
@@ -40,7 +40,7 @@ class @CiBuild
# Check for new build output if user still watching build page
# Only valid for runnig build when output changes during time
#
- CiBuild.interval = setInterval =>
+ Build.interval = setInterval =>
if window.location.href.split("#").first() is @page_url
@getBuildTrace()
, 4000
diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee
deleted file mode 100644
index ca24c1d759f..00000000000
--- a/app/assets/javascripts/ci/application.js.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-#= require pager
-#= require jquery_nested_form
-#= require_tree .
-
-$(document).on 'click', '.assign-all-runner', ->
- $(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..')
-
-window.unbindEvents = ->
- $(document).unbind('scroll')
- $(document).off('scroll')
-
-document.addEventListener("page:fetch", unbindEvents)
diff --git a/app/assets/javascripts/ci/projects.js.coffee b/app/assets/javascripts/ci/projects.js.coffee
deleted file mode 100644
index e6406011d11..00000000000
--- a/app/assets/javascripts/ci/projects.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-$(document).on 'click', '.badge-codes-toggle', ->
- $('.badge-codes-block').toggleClass("hide")
- return false
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index a39df421832..74fd77cf7ab 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -131,7 +131,6 @@ class Dispatcher
when 'dashboard', 'root'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles'
- new Profile()
new NotificationsForm()
new NotificationsDropdown()
when 'projects'
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index b76d214790a..5a493041538 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,24 +1,28 @@
class @Flash
- constructor: (message, type = 'alert')->
- @flash = $(".flash-container")
- @flash.html("")
+ hideFlash = -> $(@).fadeOut()
- innerDiv = $('<div/>',
+ constructor: (message, type = 'alert', parent = null)->
+ if parent
+ @flashContainer = parent.find('.flash-container')
+ else
+ @flashContainer = $('.flash-container-page')
+
+ @flashContainer.html('')
+
+ flash = $('<div/>',
class: "flash-#{type}"
)
- innerDiv.appendTo(".flash-container")
+ flash.on 'click', hideFlash
- textDiv = $("<div/>",
- class: "flash-text",
+ textDiv = $('<div/>',
+ class: 'flash-text',
text: message
)
- textDiv.appendTo(innerDiv)
+ textDiv.appendTo(flash)
- if @flash.parent().hasClass('content-wrapper')
+ if @flashContainer.parent().hasClass('content-wrapper')
textDiv.addClass('container-fluid container-limited')
- @flash.click -> $(@).fadeOut()
- @flash.show()
+ flash.appendTo(@flashContainer)
+ @flashContainer.show()
- pinTo: (selector) ->
- @flash.detach().appendTo(selector)
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index ed9dfcc917e..1c65e833d47 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -56,6 +56,7 @@ class GitLabDropdownFilter
return BLUR_KEYCODES.indexOf(keyCode) >= 0
filter: (search_text) ->
+ @options.onFilter(search_text) if @options.onFilter
data = @options.data()
if data? and not @options.filterByText
@@ -195,6 +196,7 @@ class GitLabDropdown
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
filterByText: @options.filterByText
+ onFilter: @options.onFilter
remote: @options.filterRemote
query: @options.data
keys: searchFields
@@ -530,7 +532,7 @@ class GitLabDropdown
if $el.length
e.preventDefault()
e.stopImmediatePropagation()
- $(selector, @dropdown)[0].click()
+ $el.first().trigger('click')
addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40]
diff --git a/app/assets/javascripts/lib/cropper.js.coffee b/app/assets/javascripts/lib/cropper.js.coffee
new file mode 100644
index 00000000000..32536d23fe3
--- /dev/null
+++ b/app/assets/javascripts/lib/cropper.js.coffee
@@ -0,0 +1 @@
+#= require cropper
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee
index e39dcb2daa9..d4dd3dc329a 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.coffee
+++ b/app/assets/javascripts/lib/utils/common_utils.js.coffee
@@ -5,12 +5,12 @@
w.gl.utils.isInGroupsPage = ->
- return $('body').data('page').split(':')[0] is 'groups'
+ return gl.utils.getPagePath() is 'groups'
w.gl.utils.isInProjectPage = ->
- return $('body').data('page').split(':')[0] is 'projects'
+ return gl.utils.getPagePath() is 'projects'
w.gl.utils.getProjectSlug = ->
@@ -40,6 +40,9 @@
e.stopImmediatePropagation()
return false
+ gl.utils.getPagePath = ->
+ return $('body').data('page').split(':')[0]
+
jQuery.timefor = (time, suffix, expiredLabel) ->
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 87916823f81..86539e0d725 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -159,6 +159,7 @@ class @MergeRequestTabs
$('#diffs').html data.html
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('#diffs .js-syntax-highlight').syntaxHighlight()
+ $('#diffs .diff-file').singleFileDiff()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 0b7d8f64456..0ea54faae1a 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -194,8 +194,7 @@ class @Notes
renderNote: (note) ->
unless note.valid
if note.award
- flash = new Flash('You have already awarded this emoji!', 'alert')
- flash.pinTo('.header-content')
+ new Flash('You have already awarded this emoji!', 'alert')
return
if note.award
@@ -325,6 +324,8 @@ class @Notes
form.find("#note_position").remove()
form.find("#note_type").remove()
+ @parentTimeline = form.parents('.timeline')
+
###
General note form setup.
@@ -357,8 +358,7 @@ class @Notes
@renderNote(note)
addNoteError: (xhr, note, status) =>
- flash = new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert')
- flash.pinTo('.md-area')
+ new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', @parentTimeline)
###
Called in response to the new note form being submitted
diff --git a/app/assets/javascripts/profile/application.js.coffee b/app/assets/javascripts/profile/application.js.coffee
new file mode 100644
index 00000000000..91cacfece46
--- /dev/null
+++ b/app/assets/javascripts/profile/application.js.coffee
@@ -0,0 +1,2 @@
+#
+#= require_tree .
diff --git a/app/assets/javascripts/gl_crop.js.coffee b/app/assets/javascripts/profile/gl_crop.js.coffee
index df9bfdfa6cc..df9bfdfa6cc 100644
--- a/app/assets/javascripts/gl_crop.js.coffee
+++ b/app/assets/javascripts/profile/gl_crop.js.coffee
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee
index 1583d1ba6f9..f3b05f2c646 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile/profile.js.coffee
@@ -78,3 +78,6 @@ $ ->
if comment && comment.length > 1 && $title.val() == ''
$title.val(comment[1]).change()
+
+ if gl.utils.getPagePath() == 'profiles'
+ new Profile()
diff --git a/app/assets/javascripts/protected_branch_select.js.coffee b/app/assets/javascripts/protected_branch_select.js.coffee
new file mode 100644
index 00000000000..6d45770ace9
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_select.js.coffee
@@ -0,0 +1,40 @@
+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();
+
diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee
index 5753c9d4e72..79c2306e4d2 100644
--- a/app/assets/javascripts/protected_branches.js.coffee
+++ b/app/assets/javascripts/protected_branches.js.coffee
@@ -11,7 +11,8 @@ $ ->
dataType: "json"
data:
id: id
- developers_can_push: checked
+ protected_branch:
+ developers_can_push: checked
success: ->
row = $(e.target)
diff --git a/app/assets/javascripts/single_file_diff.js.coffee b/app/assets/javascripts/single_file_diff.js.coffee
new file mode 100644
index 00000000000..f3e225c3728
--- /dev/null
+++ b/app/assets/javascripts/single_file_diff.js.coffee
@@ -0,0 +1,54 @@
+class @SingleFileDiff
+
+ WRAPPER = '<div class="diff-content diff-wrap-lines"></div>'
+ LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'
+ ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'
+ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>'
+
+ constructor: (@file) ->
+ @content = $('.diff-content', @file)
+ @diffForPath = @content.find('[data-diff-for-path]').data 'diff-for-path'
+ @isOpen = !@diffForPath
+
+ if @diffForPath
+ @collapsedContent = @content
+ @loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide()
+ @content = null
+ @collapsedContent.after(@loadingContent)
+ else
+ @collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide()
+ @content.after(@collapsedContent)
+
+ @collapsedContent.on 'click', @toggleDiff
+
+ $('.file-title > a', @file).on 'click', @toggleDiff
+
+ toggleDiff: (e) =>
+ @isOpen = !@isOpen
+ if not @isOpen and not @hasError
+ @content.hide()
+ @collapsedContent.show()
+ else if @content
+ @collapsedContent.hide()
+ @content.show()
+ else
+ @getContentHTML()
+
+ getContentHTML: ->
+ @collapsedContent.hide()
+ @loadingContent.show()
+ $.get @diffForPath, (data) =>
+ @loadingContent.hide()
+ if data.html
+ @content = $(data.html)
+ @content.syntaxHighlight()
+ else
+ @hasError = true
+ @content = $(ERROR_HTML)
+ @collapsedContent.after(@content)
+ return
+
+$.fn.singleFileDiff = ->
+ return @each ->
+ if not $.data this, 'singleFileDiff'
+ $.data this, 'singleFileDiff', new SingleFileDiff this
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
index 40b5171a8c6..d28cda6d62d 100644
--- a/app/assets/stylesheets/framework/blank.scss
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -1,3 +1,12 @@
+.blank-state-welcome {
+ text-align: center;
+ border-bottom: 1px solid $border-color;
+
+ .blank-state-text {
+ margin-bottom: 0;
+ }
+}
+
.blank-state {
padding-top: 20px;
padding-bottom: 20px;
@@ -6,7 +15,15 @@
.blank-state-no-icon {
padding-top: 40px;
- padding-bottom: 40px;
+ padding-bottom: 40px;
+}
+
+.blank-state-icon {
+ padding-bottom: 20px;
+
+ path {
+ fill: $gray-darkest;
+ }
}
.blank-state-title {
@@ -21,3 +38,7 @@
margin-bottom: $gl-padding;
font-size: 15px;
}
+
+.blank-state-welcome-title {
+ font-size: 24px;
+}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 41e77a4ac68..24b1ebab4b0 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -16,6 +16,9 @@
font-weight: normal;
font-size: 16px;
line-height: 36px;
+ &.diff-collapsed {
+ cursor: pointer;
+ }
}
.row-content-block {
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index a951a2b97fe..0c21d0240b3 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -1,8 +1,8 @@
.flash-container {
cursor: pointer;
margin: 0;
+ margin-bottom: $gl-padding;
font-size: 14px;
- width: 100%;
z-index: 100;
.flash-notice {
@@ -18,9 +18,27 @@
}
.flash-notice, .flash-alert {
- .container-fluid.flash-text {
+ border-radius: $border-radius-default;
+
+ .container-fluid.container-limited.flash-text {
background: transparent;
}
}
+
+ &.flash-container-page {
+ margin-bottom: 0;
+
+ .flash-notice, .flash-alert {
+ border-radius: 0;
+ }
+ }
+}
+
+@media (max-width: $screen-md-min) {
+ ul.notes {
+ .flash-container.timeline-content {
+ margin-left: 0;
+ }
+ }
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index aed0b44d91b..2c40ec430ca 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -175,6 +175,12 @@ ul.content-list {
.panel > .content-list > li {
padding: $gl-padding-top $gl-padding;
+
+ &.commit {
+ @media (min-width: $screen-sm-min) {
+ padding-left: 46px + $gl-padding;
+ }
+ }
}
ul.controls {
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 02ea98e9d94..364952d3b4a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -77,10 +77,10 @@
&.sub-nav {
text-align: center;
- background-color: $background-color;
+ background-color: $dark-background-color;
.container-fluid {
- background-color: $background-color;
+ background-color: $dark-background-color;
margin-bottom: 0;
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 188823054fd..1a2220f3b40 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -3,6 +3,12 @@
padding-bottom: 25px;
transition: padding $sidebar-transition-duration;
+ &.page-sidebar-pinned {
+ .sidebar-wrapper {
+ @include box-shadow(none);
+ }
+ }
+
.sidebar-wrapper {
position: fixed;
top: 0;
@@ -11,6 +17,7 @@
height: 100%;
overflow: hidden;
transition: width $sidebar-transition-duration;
+ @include box-shadow(2px 0 16px 0 #bbb);
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 211a9af2348..4337fab5d87 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -12,10 +12,11 @@ $sidebar-breakpoint: 1024px;
/*
* UI elements
*/
-$border-color: #e5e5e5;
-$focus-border-color: #3aabf0;
-$table-border-color: #f0f0f0;
-$background-color: #fafafa;
+$border-color: #e5e5e5;
+$focus-border-color: #3aabf0;
+$table-border-color: #f0f0f0;
+$background-color: #fafafa;
+$dark-background-color: #f7f7f7;
/*
* Text
@@ -153,9 +154,6 @@ $warning-message-bg: #fbf2d9;
$warning-message-color: #9e8e60;
$warning-message-border: #f0e2bb;
-/* header */
-$light-grey-header: #faf9f9;
-
/* tanuki logo colors */
$tanuki-red: #e24329;
$tanuki-orange: #fc6d26;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 4e35ca329e4..0e4d8c140aa 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -63,8 +63,8 @@ form.edit-issue {
.merge-request,
.issue {
&.today {
- background: #efe;
- border-color: #cec;
+ background: #f8feef;
+ border-color: #e1e8d5;
}
&.closed {
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index cbdf2859898..23ba83aba0e 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -87,6 +87,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:version_check_enabled,
:admin_notification_email,
:user_oauth_applications,
+ :user_default_external,
:shared_runners_enabled,
:shared_runners_text,
:max_artifacts_size,
diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb
new file mode 100644
index 00000000000..e09b8789eb2
--- /dev/null
+++ b/app/controllers/concerns/diff_for_path.rb
@@ -0,0 +1,25 @@
+module DiffForPath
+ extend ActiveSupport::Concern
+
+ def render_diff_for_path(diffs, diff_refs, project)
+ diff_file = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository).find do |diff|
+ diff.old_path == params[:old_path] && diff.new_path == params[:new_path]
+ end
+
+ return render_404 unless diff_file
+
+ diff_commit = commit_for_diff(diff_file)
+ blob = diff_file.blob(diff_commit)
+ @expand_all_diffs = true
+
+ locals = {
+ diff_file: diff_file,
+ diff_commit: diff_commit,
+ diff_refs: diff_refs,
+ blob: blob,
+ project: project
+ }
+
+ render json: { html: view_to_html_string('projects/diffs/_content', locals) }
+ end
+end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index f11c8321464..7241949393b 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -23,10 +23,9 @@ class Projects::ArtifactsController < Projects::ApplicationController
entry = build.artifacts_metadata_entry(params[:path])
if entry.exists?
- render json: { archive: build.artifacts_file.path,
- entry: Base64.encode64(entry.path) }
+ send_artifacts_entry(build, entry)
else
- render json: {}, status: 404
+ render_404
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 37d6521026c..727e84b40a1 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -3,6 +3,7 @@
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
include CreatesCommit
+ include DiffForPath
include DiffHelper
# Authorize
@@ -11,29 +12,14 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
- before_action :define_show_vars, only: [:show, :builds]
+ before_action :define_commit_vars, only: [:show, :diff_for_path, :builds]
+ before_action :define_status_vars, only: [:show, :builds]
+ before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
def show
apply_diff_view_cookie!
- @grouped_diff_notes = commit.notes.grouped_diff_notes
- @notes = commit.notes.non_diff_notes.fresh
-
- Banzai::NoteRenderer.render(
- @grouped_diff_notes.values.flatten + @notes,
- @project,
- current_user,
- )
-
- @note = @project.build_commit_note(commit)
-
- @noteable = @commit
- @comments_target = {
- noteable_type: 'Commit',
- commit_id: @commit.id
- }
-
respond_to do |format|
format.html
format.diff { render text: @commit.to_diff }
@@ -41,6 +27,10 @@ class Projects::CommitController < Projects::ApplicationController
end
end
+ def diff_for_path
+ render_diff_for_path(@diffs, @commit.diff_refs, @project)
+ end
+
def builds
end
@@ -114,7 +104,7 @@ class Projects::CommitController < Projects::ApplicationController
@ci_builds ||= Ci::Build.where(pipeline: pipelines)
end
- def define_show_vars
+ def define_commit_vars
return git_not_found! unless commit
opts = diff_options
@@ -122,7 +112,28 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs(opts)
@notes_count = commit.notes.count
+ end
+
+ def define_note_vars
+ @grouped_diff_notes = commit.notes.grouped_diff_notes
+ @notes = commit.notes.non_diff_notes.fresh
+
+ Banzai::NoteRenderer.render(
+ @grouped_diff_notes.values.flatten + @notes,
+ @project,
+ current_user,
+ )
+
+ @note = @project.build_commit_note(commit)
+
+ @noteable = @commit
+ @comments_target = {
+ noteable_type: 'Commit',
+ commit_id: @commit.id
+ }
+ end
+ def define_status_vars
@statuses = CommitStatus.where(pipeline: pipelines)
@builds = Ci::Build.where(pipeline: pipelines)
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index d240b9fe989..5f3ee71444d 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -1,29 +1,51 @@
require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController
+ include DiffForPath
include DiffHelper
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :assign_ref_vars, only: [:index, :show]
+ before_action :define_ref_vars, only: [:index, :show, :diff_for_path]
+ before_action :define_diff_vars, only: [:show, :diff_for_path]
before_action :merge_request, only: [:index, :show]
def index
end
def show
- compare = CompareService.new.
- execute(@project, @head_ref, @project, @start_ref, diff_options)
+ end
+
+ def diff_for_path
+ return render_404 unless @compare
+
+ render_diff_for_path(@diffs, @diff_refs, @project)
+ end
+
+ def create
+ redirect_to namespace_project_compare_path(@project.namespace, @project,
+ params[:from], params[:to])
+ end
+
+ private
- if compare
- @commits = Commit.decorate(compare.commits, @project)
+ def define_ref_vars
+ @start_ref = Addressable::URI.unescape(params[:from])
+ @ref = @head_ref = Addressable::URI.unescape(params[:to])
+ end
+
+ def define_diff_vars
+ @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref)
+
+ if @compare
+ @commits = Commit.decorate(@compare.commits, @project)
@start_commit = @project.commit(@start_ref)
@commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@start_ref, @head_ref)
- @diffs = compare.diffs(diff_options)
+ @diffs = @compare.diffs(diff_options)
@diff_refs = Gitlab::Diff::DiffRefs.new(
base_sha: @base_commit.try(:sha),
start_sha: @start_commit.try(:sha),
@@ -35,18 +57,6 @@ class Projects::CompareController < Projects::ApplicationController
end
end
- def create
- redirect_to namespace_project_compare_path(@project.namespace, @project,
- params[:from], params[:to])
- end
-
- private
-
- def assign_ref_vars
- @start_ref = Addressable::URI.unescape(params[:from])
- @ref = @head_ref = Addressable::URI.unescape(params[:to])
- end
-
def merge_request
@merge_request ||= @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index df1943dd9bb..941d68cda17 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,5 +1,6 @@
class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
+ include DiffForPath
include DiffHelper
include IssuableActions
include ToggleAwardEmoji
@@ -9,10 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
: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 :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_commit_vars, only: [:diffs]
+ before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
@@ -53,12 +55,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def show
- @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
- group(:commit_id).count
-
respond_to do |format|
format.html
-
+
format.json do
render json: @merge_request
end
@@ -80,25 +79,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs
apply_diff_view_cookie!
- @commit = @merge_request.diff_head_commit
- @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
-
- @comments_target = {
- noteable_type: 'MergeRequest',
- noteable_id: @merge_request.id
- }
-
- @use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
- @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
-
- Banzai::NoteRenderer.render(
- @grouped_diff_notes.values.flatten,
- @project,
- current_user,
- @path,
- @project_wiki,
- @ref
- )
+ @merge_request_diff = @merge_request.merge_request_diff
respond_to do |format|
format.html
@@ -106,10 +87,37 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new
+ # and uses that (unsaved) MR.
+ #
+ def diff_for_path
+ if params[:id]
+ merge_request
+ define_diff_comment_vars
+ else
+ build_merge_request
+ @diff_notes_disabled = true
+ @grouped_diff_notes = {}
+ end
+
+ define_commit_vars
+ diffs = @merge_request.diffs(diff_options)
+
+ render_diff_for_path(diffs, @merge_request.diff_refs, @merge_request.project)
+ end
+
def commits
respond_to do |format|
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
@@ -121,8 +129,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def new
- params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
- @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ build_merge_request
@noteable = @merge_request
@target_branches = if @merge_request.target_project
@@ -308,10 +315,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
- def closes_issues
- @closes_issues ||= @merge_request.closes_issues
- end
-
def authorize_update_merge_request!
return render_404 unless can?(current_user, :update_merge_request, @merge_request)
end
@@ -340,14 +343,33 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
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
- @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.
fresh.
discussions
+ # This is not executed lazily
@notes = Banzai::NoteRenderer.render(
@discussions.flatten,
@project,
@@ -356,28 +378,35 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@project_wiki,
@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
def define_widget_vars
@pipeline = @merge_request.pipeline
@pipelines = [@pipeline].compact
- closes_issues
+ end
+
+ def define_commit_vars
+ @commit = @merge_request.diff_head_commit
+ @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
+ end
+
+ def define_diff_comment_vars
+ @comments_target = {
+ noteable_type: 'MergeRequest',
+ noteable_id: @merge_request.id
+ }
+
+ @use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
+ @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
+
+ Banzai::NoteRenderer.render(
+ @grouped_diff_notes.values.flatten,
+ @project,
+ current_user,
+ @path,
+ @project_wiki,
+ @ref
+ )
end
def invalid_mr
@@ -408,4 +437,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active?
end
+
+ def build_merge_request
+ params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
+ @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ end
end
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index efa7bf14d0f..80dad758afa 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -2,12 +2,14 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_admin_project!
+ before_action :load_protected_branch, only: [:show, :update, :destroy]
layout "project_settings"
def index
- @branches = @project.protected_branches.to_a
+ @protected_branches = @project.protected_branches.order(:name).page(params[:page])
@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
def create
@@ -16,26 +18,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
@project)
end
- def update
- protected_branch = @project.protected_branches.find(params[:id])
-
- if protected_branch &&
- protected_branch.update_attributes(
- developers_can_push: params[:developers_can_push]
- )
+ def show
+ @matching_branches = @protected_branch.matching(@project.repository.branches)
+ end
+ def update
+ if @protected_branch && @protected_branch.update_attributes(protected_branch_params)
respond_to do |format|
- format.json { render json: protected_branch, status: :ok }
+ format.json { render json: @protected_branch, status: :ok }
end
else
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
def destroy
- @project.protected_branches.find(params[:id]).destroy
+ @protected_branch.destroy
respond_to do |format|
format.html { redirect_to namespace_project_protected_branches_path }
@@ -45,6 +45,10 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
private
+ def load_protected_branch
+ @protected_branch = @project.protected_branches.find(params[:id])
+ end
+
def protected_branch_params
params.require(:protected_branch).permit(:name, :developers_can_push)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 12e0d5a8413..1803aa8eab4 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -53,11 +53,11 @@ class ProjectsController < Projects::ApplicationController
notice: "Project '#{@project.name}' was successfully updated."
)
end
- format.js
else
format.html { render 'edit' }
- format.js
end
+
+ format.js
end
end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index f240584ccbf..950f323e383 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -31,7 +31,7 @@ module AppearancesHelper
end
end
- def navbar_icon(icon_name)
- render "shared/icons/#{icon_name}.svg"
+ def navbar_icon(icon_name, size: 16)
+ render "shared/icons/#{icon_name}.svg", size: size
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index b61266465e2..adab901700c 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -8,6 +8,10 @@ module DiffHelper
[marked_old_line, marked_new_line]
end
+ def expand_all_diffs?
+ @expand_all_diffs || params[:expand_all_diffs].present?
+ end
+
def diff_view
diff_views = %w(inline parallel)
@@ -18,16 +22,14 @@ module DiffHelper
end
end
- def diff_hard_limit_enabled?
- params[:force_show_diff].present?
- end
-
def diff_options
- options = { ignore_whitespace_change: hide_whitespace? }
- if diff_hard_limit_enabled?
- options.merge!(Commit.max_diff_options)
+ default_options = Commit.max_diff_options
+
+ if action_name == 'diff_for_path'
+ default_options[:paths] = params.values_at(:old_path, :new_path)
end
- options
+
+ default_options.merge(ignore_whitespace_change: hide_whitespace?)
end
def safe_diff_files(diffs, diff_refs: nil, repository: nil)
@@ -90,7 +92,7 @@ module DiffHelper
def commit_for_diff(diff_file)
return diff_file.content_commit if diff_file.content_commit
-
+
if diff_file.deleted_file
@base_commit || @commit.parent || @commit
else
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index c7dedfe9254..db6e731c744 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -55,6 +55,10 @@ module MergeRequestsHelper
end.sort.to_sentence
end
+ def mr_closes_issues
+ @mr_closes_issues ||= @merge_request.closes_issues
+ end
+
def mr_change_branches_path(merge_request)
new_namespace_project_merge_request_path(
@project.namespace, @project,
@@ -92,4 +96,8 @@ module MergeRequestsHelper
["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"]
end
end
+
+ def merge_request_button_visibility(merge_request, closed)
+ return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?)
+ end
end
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index b04b0a5114c..8cb82c2d5cc 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -23,4 +23,11 @@ module TimeHelper
def date_from_to(from, to)
"#{from.to_s(:short)} - #{to.to_s(:short)}"
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
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index 65598ad9ed3..d887cdadc34 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -28,4 +28,10 @@ module WorkhorseHelper
headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
head :ok
end
+
+ # Send an entry from artifacts through Workhorse
+ def send_artifacts_entry(build, entry)
+ headers.store(*Gitlab::Workhorse.send_artifacts_entry(build, entry))
+ head :ok
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 7bf618d60b9..c6f77cc055f 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -142,6 +142,7 @@ class ApplicationSetting < ActiveRecord::Base
send_user_confirmation_email: false,
container_registry_token_expire_delay: 5,
repository_storage: 'default',
+ user_default_external: false,
)
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5c973749975..e189dbac285 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -13,6 +13,7 @@ module Ci
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
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_metadata, ArtifactUploader
@@ -25,10 +26,6 @@ module Ci
after_create :execute_hooks
class << self
- def last_month
- where('created_at > ?', Date.today - 1.month)
- end
-
def first_pending
pending.unstarted.order('created_at ASC').first
end
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 4066958f67c..630ee9601e0 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -23,7 +23,7 @@ class CommitRange
attr_reader :commit_from, :notation, :commit_to
attr_reader :ref_from, :ref_to
- # Optional Project model
+ # The Project model
attr_accessor :project
# The beginning and ending refs can be named or SHAs, and
@@ -56,7 +56,7 @@ class CommitRange
# Initialize a CommitRange
#
# range_string - The String commit range.
- # project - An optional Project model.
+ # project - The Project model.
#
# Raises ArgumentError if `range_string` does not match `PATTERN`.
def initialize(range_string, project)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 083e93f1ee7..157901378d3 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -19,7 +19,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff, unless: :importing?
after_update :update_merge_request_diff
- delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil
+ delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -164,6 +164,10 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
+ def diffs(*args)
+ merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args)
+ end
+
def diff_size
merge_request_diff.size
end
@@ -318,11 +322,11 @@ class MergeRequest < ActiveRecord::Base
end
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
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
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index ba235750aeb..feaba925bad 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -46,7 +46,8 @@ class MergeRequestDiff < ActiveRecord::Base
compare.diffs(options)
end
else
- @diffs ||= load_diffs(st_diffs, options)
+ @diffs ||= {}
+ @diffs[options] ||= load_diffs(st_diffs, options)
end
end
@@ -144,6 +145,12 @@ class MergeRequestDiff < ActiveRecord::Base
def load_diffs(raw, options)
if raw.respond_to?(:each)
+ if paths = options[:paths]
+ raw = raw.select do |diff|
+ paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
+ end
+ end
+
Gitlab::Git::DiffCollection.new(raw, options)
else
Gitlab::Git::DiffCollection.new([])
diff --git a/app/models/project.rb b/app/models/project.rb
index 029026a4e56..a66b750cd48 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -425,8 +425,8 @@ class Project < ActiveRecord::Base
container_registry_repository.tags.any?
end
- def commit(id = 'HEAD')
- repository.commit(id)
+ def commit(ref = 'HEAD')
+ repository.commit(ref)
end
def merge_base_commit(first_commit_id, second_commit_id)
@@ -802,18 +802,12 @@ class Project < ActiveRecord::Base
@repo_exists = false
end
+ # Branches that are not _exactly_ matched by a protected branch.
def open_branches
- # We're using a Set here as checking values in a large Set is faster than
- # checking values in a large Array.
- protected_set = Set.new(protected_branch_names)
-
- repository.branches.reject do |branch|
- protected_set.include?(branch.name)
- end
- end
-
- def protected_branch_names
- @protected_branch_names ||= protected_branches.pluck(:name)
+ exact_protected_branch_names = protected_branches.reject(&:wildcard?).map(&:name)
+ branch_names = repository.branches.map(&:name)
+ 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 }
end
def root_ref?(branch)
@@ -830,11 +824,12 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
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
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
def forked?
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 58cb720c3c1..ce7d1c5d5b1 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -112,15 +112,7 @@ class IrkerService < Service
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil?
- # Do not authorize irc://domain.com/
- if uri.fragment.nil? && uri.path.length > 1
- uri.to_s
- 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
+ uri.to_s
end
end
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 33cf046fa75..b7011d7afdf 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -8,4 +8,51 @@ class ProtectedBranch < ActiveRecord::Base
def commit
project.commit(self.name)
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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ba66bc47c29..5b670cb4b8f 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -78,9 +78,9 @@ class Repository
end
end
- def commit(id = 'HEAD')
+ def commit(ref = 'HEAD')
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
rescue Rugged::OdbError
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 016172c6d7e..f4bcb49b34d 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -72,6 +72,19 @@ class SentNotification < ActiveRecord::Base
end
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 to_param
self.reply_key
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 695a47ba6eb..79c670cb35a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -15,7 +15,7 @@ class User < ActiveRecord::Base
add_authentication_token_field :authentication_token
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_team, false
default_value_for :hide_no_ssh_key, false
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index e2bccbdbcc3..149822aa647 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -3,7 +3,7 @@ require 'securerandom'
# Compare 2 branches for one repo or between repositories
# and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService
- def execute(source_project, source_branch, target_project, target_branch, diff_options = {})
+ def execute(source_project, source_branch, target_project, target_branch)
source_commit = source_project.commit(source_branch)
return unless source_commit
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 21490ac77ea..b11ecd97a57 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -61,19 +61,14 @@ module MergeRequests
merge_requests.each do |merge_request|
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff
- merge_request.mark_as_unchecked
else
mr_commit_ids = merge_request.commits.map(&:id)
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
-
- if matches.any?
- merge_request.reload_diff
- merge_request.mark_as_unchecked
- else
- merge_request.mark_as_unchecked
- end
+ merge_request.reload_diff if matches.any?
end
+
+ merge_request.mark_as_unchecked
end
end
diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml
index 862b86d9d4a..dd2e7ebd030 100644
--- a/app/views/admin/abuse_reports/_abuse_report.html.haml
+++ b/app/views/admin/abuse_reports/_abuse_report.html.haml
@@ -3,14 +3,14 @@
%tr
%td
- if user
- = link_to user.name, [:admin, user]
+ = link_to user.name, user
.light.small
Joined #{time_ago_with_tooltip(user.created_at)}
- else
(removed)
%td
- if reporter
- = link_to reporter.name, [:admin, reporter]
+ = link_to reporter.name, reporter
- else
(removed)
.light.small
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index eb325576e4f..8de28528cda 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -100,6 +100,13 @@
= f.label :user_oauth_applications do
= f.check_box :user_oauth_applications
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
%legend Sign-in Restrictions
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index fe0b9d3a491..3145212728f 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -44,7 +44,7 @@
%legend Access
.form-group
= 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
= f.label :can_create_group, class: 'control-label'
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 9b838b9f3b7..6306fe6d0bf 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -10,7 +10,6 @@
- if current_user
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
- = icon('plus')
New Snippet
.oneline
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index cc8ea066cb9..3612f1ce5c6 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -1,4 +1,4 @@
-.flash-container
+.flash-container.flash-container-page
- if alert
.flash-alert
= alert
diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml
new file mode 100644
index 00000000000..003884a5bd9
--- /dev/null
+++ b/app/views/profiles/_head.html.haml
@@ -0,0 +1,3 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/cropper.js')
+ = page_specific_javascript_tag('profile/application.js')
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 8efe486e01b..57d16d29158 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Account"
+= render 'profiles/head'
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index 9c404b6935f..9fe86e6b291 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,4 +1,5 @@
- page_title "Audit Log"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 6f7fefdb46d..dc499be885b 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Emails"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index b3ed59a1a4a..6ea358d9f63 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -4,7 +4,7 @@
.form-group
= 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
= f.label :title, class: 'label-light'
= f.text_field :title, class: "form-control", required: true
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 89f6f01581a..6283ceebf10 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,2 +1,3 @@
- page_title @key.title, "SSH Keys"
+= render 'profiles/head'
= render "key_details"
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index f77738f97f5..844fce59704 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Notifications"
+= render 'profiles/head'
%div
- if @user.errors.any?
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 1b45548bd02..71ac367830d 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Personal Access Tokens"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 1b1b16d656f..b4d35dc9a3e 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,4 +1,5 @@
- 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|
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index eef50d887c7..d9fa74fad90 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,3 +1,5 @@
+= 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_errors(@user)
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 593be2617c1..5890456bee2 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,5 +1,6 @@
- page_title 'Two-Factor Authentication', 'Account'
- header_title "Two-Factor Authentication", profile_two_factor_auth_path
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 4e801cc72fe..4421f3b9562 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -67,4 +67,4 @@
= render "sidebar"
:javascript
- new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}")
+ new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}")
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index e38d1ff5ff0..af8dd5cd02c 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -45,7 +45,7 @@
%td
- if pipeline.started_at && pipeline.finished_at
%p.duration
- #{duration_in_words(pipeline.finished_at, pipeline.started_at)}
+ = duration_in_numbers(pipeline.finished_at, pipeline.started_at)
%td
.controls.hidden-xs.pull-right
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
new file mode 100644
index 00000000000..0c0424edffd
--- /dev/null
+++ b/app/views/projects/diffs/_content.html.haml
@@ -0,0 +1,29 @@
+.diff-content.diff-wrap-lines
+ - # Skip all non non-supported blobs
+ - return unless blob.respond_to?(:text?)
+ - if diff_file.too_large?
+ .nothing-here-block This diff could not be displayed because it is too large.
+ - elsif blob.only_display_raw?
+ .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_file.collapsed_by_default? && !expand_all_diffs?
+ - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path))
+ .nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
+ This diff is collapsed. Click to expand it.
+ - elsif diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
+ - else
+ = render "projects/diffs/text_file", diff_file: diff_file
+ - 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?
+ - old_blob = diff_file.old_blob(diff_commit)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 2a15c306f07..20aaab5accf 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -6,6 +6,8 @@
.content-block.oneline-block.files-changed
.inline-parallel-buttons
+ - unless expand_all_diffs?
+ = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 3b758a1ec4e..c306909fb1a 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -16,28 +16,4 @@
= view_file_btn(diff_commit.id, diff_file, project)
- .diff-content.diff-wrap-lines
- - # Skip all non non-supported blobs
- - return unless blob.respond_to?(:text?)
- - if diff_file.too_large?
- .nothing-here-block This diff could not be displayed because it is too large.
- - elsif blob.only_display_raw?
- .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?
- - old_blob = diff_file.old_blob(diff_commit)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob, index: i
- - else
- .nothing-here-block No preview for this file type
+ = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 15536c17f8e..10fa1ddf2e5 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -2,9 +2,6 @@
%h4
Too many changes to show.
.pull-right
- - unless diff_hard_limit_enabled?
- = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm"
-
- if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit)
= link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7ce4c1e5555..312bd86ed04 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -6,21 +6,37 @@
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
-%div{ class: container_class }
- .top-area
- = render 'shared/issuable/nav', type: :issues
- .nav-controls
- - if current_user
- = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
- = icon('rss')
- %span.icon-label
- Subscribe
- = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
+%div{ class: (container_class) }
+ - if @project.issues.any?
+ .top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls
+ - if current_user
+ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ = icon('rss')
+ %span.icon-label
+ Subscribe
+ = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
+ - if can? current_user, :create_issue, @project
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ New Issue
+ = render 'shared/issuable/filter', type: :issues
+
+ .issues-holder
+ = render "issues"
+ - else
+ .blank-state.blank-state-welcome
+ %h2.blank-state-title.blank-state-welcome-title
+ Welcome to GitLab Issues
+ %p.blank-state-text
+ Code, test, and deploy together
+ .blank-state
+ .blank-state-icon
+ = navbar_icon("issues", size: 50)
+ %h3.blank-state-title
+ You don't have any issues right now.
+ %p.blank-state-text
+ Issues are the best way to track your project progress
- if can? current_user, :create_issue, @project
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
New Issue
-
- = render 'shared/issuable/filter', type: :issues
-
- .issues-holder
- = render "issues"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 2ec96308fd7..873ed9b59ee 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -42,7 +42,7 @@
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- - if @commits.present?
+ - if @commits_count.nonzero?
%ul.merge-request-tabs.nav-links.no-top.no-bottom
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
@@ -51,7 +51,7 @@
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
- %span.badge= @commits.size
+ %span.badge= @commits_count
- if @pipeline
%li.builds-tab
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 5bf5210aeab..b24bdf22ceb 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -19,13 +19,13 @@
Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- %li{ class: issue_button_visibility(@merge_request, true) }
+ %li{ class: merge_request_button_visibility(@merge_request, true) }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
- %li{ class: issue_button_visibility(@merge_request, false) }
+ %li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
%li
= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
Edit
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 0e0af57d76e..dc18f715f25 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -22,10 +22,10 @@
- elsif @merge_request.can_be_merged?
= render 'projects/merge_requests/widget/open/accept'
- - if @closes_issues.present?
+ - if mr_closes_issues.present?
.mr-widget-footer
%span
%i.fa.fa-check
- Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
+ Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)}
= succeed '.' do
- != markdown issues_sentence(@closes_issues), pipeline: :gfm, author: @merge_request.author
+ != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 1c39ce897a3..56d302fab82 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -2,6 +2,8 @@
= render "projects/notes/notes"
%ul.notes.notes-form.timeline
%li.timeline-entry
+ .flash-container.timeline-content
+
- if can? current_user, :create_note, @project
.timeline-icon.hidden-xs.hidden-sm
%a.author_link{ href: user_path(current_user) }
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index 565905cbe7b..97cb1a9052b 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,6 +1,6 @@
%h5.prepend-top-0
- Already Protected (#{@branches.size})
-- if @branches.empty?
+ Already Protected (#{@protected_branches.size})
+- if @protected_branches.empty?
%p.settings-message.text-center
No branches are protected, protect a branch with the form above.
- else
@@ -9,33 +9,18 @@
%table.table.protected-branches-list
%colgroup
%col{ width: "30%" }
- %col{ width: "30%" }
+ %col{ width: "25%" }
%col{ width: "25%" }
- if can_admin_project
%col
%thead
%tr
- %th Branch
- %th Last commit
- %th Developers can push
+ %th Protected Branch
+ %th Commit
+ %th Developers Can Push
- if can_admin_project
%th
%tbody
- - @branches.each do |branch|
- - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
- %tr
- %td
- = link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name))
- - if @project.root_ref?(branch.name)
- %span.label.label-info.prepend-left-5 default
- %td
- - if commit = branch.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
- %td
- = check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url })
- - if can_admin_project
- %td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm"
+ = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
+
+ = paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/_dropdown.html.haml
new file mode 100644
index 00000000000..b803d932e67
--- /dev/null
+++ b/app/views/projects/protected_branches/_dropdown.html.haml
@@ -0,0 +1,17 @@
+= f.hidden_field(:name)
+
+= dropdown_tag("Protected Branch",
+ options: { title: "Pick protected branch", toggle_class: 'js-protected-branch-select js-filter-submit',
+ filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches",
+ footer_content: true,
+ data: { show_no: true, show_any: true, show_upcoming: true,
+ selected: params[:protected_branch_name],
+ project_id: @project.try(:id) } }) do
+
+ %ul.dropdown-footer-list.hidden.protected-branch-select-footer-list
+ %li
+ = link_to '#', title: "New Protected Branch", class: "create-new-protected-branch" do
+ Create new
+
+:javascript
+ new ProtectedBranchSelect();
diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/_matching_branch.html.haml
new file mode 100644
index 00000000000..8a5332ca5bb
--- /dev/null
+++ b/app/views/projects/protected_branches/_matching_branch.html.haml
@@ -0,0 +1,9 @@
+%tr
+ %td
+ = link_to matching_branch.name, namespace_project_tree_path(@project.namespace, @project, matching_branch.name)
+ - if @project.root_ref?(matching_branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - commit = @project.commit(matching_branch.name)
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
new file mode 100644
index 00000000000..474aec3a97c
--- /dev/null
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -0,0 +1,21 @@
+- url = namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+%tr
+ %td
+ = protected_branch.name
+ - if @project.root_ref?(protected_branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - if protected_branch.wildcard?
+ - matching_branches = protected_branch.matching(repository.branches)
+ = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+ - else
+ - if commit = protected_branch.commit
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = time_ago_with_tooltip(commit.committed_date)
+ - else
+ (branch was removed from repository)
+ %td
+ = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url })
+ - if can_admin_project
+ %td
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index c7d317dbaee..5669713d9a1 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -4,30 +4,38 @@
.col-lg-3
%h4.prepend-top-0
= page_title
- %p Keep stable branches secure and force developers to use Merge Requests
- .col-lg-9
- %h5.prepend-top-0
- Protect a branch
- .account-well.append-bottom-default
- %p.light-header.append-bottom-0 Protected branches are designed to
+ %p Keep stable branches secure and force developers to use merge requests.
+ %p.prepend-top-20
+ Protected branches are designed to:
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
%li prevent anyone from force pushing to the branch
%li prevent anyone from deleting the branch
%p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
+ .col-lg-9
+ %h5.prepend-top-0
+ Protect a branch
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
= form_errors(@protected_branch)
.form-group
= f.label :name, "Branch", class: "label-light"
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
+ = render partial: "dropdown", locals: { f: f }
+ %p.help-block
+ = link_to "Wildcards", help_page_path(category: 'workflow', file: 'protected_branches', format: 'md', anchor: "wildcard-protected-branches")
+ such as
+ %code *-stable
+ or
+ %code production/*
+ are supported.
+
.form-group
= f.check_box :developers_can_push, class: "pull-left"
.prepend-left-20
= f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
%p.light.append-bottom-0
Allow developers to push to this branch
- = f.submit "Protect", class: "btn-create btn"
+ = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
%hr
= render "branches_list"
diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml
new file mode 100644
index 00000000000..4d8169815b3
--- /dev/null
+++ b/app/views/projects/protected_branches/show.html.haml
@@ -0,0 +1,25 @@
+- page_title @protected_branch.name, "Protected Branches"
+
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ = @protected_branch.name
+
+ .col-lg-9
+ %h5 Matching Branches
+ - if @matching_branches.present?
+ .table-responsive
+ %table.table.protected-branches-list
+ %colgroup
+ %col{ width: "30%" }
+ %col{ width: "30%" }
+ %thead
+ %tr
+ %th Branch
+ %th Last commit
+ %tbody
+ - @matching_branches.each do |matching_branch|
+ = render partial: "matching_branch", object: matching_branch
+ - else
+ %p.settings-message.text-center
+ Couldn't find any matching branches.
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index bf57beb9d07..bdbf3e5f4d6 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -1,27 +1,29 @@
.hidden-xs
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
+ New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :update_project_snippet, @snippet)
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
Delete
-.visible-xs-block.dropdown
- %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
- Options
- %span.caret
- .dropdown-menu.dropdown-menu-full-width
- %ul
- %li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
- New Snippet
- - if can?(current_user, :update_project_snippet, @snippet)
- %li
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
- Edit
- - if can?(current_user, :update_project_snippet, @snippet)
- %li
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
- Delete
+- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
+ .visible-xs-block.dropdown
+ %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-full-width
+ %ul
+ - if can?(current_user, :create_project_snippet, @project)
+ %li
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
+ New Snippet
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+ Edit
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ Delete
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 96fee3b17b2..6c994ae486b 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -2,9 +2,9 @@
.row-content-block.top-block
.pull-right
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
+ New Snippet
.oneline
Share code pastes with others out of git repository
diff --git a/app/views/shared/icons/_issues.svg b/app/views/shared/icons/_issues.svg
deleted file mode 100644
index 2682c27ade9..00000000000
--- a/app/views/shared/icons/_issues.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
- <title>Group</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="Group" fill="#7E7C7C">
- <path d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2" id="Fill-1"></path>
- <path d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z" id="Combined-Shape"></path>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_issues.svg.erb b/app/views/shared/icons/_issues.svg.erb
new file mode 100644
index 00000000000..fa8655b5609
--- /dev/null
+++ b/app/views/shared/icons/_issues.svg.erb
@@ -0,0 +1,4 @@
+<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16" class="gitlab-icon">
+ <path fill="#7E7C7C" d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2"></path>
+ <path fill="#7E7C7C" d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z"></path>
+</svg>
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index a7769654b61..160c6cd84da 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -1,27 +1,28 @@
.hidden-xs
- = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if current_user
+ = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
+ New Snippet
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete
-.visible-xs-block.dropdown
- %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
- Options
- %span.caret
- .dropdown-menu.dropdown-menu-full-width
- %ul
- %li
- = link_to new_snippet_path, title: "New Snippet" do
- New Snippet
- - if can?(current_user, :update_personal_snippet, @snippet)
+- if current_user
+ .visible-xs-block.dropdown
+ %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-full-width
+ %ul
%li
- = link_to edit_snippet_path(@snippet) do
- Edit
- - if can?(current_user, :admin_personal_snippet, @snippet)
- %li
- = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
- Delete
+ = link_to new_snippet_path, title: "New Snippet" do
+ New Snippet
+ - if can?(current_user, :update_personal_snippet, @snippet)
+ %li
+ = link_to edit_snippet_path(@snippet) do
+ Edit
+ - if can?(current_user, :admin_personal_snippet, @snippet)
+ %li
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ Delete
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 68665858c3e..db2b4885861 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -29,6 +29,11 @@
&nbsp;
= link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do
= icon('rss')
+ - if current_user.admin?
+ &nbsp;
+ = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area',
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('users')
.avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank' do
diff --git a/config/application.rb b/config/application.rb
index 2b0595ede2b..21e7cc7b6e8 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -84,6 +84,7 @@ module Gitlab
config.assets.precompile << "graphs/application.js"
config.assets.precompile << "users/application.js"
config.assets.precompile << "network/application.js"
+ config.assets.precompile << "profile/application.js"
config.assets.precompile << "lib/utils/*.js"
config.assets.precompile << "lib/*.js"
diff --git a/config/routes.rb b/config/routes.rb
index 1572656b8c5..b4f83c58bbd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -615,10 +615,18 @@ Rails.application.routes.draw do
post :retry_builds
post :revert
post :cherry_pick
+ get :diff_for_path
end
end
- resources :compare, only: [:index, :create]
+ resources :compare, only: [:index, :create] do
+ collection do
+ get :diff_for_path
+ end
+ end
+
+ get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
+
resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
@@ -629,9 +637,6 @@ Rails.application.routes.draw do
end
end
- get '/compare/:from...:to' => 'compare#show', :as => 'compare',
- :constraints => { from: /.+/, to: /.+/ }
-
resources :snippets, constraints: { id: /\d+/ } do
member do
get 'raw'
@@ -706,12 +711,14 @@ Rails.application.routes.draw do
post :toggle_subscription
post :toggle_award_emoji
post :remove_wip
+ get :diff_for_path
end
collection do
get :branch_from
get :branch_to
get :update_branches
+ get :diff_for_path
end
end
@@ -720,7 +727,7 @@ Rails.application.routes.draw do
resource :release, only: [:edit, :update]
end
- resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+ resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :variables, only: [:index, :show, :update, :create, :destroy]
resources :triggers, only: [:index, :create, :destroy]
diff --git a/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb b/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb
new file mode 100644
index 00000000000..34c702e3fa6
--- /dev/null
+++ b/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb
@@ -0,0 +1,13 @@
+class AddUserDefaultExternalToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:application_settings, :user_default_external, :boolean,
+ default: false, allow_null: false)
+ end
+
+ def down
+ remove_column(:application_settings, :user_default_external)
+ end
+end
diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb
index 00a45d7fe73..180a75e0998 100644
--- a/db/migrate/20160616102642_remove_duplicated_keys.rb
+++ b/db/migrate/20160616102642_remove_duplicated_keys.rb
@@ -4,12 +4,12 @@ class RemoveDuplicatedKeys < ActiveRecord::Migration
select_all("SELECT fingerprint FROM #{quote_table_name(:keys)} GROUP BY fingerprint HAVING COUNT(*) > 1").each do |row|
fingerprint = connection.quote(row['fingerprint'])
execute(%Q{
- DELETE FROM keys
+ DELETE FROM #{quote_table_name(:keys)}
WHERE fingerprint = #{fingerprint}
AND id != (
SELECT id FROM (
SELECT max(id) AS id
- FROM keys
+ FROM #{quote_table_name(:keys)}
WHERE fingerprint = #{fingerprint}
) max_ids
)
diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb
deleted file mode 100644
index 82a616c62d9..00000000000
--- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-# Updates project records containing invalid URLs using the AddressableUrlValidator.
-# This is optimized assuming the number of invalid records is low, but
-# we still need to loop through all the projects with an +import_url+
-# so we use batching for the latter.
-#
-# This migration is non-reversible as we would have to keep the old data.
-
-class FixNoValidatableImportUrl < ActiveRecord::Migration
- include Gitlab::Database::MigrationHelpers
- class SqlBatches
-
- attr_reader :results, :query
-
- def initialize(batch_size: 100, query:)
- @offset = 0
- @batch_size = batch_size
- @query = query
- @results = []
- end
-
- def next?
- @results = ActiveRecord::Base.connection.exec_query(batched_sql)
- @offset += @batch_size
- @results.any?
- end
-
- private
-
- def batched_sql
- "#{@query} LIMIT #{@batch_size} OFFSET #{@offset}"
- end
- end
-
- # AddressableValidator - Snapshot of AddressableUrlValidator
- module AddressableUrlValidatorSnap
- extend self
-
- def valid_url?(value)
- return false unless value
-
- valid_uri?(value) && valid_protocol?(value)
- rescue Addressable::URI::InvalidURIError
- false
- end
-
- def valid_uri?(value)
- Addressable::URI.parse(value).is_a?(Addressable::URI)
- end
-
- def valid_protocol?(value)
- value =~ /\A#{URI.regexp(%w(http https ssh git))}\z/
- end
- end
-
- def up
- unless defined?(Addressable::URI::InvalidURIError)
- say('Skipping cleaning up invalid import URLs as class from Addressable is missing')
- return
- end
-
- say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.')
-
- invalid_import_url_project_ids.each { |project_id| cleanup_import_url(project_id) }
- end
-
- def invalid_import_url_project_ids
- ids = []
- batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL")
-
- while batches.next?
- batches.results.each do |result|
- ids << result['id'] unless valid_url?(result['import_url'])
- end
- end
-
- ids
- end
-
- def valid_url?(url)
- AddressableUrlValidatorSnap.valid_url?(url)
- end
-
- def cleanup_import_url(project_id)
- execute("UPDATE projects SET import_url = NULL WHERE id = #{project_id}")
- end
-end
diff --git a/db/schema.rb b/db/schema.rb
index 68b9425253c..a5eea3a697c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -84,6 +84,7 @@ ActiveRecord::Schema.define(version: 20160705163108) do
t.string "health_check_access_token"
t.boolean "send_user_confirmation_email", default: false
t.integer "container_registry_token_expire_delay", default: 5
+ t.boolean "user_default_external", default: false, null: false
t.text "after_sign_up_text"
t.string "repository_storage", default: "default"
t.string "enabled_git_access_protocol"
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 9fd7b71d2dc..e3306c22d3f 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -48,7 +48,8 @@ as appropriate.
This feature was [introduced][5073] in GitLab 8.10.
If the commit is declined or an error occurs during the Git hook check,
-the STDERR and/or SDOUT message of the hook will be present in GitLab's UI.
+the STDERR or STDOUT message of the hook will be present in GitLab's UI.
+STDERR takes precedence over STDOUT.
![Custom message from custom Git hook](img/custom_hooks_error_msg.png)
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 1ccb9715e96..87480bebfc4 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -42,46 +42,49 @@ Parameters:
```json
[
{
- "id": 4,
- "description": null,
+ "id": 9,
+ "description": "foo",
"default_branch": "master",
+ "tag_list": [],
"public": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
- "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
- "web_url": "http://example.com/diaspora/diaspora-client",
- "tag_list": [
- "example",
- "disapora client"
- ],
- "owner": {
- "id": 3,
- "name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
- },
- "name": "Diaspora Client",
- "name_with_namespace": "Diaspora / Diaspora Client",
- "path": "diaspora-client",
- "path_with_namespace": "diaspora/diaspora-client",
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
+ "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "Experimental / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
"issues_enabled": true,
"merge_requests_enabled": true,
- "builds_enabled": true,
"wiki_enabled": true,
- "snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
- "creator_id": 3,
+ "builds_enabled": true,
+ "snippets_enabled": true,
+ "created_at": "2016-04-05T21:40:50.169Z",
+ "last_activity_at": "2016-04-06T16:52:08.432Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
- "description": "",
- "id": 3,
- "name": "Diaspora",
- "owner_id": 1,
- "path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "id": 5,
+ "name": "Experimental",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-04-05T21:40:49.152Z",
+ "updated_at": "2016-04-07T08:07:48.466Z",
+ "description": "foo",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 10
},
- "archived": false,
- "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
+ "avatar_url": null,
+ "star_count": 1,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
}
]
```
@@ -96,7 +99,180 @@ GET /groups/:id
Parameters:
-- `id` (required) - The ID or path of a group
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or path of a group |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4
+```
+
+Example response:
+
+```json
+{
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "visibility_level": 20,
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/twitter",
+ "projects": [
+ {
+ "id": 7,
+ "description": "Voluptas veniam qui et beatae voluptas doloremque explicabo facilis.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/typeahead-js.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/typeahead-js.git",
+ "web_url": "https://gitlab.example.com/twitter/typeahead-js",
+ "name": "Typeahead.Js",
+ "name_with_namespace": "Twitter / Typeahead.Js",
+ "path": "typeahead-js",
+ "path_with_namespace": "twitter/typeahead-js",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:25.578Z",
+ "last_activity_at": "2016-06-17T07:47:25.881Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
+ },
+ {
+ "id": 6,
+ "description": "Aspernatur omnis repudiandae qui voluptatibus eaque.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/flight.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/flight.git",
+ "web_url": "https://gitlab.example.com/twitter/flight",
+ "name": "Flight",
+ "name_with_namespace": "Twitter / Flight",
+ "path": "flight",
+ "path_with_namespace": "twitter/flight",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:24.661Z",
+ "last_activity_at": "2016-06-17T07:47:24.838Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 8,
+ "public_builds": true,
+ "shared_with_groups": []
+ }
+ ],
+ "shared_projects": [
+ {
+ "id": 8,
+ "description": "Velit eveniet provident fugiat saepe eligendi autem.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@gitlab.example.com:h5bp/html5-boilerplate.git",
+ "http_url_to_repo": "https://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "https://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "H5bp / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:27.089Z",
+ "last_activity_at": "2016-06-17T07:47:27.310Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "H5bp",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:26.621Z",
+ "updated_at": "2016-06-17T07:47:26.621Z",
+ "description": "Id consequatur rem vel qui doloremque saepe.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 4,
+ "public_builds": true,
+ "shared_with_groups": [
+ {
+ "group_id": 4,
+ "group_name": "Twitter",
+ "group_access_level": 30
+ },
+ {
+ "group_id": 3,
+ "group_name": "Gitlab Org",
+ "group_access_level": 10
+ }
+ ]
+ }
+ ]
+}
+```
## New group
@@ -201,7 +377,8 @@ Example response:
"star_count": 1,
"forks_count": 0,
"open_issues_count": 3,
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
}
]
}
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index aee94b3fc36..816f09e1007 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -49,10 +49,10 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "source_project_id": "2",
- "target_project_id": "3",
+ "source_project_id": 2,
+ "target_project_id": 3,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -113,10 +113,10 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "source_project_id": "2",
- "target_project_id": "3",
+ "source_project_id": 2,
+ "target_project_id": 3,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -296,7 +296,7 @@ Parameters:
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -465,7 +465,7 @@ Parameters:
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -531,7 +531,7 @@ Parameters:
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
diff --git a/doc/api/projects.md b/doc/api/projects.md
index f5f195b97df..dceee7b4ea7 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -52,7 +52,7 @@ Parameters:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Client",
"name_with_namespace": "Diaspora / Diaspora Client",
@@ -64,17 +64,18 @@ Parameters:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
@@ -82,7 +83,8 @@ Parameters:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
},
{
"id": 6,
@@ -112,6 +114,7 @@ Parameters:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
@@ -140,7 +143,8 @@ Parameters:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
}
]
```
@@ -223,7 +227,7 @@ Parameters:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
@@ -235,17 +239,18 @@ Parameters:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"permissions": {
"project_access": {
@@ -262,7 +267,20 @@ Parameters:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
+ "public_builds": true,
+ "shared_with_groups": [
+ {
+ "group_id": 4,
+ "group_name": "Twitter",
+ "group_access_level": 30
+ },
+ {
+ "group_id": 3,
+ "group_name": "Gitlab Org",
+ "group_access_level": 10
+ }
+ ]
}
```
@@ -425,6 +443,7 @@ Parameters:
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
- `container_registry_enabled` (optional)
+- `shared_runners_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -449,6 +468,7 @@ Parameters:
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
- `container_registry_enabled` (optional)
+- `shared_runners_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -475,6 +495,7 @@ Parameters:
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
- `container_registry_enabled` (optional)
+- `shared_runners_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `public_builds` (optional)
@@ -537,23 +558,26 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
- "star_count": 1
+ "star_count": 1,
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -600,23 +624,26 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
- "star_count": 0
+ "star_count": 0,
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -660,7 +687,7 @@ Example response:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
@@ -672,17 +699,18 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"permissions": {
"project_access": {
@@ -699,7 +727,9 @@ Example response:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -713,7 +743,7 @@ have the proper access rights, code 403 is returned. Status 404 is returned if t
doesn't exist, or is hidden to the user.
```
-POST /projects/:id/archive
+POST /projects/:id/unarchive
```
| Attribute | Type | Required | Description |
@@ -743,7 +773,7 @@ Example response:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
@@ -755,17 +785,18 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"permissions": {
"project_access": {
@@ -782,7 +813,9 @@ Example response:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -965,11 +998,11 @@ Parameters:
"id": 1,
"url": "http://example.com/hook",
"project_id": 3,
- "push_events": "true",
- "issues_events": "true",
- "merge_requests_events": "true",
- "note_events": "true",
- "enable_ssl_verification": "true",
+ "push_events": true,
+ "issues_events": true,
+ "merge_requests_events": true,
+ "note_events": true,
+ "enable_ssl_verification": true,
"created_at": "2012-10-12T17:04:47Z"
}
```
@@ -1089,8 +1122,8 @@ Parameters:
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
- "authored_date": "2013-09-07T12: 58: 21+00: 00",
- "committed_date": "2013-09-07T12: 58: 21+00: 00"
+ "authored_date": "2013-09-07T12:58:21+00:00",
+ "committed_date": "2013-09-07T12:58:21+00:00"
},
"protected": false
}
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index 17e1c64bb8a..bfafcc44d66 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -49,7 +49,7 @@ apt-get update -yqq
apt-get install git -yqq
# Install phpunit, the tool that we will use for testing
-curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar
+curl -Lo /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar
chmod +x /usr/local/bin/phpunit
# Install mysql driver
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index d2d1b04f893..16a1461a7e4 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -133,7 +133,7 @@ builds, including deploy builds. This can be an array or a multi-line string.
### after_script
>**Note:**
-Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 (not yet released)
+Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
`after_script` is used to define the command that will be run after for all
builds. This has to be an array or a multi-line string.
@@ -811,7 +811,7 @@ deploy:
It's possible to overwrite globally defined `before_script` and `after_script`:
```yaml
-before_script
+before_script:
- global before script
job:
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
index 5893b7c219e..ce0aaa2fd25 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -52,5 +52,6 @@ information from database or file system
* Use red button for destructive actions (not revertable). For example removing issue.
* Use green or blue button for primary action. Primary button should be only one.
Do not use both green and blue button in one form.
-* For all other cases use default white button
+* For all other cases use default white button.
+* Text button should have only first word capitalized. So should be "Create issue" instead of "Create Issue"
diff --git a/doc/integration/github.md b/doc/integration/github.md
index e7497e475c9..340c8a55fb3 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -19,7 +19,7 @@ GitHub will generate an application ID and secret key for you to use.
- Application name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive.
- Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
- Application description: Fill this in if you wish.
- - Default authorization callback URL is '${YOUR_DOMAIN}/import/github/callback'
+ - Authorization callback URL is 'http(s)://${YOUR_DOMAIN}'
1. Select "Register application".
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 236eb7b12c4..fb2dd582754 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -7,11 +7,12 @@
* [Newlines](#newlines)
* [Multiple underscores in words](#multiple-underscores-in-words)
* [URL auto-linking](#url-auto-linking)
+* [Multiline Blockquote](#multiline-blockquote)
* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
* [Inline Diff](#inline-diff)
* [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references)
-* [Task lists](#task-lists)
+* [Task Lists](#task-lists)
**[Standard Markdown](#standard-markdown)**
@@ -89,6 +90,37 @@ GFM will autolink almost any URL you copy and paste into your text.
* irc://irc.freenode.net/gitlab
* http://localhost:3000
+## Multiline Blockquote
+
+On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines,
+GFM supports multiline blockquotes fenced by <code>>>></code>.
+
+```no-highlight
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+```
+
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+
## Code and Syntax Highlighting
_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 963b35de3a0..44f3f6d3b12 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -99,3 +99,6 @@ An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you
will find the option to flag the user as external.
+
+By default new users are not set as external users. This behavior can be changed
+by an administrator under **Admin > Application Settings**. \ No newline at end of file
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index a51790b0bda..84065a84e50 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
-sudo -u git -H git checkout v3.1.0
+sudo -u git -H git checkout v3.2.0
```
### 5. Update gitlab-workhorse
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
index d854ec1e025..67adfc2f43a 100644
--- a/doc/workflow/protected_branches.md
+++ b/doc/workflow/protected_branches.md
@@ -1,4 +1,4 @@
-# Protected branches
+# Protected Branches
Permissions in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
@@ -28,4 +28,28 @@ For those workflows, you can allow everyone with write access to push to a prote
On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box.
-![Developers can push](protected_branches/protected_branches2.png) \ No newline at end of file
+![Developers can push](protected_branches/protected_branches2.png)
+
+## Wildcard Protected Branches
+
+>**Note:**
+This feature was added in GitLab 8.10.
+
+1. You can specify a wildcard protected branch, which will protect all branches matching the wildcard. For example:
+
+ | Wildcard Protected Branch | Matching Branches |
+ |---------------------------+--------------------------------------------------------|
+ | `*-stable` | `production-stable`, `staging-stable` |
+ | `production/*` | `production/app-server`, `production/load-balancer` |
+ | `*gitlab*` | `gitlab`, `gitlab/staging`, `master/gitlab/production` |
+
+1. Protected branch settings (like "Developers Can Push") apply to all matching branches.
+
+1. Two different wildcards can potentially match the same branch. For example, `*-stable` and `production-*` would both match a `production-stable` branch.
+ >**Note:**
+ If _any_ of these protected branches have "Developers Can Push" set to true, then `production-stable` has it set to true.
+
+1. If you click on a protected branch's name, you will be presented with a list of all matching branches:
+
+ ![protected branch matches](protected_branches/protected_branches3.png)
+
diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png
index bb3ab7d7913..c00443803de 100644
--- a/doc/workflow/protected_branches/protected_branches1.png
+++ b/doc/workflow/protected_branches/protected_branches1.png
Binary files differ
diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png
index 58ace31ac57..a4f664d3b21 100644
--- a/doc/workflow/protected_branches/protected_branches2.png
+++ b/doc/workflow/protected_branches/protected_branches2.png
Binary files differ
diff --git a/doc/workflow/protected_branches/protected_branches3.png b/doc/workflow/protected_branches/protected_branches3.png
new file mode 100644
index 00000000000..2a50cb174bb
--- /dev/null
+++ b/doc/workflow/protected_branches/protected_branches3.png
Binary files differ
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index db73309804c..1f4c9020731 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -7,6 +7,7 @@ Feature: Dashboard
And project "Shop" has CI enabled
And project "Shop" has CI build
And project "Shop" has labels: "bug", "feature", "enhancement"
+ And project "Shop" has issue: "bug report"
And I visit dashboard page
Scenario: I should see projects list
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index a95df038357..8b0cb90765e 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -83,11 +83,6 @@ Feature: Project Commits
#Given I visit my project's commits stats page
#Then I see commits stats
- Scenario: I browse big commit
- Given I visit big commit page
- Then I see big commit warning
- And I see "Reload with full diff" link
-
Scenario: I browse a commit with an image
Given I visit a commit with an image that changed
Then The diff links to both the previous and current image
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 0e97e4d5954..21768c15c17 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -89,13 +89,6 @@ Feature: Project Merge Requests
Then The list should be sorted by "Oldest updated"
@javascript
- Scenario: Visiting Issues after being sorted the list
- Given I visit project "Shop" merge requests page
- And I sort the list by "Oldest updated"
- And I visit project "Shop" issues page
- Then The list should be sorted by "Oldest updated"
-
- @javascript
Scenario: Visiting Merge Requests from a differente Project after sorting
Given I visit project "Shop" merge requests page
And I sort the list by "Oldest updated"
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 2876e8812e9..b4a32ed2e38 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -68,10 +68,16 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
end
step 'download of a file extracted from build artifacts should start' do
- # this will be accelerated by Workhorse
- response_json = JSON.parse(page.body, symbolize_names: true)
- expect(response_json[:archive]).to end_with('build_artifacts.zip')
- expect(response_json[:entry]).to eq Base64.encode64('ci_artifacts.txt')
+ send_data = response_headers[Gitlab::Workhorse::SEND_DATA_HEADER]
+
+ expect(send_data).to start_with('artifacts-entry:')
+
+ base64_params = send_data.sub(/\Aartifacts\-entry:/, '')
+ params = JSON.parse(Base64.urlsafe_decode64(base64_params))
+
+ expect(params.keys).to eq(['Archive', 'Entry'])
+ expect(params['Archive']).to end_with('build_artifacts.zip')
+ expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt'))
end
step 'I click a first row within build artifacts table' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index 239036e431d..bea9f9d198b 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -125,25 +125,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content 'Authors'
end
- step 'I visit big commit page' do
- # Create a temporary scope to ensure that the stub_const is removed after user
- RSpec::Mocks.with_temporary_scope do
- stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_lines: 1, max_files: 1 })
- visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
- end
- end
-
- step 'I see big commit warning' do
- expect(page).to have_content sample_big_commit.message
- expect(page).to have_content "Too many changes"
- end
-
- step 'I see "Reload with full diff" link' do
- link = find_link('Reload with full diff')
- expect(link[:href]).to end_with('?force_show_diff=true')
- expect(link[:href]).not_to include('.html')
- end
-
step 'I visit a commit with an image that changed' do
visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index b3411c03118..0b4920883b8 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -223,6 +223,11 @@ module SharedProject
create(:label, project: project, title: 'enhancement')
end
+ step 'project "Shop" has issue: "bug report"' do
+ project = Project.find_by(name: "Shop")
+ create(:issue, project: project, title: "bug report")
+ end
+
step 'project "Shop" has CI enabled' do
project = Project.find_by(name: "Shop")
project.enable_ci
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index db877d2eeb0..8edb80177da 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -58,6 +58,14 @@ module API
expose :path, :path_with_namespace
end
+ class SharedGroup < Grape::Entity
+ expose :group_id
+ expose :group_name do |group_link, options|
+ group_link.group.name
+ end
+ expose :group_access, as: :group_access_level
+ end
+
class Project < Grape::Entity
expose :id, :description, :default_branch, :tag_list
expose :public?, as: :public
@@ -77,6 +85,9 @@ module API
expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds
+ expose :shared_with_groups do |project, options|
+ SharedGroup.represent(project.project_group_links.all, options)
+ end
end
class ProjectMember < UserBasic
@@ -93,6 +104,7 @@ module API
class GroupDetail < Group
expose :projects, using: Entities::Project
+ expose :shared_projects, using: Entities::Project
end
class GroupMember < UserBasic
@@ -187,7 +199,6 @@ module API
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
- expose :description
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds
diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb
new file mode 100644
index 00000000000..d2c4b1e4d76
--- /dev/null
+++ b/lib/banzai/filter/blockquote_fence_filter.rb
@@ -0,0 +1,71 @@
+module Banzai
+ module Filter
+ class BlockquoteFenceFilter < HTML::Pipeline::TextFilter
+ REGEX = %r{
+ (?<code>
+ # Code blocks:
+ # ```
+ # Anything, including `>>>` blocks which are ignored by this filter
+ # ```
+
+ ^```
+ .+?
+ \n```$
+ )
+ |
+ (?<html>
+ # HTML block:
+ # <tag>
+ # Anything, including `>>>` blocks which are ignored by this filter
+ # </tag>
+
+ ^<[^>]+?>\n
+ .+?
+ \n<\/[^>]+?>$
+ )
+ |
+ (?:
+ # Blockquote:
+ # >>>
+ # Anything, including code and HTML blocks
+ # >>>
+
+ ^>>>\n
+ (?<quote>
+ (?:
+ # Any character that doesn't introduce a code or HTML block
+ (?!
+ ^```
+ |
+ ^<[^>]+?>\n
+ )
+ .
+ |
+ # A code block
+ \g<code>
+ |
+ # An HTML block
+ \g<html>
+ )+?
+ )
+ \n>>>$
+ )
+ }mx.freeze
+
+ def initialize(text, context = nil, result = nil)
+ super text, context, result
+ @text = @text.delete("\r")
+ end
+
+ def call
+ @text.gsub(REGEX) do
+ if $~[:quote]
+ $~[:quote].gsub(/^/, "> ").gsub(/^> $/, ">")
+ else
+ $~[0]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb
index 50dc978b452..6cf219661d3 100644
--- a/lib/banzai/pipeline/pre_process_pipeline.rb
+++ b/lib/banzai/pipeline/pre_process_pipeline.rb
@@ -3,7 +3,8 @@ module Banzai
class PreProcessPipeline < BasePipeline
def self.filters
FilterArray[
- Filter::YamlFrontMatterFilter
+ Filter::YamlFrontMatterFilter,
+ Filter::BlockquoteFenceFilter,
]
end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 3d7b9c4a024..6cf218aaa0d 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -133,8 +133,9 @@ module Banzai
return {} if nodes.empty?
ids = unique_attribute_values(nodes, attribute)
+ rows = collection_objects_for_ids(collection, ids)
- collection.where(id: ids).each_with_object({}) do |row, hash|
+ rows.each_with_object({}) do |row, hash|
hash[row.id] = row
end
end
@@ -153,6 +154,31 @@ module Banzai
values.to_a
end
+ # Queries the collection for the objects with the given IDs.
+ #
+ # If the RequestStore module is enabled this method will only query any
+ # objects that have not yet been queried. For objects that have already
+ # been queried the object is returned from the cache.
+ def collection_objects_for_ids(collection, ids)
+ if RequestStore.active?
+ cache = collection_cache[collection_cache_key(collection)]
+ to_query = ids.map(&:to_i) - cache.keys
+
+ unless to_query.empty?
+ collection.where(id: to_query).each { |row| cache[row.id] = row }
+ end
+
+ cache.values
+ else
+ collection.where(id: ids)
+ end
+ end
+
+ # Returns the cache key to use for a collection.
+ def collection_cache_key(collection)
+ collection.respond_to?(:model) ? collection.model : collection
+ end
+
# Processes the list of HTML documents and returns an Array containing all
# the references.
def process(documents)
@@ -189,7 +215,7 @@ module Banzai
end
def find_projects_for_hash_keys(hash)
- Project.where(id: hash.keys)
+ collection_objects_for_ids(Project, hash.keys)
end
private
@@ -199,6 +225,12 @@ module Banzai
def lazy(&block)
Gitlab::Lazy.new(&block)
end
+
+ def collection_cache
+ RequestStore[:banzai_collection_cache] ||= Hash.new do |hash, key|
+ hash[key] = {}
+ end
+ end
end
end
end
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index a12b0d19560..863f5725d3b 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -73,7 +73,7 @@ module Banzai
def find_users(ids)
return [] if ids.empty?
- User.where(id: ids).to_a
+ collection_objects_for_ids(User, ids)
end
def find_users_for_groups(ids)
@@ -85,7 +85,8 @@ module Banzai
def find_users_for_projects(ids)
return [] if ids.empty?
- Project.where(id: ids).flat_map { |p| p.team.members.to_a }
+ collection_objects_for_ids(Project, ids).
+ flat_map { |p| p.team.members.to_a }
end
end
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 54b46e5d23f..ffc1814b29d 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -48,6 +48,7 @@ module Gitlab
akismet_enabled: false,
repository_checks_enabled: true,
container_registry_token_expire_delay: 5,
+ user_default_external: false,
)
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index b0c50edba59..7e01f7b61fb 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -68,6 +68,10 @@ module Gitlab
@lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
end
+ def collapsed_by_default?
+ diff.diff.bytesize > 10240 # 10 KB
+ end
+
def highlighted_diff_lines
@highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index 57c41b41298..9b681e636c7 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def trigger(gl_id, oldrev, newrev, ref)
- return true unless exists?
+ return [true, nil] unless exists?
case name
when "pre-receive", "post-receive"
@@ -70,13 +70,10 @@ module Gitlab
end
def call_update_hook(gl_id, oldrev, newrev, ref)
- status = nil
-
Dir.chdir(repo_path) do
- status = system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
+ stdout, stderr, status = Open3.capture3({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
+ [status.success?, stderr.presence || stdout]
end
-
- [status, nil]
end
def retrieve_error_message(stderr, stdout)
diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb
index 2122339d2db..3caf9036459 100644
--- a/lib/gitlab/graphs/commits.rb
+++ b/lib/gitlab/graphs/commits.rb
@@ -18,7 +18,7 @@ module Gitlab
end
def commit_per_day
- @commit_per_day ||= (@commits.size.to_f / @duration).round(1)
+ @commit_per_day ||= @commits.size / (@duration + 1)
end
def collect_data
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 0ac6ff01e3b..025ecc12f9f 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -69,10 +69,19 @@ module Gitlab
# Example:
# +relation_key+ issues, loops through the list of *issues* and for each individual
# issue, finds any subrelations such as notes, creates them and assign them back to the hash
+ #
+ # Recursively calls this method if the sub-relation is a hash containing more sub-relations
def create_sub_relations(relation, tree_hash)
relation_key = relation.keys.first.to_s
+ return if tree_hash[relation_key].blank?
+
tree_hash[relation_key].each do |relation_item|
relation.values.flatten.each do |sub_relation|
+ # We just use author to get the user ID, do not attempt to create an instance.
+ next if sub_relation == :author
+
+ create_sub_relations(sub_relation, relation_item) if sub_relation.is_a?(Hash)
+
relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation)
relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank?
end
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 86ed18fb50d..19dad699edf 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -4,6 +4,8 @@ module Gitlab
regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git'])
content.gsub(regexp) { |url| new(url).masked_url }
+ rescue Addressable::URI::InvalidURIError
+ content.gsub(regexp, '')
end
def self.valid?(url)
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index bc0193a6c32..6aeb49c0219 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -63,6 +63,18 @@ module Gitlab
]
end
+ def send_artifacts_entry(build, entry)
+ params = {
+ 'Archive' => build.artifacts_file.path,
+ 'Entry' => Base64.encode64(entry.path)
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "artifacts-entry:#{encode(params)}"
+ ]
+ end
+
protected
def encode(hash)
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index 8c309efc7b8..3358ed6773e 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -143,18 +143,14 @@ module Rouge
'</span>'
end
end
- lines.join("\n")
- else
- if @linenos == 'inline'
- lines = lines.each_with_index.map do |line, index|
- number = index + @linenostart
- "<span class=\"linenos\">#{number}</span>#{line}"
- end
- lines.join("\n")
- else
- lines.join("\n")
+ elsif @linenos == 'inline'
+ lines = lines.each_with_index.map do |line, index|
+ number = index + @linenostart
+ "<span class=\"linenos\">#{number}</span>#{line}"
end
end
+
+ lines.join("\n")
end
def span(tok, val)
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
deleted file mode 100644
index a3a3309e15e..00000000000
--- a/spec/controllers/commit_controller_spec.rb
+++ /dev/null
@@ -1,246 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CommitController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:commit) { project.commit("master") }
- let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
- let(:master_pickable_commit) { project.commit(master_pickable_sha) }
-
- before do
- sign_in(user)
- project.team << [user, :master]
- end
-
- describe "#show" do
- shared_examples "export as" do |format|
- it "should generally work" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response).to be_success
- end
-
- it "should generate it" do
- expect_any_instance_of(Commit).to receive(:"to_#{format}")
-
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
- end
-
- it "should render it" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
-
- expect(response.body).to eq(commit.send(:"to_#{format}"))
- end
-
- it "should not escape Html" do
- allow_any_instance_of(Commit).to receive(:"to_#{format}").
- and_return('HTML entities &<>" ')
-
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
-
- expect(response.body).not_to include('&amp;')
- expect(response.body).not_to include('&gt;')
- expect(response.body).not_to include('&lt;')
- expect(response.body).not_to include('&quot;')
- end
- end
-
- describe "as diff" do
- include_examples "export as", :diff
- let(:format) { :diff }
-
- it "should really only be a git diff" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to start_with("diff --git")
- end
-
- it "should really only be a git diff without whitespace changes" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: '66eceea0db202bb39c4e445e8ca28689645366c5',
- # id: commit.id,
- format: format,
- w: 1)
-
- expect(response.body).to start_with("diff --git")
- # without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs).first.diff.split("\n")
- expect(diff_splits.length).to be <= 2
- end
- end
-
- describe "as patch" do
- include_examples "export as", :patch
- let(:format) { :patch }
-
- it "should really be a git email patch" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to start_with("From #{commit.id}")
- end
-
- it "should contain a git diff" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to match(/^diff --git/)
- end
- end
-
- context 'commit that removes a submodule' do
- render_views
-
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:commit) { fork_project.commit('remove-submodule') }
-
- before do
- fork_project.team << [user, :master]
- end
-
- it 'renders it' do
- get(:show,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project.to_param,
- id: commit.id)
-
- expect(response).to be_success
- end
- end
- end
-
- describe "#branches" do
- it "contains branch and tags information" do
- get(:branches,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id)
-
- expect(assigns(:branches)).to include("master", "feature_conflict")
- expect(assigns(:tags)).to include("v1.1.0")
- end
- end
-
- describe '#revert' do
- context 'when target branch is not provided' do
- it 'should render the 404 page' do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id)
-
- expect(response).not_to be_success
- expect(response).to have_http_status(404)
- end
- end
-
- context 'when the revert was successful' do
- it 'should redirect to the commits page' do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
-
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
- expect(flash[:notice]).to eq('The commit has been successfully reverted.')
- end
- end
-
- context 'when the revert failed' do
- before do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
- end
-
- it 'should redirect to the commit page' do
- # Reverting a commit that has been already reverted.
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
-
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
- expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
- end
- end
- end
-
- describe '#cherry_pick' do
- context 'when target branch is not provided' do
- it 'should render the 404 page' do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: master_pickable_commit.id)
-
- expect(response).not_to be_success
- expect(response).to have_http_status(404)
- end
- end
-
- context 'when the cherry-pick was successful' do
- it 'should redirect to the commits page' do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
-
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
- expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
- end
- end
-
- context 'when the cherry_pick failed' do
- before do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
- end
-
- it 'should redirect to the commit page' do
- # Cherry-picking a commit that has been already cherry-picked.
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
-
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
- expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
- end
- end
- end
-end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 6e3db10e451..3001d32e719 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -1,9 +1,29 @@
-require 'rails_helper'
+require 'spec_helper'
describe Projects::CommitController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:commit) { project.commit("master") }
+ let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
+ let(:master_pickable_commit) { project.commit(master_pickable_sha) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
describe 'GET show' do
render_views
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :show, params.merge(extra_params)
+ end
+
let(:project) { create(:project) }
before do
@@ -15,7 +35,7 @@ describe Projects::CommitController do
context 'with valid id' do
it 'responds with 200' do
- go id: project.commit.id
+ go(id: commit.id)
expect(response).to be_ok
end
@@ -23,27 +43,274 @@ describe Projects::CommitController do
context 'with invalid id' do
it 'responds with 404' do
- go id: project.commit.id.reverse
+ go(id: commit.id.reverse)
expect(response).to be_not_found
end
end
it 'handles binary files' do
- get(:show,
+ go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html')
+
+ expect(response).to be_success
+ end
+
+ shared_examples "export as" do |format|
+ it "should generally work" do
+ go(id: commit.id, format: format)
+
+ expect(response).to be_success
+ end
+
+ it "should generate it" do
+ expect_any_instance_of(Commit).to receive(:"to_#{format}")
+
+ go(id: commit.id, format: format)
+ end
+
+ it "should render it" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to eq(commit.send(:"to_#{format}"))
+ end
+
+ it "should not escape Html" do
+ allow_any_instance_of(Commit).to receive(:"to_#{format}").
+ and_return('HTML entities &<>" ')
+
+ go(id: commit.id, format: format)
+
+ expect(response.body).not_to include('&amp;')
+ expect(response.body).not_to include('&gt;')
+ expect(response.body).not_to include('&lt;')
+ expect(response.body).not_to include('&quot;')
+ end
+ end
+
+ describe "as diff" do
+ include_examples "export as", :diff
+ let(:format) { :diff }
+
+ it "should really only be a git diff" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to start_with("diff --git")
+ end
+
+ it "should really only be a git diff without whitespace changes" do
+ go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1)
+
+ expect(response.body).to start_with("diff --git")
+ # without whitespace option, there are more than 2 diff_splits
+ diff_splits = assigns(:diffs).first.diff.split("\n")
+ expect(diff_splits.length).to be <= 2
+ end
+ end
+
+ describe "as patch" do
+ include_examples "export as", :patch
+ let(:format) { :patch }
+
+ it "should really be a git email patch" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to start_with("From #{commit.id}")
+ end
+
+ it "should contain a git diff" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to match(/^diff --git/)
+ end
+ end
+
+ context 'commit that removes a submodule' do
+ render_views
+
+ let(:fork_project) { create(:forked_project_with_submodules, visibility_level: 20) }
+ let(:commit) { fork_project.commit('remove-submodule') }
+
+ it 'renders it' do
+ get(:show,
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project.to_param,
+ id: commit.id)
+
+ expect(response).to be_success
+ end
+ end
+ end
+
+ describe "GET branches" do
+ it "contains branch and tags information" do
+ get(:branches,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
- id: TestEnv::BRANCH_SHA['binary-encoding'],
- format: "html")
+ id: commit.id)
- expect(response).to be_success
+ expect(assigns(:branches)).to include("master", "feature_conflict")
+ expect(assigns(:tags)).to include("v1.1.0")
+ end
+ end
+
+ describe 'POST revert' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: commit.id)
+
+ expect(response).not_to be_success
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the revert was successful' do
+ it 'should redirect to the commits page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully reverted.')
+ end
+ end
+
+ context 'when the revert failed' do
+ before do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Reverting a commit that has been already reverted.
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
+ end
+ end
+ end
+
+ describe 'POST cherry_pick' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: master_pickable_commit.id)
+
+ expect(response).not_to be_success
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the cherry-pick was successful' do
+ it 'should redirect to the commits page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
+ end
end
- def go(id:)
- get :show,
+ context 'when the cherry_pick failed' do
+ before do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Cherry-picking a commit that has been already cherry-picked.
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
+ end
+ end
+ end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: id
+ project_id: project.to_param
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { '.gitmodules' }
+
+ context 'when the commit exists' do
+ context 'when the user has access to the project' do
+ context 'when the path exists in the diff' do
+ it 'enables diff notes' do
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_falsey
+ expect(assigns(:comments_target)).to eq(noteable_type: 'Commit',
+ commit_id: commit.id)
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ before do
+ project.team.truncate
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the commit does not exist' do
+ before { diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
end
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 4018dac95a2..4058d5e2453 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -64,4 +64,73 @@ describe Projects::CompareController do
expect(assigns(:commits)).to eq(nil)
end
end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { 'files/ruby/feature.rb' }
+
+ context 'when the from and to refs exist' do
+ context 'when the user has access to the project' do
+ context 'when the path exists in the diff' do
+ it 'disables diff notes' do
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ before do
+ project.team.truncate
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the from ref does not exist' do
+ before { diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the to ref does not exist' do
+ before { diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c4b57e77804..210085e3b1a 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -10,7 +10,7 @@ describe Projects::MergeRequestsController do
project.team << [user, :master]
end
- describe '#new' do
+ describe 'GET new' do
context 'merge request that removes a submodule' do
render_views
@@ -34,7 +34,7 @@ describe Projects::MergeRequestsController do
end
end
- describe "#show" do
+ describe "GET show" do
shared_examples "export merge as" do |format|
it "should generally work" do
get(:show,
@@ -108,7 +108,7 @@ describe Projects::MergeRequestsController do
end
end
- describe 'GET #index' do
+ describe 'GET index' do
def get_merge_requests
get :index,
namespace_id: project.namespace.to_param,
@@ -140,7 +140,7 @@ describe Projects::MergeRequestsController do
end
end
- describe 'PUT #update' do
+ describe 'PUT update' do
context 'there is no source project' do
let(:project) { create(:project) }
let(:fork_project) { create(:forked_project_with_submodules) }
@@ -168,7 +168,7 @@ describe Projects::MergeRequestsController do
end
end
- describe 'POST #merge' do
+ describe 'POST merge' do
let(:base_params) do
{
namespace_id: project.namespace.path,
@@ -266,7 +266,7 @@ describe Projects::MergeRequestsController do
end
end
- describe "DELETE #destroy" do
+ describe "DELETE destroy" do
it "denies access to users unless they're admin or project owner" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
@@ -290,96 +290,210 @@ describe Projects::MergeRequestsController do
end
describe 'GET diffs' do
- def go(format: 'html')
- get :diffs,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid,
- format: format
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid
+ }
+
+ get :diffs, params.merge(extra_params)
end
- context 'as html' do
- it 'renders the diff template' do
- go
+ context 'with default params' do
+ context 'as html' do
+ before { go(format: 'html') }
- expect(response).to render_template('diffs')
+ it 'renders the diff template' do
+ expect(response).to render_template('diffs')
+ end
end
- end
- context 'as json' do
- it 'renders the diffs template to a string' do
- go format: 'json'
+ context 'as json' do
+ before { go(format: 'json') }
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/show/_diffs')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
end
- end
-
- context 'with forked projects with submodules' do
- render_views
- let(:project) { create(:project) }
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
+ context 'with forked projects with submodules' do
+ render_views
- before do
- fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
- fork_project.save
- merge_request.reload
- end
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+ let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
- it 'renders' do
- go format: 'json'
+ before do
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+ merge_request.reload
+ go(format: 'json')
+ end
- expect(response).to be_success
- expect(response.body).to have_content('Subproject commit')
+ it 'renders' do
+ expect(response).to be_success
+ expect(response.body).to have_content('Subproject commit')
+ end
end
end
- end
- describe 'GET diffs with ignore_whitespace_change' do
- def go(format: 'html')
- get :diffs,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid,
- format: format,
- w: 1
- end
+ context 'with ignore_whitespace_change' do
+ context 'as html' do
+ before { go(format: 'html', w: 1) }
- context 'as html' do
- it 'renders the diff template' do
- go
+ it 'renders the diff template' do
+ expect(response).to render_template('diffs')
+ end
+ end
+
+ context 'as json' do
+ before { go(format: 'json', w: 1) }
- expect(response).to render_template('diffs')
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/show/_diffs')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
end
end
- context 'as json' do
- it 'renders the diffs template to a string' do
- go format: 'json'
+ context 'with view' do
+ before { go(view: 'parallel') }
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
+ it 'saves the preferred diff view in a cookie' do
+ expect(response.cookies['diff_view']).to eq('parallel')
end
end
end
- describe 'GET diffs with view' do
- def go(extra_params = {})
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid
+ project_id: project.to_param
}
- get :diffs, params.merge(extra_params)
+ get :diff_for_path, params.merge(extra_params)
end
- it 'saves the preferred diff view in a cookie' do
- go view: 'parallel'
+ context 'when an ID param is passed' do
+ let(:existing_path) { 'files/ruby/popen.rb' }
- expect(response.cookies['diff_view']).to eq('parallel')
+ context 'when the merge request exists' do
+ context 'when the user can view the merge request' do
+ context 'when the path exists in the diff' do
+ it 'enables diff notes' do
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_falsey
+ expect(assigns(:comments_target)).to eq(noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id)
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user cannot view the merge request' do
+ before do
+ project.team.truncate
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the merge request does not exist' do
+ before { diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the merge request belongs to a different project' do
+ let(:other_project) { create(:empty_project) }
+
+ before do
+ other_project.team << [user, :master]
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project.to_param)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when source and target params are passed' do
+ let(:existing_path) { 'files/ruby/feature.rb' }
+
+ context 'when both branches are in the same project' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the source branch is in a different project to the target' do
+ let(:other_project) { create(:project) }
+
+ before { other_project.team << [user, :master] }
+
+ context 'when the path exists in the diff' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 5c8ddbebf0d..b682ced75ac 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -2,7 +2,7 @@ FactoryGirl.define do
# Project without repository
#
# Project does not have bare repository.
- # Use this factory if you dont need repository in tests
+ # Use this factory if you don't need repository in tests
factory :empty_project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
new file mode 100644
index 00000000000..16baf7e9516
--- /dev/null
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe "Admin::AbuseReports", feature: true, js: true do
+ let(:user) { create(:user) }
+
+ context 'as an admin' do
+ describe 'if a user has been reported for abuse' do
+ before do
+ create(:abuse_report, user: user)
+ login_as :admin
+ end
+
+ describe 'in the abuse report view' do
+ it "should present a link to the user's profile" do
+ visit admin_abuse_reports_path
+
+ expect(page).to have_link user.name, href: user_path(user)
+ end
+ end
+
+ describe 'in the profile page of the user' do
+ it 'should show a link to the admin view of the user' do
+ visit user_path(user)
+
+ expect(page).to have_link '', href: admin_user_path(user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
new file mode 100644
index 00000000000..7cff196c8d9
--- /dev/null
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -0,0 +1,207 @@
+require 'spec_helper'
+
+feature 'Expand and collapse diffs', js: true, feature: true do
+ include WaitForAjax
+
+ before do
+ login_as :admin
+ project = create(:project)
+ branch = 'expand-collapse-diffs'
+
+ # Ensure that undiffable.md is in .gitattributes
+ project.repository.copy_gitattributes(branch)
+ visit namespace_project_commit_path(project.namespace, project, project.commit(branch))
+ execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
+ end
+
+ def file_container(filename)
+ find("[data-blob-diff-path*='#{filename}']")
+ end
+
+ # Use define_method instead of let (which is memoized) so that this just works across a
+ # reload.
+ #
+ files = [
+ 'small_diff.md', 'large_diff.md', 'large_diff_renamed.md', 'undiffable.md',
+ 'too_large.md', 'too_large_image.jpg'
+ ]
+
+ files.each do |file|
+ define_method(file.split('.').first) { file_container(file) }
+ end
+
+ context 'visiting a commit with collapsed diffs' do
+ it 'shows small diffs immediately' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'collapses large diffs by default' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+ end
+
+ it 'collapses large diffs for renamed files by default' do
+ expect(large_diff_renamed).not_to have_selector('.code')
+ expect(large_diff_renamed).to have_selector('.nothing-here-block')
+ expect(large_diff_renamed).to have_selector('.file-title .deletion')
+ expect(large_diff_renamed).to have_selector('.file-title .addition')
+ end
+
+ it 'shows non-renderable diffs as such immediately, regardless of their size' do
+ expect(undiffable).not_to have_selector('.code')
+ expect(undiffable).to have_selector('.nothing-here-block')
+ expect(undiffable).to have_content('gitattributes')
+ end
+
+ it 'does not allow diffs that are larger than the maximum size to be expanded' do
+ expect(too_large).not_to have_selector('.code')
+ expect(too_large).to have_selector('.nothing-here-block')
+ expect(too_large).to have_content('too large')
+ end
+
+ it 'shows image diffs immediately, regardless of their size' do
+ expect(too_large_image).not_to have_selector('.nothing-here-block')
+ expect(too_large_image).to have_selector('.image')
+ end
+
+ context 'expanding a diff for a renamed file' do
+ before do
+ large_diff_renamed.find('.nothing-here-block').click
+ wait_for_ajax
+ end
+
+ it 'shows the old content' do
+ old_line = large_diff_renamed.find('.line_content.old')
+
+ expect(old_line).to have_content('two copies')
+ end
+
+ it 'shows the new content' do
+ new_line = large_diff_renamed.find('.line_content.new', match: :prefer_exact)
+
+ expect(new_line).to have_content('three copies')
+ end
+ end
+
+ context 'expanding a large diff' do
+ before do
+ click_link('large_diff.md')
+ wait_for_ajax
+ end
+
+ it 'makes a request to get the content' do
+ ajax_uris = evaluate_script('ajaxUris')
+
+ expect(ajax_uris).not_to be_empty
+ expect(ajax_uris.first).to include('large_diff.md')
+ end
+
+ it 'shows the diff content' do
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ context 'adding a comment to the expanded diff' do
+ let(:comment_text) { 'A comment' }
+
+ before do
+ large_diff.find('.line_holder', match: :prefer_exact).hover
+ large_diff.find('.add-diff-note').click
+ large_diff.find('.note-textarea').send_keys comment_text
+ large_diff.find_button('Comment').click
+ wait_for_ajax
+ end
+
+ it 'adds the comment' do
+ expect(large_diff.find('.notes')).to have_content comment_text
+ end
+
+ context 'reloading the page' do
+ before { refresh }
+
+ it 'collapses the large diff by default' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 'expanding the diff' do
+ before do
+ click_link('large_diff.md')
+ wait_for_ajax
+ end
+
+ it 'shows the diff content' do
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'shows the diff comment' do
+ expect(large_diff.find('.notes')).to have_content comment_text
+ end
+ end
+ end
+ end
+ end
+
+ context 'collapsing an expanded diff' do
+ before { click_link('small_diff.md') }
+
+ it 'hides the diff content' do
+ expect(small_diff).not_to have_selector('.code')
+ expect(small_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 're-expanding the same diff' do
+ before { click_link('small_diff.md') }
+
+ it 'shows the diff content' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'does not make a new HTTP request' do
+ expect(evaluate_script('ajaxUris')).to be_empty
+ end
+ end
+ end
+ end
+
+ context 'expanding all diffs' do
+ before do
+ click_link('Expand all')
+ wait_for_ajax
+ execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
+ end
+
+ it 'reloads the page with all diffs expanded' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ context 'collapsing an expanded diff' do
+ before { click_link('small_diff.md') }
+
+ it 'hides the diff content' do
+ expect(small_diff).not_to have_selector('.code')
+ expect(small_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 're-expanding the same diff' do
+ before { click_link('small_diff.md') }
+
+ it 'shows the diff content' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'does not make a new HTTP request' do
+ expect(evaluate_script('ajaxUris')).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 006a06b8235..4b9b5394b61 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -7,6 +7,7 @@ describe 'Filter issues', feature: true do
let!(:user) { create(:user)}
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
+ let!(:issue1) { create(:issue, project: project) }
before do
project.team << [user, :master]
@@ -196,6 +197,7 @@ describe 'Filter issues', feature: true do
page.within '.labels-filter' do
click_link 'bug'
end
+ find('.dropdown-menu-close-icon').click
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 1)
@@ -287,7 +289,7 @@ describe 'Filter issues', feature: true do
wait_for_ajax
page.within '.issues-list' do
- expect(first('.issue')).to have_content('Frontend')
+ expect(page).to have_content('Frontend')
end
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 6a39c302f55..98ba93b4036 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -76,7 +76,7 @@ feature 'Prioritize labels', feature: true do
expect(page.all('li').last).to have_content('bug')
end
- visit current_url
+ refresh
wait_for_ajax
page.within('.prioritized-labels') do
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
new file mode 100644
index 00000000000..d94dee0c797
--- /dev/null
+++ b/spec/features/protected_branches_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+feature 'Projected Branches', feature: true, js: true do
+ let(:user) { create(:user, :admin) }
+ let(:project) { create(:project) }
+
+ before { login_as(user) }
+
+ def set_protected_branch_name(branch_name)
+ find(".js-protected-branch-select").click
+ find(".dropdown-input-field").set(branch_name)
+ click_on "Create Protected Branch: #{branch_name}"
+ end
+
+ describe "explicit protected branches" do
+ it "allows creating explicit protected branches" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('some-branch') }
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.name).to eq('some-branch')
+ end
+
+ it "displays the last commit on the matching branch if it exists" do
+ commit = create(:commit, project: project)
+ project.repository.add_branch(user, 'some-branch', commit.id)
+
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content(commit.id[0..7]) }
+ end
+
+ it "displays an error message if the named branch does not exist" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('branch was removed') }
+ end
+ end
+
+ describe "wildcard protected branches" do
+ it "allows creating protected branches with a wildcard" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('*-stable')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('*-stable') }
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.name).to eq('*-stable')
+ end
+
+ it "displays the number of matching branches" do
+ project.repository.add_branch(user, 'production-stable', 'master')
+ project.repository.add_branch(user, 'staging-stable', 'master')
+
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('*-stable')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content("2 matching branches") }
+ end
+
+ it "displays all the branches matching the wildcard" do
+ project.repository.add_branch(user, 'production-stable', 'master')
+ project.repository.add_branch(user, 'staging-stable', 'master')
+ project.repository.add_branch(user, 'development', 'master')
+ create(:protected_branch, project: project, name: "*-stable")
+
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ click_on "2 matching branches"
+
+ within(".protected-branches-list") do
+ expect(page).to have_content("production-stable")
+ expect(page).to have_content("staging-stable")
+ expect(page).not_to have_content("development")
+ end
+ end
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 85923f0a19d..d0a301038c4 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
describe "Search", feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
+ let!(:issue) { create(:issue, project: project, assignee: user) }
+ let!(:issue2) { create(:issue, project: project, author: user) }
before do
login_with(user)
diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md
new file mode 100644
index 00000000000..2652a842c0e
--- /dev/null
+++ b/spec/fixtures/blockquote_fence_after.md
@@ -0,0 +1,115 @@
+Single `>>>` inside code block:
+
+```
+# Code
+>>>
+# Code
+```
+
+Double `>>>` inside code block:
+
+```txt
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Blockquote outside code block:
+
+> Quote
+
+Code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> ```
+>
+> Quote
+
+Single `>>>` inside code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> >>>
+> # Code
+> ```
+>
+> Quote
+
+Double `>>>` inside code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> >>>
+> # Code
+> >>>
+> # Code
+> ```
+>
+> Quote
+
+Single `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+</pre>
+
+Double `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+>>>
+# Code
+</pre>
+
+Blockquote outside HTML:
+
+> Quote
+
+HTML inside blockquote:
+
+> Quote
+>
+> <pre>
+> # Code
+> </pre>
+>
+> Quote
+
+Single `>>>` inside HTML inside blockquote:
+
+> Quote
+>
+> <pre>
+> # Code
+> >>>
+> # Code
+> </pre>
+>
+> Quote
+
+Double `>>>` inside HTML inside blockquote:
+
+> Quote
+>
+> <pre>
+> # Code
+> >>>
+> # Code
+> >>>
+> # Code
+> </pre>
+>
+> Quote
diff --git a/spec/fixtures/blockquote_fence_before.md b/spec/fixtures/blockquote_fence_before.md
new file mode 100644
index 00000000000..d52eec72896
--- /dev/null
+++ b/spec/fixtures/blockquote_fence_before.md
@@ -0,0 +1,131 @@
+Single `>>>` inside code block:
+
+```
+# Code
+>>>
+# Code
+```
+
+Double `>>>` inside code block:
+
+```txt
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Blockquote outside code block:
+
+>>>
+Quote
+>>>
+
+Code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+```
+
+Quote
+>>>
+
+Single `>>>` inside code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+>>>
+# Code
+```
+
+Quote
+>>>
+
+Double `>>>` inside code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Quote
+>>>
+
+Single `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+</pre>
+
+Double `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+>>>
+# Code
+</pre>
+
+Blockquote outside HTML:
+
+>>>
+Quote
+>>>
+
+HTML inside blockquote:
+
+>>>
+Quote
+
+<pre>
+# Code
+</pre>
+
+Quote
+>>>
+
+Single `>>>` inside HTML inside blockquote:
+
+>>>
+Quote
+
+<pre>
+# Code
+>>>
+# Code
+</pre>
+
+Quote
+>>>
+
+Double `>>>` inside HTML inside blockquote:
+
+>>>
+Quote
+
+<pre>
+# Code
+>>>
+# Code
+>>>
+# Code
+</pre>
+
+Quote
+>>>
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 7ea9fa933f0..4b134a48410 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -31,26 +31,11 @@ describe DiffHelper do
end
end
- describe 'diff_hard_limit_enabled?' do
- it 'should return true if param is provided' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(diff_hard_limit_enabled?).to be_truthy
- end
-
- it 'should return false if param is not provided' do
- expect(diff_hard_limit_enabled?).to be_falsey
- end
- end
-
describe 'diff_options' do
- it 'should return hard limit for a diff if force diff is true' do
+ it 'should return hard limit for a diff' do
allow(controller).to receive(:params) { { force_show_diff: true } }
expect(diff_options).to include(Commit.max_diff_options)
end
-
- it 'should return safe limit for a diff if force diff is false' do
- expect(diff_options).not_to include(:max_lines, :max_files)
- end
end
describe 'unfold_bottom_class' do
diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
index 470cabeafbb..06c2ab1e823 100644
--- a/spec/javascripts/fixtures/issues_show.html.haml
+++ b/spec/javascripts/fixtures/issues_show.html.haml
@@ -1,7 +1,7 @@
:css
.hidden { display: none !important; }
-.flash-container
+.flash-container.flash-container-page
.flash-alert
.flash-notice
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
new file mode 100644
index 00000000000..2799249ae3e
--- /dev/null
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+describe Banzai::Filter::BlockquoteFenceFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'converts blockquote fences to blockquote lines' do
+ content = File.read(Rails.root.join('spec/fixtures/blockquote_fence_before.md'))
+ expected = File.read(Rails.root.join('spec/fixtures/blockquote_fence_after.md'))
+
+ output = filter(content)
+
+ expect(output).to eq(expected)
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index 543b4786d84..ac9c66e2663 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -234,4 +234,79 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
to eq([project])
end
end
+
+ describe '#collection_objects_for_ids' do
+ context 'with RequestStore disabled' do
+ it 'queries the collection directly' do
+ collection = User.all
+
+ expect(collection).to receive(:where).twice.and_call_original
+
+ 2.times do
+ expect(subject.collection_objects_for_ids(collection, [user.id])).
+ to eq([user])
+ end
+ end
+ end
+
+ context 'with RequestStore enabled' do
+ before do
+ cache = Hash.new { |hash, key| hash[key] = {} }
+
+ allow(RequestStore).to receive(:active?).and_return(true)
+ allow(subject).to receive(:collection_cache).and_return(cache)
+ end
+
+ it 'queries the collection on the first call' do
+ expect(subject.collection_objects_for_ids(User, [user.id])).
+ to eq([user])
+ end
+
+ it 'does not query previously queried objects' do
+ collection = User.all
+
+ expect(collection).to receive(:where).once.and_call_original
+
+ 2.times do
+ expect(subject.collection_objects_for_ids(collection, [user.id])).
+ to eq([user])
+ end
+ end
+
+ it 'casts String based IDs to Fixnums before querying objects' do
+ 2.times do
+ expect(subject.collection_objects_for_ids(User, [user.id.to_s])).
+ to eq([user])
+ end
+ end
+
+ it 'queries any additional objects after the first call' do
+ other_user = create(:user)
+
+ expect(subject.collection_objects_for_ids(User, [user.id])).
+ to eq([user])
+
+ expect(subject.collection_objects_for_ids(User, [user.id, other_user.id])).
+ to eq([user, other_user])
+ end
+
+ it 'caches objects on a per collection class basis' do
+ expect(subject.collection_objects_for_ids(User, [user.id])).
+ to eq([user])
+
+ expect(subject.collection_objects_for_ids(Project, [project.id])).
+ to eq([project])
+ end
+ end
+ end
+
+ describe '#collection_cache_key' do
+ it 'returns the cache key for a Class' do
+ expect(subject.collection_cache_key(Project)).to eq(Project)
+ end
+
+ it 'returns the cache key for an ActiveRecord::Relation' do
+ expect(subject.collection_cache_key(Project.all)).to eq(Project)
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
new file mode 100644
index 00000000000..a15aa173fbd
--- /dev/null
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+require 'fileutils'
+
+describe Gitlab::Git::Hook, lib: true do
+ describe "#trigger" do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ def create_hook(name)
+ FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
+ File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+ f.write('exit 0')
+ end
+ end
+
+ def create_failing_hook(name)
+ FileUtils.mkdir_p(File.join(project.repository.path, 'hooks'))
+ File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f|
+ f.write(<<-HOOK)
+ echo 'regular message from the hook'
+ echo 'error message from the hook' 1>&2
+ exit 1
+ HOOK
+ end
+ end
+
+ ['pre-receive', 'post-receive', 'update'].each do |hook_name|
+
+ context "when triggering a #{hook_name} hook" do
+ context "when the hook is successful" do
+ it "returns success with no errors" do
+ create_hook(hook_name)
+ hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+ blank = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
+
+ status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+ expect(status).to be true
+ expect(errors).to be_blank
+ end
+ end
+
+ context "when the hook is unsuccessful" do
+ it "returns failure with errors" do
+ create_failing_hook(hook_name)
+ hook = Gitlab::Git::Hook.new(hook_name, project.repository.path)
+ blank = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
+
+ status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+ expect(status).to be false
+ expect(errors).to eq("error message from the hook\n")
+ end
+ end
+ end
+ end
+
+ context "when the hook doesn't exist" do
+ it "returns success with no errors" do
+ hook = Gitlab::Git::Hook.new('unknown_hook', project.repository.path)
+ blank = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
+
+ status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref)
+ expect(status).to be true
+ expect(errors).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
new file mode 100644
index 00000000000..f5c064303ad
--- /dev/null
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::Graphs::Commits, lib: true do
+ let!(:project) { create(:project, :public, :empty_repo) }
+
+ let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) }
+ let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)}
+
+ let!(:commit2) { create(:commit, git_commit: RepoHelpers.another_sample_commit, project: project, committed_date: Time.now) }
+
+ describe '#commit_per_day' do
+ context 'when range is only commits from today' do
+ subject { described_class.new([commit2, commit1]).commit_per_day }
+ it { is_expected.to eq 2 }
+ end
+ end
+
+ context 'when range is only commits from today' do
+ subject { described_class.new([commit2, commit1]) }
+ describe '#commit_per_day' do
+ it { expect(subject.commit_per_day).to eq 2 }
+ end
+
+ describe '#duration' do
+ it { expect(subject.duration).to eq 0 }
+ end
+ end
+
+ context 'with commits from yesterday and today' do
+ subject { described_class.new([commit2, commit1_yesterday]) }
+ describe '#commit_per_day' do
+ it { expect(subject.commit_per_day).to eq 1 }
+ end
+
+ describe '#duration' do
+ it { expect(subject.duration).to eq 1 }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 0b30e8c9b04..7286b0c39c0 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -4208,7 +4208,18 @@
"name": "User 4"
},
"events": [
-
+ {
+ "id": 529,
+ "target_type": "Note",
+ "target_id": 2521,
+ "title": "test levels",
+ "data": null,
+ "project_id": 4,
+ "created_at": "2016-07-07T14:35:12.128Z",
+ "updated_at": "2016-07-07T14:35:12.128Z",
+ "action": 6,
+ "author_id": 1
+ }
]
},
{
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index a72aaa44e82..05ffec8ea0a 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -24,11 +24,27 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(Ci::Pipeline.first.notes).not_to be_empty
end
- it 'restores the correct event' do
+ it 'restores the correct event with symbolised data' do
restored_project_json
expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty
end
+
+ context 'event at forth level of the tree' do
+ let(:event) { Event.where(title: 'test levels').first }
+
+ before do
+ restored_project_json
+ end
+
+ it 'restores the event' do
+ expect(event).not_to be_nil
+ end
+
+ it 'event belongs to note, belongs to merge request, belongs to a project' do
+ expect(event.note.noteable.project).not_to be_nil
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 59024d3290b..2cb74629da8 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -45,6 +45,12 @@ describe Gitlab::UrlSanitizer, lib: true do
expect(filtered_content).to include("user@server:project.git")
end
+
+ it 'returns an empty string for invalid URLs' do
+ filtered_content = sanitize_url('ssh://')
+
+ expect(filtered_content).to include("repository '' not found")
+ end
end
describe '#sanitized_url' do
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
new file mode 100644
index 00000000000..9a637c94fbe
--- /dev/null
+++ b/spec/models/merge_request_diff_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe MergeRequestDiff, models: true do
+ describe '#diffs' do
+ let(:mr) { create(:merge_request, :with_diffs) }
+ let(:mr_diff) { mr.merge_request_diff }
+
+ context 'when the :ignore_whitespace_change option is set' do
+ it 'creates a new compare object instead of loading from the DB' do
+ expect(mr_diff).not_to receive(:load_diffs)
+ expect(Gitlab::Git::Compare).to receive(:new).and_call_original
+
+ mr_diff.diffs(ignore_whitespace_change: true)
+ end
+ end
+
+ context 'when the raw diffs are empty' do
+ before { mr_diff.update_attributes(st_diffs: '') }
+
+ it 'returns an empty DiffCollection' do
+ expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
+ expect(mr_diff.diffs).to be_empty
+ end
+ end
+
+ context 'when the raw diffs exist' do
+ it 'returns the diffs' do
+ expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
+ expect(mr_diff.diffs).not_to be_empty
+ end
+
+ context 'when the :paths option is set' do
+ let(:diffs) { mr_diff.diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) }
+
+ it 'only returns diffs that match the (old path, new path) given' do
+ expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb')
+ end
+
+ it 'uses the diffs from the DB' do
+ expect(mr_diff).to receive(:load_diffs)
+
+ diffs
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index a4b6ff8f8ad..c8ad7ab3e7f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -116,6 +116,31 @@ describe MergeRequest, models: true do
end
end
+ describe '#diffs' do
+ let(:merge_request) { build(:merge_request) }
+ let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
+
+ context 'when there are MR diffs' do
+ it 'delegates to the MR diffs' do
+ merge_request.merge_request_diff = MergeRequestDiff.new
+
+ expect(merge_request.merge_request_diff).to receive(:diffs).with(options)
+
+ merge_request.diffs(options)
+ end
+ end
+
+ context 'when there are no MR diffs' do
+ it 'delegates to the compare object' do
+ merge_request.compare = double(:compare)
+
+ expect(merge_request.compare).to receive(:diffs).with(options)
+
+ merge_request.diffs(options)
+ end
+ end
+ end
+
describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1b434a726dc..5a27ccbab0a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -449,6 +449,14 @@ describe Project, models: true do
it { expect(project.open_branches.map(&:name)).to include('feature') }
it { expect(project.open_branches.map(&:name)).not_to include('master') }
+
+ it "includes branches matching a protected branch wildcard" do
+ expect(project.open_branches.map(&:name)).to include('feature')
+
+ create(:protected_branch, name: 'feat*', project: project)
+
+ expect(Project.find(project.id).open_branches.map(&:name)).to include('feature')
+ end
end
describe '#star_count' do
@@ -949,15 +957,67 @@ describe Project, models: true do
describe '#protected_branch?' do
let(:project) { create(:empty_project) }
- it 'returns true when a branch is a protected branch' do
+ it 'returns true when the branch matches a protected branch via direct match' do
project.protected_branches.create!(name: 'foo')
expect(project.protected_branch?('foo')).to eq(true)
end
- it 'returns false when a branch is not a protected branch' do
+ it 'returns true when the branch matches a protected branch via wildcard match' do
+ project.protected_branches.create!(name: 'production/*')
+
+ expect(project.protected_branch?('production/some-branch')).to eq(true)
+ end
+
+ it 'returns false when the branch does not match a protected branch via direct match' do
expect(project.protected_branch?('foo')).to eq(false)
end
+
+ it 'returns false when the branch does not match a protected branch via wildcard match' do
+ project.protected_branches.create!(name: 'production/*')
+
+ expect(project.protected_branch?('staging/some-branch')).to eq(false)
+ end
+ end
+
+ describe "#developers_can_push_to_protected_branch?" do
+ let(:project) { create(:empty_project) }
+
+ context "when the branch matches a protected branch via direct match" do
+ it "returns true if 'Developers can Push' is turned on" do
+ create(:protected_branch, name: "production", project: project, developers_can_push: true)
+
+ expect(project.developers_can_push_to_protected_branch?('production')).to be true
+ end
+
+ it "returns false if 'Developers can Push' is turned off" do
+ create(:protected_branch, name: "production", project: project, developers_can_push: false)
+
+ expect(project.developers_can_push_to_protected_branch?('production')).to be false
+ end
+ end
+
+ context "when the branch matches a protected branch via wilcard match" do
+ it "returns true if 'Developers can Push' is turned on" do
+ create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
+
+ expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true
+ end
+
+ it "returns false if 'Developers can Push' is turned off" do
+ create(:protected_branch, name: "production/*", project: project, developers_can_push: false)
+
+ expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false
+ end
+ end
+
+ context "when the branch does not match a protected branch" do
+ it "returns false" do
+ create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
+
+ expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false
+ end
+ end
end
describe '#container_registry_path_with_namespace' do
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index b523834c6e9..8bf0d24a128 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe ProtectedBranch, models: true do
+ subject { build_stubbed(:protected_branch) }
+
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
@@ -12,4 +14,127 @@ describe ProtectedBranch, models: true do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:name) }
end
+
+ describe "#matches?" do
+ context "when the protected branch setting is not a wildcard" do
+ let(:protected_branch) { build(:protected_branch, name: "production/some-branch") }
+
+ it "returns true for branch names that are an exact match" do
+ expect(protected_branch.matches?("production/some-branch")).to be true
+ end
+
+ it "returns false for branch names that are not an exact match" do
+ expect(protected_branch.matches?("staging/some-branch")).to be false
+ end
+ end
+
+ context "when the protected branch name contains wildcard(s)" do
+ context "when there is a single '*'" do
+ let(:protected_branch) { build(:protected_branch, name: "production/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch")).to be true
+ expect(protected_branch.matches?("production/")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("staging/some-branch")).to be false
+ expect(protected_branch.matches?("production")).to be false
+ end
+ end
+
+ context "when the wildcard contains regex symbols other than a '*'" do
+ let(:protected_branch) { build(:protected_branch, name: "pro.duc.tion/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("pro.duc.tion/some-branch")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch")).to be false
+ expect(protected_branch.matches?("proXducYtion/some-branch")).to be false
+ end
+ end
+
+ context "when there are '*'s at either end" do
+ let(:protected_branch) { build(:protected_branch, name: "*/production/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("gitlab/production/some-branch")).to be true
+ expect(protected_branch.matches?("/production/some-branch")).to be true
+ expect(protected_branch.matches?("gitlab/production/")).to be true
+ expect(protected_branch.matches?("/production/")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("gitlabproductionsome-branch")).to be false
+ expect(protected_branch.matches?("production/some-branch")).to be false
+ expect(protected_branch.matches?("gitlab/production")).to be false
+ expect(protected_branch.matches?("production")).to be false
+ end
+ end
+
+ context "when there are arbitrarily placed '*'s" do
+ let(:protected_branch) { build(:protected_branch, name: "pro*duction/*/gitlab/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch/gitlab/second-branch")).to be true
+ expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/second-branch")).to be true
+ expect(protected_branch.matches?("proXYZduction/gitlab/gitlab/gitlab")).to be true
+ expect(protected_branch.matches?("proXYZduction//gitlab/")).to be true
+ expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/")).to be true
+ expect(protected_branch.matches?("proXYZduction//gitlab/some-branch")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch/not-gitlab/second-branch")).to be false
+ expect(protected_branch.matches?("prodXYZuction/some-branch/gitlab/second-branch")).to be false
+ expect(protected_branch.matches?("proXYZduction/gitlab/some-branch/gitlab")).to be false
+ expect(protected_branch.matches?("proXYZduction/gitlab//")).to be false
+ expect(protected_branch.matches?("proXYZduction/gitlab/")).to be false
+ expect(protected_branch.matches?("proXYZduction//some-branch/gitlab")).to be false
+ end
+ end
+ end
+ end
+
+ describe "#matching" do
+ context "for direct matches" do
+ it "returns a list of protected branches matching the given branch name" do
+ production = create(:protected_branch, name: "production")
+ staging = create(:protected_branch, name: "staging")
+
+ expect(ProtectedBranch.matching("production")).to include(production)
+ expect(ProtectedBranch.matching("production")).not_to include(staging)
+ end
+
+ it "accepts a list of protected branches to search from, so as to avoid a DB call" do
+ production = build(:protected_branch, name: "production")
+ staging = build(:protected_branch, name: "staging")
+
+ expect(ProtectedBranch.matching("production")).to be_empty
+ expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).to include(production)
+ expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).not_to include(staging)
+ end
+ end
+
+ context "for wildcard matches" do
+ it "returns a list of protected branches matching the given branch name" do
+ production = create(:protected_branch, name: "production/*")
+ staging = create(:protected_branch, name: "staging/*")
+
+ expect(ProtectedBranch.matching("production/some-branch")).to include(production)
+ expect(ProtectedBranch.matching("production/some-branch")).not_to include(staging)
+ end
+
+ it "accepts a list of protected branches to search from, so as to avoid a DB call" do
+ production = build(:protected_branch, name: "production/*")
+ staging = build(:protected_branch, name: "staging/*")
+
+ expect(ProtectedBranch.matching("production/some-branch")).to be_empty
+ expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).to include(production)
+ expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).not_to include(staging)
+ end
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 328254ed56b..3984b30ddf8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -446,6 +446,7 @@ describe User, models: true do
it { expect(user.can_create_group?).to be_truthy }
it { expect(user.can_create_project?).to be_truthy }
it { expect(user.first_name).to eq('John') }
+ it { expect(user.external).to be_falsey }
end
describe 'with defaults' do
@@ -468,6 +469,26 @@ describe User, models: true do
expect(user.theme_id).to eq(1)
end
end
+
+ context 'when current_application_settings.user_default_external is true' do
+ before do
+ stub_application_setting(user_default_external: true)
+ end
+
+ it "creates external user by default" do
+ user = build(:user)
+
+ expect(user.external).to be_truthy
+ end
+
+ describe 'with default overrides' do
+ it "creates a non-external user" do
+ user = build(:user, external: false)
+
+ expect(user.external).to be_falsey
+ end
+ end
+ end
end
describe '.find_by_any_email' do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 04141a45031..c2c94040ece 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -49,10 +49,25 @@ describe API::API, api: true do
describe "GET /groups/:id" do
context "when authenticated as user" do
- it "should return one of user1's groups" do
+ it "returns one of user1's groups" do
+ project = create(:project, namespace: group2, path: 'Foo')
+ create(:project_group_link, project: project, group: group1)
+
get api("/groups/#{group1.id}", user1)
+
expect(response).to have_http_status(200)
- json_response['name'] == group1.name
+ expect(json_response['id']).to eq(group1.id)
+ expect(json_response['name']).to eq(group1.name)
+ expect(json_response['path']).to eq(group1.path)
+ expect(json_response['description']).to eq(group1.description)
+ expect(json_response['visibility_level']).to eq(group1.visibility_level)
+ expect(json_response['avatar_url']).to eq(group1.avatar_url)
+ expect(json_response['web_url']).to eq(group1.web_url)
+ expect(json_response['projects']).to be_an Array
+ expect(json_response['projects'].length).to eq(2)
+ expect(json_response['shared_projects']).to be_an Array
+ expect(json_response['shared_projects'].length).to eq(1)
+ expect(json_response['shared_projects'][0]['id']).to eq(project.id)
end
it "should not return a non existing group" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 611dd2a2a88..8a52725a893 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -392,11 +392,47 @@ describe API::API, api: true do
before { project }
before { project_member }
- it 'should return a project by id' do
+ it 'returns a project by id' do
+ group = create(:group)
+ link = create(:project_group_link, project: project, group: group)
+
get api("/projects/#{project.id}", user)
+
expect(response).to have_http_status(200)
+ expect(json_response['id']).to eq(project.id)
+ expect(json_response['description']).to eq(project.description)
+ expect(json_response['default_branch']).to eq(project.default_branch)
+ expect(json_response['tag_list']).to be_an Array
+ expect(json_response['public']).to be_falsey
+ expect(json_response['archived']).to be_falsey
+ expect(json_response['visibility_level']).to be_present
+ expect(json_response['ssh_url_to_repo']).to be_present
+ expect(json_response['http_url_to_repo']).to be_present
+ expect(json_response['web_url']).to be_present
+ expect(json_response['owner']).to be_a Hash
+ expect(json_response['owner']).to be_a Hash
expect(json_response['name']).to eq(project.name)
- expect(json_response['owner']['username']).to eq(user.username)
+ expect(json_response['path']).to be_present
+ expect(json_response['issues_enabled']).to be_present
+ expect(json_response['merge_requests_enabled']).to be_present
+ expect(json_response['wiki_enabled']).to be_present
+ expect(json_response['builds_enabled']).to be_present
+ expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['container_registry_enabled']).to be_present
+ expect(json_response['created_at']).to be_present
+ expect(json_response['last_activity_at']).to be_present
+ expect(json_response['shared_runners_enabled']).to be_present
+ expect(json_response['creator_id']).to be_present
+ expect(json_response['namespace']).to be_present
+ expect(json_response['avatar_url']).to be_nil
+ expect(json_response['star_count']).to be_present
+ expect(json_response['forks_count']).to be_present
+ expect(json_response['public_builds']).to be_present
+ expect(json_response['shared_with_groups']).to be_an Array
+ expect(json_response['shared_with_groups'].length).to eq(1)
+ expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
+ expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+ expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
end
it 'should return a project by path name' do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 54719cbb8d8..d3dddfb4817 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -293,6 +293,30 @@ describe NotificationService, services: true do
end
end
end
+
+ context "merge request diff note" do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, source_project: project, assignee: user) }
+ let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
+
+ before do
+ build_team(note.project)
+ project.team << [merge_request.author, :master]
+ project.team << [merge_request.assignee, :master]
+ end
+
+ describe :new_note do
+ it "records sent notifications" do
+ # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note
+ expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original
+
+ notification.new_note(note)
+
+ expect(SentNotification.last.position).to eq(note.position)
+ end
+ end
+ end
end
describe 'Issues' do
diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb
index 9b5c3065eed..b57a3493aff 100644
--- a/spec/support/capybara_helpers.rb
+++ b/spec/support/capybara_helpers.rb
@@ -27,6 +27,14 @@ module CapybaraHelpers
end
end
end
+
+ # Refresh the page. Calling `visit current_url` doesn't seem to work consistently.
+ #
+ def refresh
+ url = current_url
+ visit 'about:blank'
+ visit url
+ end
end
RSpec.configure do |config|
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 9f9ef20f99b..bb6c84262f6 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,19 +5,20 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
- 'empty-branch' => '7efb185',
- 'flatten-dir' => 'e56497b',
- 'feature' => '0b4bc9a',
- 'feature_conflict' => 'bb5206f',
- 'fix' => '48f0be4',
- 'improve/awesome' => '5937ac0',
- 'markdown' => '0ed8c6c',
- 'lfs' => 'be93687',
- 'master' => '5937ac0',
- "'test'" => 'e56497b',
- 'orphaned-branch' => '45127a9',
- 'binary-encoding' => '7b1cf43',
- 'gitattributes' => '5a62481',
+ 'empty-branch' => '7efb185',
+ 'flatten-dir' => 'e56497b',
+ 'feature' => '0b4bc9a',
+ 'feature_conflict' => 'bb5206f',
+ 'fix' => '48f0be4',
+ 'improve/awesome' => '5937ac0',
+ 'markdown' => '0ed8c6c',
+ 'lfs' => 'be93687',
+ 'master' => '5937ac0',
+ "'test'" => 'e56497b',
+ 'orphaned-branch' => '45127a9',
+ 'binary-encoding' => '7b1cf43',
+ 'gitattributes' => '5a62481',
+ 'expand-collapse-diffs' => '4842455'
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
@@ -63,7 +64,7 @@ module TestEnv
end
def disable_pre_receive
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
end
# Clean /tmp/tests