summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG25
-rw-r--r--CONTRIBUTING.md36
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile13
-rw-r--r--Gemfile.lock23
-rw-r--r--app/assets/javascripts/awards_handler.coffee1
-rw-r--r--app/assets/javascripts/behaviors/autosize.js.coffee20
-rw-r--r--app/assets/javascripts/project.js.coffee16
-rw-r--r--app/assets/stylesheets/framework/buttons.scss8
-rw-r--r--app/assets/stylesheets/framework/common.scss4
-rw-r--r--app/assets/stylesheets/framework/files.scss26
-rw-r--r--app/assets/stylesheets/framework/header.scss8
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/nav.scss86
-rw-r--r--app/assets/stylesheets/pages/groups.scss20
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss92
-rw-r--r--app/controllers/admin/application_settings_controller.rb4
-rw-r--r--app/controllers/admin/spam_logs_controller.rb17
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/projects/avatars_controller.rb12
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb15
-rw-r--r--app/controllers/projects_controller.rb8
-rw-r--r--app/controllers/registrations_controller.rb5
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb12
-rw-r--r--app/helpers/diff_helper.rb11
-rw-r--r--app/helpers/projects_helper.rb13
-rw-r--r--app/mailers/email_rejection_mailer.rb2
-rw-r--r--app/models/application_setting.rb10
-rw-r--r--app/models/broadcast_message.rb4
-rw-r--r--app/models/ci/commit.rb6
-rw-r--r--app/models/commit.rb6
-rw-r--r--app/models/commit_range.rb4
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/spam_log.rb10
-rw-r--r--app/models/spam_report.rb5
-rw-r--r--app/models/tree.rb2
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/wiki_page.rb2
-rw-r--r--app/services/create_spam_log_service.rb13
-rw-r--r--app/services/delete_user_service.rb2
-rw-r--r--app/services/destroy_group_service.rb2
-rw-r--r--app/services/notes/create_service.rb19
-rw-r--r--app/services/notes/post_process_service.rb30
-rw-r--r--app/services/projects/destroy_service.rb6
-rw-r--r--app/views/admin/application_settings/_form.html.haml45
-rw-r--r--app/views/admin/spam_logs/_spam_log.html.haml32
-rw-r--r--app/views/admin/spam_logs/index.html.haml21
-rw-r--r--app/views/dashboard/_groups_head.html.haml20
-rw-r--r--app/views/dashboard/_projects_head.html.haml8
-rw-r--r--app/views/dashboard/groups/index.html.haml9
-rw-r--r--app/views/dashboard/issues.html.haml3
-rw-r--r--app/views/dashboard/merge_requests.html.haml3
-rw-r--r--app/views/dashboard/milestones/index.html.haml3
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml11
-rw-r--r--app/views/groups/_projects.html.haml17
-rw-r--r--app/views/layouts/nav/_admin.html.haml14
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml5
-rw-r--r--app/views/projects/blob/_image.html.haml2
-rw-r--r--app/views/projects/blob/_text.html.haml1
-rw-r--r--app/views/projects/diffs/_file.html.haml22
-rw-r--r--app/views/projects/forks/index.html.haml64
-rw-r--r--app/views/projects/milestones/index.html.haml3
-rw-r--r--app/views/shared/_file_highlight.html.haml2
-rw-r--r--app/views/shared/_new_project_item_select.html.haml8
-rw-r--r--app/views/shared/groups/_group.html.haml19
-rw-r--r--app/views/shared/projects/_project.html.haml4
-rw-r--r--app/views/votes/_votes_block.html.haml2
-rw-r--r--app/workers/new_note_worker.rb12
-rw-r--r--app/workers/project_destroy_worker.rb17
-rw-r--r--config/database.yml.env12
-rw-r--r--config/initializers/metrics.rb16
-rw-r--r--config/routes.rb4
-rw-r--r--db/migrate/20151231152326_add_akismet_to_application_settings.rb8
-rw-r--r--db/migrate/20160109054846_create_spam_logs.rb16
-rw-r--r--db/migrate/20160122185421_add_pending_delete_to_project.rb5
-rw-r--r--db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb6
-rw-r--r--db/migrate/20160128233227_change_lfs_objects_size_column.rb5
-rw-r--r--db/schema.rb24
-rw-r--r--doc/administration/environment_variables.md79
-rw-r--r--doc/api/builds.md10
-rw-r--r--doc/ci/api/projects.md2
-rw-r--r--doc/ci/languages/php.md4
-rw-r--r--doc/ci/yaml/README.md8
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/integration/README.md1
-rw-r--r--doc/integration/akismet.md30
-rw-r--r--doc/integration/img/akismet_settings.pngbin0 -> 55837 bytes
-rw-r--r--doc/integration/img/oauth_provider_admin_application.pngbin55533 -> 40579 bytes
-rw-r--r--doc/integration/img/oauth_provider_application_form.pngbin25075 -> 27974 bytes
-rw-r--r--doc/integration/img/oauth_provider_application_id_secret.pngbin0 -> 33901 bytes
-rw-r--r--doc/integration/img/oauth_provider_authorized_application.pngbin17260 -> 32225 bytes
-rw-r--r--doc/integration/img/oauth_provider_user_wide_applications.pngbin46238 -> 40632 bytes
-rw-r--r--doc/integration/oauth_provider.md89
-rw-r--r--doc/security/README.md2
-rw-r--r--doc/update/6.x-or-7.x-to-7.14.md6
-rw-r--r--doc/workflow/gitlab_flow.md7
-rw-r--r--features/admin/spam_logs.feature8
-rw-r--r--features/project/issues/award_emoji.feature3
-rw-r--r--features/project/source/browse_files.feature10
-rw-r--r--features/steps/admin/spam_logs.rb28
-rw-r--r--features/steps/project/issues/award_emoji.rb4
-rw-r--r--features/steps/project/source/browse_files.rb19
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--lib/api/files.rb6
-rw-r--r--lib/api/helpers.rb20
-rw-r--r--lib/api/issues.rb23
-rw-r--r--lib/api/projects.rb4
-rw-r--r--lib/api/repositories.rb4
-rw-r--r--lib/dnsxl_check.rb105
-rw-r--r--lib/gitlab/akismet_helper.rb39
-rw-r--r--lib/gitlab/current_settings.rb3
-rw-r--r--lib/gitlab/diff/highlight.rb17
-rw-r--r--lib/gitlab/diff/inline_diff.rb57
-rw-r--r--lib/gitlab/diff/inline_diff_marker.rb14
-rw-r--r--lib/gitlab/highlight.rb1
-rw-r--r--lib/gitlab/ip_check.rb34
-rw-r--r--lib/gitlab/regex.rb8
-rw-r--r--lib/gitlab/workhorse.rb21
-rwxr-xr-xlib/support/init.d/gitlab2
-rw-r--r--spec/controllers/admin/spam_logs_controller_spec.rb37
-rw-r--r--spec/factories/spam_logs.rb11
-rw-r--r--spec/features/projects_spec.rb21
-rw-r--r--spec/fixtures/logo_sample.svg27
-rw-r--r--spec/fixtures/parallel_diff_result.yml2
-rw-r--r--spec/helpers/diff_helper_spec.rb17
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js.coffee11
-rw-r--r--spec/javascripts/fixtures/project_title.html.haml7
-rw-r--r--spec/javascripts/fixtures/projects.json1
-rw-r--r--spec/javascripts/project_title_spec.js.coffee46
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb4
-rw-r--r--spec/lib/dnsxl_check_spec.rb68
-rw-r--r--spec/lib/gitlab/akismet_helper_spec.rb35
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb72
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb26
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb17
-rw-r--r--spec/lib/gitlab/regex_spec.rb8
-rw-r--r--spec/models/hooks/system_hook_spec.rb6
-rw-r--r--spec/models/merge_request_spec.rb22
-rw-r--r--spec/models/spam_log_spec.rb25
-rw-r--r--spec/models/user_spec.rb1
-rw-r--r--spec/models/wiki_page_spec.rb32
-rw-r--r--spec/requests/api/issues_spec.rb37
-rw-r--r--spec/routing/project_routing_spec.rb8
-rw-r--r--spec/services/notes/create_service_spec.rb4
-rw-r--r--spec/services/notes/post_process_service_spec.rb26
-rw-r--r--spec/support/filter_spec_helper.rb4
-rw-r--r--vendor/assets/javascripts/jquery.ba-resize.js246
154 files changed, 1842 insertions, 724 deletions
diff --git a/CHANGELOG b/CHANGELOG
index fe0504ec996..5f081236c10 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,8 +2,11 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased)
- Ensure rake tasks that don't need a DB connection can be run without one
+ - Update New Relic gem to 3.14.1.311 (Stan Hu)
- Add "visibility" flag to GET /projects api endpoint
- Ignore binary files in code search to prevent Error 500 (Stan Hu)
+ - Render sanitized SVG images (Stan Hu)
+ - Support download access by PRIVATE-TOKEN header (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
@@ -14,9 +17,27 @@ v 8.5.0 (unreleased)
- Display 404 error on group not found
- Track project import failure
- Fix visibility level text in admin area (Zeger-Jan van de Weg)
+ - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock)
+ - Optimized performance of finding issues to be closed by a merge request
+ - Revert "Add IP check against DNSBLs at account sign-up"
+ - Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
+ - Mark inline difference between old and new paths when a file is renamed
+ - Support Akismet spam checking for creation of issues via API (Stan Hu)
+ - Improve UI consistency between projects and groups lists
+
+v 8.4.3
+ - Increase lfs_objects size column to 8-byte integer to allow files larger
+ than 2.1GB
+ - Correctly highlight MR diff when MR has merge conflicts
+ - Fix highlighting in blame view
+ - Update sentry-raven gem to prevent "Not a git repository" console output
+ when running certain commands
+ - Add instrumentation to additional Gitlab::Git and Rugged methods for
+ performance monitoring
+ - Allow autosize textareas to also be manually resized
v 8.4.2
- Bump required gitlab-workhorse version to bring in a fix for missing
@@ -27,7 +48,6 @@ v 8.4.2
improvement when checking if a repository was empty
- Add instrumentation for Gitlab::Git::Repository instance methods so we can
track them in Performance Monitoring.
- - Correctly highlight MR diff when MR has merge conflicts
- Increase contrast between highlighted code comments and inline diff marker
- Fix method undefined when using external commit status in builds
- Fix highlighting in blame view.
@@ -37,6 +57,7 @@ v 8.4.1
and Nokogiri (1.6.7.2)
- Fix redirect loop during import
- Fix diff highlighting for all syntax themes
+ - Delete project and associations in a background worker
v 8.4.0
- Allow LDAP users to change their email if it was not set by the LDAP server
@@ -80,7 +101,7 @@ v 8.4.0
- Show 'All' tab by default in the builds page
- Add Open Graph and Twitter Card data to all pages
- Fix API project lookups when querying with a namespace with dots (Stan Hu)
- - Enable forcing Two-Factor authentication sitewide, with optional grace period
+ - Enable forcing Two-factor authentication sitewide, with optional grace period
- Import GitHub Pull Requests into GitLab
- Change single user API endpoint to return more detailed data (Michael Potthoff)
- Update version check images to use SVG
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1eabbdc5cad..a7a2307492f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -147,7 +147,7 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
sudo gitlab-rake gitlab:env:info)
(For installations from source run and paste the output of:
-sudo -u git -H bundle exec rake gitlab:env:info)
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
## Possible fixes
@@ -177,6 +177,26 @@ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
issues or chunks. You can simply not set the weight of a parent issue and set
weights to children issues.
+### Regression issues
+
+Every monthly release has a corresponding issue on the CE issue tracker to keep
+track of functionality broken by that release and any fixes that need to be
+included in a patch release (see [8.3 Regressions] as an example).
+
+As outlined in the issue description, the intended workflow is to post one note
+with a reference to an issue describing the regression, and then to update that
+note with a reference to the merge request that fixes it as it becomes available.
+
+If you're a contributor who doesn't have the required permissions to update
+other users' notes, please post a new note with a reference to both the issue
+and the merge request.
+
+The release manager will [update the notes] in the regression issue as fixes are
+addressed.
+
+[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
+[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
+
## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests,
@@ -255,6 +275,20 @@ For examples of feedback on merge requests please look at already
request feel free to mention one of the Merge Marshalls of the [core team][].
Please ensure that your merge request meets the contribution acceptance criteria.
+When having your code reviewed and when reviewing merge requests please take the [thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) into account.
+
+## Changes for Stable Releases
+
+Sometimes certain changes have to be added to an existing stable release.
+Two examples are bug fixes and performance improvements. In these cases the
+corresponding merge request should be updated to have the following:
+
+1. A milestone indicating what release the merge request should be merged into.
+1. The label "Pick into Stable"
+
+This makes it easier for release managers to keep track of what still has to be
+merged and where changes have to be merged into.
+
## Definition of done
If you contribute to GitLab please know that changes involve more than just
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index b6160487433..844f6a91acb 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.6.2
+0.6.3
diff --git a/Gemfile b/Gemfile
index a09d44f8bfd..a06dbe8e061 100644
--- a/Gemfile
+++ b/Gemfile
@@ -30,14 +30,15 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
-gem 'omniauth-saml', '~> 1.4.0'
+gem 'omniauth-saml', '~> 1.4.2'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1'
-# reCAPTCHA protection
+# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
+gem 'akismet', '~> 2.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
@@ -49,7 +50,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.23'
+gem "gitlab_git", '~> 8.0.0'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -179,6 +180,9 @@ gem "underscore-rails", "~> 1.8.0"
gem "sanitize", '~> 2.0'
gem 'babosa', '~> 1.0.2'
+# Sanitizes SVG input
+gem "loofah", "~> 2.0.3"
+
# Protect against bruteforcing
gem "rack-attack", '~> 4.3.1'
@@ -299,8 +303,7 @@ group :production do
gem "gitlab_meta", '7.0'
end
-gem "newrelic_rpm", '~> 3.9.4.245'
-gem 'newrelic-grape'
+gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 3.8.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index ec92964df25..bd767016108 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -49,6 +49,7 @@ GEM
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
+ akismet (2.0.0)
allocations (1.0.3)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
@@ -356,7 +357,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.2.0)
gemojione (~> 2.1)
- gitlab_git (7.2.24)
+ gitlab_git (8.0.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -478,10 +479,7 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
- newrelic-grape (2.1.0)
- grape
- newrelic_rpm
- newrelic_rpm (3.9.4.245)
+ newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
@@ -534,9 +532,9 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
- omniauth-saml (1.4.1)
+ omniauth-saml (1.4.2)
omniauth (~> 1.1)
- ruby-saml (~> 1.0.0)
+ ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
@@ -692,7 +690,7 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
- ruby-saml (1.0.0)
+ ruby-saml (1.1.1)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.2.0)
@@ -884,6 +882,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
+ akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0)
@@ -934,7 +933,7 @@ DEPENDENCIES
github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.2.0)
- gitlab_git (~> 7.2.23)
+ gitlab_git (~> 8.0.0)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
@@ -953,6 +952,7 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
+ loofah (~> 2.0.3)
mail_room (~> 0.6.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
@@ -960,8 +960,7 @@ DEPENDENCIES
mysql2 (~> 0.3.16)
nested_form (~> 0.3.2)
net-ssh (~> 3.0.1)
- newrelic-grape
- newrelic_rpm (~> 3.9.4.245)
+ newrelic_rpm (~> 3.14)
nokogiri (= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
@@ -975,7 +974,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0)
- omniauth-saml (~> 1.4.0)
+ omniauth-saml (~> 1.4.2)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 1ef31c7700e..047df4786a9 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -4,6 +4,7 @@ class @AwardsHandler
event.stopPropagation()
event.preventDefault()
$(".emoji-menu").show()
+ $("#emoji_search").focus()
$("html").on 'click', (event) ->
if !$(event.target).closest(".emoji-menu").length
diff --git a/app/assets/javascripts/behaviors/autosize.js.coffee b/app/assets/javascripts/behaviors/autosize.js.coffee
index b32072e61ee..a072fe48a98 100644
--- a/app/assets/javascripts/behaviors/autosize.js.coffee
+++ b/app/assets/javascripts/behaviors/autosize.js.coffee
@@ -1,4 +1,22 @@
+#= require jquery.ba-resize
#= require autosize
$ ->
- autosize($('.js-autosize'))
+ $fields = $('.js-autosize')
+
+ $fields.on 'autosize:resized', ->
+ $field = $(@)
+ $field.data('height', $field.outerHeight())
+
+ $fields.on 'resize.autosize', ->
+ $field = $(@)
+
+ if $field.data('height') != $field.outerHeight()
+ $field.data('height', $field.outerHeight())
+ autosize.destroy($field)
+ $field.css('max-height', window.outerHeight)
+
+ autosize($fields)
+ autosize.update($fields)
+
+ $fields.css('resize', 'vertical')
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index d7a658f8faa..76bc4ff42a2 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -50,3 +50,19 @@ class @Project
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
+
+ @projectSelectDropdown()
+
+ projectSelectDropdown: ->
+ new ProjectSelect()
+
+ $('.project-item-select').on 'click', (e) =>
+ @changeProject $(e.currentTarget).val()
+
+ $('.js-projects-dropdown-toggle').on 'click', (e) ->
+ e.preventDefault()
+
+ $('.js-projects-dropdown').select2('open')
+
+ changeProject: (url) ->
+ window.location = url
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index c99292c3f83..11df4c24056 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -82,8 +82,7 @@
&.btn-success,
&.btn-new,
&.btn-create,
- &.btn-save,
- &.btn-green {
+ &.btn-save {
@include btn-green;
}
@@ -159,7 +158,6 @@
.input-group-btn {
.btn {
- @include btn-gray;
@include btn-middle;
&:hover {
@@ -186,8 +184,4 @@
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
}
-
- .btn-green {
- @include btn-green
- }
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 6ea2219073c..ea56d9e12a0 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -376,11 +376,11 @@ table {
margin-bottom: $gl-padding;
}
-.new-project-item-select-holder {
+.project-item-select-holder {
display: inline-block;
position: relative;
- .new-project-item-select {
+ .project-item-select {
position: absolute;
top: 0;
right: 0;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 00cb756b376..c7f3604850d 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -36,6 +36,20 @@
}
}
+ .filename {
+ &.old {
+ span.idiff {
+ background-color: #f8cbcb;
+ }
+ }
+
+ &.new {
+ span.idiff {
+ background-color: #a6f3a6;
+ }
+ }
+ }
+
.left-options {
margin-top: -3px;
}
@@ -158,3 +172,15 @@
}
}
}
+
+span.idiff {
+ &.left {
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+ }
+
+ &.right {
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f875b1460e7..7871a33b6c5 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -73,7 +73,6 @@ header {
.title {
margin: 0;
- overflow: hidden;
font-size: 19px;
line-height: $header-height;
font-weight: normal;
@@ -88,6 +87,13 @@ header {
text-decoration: underline;
}
}
+
+ .dropdown-toggle-caret {
+ position: relative;
+ top: -2px;
+ margin-left: 5px;
+ font-size: 10px;
+ }
}
.navbar-collapse {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 6732343802a..1d8611b04dc 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -83,7 +83,7 @@
background: #FFF;
border: 1px solid #ddd;
min-height: 140px;
- max-height: 430px;
+ max-height: 500px;
padding: 5px;
box-shadow: none;
width: 100%;
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index c537d97fb24..e6c59f5a291 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -37,3 +37,89 @@
}
}
}
+
+.top-area {
+ @include clearfix;
+
+ border-bottom: 1px solid #EEE;
+
+ .nav-text {
+ padding-top: 16px;
+ padding-bottom: 11px;
+ display: inline-block;
+ width: 50%;
+ line-height: 28px;
+
+ /* Small devices (phones, tablets, 768px and lower) */
+ @media (max-width: $screen-sm-min) {
+ width: 100%;
+ }
+ }
+
+ .nav-links {
+ display: inline-block;
+ width: 50%;
+ margin-bottom: 0px;
+ border-bottom: none;
+
+ /* Small devices (phones, tablets, 768px and lower) */
+ @media (max-width: $screen-sm-min) {
+ width: 100%;
+ }
+ }
+
+ .nav-controls {
+ width: 50%;
+ display: inline-block;
+ float: right;
+ text-align: right;
+ padding: 11px 0;
+ margin-bottom: 0px;
+
+ > .dropdown {
+ margin-right: 10px;
+ display: inline-block;
+ }
+
+ > .btn {
+ display: inline-block;
+ }
+
+ input {
+ height: 34px;
+ display: inline-block;
+ position: relative;
+ top: 1px;
+ margin-right: 10px;
+
+ /* Medium devices (desktops, 992px and up) */
+ @media (min-width: $screen-md-min) { width: 200px; }
+
+ /* Large devices (large desktops, 1200px and up) */
+ @media (min-width: $screen-lg-min) { width: 250px; }
+
+ &.input-short {
+ /* Medium devices (desktops, 992px and up) */
+ @media (min-width: $screen-md-min) { width: 170px; }
+
+ /* Large devices (large desktops, 1200px and up) */
+ @media (min-width: $screen-lg-min) { width: 210px; }
+ }
+ }
+
+ /* Hide on extra small devices (phones) */
+ @media (max-width: $screen-xs-max) {
+ display: none;
+ }
+
+ /* Small devices (tablets, 768px and lower) */
+ @media (max-width: $screen-sm-max) {
+ width: 100%;
+ text-align: left;
+
+ input {
+ width: 300px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index fdd86979a36..ec6c099df5b 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -4,7 +4,7 @@
input[type='search'] {
width: 225px;
vertical-align: bottom;
-
+
@media (max-width: $screen-xs-max) {
width: 100px;
vertical-align: bottom;
@@ -21,3 +21,21 @@
height: 42px;
}
}
+
+.group-row {
+ &.no-description {
+ .group-name {
+ line-height: 44px;
+ }
+ }
+
+ .stats {
+ float: right;
+ line-height: 44px;
+ color: $gl-gray;
+
+ span {
+ margin-right: 15px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 32ba1676333..158c2a47862 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -147,7 +147,7 @@
.edit_note {
.markdown-area {
min-height: 140px;
- max-height: 430px;
+ max-height: 500px;
}
.note-form-actions {
background: transparent;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index e4ea47cc4a2..dd4ff39c5b8 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -281,36 +281,6 @@
margin-top: -1px;
}
-.top-area {
- border-bottom: 1px solid #EEE;
-
- ul.nav-links {
- display: inline-block;
- width: 50%;
- margin-bottom: 0px;
- border-bottom: none;
- }
-
- .projects-search-form {
- width: 50%;
- display: inline-block;
- float: right;
- padding-top: 11px;
- text-align: right;
-
- .btn-green {
- margin-left: 10px;
- float: right;
- }
- }
-
- @media (max-width: $screen-xs-max) {
- .projects-search-form {
- padding-top: 15px;
- }
- }
-}
-
.fork-namespaces {
.fork-thumbnail {
text-align: center;
@@ -386,22 +356,6 @@ pre.light-well {
border-color: #f1f1f1;
}
-.projects-search-form {
- padding: $gl-padding 0;
- padding-bottom: 0;
- margin-bottom: 0px;
-
- input {
- display: inline-block;
- width: calc(100% - 151px);
- }
-
- .btn {
- display: inline-block;
- width: 135px;
- }
-}
-
.git-empty {
margin: 0 7px 0 7px;
@@ -461,6 +415,10 @@ pre.light-well {
a:hover {
text-decoration: none;
}
+
+ > span {
+ margin-left: 10px;
+ }
}
.project-description {
@@ -559,52 +517,12 @@ pre.light-well {
}
}
-.cannot-be-merged,
+.cannot-be-merged,
.cannot-be-merged:hover {
color: #E62958;
margin-top: 2px;
}
-/*
- * Forks list rendered on Project's forks page
- */
-
-.forks-top-block {
- padding: 16px 0;
-}
-
-.projects-search-form {
- .dropdown-toggle.btn {
- margin-top: -3px;
- }
-
- &.fork-search-form {
- margin: 0;
- margin-top: -$gl-padding;
- padding-bottom: 0;
-
- input {
- /* Small devices (tablets, 768px and up) */
- @media (min-width: $screen-sm-min) { width: 180px; }
-
- /* Medium devices (desktops, 992px and up) */
- @media (min-width: $screen-md-min) { width: 350px; }
-
- /* Large devices (large desktops, 1200px and up) */
- @media (min-width: $screen-lg-min) { width: 400px; }
- }
-
- .sort-forks {
- width: 160px;
- }
-
- .fork-link {
- float: right;
- margin-left: $gl-padding;
- }
- }
-}
-
.private-forks-notice .private-fork-icon {
i:nth-child(1) {
color: #2AA056;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 094eef28a43..1515086b16d 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -74,13 +74,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_timeout,
:metrics_method_call_threshold,
:metrics_sample_interval,
- :ip_blocking_enabled,
- :dnsbl_servers_list,
:recaptcha_enabled,
:recaptcha_site_key,
:recaptcha_private_key,
:sentry_enabled,
:sentry_dsn,
+ :akismet_enabled,
+ :akismet_api_key,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
new file mode 100644
index 00000000000..377e9741e5f
--- /dev/null
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -0,0 +1,17 @@
+class Admin::SpamLogsController < Admin::ApplicationController
+ def index
+ @spam_logs = SpamLog.order(id: :desc).page(params[:page])
+ end
+
+ def destroy
+ spam_log = SpamLog.find(params[:id])
+
+ if params[:remove_user]
+ spam_log.remove_user
+ redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
+ else
+ spam_log.destroy
+ render nothing: true
+ end
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 824175c8a6c..7fa2f68ef07 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -60,6 +60,8 @@ class ApplicationController < ActionController::Base
params[:authenticity_token].presence
elsif params[:private_token].presence
params[:private_token].presence
+ elsif request.headers['PRIVATE-TOKEN'].present?
+ request.headers['PRIVATE-TOKEN']
end
user = user_token && User.find_by_authentication_token(user_token.to_s)
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 548f1b9ebfe..f7e6bb34443 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController
before_action :project
def show
- @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
+ @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
- send_data(
- @blob.data,
- type: @blob.mime_type,
- disposition: 'inline',
- filename: @blob.name
- )
+ headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
+ headers['Content-Disposition'] = 'inline'
+ headers['Content-Type'] = @blob.content_type
+ head :ok # 'render nothing: true' messes up the Content-Type
else
render_404
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index bb72232edd7..495a432347e 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController
def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
+ blob.load_all_data!(@repository)
end
def update
@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
def preview
@content = params[:content]
+ @blob.load_all_data!(@repository)
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 4a2599dda37..1b9dd568043 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -106,7 +106,7 @@ class Projects::NotesController < Projects::ApplicationController
{ notes_left: [note], notes_right: [] }
else
{ notes_left: [], notes_right: [note] }
- end
+ end
else
template = "projects/notes/_diff_notes_with_reply"
locals = { notes: [note] }
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index be7d5c187fe..87b4d08da0e 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer?
send_lfs_object
else
- stream_data
+ headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
+ headers['Content-Disposition'] = 'inline'
+ headers['Content-Type'] = get_blob_type
+ head :ok # 'render nothing: true' messes up the Content-Type
end
else
render_404
@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController
end
end
- def stream_data
- type = get_blob_type
-
- send_data(
- @blob.data,
- type: type,
- disposition: 'inline'
- )
- end
-
def send_lfs_object
lfs_object = find_lfs_object
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 935f7d75c6a..4df5095bd94 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -93,6 +93,10 @@ class ProjectsController < ApplicationController
return
end
+ if @project.pending_delete?
+ flash[:alert] = "Project queued for delete."
+ end
+
respond_to do |format|
format.html do
if @project.repository_exists?
@@ -120,8 +124,8 @@ class ProjectsController < ApplicationController
def destroy
return access_denied! unless can?(current_user, :remove_project, @project)
- ::Projects::DestroyService.new(@project, current_user, {}).execute
- flash[:alert] = "Project '#{@project.name}' was deleted."
+ ::Projects::DestroyService.new(@project, current_user, {}).pending_delete!
+ flash[:alert] = "Project '#{@project.name}' will be deleted."
redirect_to dashboard_projects_path
rescue Projects::DestroyService::DestroyError => ex
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 5efdd613e79..c48175a4c5a 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,11 +8,6 @@ class RegistrationsController < Devise::RegistrationsController
def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
- if Gitlab::IpCheck.new(request.remote_ip).spam?
- flash[:alert] = 'Could not create an account. This IP is listed for spam.'
- return render action: 'new'
- end
-
super
else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 7d6b58ee21a..23693629a4c 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -23,6 +23,10 @@ module ApplicationSettingsHelper
current_application_settings.user_oauth_applications
end
+ def askimet_enabled?
+ current_application_settings.akismet_enabled?
+ end
+
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 694c03206bd..16967927922 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -126,4 +126,16 @@ module BlobHelper
blob.size
end
end
+
+ def blob_svg?(blob)
+ blob.language && blob.language.name == 'SVG'
+ end
+
+ # SVGs can contain malicious JavaScript; only include whitelisted
+ # elements and attributes. Note that this whitelist is by no means complete
+ # and may omit some elements.
+ def sanitize_svg(blob)
+ blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
+ blob
+ end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 62971d1e14b..f9bacc8ba45 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -1,4 +1,13 @@
module DiffHelper
+ def mark_inline_diffs(old_line, new_line)
+ old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs
+
+ marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs)
+ marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs)
+
+ [marked_old_line, marked_new_line]
+ end
+
def diff_view
params[:view] == 'parallel' ? 'parallel' : 'inline'
end
@@ -55,7 +64,7 @@ module DiffHelper
if line.blank?
" &nbsp;".html_safe
else
- line.html_safe
+ line
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8c8b355028c..e7e472cbb5b 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -53,14 +53,19 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
- project_link = link_to(simple_sanitize(project.name), project_path(project))
+ project_link = link_to project_path(project), { class: "project-item-select-holder #{"js-projects-dropdown-toggle" if current_user}" } do
+ link_output = simple_sanitize(project.name)
+ link_output += content_tag :span, nil, { class: "fa fa-chevron-down dropdown-toggle-caret" } if current_user
+
+ link_output += project_select_tag :project_path, class: "project-item-select js-projects-dropdown", data: { include_groups: false } if current_user
+
+ link_output
+ end
full_title = namespace_link + ' / ' + project_link
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
- content_tag :span do
- full_title
- end
+ full_title
end
def remove_project_message(project)
diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 883f1c73ad4..76db31a4c45 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer
subject: "[Rejected] #{@original_message.subject}"
}
- headers['Message-ID'] = SecureRandom.hex
+ headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
headers['In-Reply-To'] = @original_message.message_id
headers['References'] = @original_message.message_id
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 2f3487b53ac..9cafc78f761 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -43,8 +43,6 @@
# metrics_port :integer default(8089)
# sentry_enabled :boolean default(FALSE)
# sentry_dsn :string
-# ip_blocking_enabled :boolean default(FALSE)
-# dns_blacklist_threshold :float default(0.33)
#
class ApplicationSetting < ActiveRecord::Base
@@ -90,6 +88,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :sentry_enabled
+ validates :akismet_api_key,
+ presence: true,
+ if: :akismet_enabled
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
@@ -145,7 +147,9 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
- two_factor_grace_period: 48
+ two_factor_grace_period: 48,
+ recaptcha_enabled: false,
+ akismet_enabled: false
)
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 61119633717..8a0a8a4c2a9 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -26,7 +26,9 @@ class BroadcastMessage < ActiveRecord::Base
default_value_for :font, '#FFFFFF'
def self.current
- where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
+ Rails.cache.fetch("broadcast_message_current", expires_in: 1.minute) do
+ where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
+ end
end
def active?
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index d2a29236942..ecbd2078b1d 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -205,7 +205,11 @@ module Ci
end
def ci_yaml_file
- @ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data
+ @ci_yaml_file ||= begin
+ blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
+ blob.load_all_data!(project.repository)
+ blob.data
+ end
rescue
nil
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 0ba7b584d91..23b771aebb7 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -68,18 +68,18 @@ class Commit
# Pattern used to extract commit references from text
#
- # The SHA can be between 6 and 40 hex characters.
+ # The SHA can be between 7 and 40 hex characters.
#
# This pattern supports cross-project references.
def self.reference_pattern
%r{
(?:#{Project.reference_pattern}#{reference_prefix})?
- (?<commit>\h{6,40})
+ (?<commit>\h{7,40})
}x
end
def self.link_reference_pattern
- super("commit", /(?<commit>\h{6,40})/)
+ super("commit", /(?<commit>\h{7,40})/)
end
def to_reference(from_project = nil)
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 14e7971fa06..289dbc57287 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -32,8 +32,8 @@ class CommitRange
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be SHAs
- # between 6 and 40 hex characters.
- STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
+ # between 7 and 40 hex characters.
+ STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/
def self.reference_prefix
'@'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 0af60645545..89b6c49b362 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -346,10 +346,10 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
- issues = commits.flat_map { |c| c.closes_issues(current_user) }
- issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
- closed_by_message(description))
- issues.uniq(&:id)
+ messages = commits.map(&:safe_message) << description
+
+ Gitlab::ClosingIssueExtractor.new(project, current_user).
+ closed_by_message(messages.join("\n"))
else
[]
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 238932f59a7..043f08b9a13 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -36,6 +36,7 @@
# build_coverage_regex :string
# build_allow_git_fetch :boolean default(TRUE), not null
# build_timeout :integer default(3600), not null
+# pending_delete :boolean
#
require 'carrierwave/orm/activerecord'
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
new file mode 100644
index 00000000000..12df68ef83b
--- /dev/null
+++ b/app/models/spam_log.rb
@@ -0,0 +1,10 @@
+class SpamLog < ActiveRecord::Base
+ belongs_to :user
+
+ validates :user, presence: true
+
+ def remove_user
+ user.block
+ user.destroy
+ end
+end
diff --git a/app/models/spam_report.rb b/app/models/spam_report.rb
new file mode 100644
index 00000000000..cdc7321b08e
--- /dev/null
+++ b/app/models/spam_report.rb
@@ -0,0 +1,5 @@
+class SpamReport < ActiveRecord::Base
+ belongs_to :user
+
+ validates :user, presence: true
+end
diff --git a/app/models/tree.rb b/app/models/tree.rb
index b28f31cdd6e..7c4ed6e393b 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -39,6 +39,8 @@ class Tree
git_repo = repository.raw_repository
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
+ @readme.load_all_data!(git_repo)
+ @readme
end
def trees
diff --git a/app/models/user.rb b/app/models/user.rb
index 4214f01f6a4..234c1cd89f9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -138,6 +138,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
has_one :abuse_report, dependent: :destroy
+ has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 2a65f0431c4..dbd70dc5a44 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -110,7 +110,7 @@ class WikiPage
# Returns boolean True or False if this instance
# is an old version of the page.
def historical?
- @page.historical?
+ @page.historical? && versions.first.sha != version.sha
end
# Returns boolean True or False if this instance
diff --git a/app/services/create_spam_log_service.rb b/app/services/create_spam_log_service.rb
new file mode 100644
index 00000000000..59a66fde47a
--- /dev/null
+++ b/app/services/create_spam_log_service.rb
@@ -0,0 +1,13 @@
+class CreateSpamLogService < BaseService
+ def initialize(project, user, params)
+ super(project, user, params)
+ end
+
+ def execute
+ spam_params = params.merge({ user_id: @current_user.id,
+ project_id: @project.id } )
+ spam_log = SpamLog.new(spam_params)
+ spam_log.save
+ spam_log
+ end
+end
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
index e622fd5ea5d..173e50c9206 100644
--- a/app/services/delete_user_service.rb
+++ b/app/services/delete_user_service.rb
@@ -13,7 +13,7 @@ class DeleteUserService
user.personal_projects.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
end
user.destroy
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
index d929a676293..9189de390a2 100644
--- a/app/services/destroy_group_service.rb
+++ b/app/services/destroy_group_service.rb
@@ -9,7 +9,7 @@ class DestroyGroupService
@group.projects.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
end
@group.destroy
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index a8486e6a5a1..8d9661167b5 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -6,27 +6,12 @@ module Notes
note.system = false
if note.save
- notification_service.new_note(note)
-
- # Skip system notes, like status changes and cross-references and awards
- unless note.system || note.is_award
- event_service.leave_note(note, note.author)
- note.create_cross_references!
- execute_hooks(note)
- end
+ # Finish the harder work in the background
+ NewNoteWorker.perform_in(2.seconds, note.id, params)
end
note
end
- def hook_data(note)
- Gitlab::NoteDataBuilder.build(note, current_user)
- end
-
- def execute_hooks(note)
- note_data = hook_data(note)
- note.project.execute_hooks(note_data, :note_hooks)
- note.project.execute_services(note_data, :note_hooks)
- end
end
end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
new file mode 100644
index 00000000000..f37d3c50cdd
--- /dev/null
+++ b/app/services/notes/post_process_service.rb
@@ -0,0 +1,30 @@
+module Notes
+ class PostProcessService
+
+ attr_accessor :note
+
+ def initialize(note)
+ @note = note
+ end
+
+ def execute
+ # Skip system notes, like status changes and cross-references and awards
+ unless @note.system || @note.is_award
+ EventCreateService.new.leave_note(@note, @note.author)
+ @note.create_cross_references!
+ execute_note_hooks
+ end
+ end
+
+ def hook_data
+ Gitlab::NoteDataBuilder.build(@note, @note.author)
+ end
+
+ def execute_note_hooks
+ note_data = hook_data
+ @note.project.execute_hooks(note_data, :note_hooks)
+ @note.project.execute_services(note_data, :note_hooks)
+ end
+
+ end
+end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 28872c89259..294157b4f0e 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -6,6 +6,12 @@ module Projects
DELETED_FLAG = '+deleted'
+ def pending_delete!
+ project.update_attribute(:pending_delete, true)
+
+ ProjectDestroyWorker.perform_in(1.minute, project.id, current_user.id, params)
+ end
+
def execute
return false unless can?(current_user, :remove_project, project)
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index baadca09518..b4e3d96d405 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -105,14 +105,14 @@
= f.check_box :signin_enabled
Sign-in enabled
.form-group
- = f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
+ = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
- Require all users to setup Two-Factor authentication
+ Require all users to setup Two-factor authentication
.form-group
- = f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
+ = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
@@ -215,39 +215,40 @@
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
- = f.label :ip_blocking_enabled do
- = f.check_box :ip_blocking_enabled
- Enable IP check against blacklist at sign-up
- .help-block Helps preventing accounts creation from 'known spam sources'
-
- .form-group
- = f.label :dnsbl_servers_list, class: 'control-label col-sm-2' do
- DNSBL servers list
- .col-sm-10
- = f.text_field :dnsbl_servers_list, class: 'form-control'
- .help-block
- Please enter DNSBL servers separated with comma
-
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
Enable reCAPTCHA
- %span.help-block#recaptcha_help_block Helps preventing bots from creating accounts
+ %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
.form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_site_key, class: 'form-control'
.help-block
- Generate site and private keys here:
- %a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha
+ Generate site and private keys at
+ %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
+
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_private_key, class: 'form-control'
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :akismet_enabled do
+ = f.check_box :akismet_enabled
+ Enable Akismet
+ %span.help-block#akismet_help_block Helps prevent bots from creating issues
+
+ .form-group
+ = f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :akismet_api_key, class: 'form-control'
+ .help-block
+ Generate API key at
+ %a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
+
%fieldset
%legend Error Reporting and Logging
%p
diff --git a/app/views/admin/spam_logs/_spam_log.html.haml b/app/views/admin/spam_logs/_spam_log.html.haml
new file mode 100644
index 00000000000..8aea67f4497
--- /dev/null
+++ b/app/views/admin/spam_logs/_spam_log.html.haml
@@ -0,0 +1,32 @@
+- user = spam_log.user
+%tr
+ %td
+ = time_ago_with_tooltip(spam_log.created_at)
+ %td
+ - if user
+ = link_to user.name, [:admin, user]
+ .light.small
+ Joined #{time_ago_with_tooltip(user.created_at)}
+ - else
+ (removed)
+ %td
+ = spam_log.source_ip
+ %td
+ = spam_log.via_api? ? 'Y' : 'N'
+ %td
+ = spam_log.noteable_type
+ %td
+ = spam_log.title
+ %td
+ = truncate(spam_log.description, length: 100)
+ %td
+ - if user
+ = link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
+ data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
+ %td
+ - if user && !user.blocked?
+ = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
+ - else
+ .btn.btn-xs.disabled
+ Already Blocked
+ = link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
diff --git a/app/views/admin/spam_logs/index.html.haml b/app/views/admin/spam_logs/index.html.haml
new file mode 100644
index 00000000000..0fdd5bd9960
--- /dev/null
+++ b/app/views/admin/spam_logs/index.html.haml
@@ -0,0 +1,21 @@
+- page_title "Spam Logs"
+%h3.page-title Spam Logs
+%hr
+- if @spam_logs.present?
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Date
+ %th User
+ %th Source IP
+ %th API?
+ %th Type
+ %th Title
+ %th Description
+ %th Primary Action
+ %th
+ = render @spam_logs
+ = paginate @spam_logs
+- else
+ %h4 There are no Spam Logs
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 6ca97a692b4..3d17f74b709 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,7 +1,13 @@
-%ul.nav-links
- = nav_link(page: dashboard_groups_path) do
- = link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do
- Your Groups
- = nav_link(page: explore_groups_path) do
- = link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do
- Explore Groups
+.top-area
+ %ul.nav-links
+ = nav_link(page: dashboard_groups_path) do
+ = link_to dashboard_groups_path, title: 'Your groups' do
+ Your Groups
+ = nav_link(page: explore_groups_path) do
+ = link_to explore_groups_path, title: 'Explore groups' do
+ Explore Groups
+ - if current_user.can_create_group?
+ .nav-controls
+ = link_to new_group_path, class: "btn btn-new" do
+ = icon('plus')
+ New Group
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 5c4b58cd688..726f669b1d2 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -8,13 +8,13 @@
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
Starred Projects
- = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
+ = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore Projects
- .projects-search-form
+ .nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
- if current_user.can_create_project?
- = link_to new_project_path, class: 'btn btn-green' do
- %i.fa.fa-plus
+ = link_to new_project_path, class: 'btn btn-new' do
+ = icon('plus')
New Project
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index d5b7e729e7b..caca91af536 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -2,15 +2,6 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
-.gray-content-block
- - if current_user.can_create_group?
- %span.pull-right.hidden-xs
- = link_to new_group_path, class: "btn btn-new" do
- %i.fa.fa-plus
- New Group
- .oneline
- Group members have access to all group projects.
-
%ul.content-list
- @group_members.each do |group_member|
- group = group_member.group
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 2d3da01178a..f363f035974 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -16,8 +16,5 @@
= render 'shared/issuable/filter', type: :issues
-.gray-content-block.second-block
- List all issues from all projects you have access to.
-
.prepend-top-default
= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index c5a5ec21f78..bbe4cc1f824 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -7,8 +7,5 @@
= render 'shared/issuable/filter', type: :merge_requests
-.gray-content-block.second-block
- List all merge requests from all projects you have access to.
-
.prepend-top-default
= render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index bec1692a4de..eb2979fc13e 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -7,9 +7,6 @@
= render 'shared/milestones_filter'
-.gray-content-block
- List all milestones from all projects you have access to.
-
.milestones
%ul.content-list
- if @milestones.blank?
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 15f9ee266c1..eae80e5210f 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -4,6 +4,15 @@
Authorize
%strong.text-info= @pre_auth.client.name
to use your account?
+
+ - if current_user.admin?
+ .text-warning.prepend-top-20
+ %p
+ = icon("exclamation-triangle fw")
+ You are an admin, which means granting access to
+ %strong #{@pre_auth.client.name}
+ will allow them to interact with GitLab as an admin as well. Proceed with caution.
+
- if @pre_auth.scopes
#oauth-permissions
%p This application will be able to:
@@ -25,4 +34,4 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
- = submit_tag "Deny", class: "btn btn-danger prepend-left-10" \ No newline at end of file
+ = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index bbafc08435a..a829479bb38 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,11 +1,10 @@
-.projects-list-holder
- .projects-search-form
- .input-group
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- - if can? current_user, :create_projects, @group
- %span.input-group-btn
- = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
- %i.fa.fa-plus
- New Project
+.projects-list-holder.prepend-top-default
+ .input-group
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
+ - if can? current_user, :create_projects, @group
+ %span.input-group-btn
+ = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new' do
+ = icon('plus')
+ New Project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index cffdb52cc23..ac1d5429382 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: "Stats" do
+ = link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw')
%span
Overview
@@ -25,13 +25,13 @@
%span
Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do
- = link_to admin_runners_path do
+ = link_to admin_runners_path, title: 'Runners' do
= icon('cog fw')
%span
Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
- = link_to admin_builds_path do
+ = link_to admin_builds_path, title: 'Builds' do
= icon('link fw')
%span
Builds
@@ -82,6 +82,14 @@
Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all))
+ - if askimet_enabled?
+ = nav_link(controller: :spam_logs) do
+ = link_to admin_spam_logs_path, title: "Spam Logs" do
+ = icon('exclamation-triangle fw')
+ %span
+ Spam Logs
+ %span.count= number_with_delimiter(SpamLog.count(:all))
+
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 3dd2595f1ad..f2e405b14fd 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -18,7 +18,7 @@
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.to_s(:iso8601)}
- %pre.commit-message
+ %pre.commit-message
= commit.safe_message
%h4 #{pluralize @message.diffs_count, "changed file"}:
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 1a5b6efce35..b2830aa0834 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -1,6 +1,6 @@
- page_title 'Two-factor Authentication', 'Account'
-%h2.page-title Two-Factor Authentication (2FA)
+%h2.page-title Two-factor Authentication (2FA)
%p
Download the Google Authenticator application from App Store for iOS or Google
Play for Android and scan this code.
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 3d8d88834e2..2c5b8dc4356 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -35,7 +35,10 @@
- if blob.lfs_pointer?
= render "download", blob: blob
- elsif blob.text?
- = render "text", blob: blob
+ - if blob_svg?(blob)
+ = render "image", blob: sanitize_svg(blob)
+ - else
+ = render "text", blob: blob
- elsif blob.image?
= render "image", blob: blob
- else
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index c090f690d1d..51fa91b08e4 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,2 +1,2 @@
.file-content.image_file
- %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
+ %img{ src: namespace_project_raw_path(@project.namespace, @project, @id)}
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 906e5ccb360..d09cd73558c 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -1,3 +1,4 @@
+- blob.load_all_data!(@repository)
- if markup?(blob.name)
.file-content.wiki
= render_markup(blob.name, blob.data)
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index fc0eaef2286..3ac058a3bf8 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -7,16 +7,20 @@
= submodule_link(blob, @commit.id, project.repository)
- else
= blob_icon blob.mode, blob.name
- = link_to "#diff-#{i}" do
- %strong
- = diff_file.new_path
- - if diff_file.deleted_file
- deleted
- - elsif diff_file.renamed_file
- renamed from
- %strong
- = diff_file.old_path
+ = link_to "#diff-#{i}" do
+ - if diff_file.renamed_file
+ - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
+ %strong.filename.old
+ = old_path
+ &rarr;
+ %strong.filename.new
+ = new_path
+ - else
+ %strong
+ = diff_file.new_path
+ - if diff_file.deleted_file
+ deleted
- if diff_file.mode_changed?
%small
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index a362185210a..acb2353d3ca 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -1,44 +1,42 @@
-.gray-content-block.top-block.clearfix.white.forks-top-block
- .pull-left
+.top-area
+ .nav-text
- public_count = @public_forks.size
- protected_count = @protected_forks.size
- full_count_title = "#{public_count} public and #{protected_count} private"
== #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
- .pull-right
- .projects-search-form.fork-search-form
- = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control',
- spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
+ .nav-controls
+ = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control input-short',
+ spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
- .dropdown.inline.prepend-left-10
- %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
- - if @sort.present?
- = sort_options_hash[@sort]
- - else
+ .dropdown
+ %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light sort:
+ - if @sort.present?
+ = sort_options_hash[@sort]
+ - else
+ = sort_title_recently_created
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li
+ - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
+ = link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do
= sort_title_recently_created
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- %li
- - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
- = link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do
- = sort_title_recently_created
- = link_to page_filter_path(sort: sort_value_oldest_created, without: excluded_filters) do
- = sort_title_oldest_created
- = link_to page_filter_path(sort: sort_value_recently_updated, without: excluded_filters) do
- = sort_title_recently_updated
- = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
- = sort_title_oldest_updated
+ = link_to page_filter_path(sort: sort_value_oldest_created, without: excluded_filters) do
+ = sort_title_oldest_created
+ = link_to page_filter_path(sort: sort_value_recently_updated, without: excluded_filters) do
+ = sort_title_recently_updated
+ = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
+ = sort_title_oldest_updated
- .fork-link.inline
- - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
- = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'pull-right btn btn-new' do
- = icon('code-fork fw')
- Fork
- - else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'pull-right btn btn-new' do
- = icon('code-fork fw')
- Fork
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
+ = icon('code-fork fw')
+ Fork
+ - else
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
+ = icon('code-fork fw')
+ Fork
.projects-list-holder
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 114b06457a5..aa185126b56 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -11,9 +11,6 @@
= render 'shared/milestones_filter'
-.gray-content-block
- Milestone allows you to group issues and set due date for it
-
.milestones
%ul.content-list
= render @milestones
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index ee242c94db8..57856031d6e 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,7 +1,7 @@
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
- - blob.data.lines.each_index do |index|
+ - blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index c4431d66927..46095912821 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,6 +1,6 @@
- if @projects.any?
- .prepend-left-10.new-project-item-select-holder
- = project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
+ .prepend-left-10.project-item-select-holder
+ = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups] }
%a.btn.btn-new.new-project-item-select-button
= icon('plus')
= local_assigns[:label]
@@ -8,12 +8,12 @@
:javascript
$('.new-project-item-select-button').on('click', function() {
- $('.new-project-item-select').select2('open');
+ $('.project-item-select').select2('open');
});
var relativePath = '#{local_assigns[:path]}';
- $('.new-project-item-select').on('click', function() {
+ $('.project-item-select').on('click', function() {
window.location = $(this).val() + '/' + relativePath;
});
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 778b20fb4f2..f7fe6b02641 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -1,5 +1,8 @@
- group_member = local_assigns[:group_member]
-%li
+- css_class = '' unless local_assigns[:css_class]
+- css_class += " no-description" if group.description.blank?
+
+%li.group-row{ class: css_class }
- if group_member
.controls.hidden-xs
- if can?(current_user, :admin_group, group)
@@ -9,6 +12,15 @@
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
%i.fa.fa-sign-out
+ .stats
+ %span
+ = icon('home')
+ = number_with_delimiter(group.projects.count)
+
+ %span
+ = icon('users')
+ = number_with_delimiter(group.users.count)
+
= image_tag group_icon(group), class: "avatar s46 hidden-xs"
= link_to group, class: 'group-name' do
%span.item-title= group.name
@@ -17,5 +29,6 @@
as
%span #{group_member.human_access}
- %div.light
- #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
+ - if group.description.present?
+ .light
+ = markdown(group.description, pipeline: :description)
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 2aeeed63c95..e196fc51d2d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -29,8 +29,8 @@
.project-controls
- if ci_commit
- = render_ci_status(ci_commit)
- &nbsp;
+ %span
+ = render_ci_status(ci_commit)
- if forks
%span
= icon('code-fork')
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index b1f8645eea0..91c5b7eac5e 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -7,7 +7,7 @@
- if current_user
.awards-controls
- %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
+ %a.add-award{"href" => "#"}
= icon('smile-o')
.emoji-menu
.emoji-menu-content
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
new file mode 100644
index 00000000000..1b3232cd365
--- /dev/null
+++ b/app/workers/new_note_worker.rb
@@ -0,0 +1,12 @@
+class NewNoteWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(note_id, note_params)
+ note = Note.find(note_id)
+
+ NotificationService.new.new_note(note)
+ Notes::PostProcessService.new(note).execute
+ end
+end
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
new file mode 100644
index 00000000000..d06e4480292
--- /dev/null
+++ b/app/workers/project_destroy_worker.rb
@@ -0,0 +1,17 @@
+class ProjectDestroyWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(project_id, user_id, params)
+ begin
+ project = Project.find(project_id)
+ rescue ActiveRecord::RecordNotFound
+ return
+ end
+
+ user = User.find(user_id)
+
+ ::Projects::DestroyService.new(project, user, params).execute
+ end
+end
diff --git a/config/database.yml.env b/config/database.yml.env
index b2ff23cb5ab..1e35651c9a6 100644
--- a/config/database.yml.env
+++ b/config/database.yml.env
@@ -1,9 +1,17 @@
<%= ENV['RAILS_ENV'] %>:
+ ## Connection information
+ # Please be aware that the DATABASE_URL environment variable will take
+ # precedence over the following 6 parameters. For more information, see
+ # doc/administration/environment_variables.md
adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %>
- encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %>
- pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %>
password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %>
host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %>
port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %>
+
+ ## Behavior information
+ # The following parameters will be used even if you're using the DATABASE_URL
+ # environment variable.
+ encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
+ pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 0945b93ed5d..3e1deb8d306 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -49,12 +49,14 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(Gitlab::Shell)
config.instrument_methods(Gitlab::Git)
- config.instrument_instance_methods(Gitlab::Git::Repository)
Gitlab::Git.constants.each do |name|
const = Gitlab::Git.const_get(name)
- config.instrument_methods(const) if const.is_a?(Module)
+ next unless const.is_a?(Module)
+
+ config.instrument_methods(const)
+ config.instrument_instance_methods(const)
end
Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
@@ -62,6 +64,16 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const)
end
+
+ [
+ :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
+ :Tag, :TagCollection, :Tree
+ ].each do |name|
+ const = Rugged.const_get(name)
+
+ config.instrument_methods(const)
+ config.instrument_instance_methods(const)
+ end
end
GC::Profiler.enable
diff --git a/config/routes.rb b/config/routes.rb
index fdfdb449085..034bfaf1bcd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -211,6 +211,8 @@ Rails.application.routes.draw do
end
resources :abuse_reports, only: [:index, :destroy]
+ resources :spam_logs, only: [:index, :destroy]
+
resources :applications
resources :groups, constraints: { id: /[^\/]+/ } do
@@ -490,7 +492,7 @@ Rails.application.routes.draw do
end
resource :avatar, only: [:show, :destroy]
- resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do
+ resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
get :branches
get :builds
diff --git a/db/migrate/20151231152326_add_akismet_to_application_settings.rb b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
new file mode 100644
index 00000000000..3f52c758f9a
--- /dev/null
+++ b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
@@ -0,0 +1,8 @@
+class AddAkismetToApplicationSettings < ActiveRecord::Migration
+ def change
+ change_table :application_settings do |t|
+ t.boolean :akismet_enabled, default: false
+ t.string :akismet_api_key
+ end
+ end
+end
diff --git a/db/migrate/20160109054846_create_spam_logs.rb b/db/migrate/20160109054846_create_spam_logs.rb
new file mode 100644
index 00000000000..f12fe9f8f78
--- /dev/null
+++ b/db/migrate/20160109054846_create_spam_logs.rb
@@ -0,0 +1,16 @@
+class CreateSpamLogs < ActiveRecord::Migration
+ def change
+ create_table :spam_logs do |t|
+ t.integer :user_id
+ t.string :source_ip
+ t.string :user_agent
+ t.boolean :via_api
+ t.integer :project_id
+ t.string :noteable_type
+ t.string :title
+ t.text :description
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20160122185421_add_pending_delete_to_project.rb b/db/migrate/20160122185421_add_pending_delete_to_project.rb
new file mode 100644
index 00000000000..046a5d8fc32
--- /dev/null
+++ b/db/migrate/20160122185421_add_pending_delete_to_project.rb
@@ -0,0 +1,5 @@
+class AddPendingDeleteToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :pending_delete, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
new file mode 100644
index 00000000000..41821cdcc42
--- /dev/null
+++ b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
@@ -0,0 +1,6 @@
+class RemoveIpBlockingSettingsFromApplicationSettings < ActiveRecord::Migration
+ def change
+ remove_column :application_settings, :ip_blocking_enabled, :boolean, default: false
+ remove_column :application_settings, :dnsbl_servers_list, :text
+ end
+end
diff --git a/db/migrate/20160128233227_change_lfs_objects_size_column.rb b/db/migrate/20160128233227_change_lfs_objects_size_column.rb
new file mode 100644
index 00000000000..e7fd1f71777
--- /dev/null
+++ b/db/migrate/20160128233227_change_lfs_objects_size_column.rb
@@ -0,0 +1,5 @@
+class ChangeLfsObjectsSizeColumn < ActiveRecord::Migration
+ def change
+ change_column :lfs_objects, :size, :integer, limit: 8
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 97594011a02..d546e06cd8a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160120172143) do
+ActiveRecord::Schema.define(version: 20160128233227) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -64,8 +64,8 @@ ActiveRecord::Schema.define(version: 20160120172143) do
t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
- t.boolean "ip_blocking_enabled", default: false
- t.text "dnsbl_servers_list"
+ t.boolean "akismet_enabled", default: false
+ t.string "akismet_api_key"
end
create_table "audit_events", force: :cascade do |t|
@@ -447,8 +447,8 @@ ActiveRecord::Schema.define(version: 20160120172143) do
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
create_table "lfs_objects", force: :cascade do |t|
- t.string "oid", null: false
- t.integer "size", null: false
+ t.string "oid", null: false
+ t.integer "size", limit: 8, null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "file"
@@ -679,6 +679,7 @@ ActiveRecord::Schema.define(version: 20160120172143) do
t.string "build_coverage_regex"
t.boolean "build_allow_git_fetch", default: true, null: false
t.integer "build_timeout", default: 3600, null: false
+ t.boolean "pending_delete", default: false
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -771,6 +772,19 @@ ActiveRecord::Schema.define(version: 20160120172143) do
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
+ create_table "spam_logs", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "source_ip"
+ t.string "user_agent"
+ t.boolean "via_api"
+ t.integer "project_id"
+ t.string "noteable_type"
+ t.string "title"
+ t.text "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "subscriptions", force: :cascade do |t|
t.integer "user_id"
t.integer "subscribable_id"
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 0faef526d43..43ab153d76d 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -1,56 +1,61 @@
# Environment Variables
-## Introduction
+GitLab exposes certain environment variables which can be used to override
+their defaults values.
-Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package.
+People usually configure GitLab via `/etc/gitlab/gitlab.rb` for Omnibus
+installations, or `gitlab.yml` for installations from source.
-But if you prefer to use environment variables we allow that too.
+Below you will find the supported environment variables which you can use to
+override certain values.
## Supported environment variables
-Variable | Type | Explanation
+Variable | Type | Description
-------- | ---- | -----------
-GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation
-GITLAB_HOST | url | hostname of the GitLab server includes http or https
-RAILS_ENV | production / development / staging / test | Rails environment
-DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
-GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab
-GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab
-GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab
-GITLAB_UNICORN_MEMORY_MIN | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
-GITLAB_UNICORN_MEMORY_MAX | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
+`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
+`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
+`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
+`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
+`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
+`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
+`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
+`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
+`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
## Complete database variables
-As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set:
-
-- adapter
-- database
-- username
-- password
-- host
-- port
-
-To do so please `cp config/database.yml.env config/database.yml` and use the following variables:
-
-Variable | Default
---- | ---
-GITLAB_DATABASE_ADAPTER | postgresql
-GITLAB_DATABASE_ENCODING | unicode
-GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV']
-GITLAB_DATABASE_POOL | 10
-GITLAB_DATABASE_USERNAME | root
-GITLAB_DATABASE_PASSWORD |
-GITLAB_DATABASE_HOST | localhost
-GITLAB_DATABASE_PORT | 5432
+The recommended way of specifying your database connection information is to set
+the `DATABASE_URL` environment variable. This variable only holds connection
+information (`adapter`, `database`, `username`, `password`, `host` and `port`),
+but not behavior information (`encoding`, `pool`). If you don't want to use
+`DATABASE_URL` and/or want to set database behavior information, you will have
+to either:
+
+- copy our template file: `cp config/database.yml.env config/database.yml`, or
+- set a value for some `GITLAB_DATABASE_XXX` variables
+
+The list of `GITLAB_DATABASE_XXX` variables that you can set is:
+
+Variable | Default value | Overridden by `DATABASE_URL`?
+-------- | ------------- | -----------------------------
+`GITLAB_DATABASE_ADAPTER` | `postgresql` (for MySQL use `mysql2`) | Yes
+`GITLAB_DATABASE_DATABASE` | `gitlab_#{ENV['RAILS_ENV']` | Yes
+`GITLAB_DATABASE_USERNAME` | `root` | Yes
+`GITLAB_DATABASE_PASSWORD` | None | Yes
+`GITLAB_DATABASE_HOST` | `localhost` | Yes
+`GITLAB_DATABASE_PORT` | `5432` | Yes
+`GITLAB_DATABASE_ENCODING` | `unicode` | No
+`GITLAB_DATABASE_POOL` | `10` | No
## Adding more variables
We welcome merge requests to make more settings configurable via variables.
-Please make changes in the file config/initializers/1_settings.rb
-Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
+Please make changes in the `config/initializers/1_settings.rb` file and stick
+to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
## Omnibus configuration
-It's possible to preconfigure the GitLab image by adding the environment variable: `GITLAB_OMNIBUS_CONFIG` to docker run command.
+It's possible to preconfigure the GitLab docker image by adding the environment
+variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
diff --git a/doc/api/builds.md b/doc/api/builds.md
index ecb50754c88..6e64d096644 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -18,7 +18,7 @@ GET /projects/:id/builds
### Example of request
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
```
### Example of response
@@ -123,7 +123,7 @@ GET /projects/:id/repository/commits/:sha/builds
### Example of request
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
```
### Example of response
@@ -213,7 +213,7 @@ GET /projects/:id/builds/:build_id
### Example of request
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
```
### Example of response
@@ -277,7 +277,7 @@ POST /projects/:id/builds/:build_id/cancel
### Example of request
```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
```
### Example of response
@@ -327,7 +327,7 @@ POST /projects/:id/builds/:build_id/retry
### Example of request
```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
```
### Example of response
diff --git a/doc/ci/api/projects.md b/doc/ci/api/projects.md
index 74a4c64d000..fe6b1c01352 100644
--- a/doc/ci/api/projects.md
+++ b/doc/ci/api/projects.md
@@ -18,7 +18,7 @@ GET /ci/projects
Returns:
```json
- [
+[
{
"id" : 271,
"name" : "gitlabhq",
diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md
index 77f9fae5bb6..aeadd6a448e 100644
--- a/doc/ci/languages/php.md
+++ b/doc/ci/languages/php.md
@@ -97,7 +97,7 @@ image: php:5.6
before_script:
# Install dependencies
-- ci/docker_install.sh > /dev/null
+- bash ci/docker_install.sh > /dev/null
test:app:
script:
@@ -112,7 +112,7 @@ with a different docker image version and the runner will do the rest:
```yaml
before_script:
# Install dependencies
-- ci/docker_install.sh > /dev/null
+- bash ci/docker_install.sh > /dev/null
# We test PHP5.6
test:5.6:
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 4d280297dbb..194c8171bb9 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -393,8 +393,12 @@ The above script will:
### artifacts
-_**Note:** Introduced in GitLab Runner v0.7.0. Also, the Windows shell executor
- does not currently support artifact uploads._
+_**Note:** Introduced in GitLab Runner v0.7.0 for non-Windows platforms._
+
+_**Note:** Limited Windows support was added in GitLab Runner v.1.0.0.
+Currently not all executors are supported._
+
+_**Note:** Build artifacts are only collected for successful builds._
`artifacts` is used to specify list of files and directories which should be
attached to build after success. Below are some examples.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 2cc2dbef41b..a2c23bd52e5 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -355,7 +355,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout 0.6.2
+ sudo -u git -H git checkout 0.6.3
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 83116bc8370..281eea8363d 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -15,6 +15,7 @@ See the documentation below for details on how to configure these services.
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
+- [Akismet](akismet.md) Configure Akismet to stop spam
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
new file mode 100644
index 00000000000..5cc09bd536d
--- /dev/null
+++ b/doc/integration/akismet.md
@@ -0,0 +1,30 @@
+# Akismet
+
+GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
+GitLab uses Akismet to prevent users who are not members of a project from
+creating spam via the GitLab API. Detected spam will be rejected, and
+an entry in the "Spam Log" section in the Admin page will be created.
+
+Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
+adding a user to a project will disable the Akismet check and prevent this
+from happening.
+
+## Configuration
+
+To use Akismet:
+
+1. Go to the URL: https://akismet.com/account/
+
+2. Sign-in or create a new account.
+
+3. Click on "Show" to reveal the API key.
+
+4. Go to Applications Settings on Admin Area (`admin/application_settings`)
+
+5. Check the `Enable Akismet` checkbox
+
+6. Fill in the API key from step 3.
+
+7. Save the configuration.
+
+![Screenshot of Akismet settings](img/akismet_settings.png)
diff --git a/doc/integration/img/akismet_settings.png b/doc/integration/img/akismet_settings.png
new file mode 100644
index 00000000000..ccdd3adb1c5
--- /dev/null
+++ b/doc/integration/img/akismet_settings.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_admin_application.png b/doc/integration/img/oauth_provider_admin_application.png
index a5f34512aa8..a2d8e14c120 100644
--- a/doc/integration/img/oauth_provider_admin_application.png
+++ b/doc/integration/img/oauth_provider_admin_application.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_application_form.png b/doc/integration/img/oauth_provider_application_form.png
index ae135db2627..3a676b22393 100644
--- a/doc/integration/img/oauth_provider_application_form.png
+++ b/doc/integration/img/oauth_provider_application_form.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_application_id_secret.png b/doc/integration/img/oauth_provider_application_id_secret.png
new file mode 100644
index 00000000000..6d68df001af
--- /dev/null
+++ b/doc/integration/img/oauth_provider_application_id_secret.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_authorized_application.png b/doc/integration/img/oauth_provider_authorized_application.png
index d3ce05be9cc..efc3b807d71 100644
--- a/doc/integration/img/oauth_provider_authorized_application.png
+++ b/doc/integration/img/oauth_provider_authorized_application.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_user_wide_applications.png b/doc/integration/img/oauth_provider_user_wide_applications.png
index 719e1974068..45ad8a6d468 100644
--- a/doc/integration/img/oauth_provider_user_wide_applications.png
+++ b/doc/integration/img/oauth_provider_user_wide_applications.png
Binary files differ
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index dbe5a175c82..5f8bb57365c 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -1,35 +1,80 @@
-## GitLab as OAuth2 authentication service provider
+# GitLab as OAuth2 authentication service provider
-This document is about using GitLab as an OAuth authentication service provider to sign into other services.
-If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md)
+This document is about using GitLab as an OAuth authentication service provider
+to sign in to other services.
-OAuth2 provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. Or you can allow users to sign in to your application with their GitLab.com account.
-In fact OAuth allows to issue access token to third-party clients by an authorization server,
-with the approval of the resource owner, or end-user.
-Mostly, OAuth2 is using for SSO (Single sign-on). But you can find a lot of different usages for this functionality.
-For example, our feature 'GitLab Importer' is using OAuth protocol to give an access to repositories without sharing user credentials to GitLab.com account.
-Also GitLab.com application can be used for authentication to your GitLab instance if needed [GitLab OmniAuth](gitlab.md).
+If you want to use other OAuth authentication service providers to sign in to
+GitLab, please see the [OAuth2 client documentation](../api/oauth2.md).
-GitLab has two ways to add new OAuth2 application to an instance, you can add application as regular user and through admin area. So GitLab actually can have an instance-wide and a user-wide applications. There is no defferences between them except the different permission levels.
+## Introduction to OAuth
-### Adding application through profile
-Go to your profile section 'Application' and press button 'New Application'
+[OAuth] provides to client applications a 'secure delegated access' to server
+resources on behalf of a resource owner. In fact, OAuth allows an authorization
+server to issue access tokens to third-party clients with the approval of the
+resource owner, or the end-user.
-![applications](img/oauth_provider_user_wide_applications.png)
+OAuth is mostly used as a Single Sign-On service (SSO), but you can find a
+lot of different uses for this functionality. For example, you can allow users
+to sign in to your application with their GitLab.com account, or GitLab.com
+can be used for authentication to your GitLab instance
+(see [GitLab OmniAuth](gitlab.md)).
-After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com.
+The 'GitLab Importer' feature is also using the OAuth protocol to give access
+to repositories without sharing user credentials to your GitLab.com account.
-![application_form](img/oauth_provider_application_form.png)
+---
-### Authorized application
-Every application you authorized will be shown in your "Authorized application" sections.
+GitLab supports two ways of adding a new OAuth2 application to an instance. You
+can either add an application as a regular user or add it in the admin area.
+What this means is that GitLab can actually have instance-wide and a user-wide
+applications. There is no difference between them except for the different
+permission levels they are set (user/admin).
-![authorized_application](img/oauth_provider_authorized_application.png)
+## Adding an application through the profile
-At any time you can revoke access just clicking button "Revoke"
+In order to add a new application via your profile, navigate to
+**Profile Settings > Applications** and select **New Application**.
-### OAuth applications in admin area
+![New OAuth application](img/oauth_provider_user_wide_applications.png)
-If you want to create application that does not belong to certain user you can create it from admin area
+---
-![admin_application](img/oauth_provider_admin_application.png)
+In the application form, enter a **Name** (arbitrary), and make sure to set up
+correctly the **Redirect URI** which is the URL where users will be sent after
+they authorize with GitLab.
+
+![New OAuth application form](img/oauth_provider_application_form.png)
+
+---
+
+When you hit **Submit** you will be provided with the application ID and
+the application secret which you can then use with your application that
+connects to GitLab.
+
+![OAuth application ID and secret](img/oauth_provider_application_id_secret.png)
+
+---
+
+## OAuth applications in the admin area
+
+To create an application that does not belong to a certain user, you can create
+it from the admin area.
+
+![OAuth admin_applications](img/oauth_provider_admin_application.png)
+
+---
+
+## Authorized applications
+
+Every application you authorized to use your GitLab credentials will be shown
+in the **Authorized applications** section under **Profile Settings > Applications**.
+
+![Authorized_applications](img/oauth_provider_authorized_application.png)
+
+---
+
+As you can see, the default scope `api` is used, which is the only scope that
+GitLab supports so far. At any time you can revoke any access by just clicking
+**Revoke**.
+
+[oauth]: http://oauth.net/2/ "OAuth website"
diff --git a/doc/security/README.md b/doc/security/README.md
index f34c792d005..be1abb88c3d 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -7,4 +7,4 @@
- [Reset your root password](reset_root_password.md)
- [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
-- [Enforce Two-Factor authentication](two_factor_authentication.md)
+- [Enforce Two-factor authentication](two_factor_authentication.md)
diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md
index 4516a102084..c45fc9340ea 100644
--- a/doc/update/6.x-or-7.x-to-7.14.md
+++ b/doc/update/6.x-or-7.x-to-7.14.md
@@ -14,6 +14,12 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2.
+## Stash changes
+
+If you [deleted the vendors folder during your original installation](https://github.com/gitlabhq/gitlabhq/issues/4883#issuecomment-31108431), [you will get an error](https://gitlab.com/gitlab-org/gitlab-ce/issues/1494) when you attempt to rebuild the assets in step 7. To avoid this, stash the changes in your GitLab working copy before starting:
+
+ git stash
+
## 0. Stop server
sudo service gitlab stop
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 8965e5b3654..be32f0c720b 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -152,9 +152,10 @@ The name of this branch should start with the issue number, for example '15-requ
When you are done or want to discuss the code you open a merge request.
This is an online place to discuss the change and review the code.
-Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
-If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request.
+Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
+If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request.
These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
+_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
When the author thinks the code is ready the merge request is assigned to reviewer.
The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
@@ -185,7 +186,7 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png)
-With git you can use an interactive rebase (rebase -i) to squash multiple commits into one and reorder them.
+With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
diff --git a/features/admin/spam_logs.feature b/features/admin/spam_logs.feature
new file mode 100644
index 00000000000..92a5389e3a4
--- /dev/null
+++ b/features/admin/spam_logs.feature
@@ -0,0 +1,8 @@
+Feature: Admin spam logs
+ Background:
+ Given I sign in as an admin
+ And spam logs exist
+
+ Scenario: Browse spam logs
+ When I visit spam logs page
+ Then I should see list of spam logs
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index 9a06fdc2ee6..bfde89fd896 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -9,6 +9,7 @@ Feature: Award Emoji
@javascript
Scenario: I add and remove award in the issue
Given I click to emoji-picker
+ Then The search field is focused
And I click to emoji in the picker
Then I have award added
And I can remove it by clicking to icon
@@ -16,11 +17,13 @@ Feature: Award Emoji
@javascript
Scenario: I can see the list of emoji categories
Given I click to emoji-picker
+ Then The search field is focused
Then I can see the activity and food categories
@javascript
Scenario: I can search emoji
Given I click to emoji-picker
+ Then The search field is focused
And I search "hand"
Then I see search result for "hand"
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index a8c276b949e..1e09dbc4c8f 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -320,3 +320,13 @@ Feature: Project Source Browse Files
Then I should see download link and object size
And I should not see lfs pointer details
And I should see buttons for allowed commands
+
+ @javascript
+ Scenario: I preview an SVG file
+ Given I click on "Upload file" link in repo
+ And I upload a new SVG file
+ And I fill the upload file commit message
+ And I fill the new branch name
+ And I click on "Upload file"
+ Given I visit the SVG file
+ Then I can see the new rendered SVG image
diff --git a/features/steps/admin/spam_logs.rb b/features/steps/admin/spam_logs.rb
new file mode 100644
index 00000000000..ad825fd414c
--- /dev/null
+++ b/features/steps/admin/spam_logs.rb
@@ -0,0 +1,28 @@
+class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'I should see list of spam logs' do
+ expect(page).to have_content('Spam Logs')
+ expect(page).to have_content spam_log.source_ip
+ expect(page).to have_content spam_log.noteable_type
+ expect(page).to have_content 'N'
+ expect(page).to have_content spam_log.title
+ expect(page).to have_content truncate(spam_log.description)
+ expect(page).to have_link('Remove user')
+ expect(page).to have_link('Block user')
+ end
+
+ step 'spam logs exist' do
+ create(:spam_log)
+ end
+
+ def spam_log
+ @spam_log ||= SpamLog.first
+ end
+
+ def truncate(description)
+ "#{spam_log.description[0...97]}..."
+ end
+end
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 2c2ed08655e..69695d493f3 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -66,4 +66,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
expect(page).to have_selector '[data-emoji="raised_hand"]'
end
end
+
+ step 'The search field is focused' do
+ page.evaluate_script("document.activeElement.id").should eq "emoji_search"
+ end
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index d08935aa101..51b15791674 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see raw file content' do
- expect(source).to eq sample_blob.data
+ expect(source).to eq '' # Body is filled in by gitlab-workhorse
end
step 'I click button "Edit"' do
@@ -351,6 +351,19 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
+ # SVG files
+ step 'I upload a new SVG file' do
+ drop_in_dropzone test_svg_file
+ end
+
+ step 'I visit the SVG file' do
+ visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg')
+ end
+
+ step 'I can see the new rendered SVG image' do
+ expect(find('.file-content')).to have_css('img')
+ end
+
private
def set_new_content
@@ -410,4 +423,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
def test_image_file
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
end
+
+ def test_svg_file
+ File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg')
+ end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 4264c9c6f1a..2c854ac7bf9 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -191,6 +191,10 @@ module SharedPaths
visit admin_application_settings_path
end
+ step 'I visit spam logs page' do
+ visit admin_spam_logs_path
+ end
+
step 'I visit applications page' do
visit admin_applications_path
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 8ad2c1883c7..c1d86f313b0 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -58,9 +58,11 @@ module API
commit = user_project.commit(ref)
not_found! 'Commit' unless commit
- blob = user_project.repository.blob_at(commit.sha, file_path)
+ repo = user_project.repository
+ blob = repo.blob_at(commit.sha, file_path)
if blob
+ blob.load_all_data!(repo)
status(200)
{
@@ -72,7 +74,7 @@ module API
ref: ref,
blob_id: blob.id,
commit_id: commit.id,
- last_commit_id: user_project.repository.last_commit_for_path(commit.sha, file_path).id
+ last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id
}
else
not_found! 'File'
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 9dacf7c1e86..a72044e8058 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -30,7 +30,7 @@ module API
end
def sudo_identifier()
- identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
+ identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
# Regex for integers
if !!(identifier =~ /^[0-9]+$/)
@@ -344,12 +344,22 @@ module API
def pagination_links(paginated_data)
request_url = request.url.split('?').first
+ request_params = params.clone
+ request_params[:per_page] = paginated_data.limit_value
links = []
- links << %(<#{request_url}?page=#{paginated_data.current_page - 1}&per_page=#{paginated_data.limit_value}>; rel="prev") unless paginated_data.first_page?
- links << %(<#{request_url}?page=#{paginated_data.current_page + 1}&per_page=#{paginated_data.limit_value}>; rel="next") unless paginated_data.last_page?
- links << %(<#{request_url}?page=1&per_page=#{paginated_data.limit_value}>; rel="first")
- links << %(<#{request_url}?page=#{paginated_data.total_pages}&per_page=#{paginated_data.limit_value}>; rel="last")
+
+ request_params[:page] = paginated_data.current_page - 1
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?
+
+ request_params[:page] = paginated_data.current_page + 1
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?
+
+ request_params[:page] = 1
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
+
+ request_params[:page] = paginated_data.total_pages
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
links.join(', ')
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 6e7a7672070..252744515da 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -3,6 +3,8 @@ module API
class Issues < Grape::API
before { authenticate! }
+ helpers ::Gitlab::AkismetHelper
+
helpers do
def filter_issues_state(issues, state)
case state
@@ -19,6 +21,17 @@ module API
def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone)
end
+
+ def create_spam_log(project, current_user, attrs)
+ params = attrs.merge({
+ source_ip: env['REMOTE_ADDR'],
+ user_agent: env['HTTP_USER_AGENT'],
+ noteable_type: 'Issue',
+ via_api: true
+ })
+
+ ::CreateSpamLogService.new(project, current_user, params).execute
+ end
end
resource :issues do
@@ -114,7 +127,15 @@ module API
render_api_error!({ labels: errors }, 400)
end
- issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
+ project = user_project
+ text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
+
+ if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
+ create_spam_log(project, current_user, attrs)
+ render_api_error!({ error: 'Spam detected' }, 400)
+ end
+
+ issue = ::Issues::CreateService.new(project, current_user, attrs).execute
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 71bb342f844..1f991e600e3 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -187,7 +187,7 @@ module API
else
present @forked_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, @forked_project)
- end
+ end
end
# Update an existing project
@@ -246,7 +246,7 @@ module API
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
- ::Projects::DestroyService.new(user_project, current_user, {}).execute
+ ::Projects::DestroyService.new(user_project, current_user, {}).pending_delete!
end
# Mark this project as forked from another
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index d7c48639eba..c95d2d2001d 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -57,7 +57,7 @@ module API
not_found! "File" unless blob
content_type 'text/plain'
- present blob.data
+ header *Gitlab::Workhorse.send_git_blob(repo, blob)
end
# Get a raw blob contents by blob sha
@@ -83,7 +83,7 @@ module API
env['api.format'] = :txt
content_type blob.mime_type
- present blob.data
+ header *Gitlab::Workhorse.send_git_blob(repo, blob)
end
# Get a an archive of the repository
diff --git a/lib/dnsxl_check.rb b/lib/dnsxl_check.rb
deleted file mode 100644
index 1e506b2d9cb..00000000000
--- a/lib/dnsxl_check.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require 'resolv'
-
-class DNSXLCheck
-
- class Resolver
- def self.search(query)
- begin
- Resolv.getaddress(query)
- true
- rescue Resolv::ResolvError
- false
- end
- end
- end
-
- IP_REGEXP = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
- DEFAULT_THRESHOLD = 0.33
-
- def self.create_from_list(list)
- dnsxl_check = DNSXLCheck.new
-
- list.each do |entry|
- dnsxl_check.add_list(entry.domain, entry.weight)
- end
-
- dnsxl_check
- end
-
- def test(ip)
- if use_threshold?
- test_with_threshold(ip)
- else
- test_strict(ip)
- end
- end
-
- def test_with_threshold(ip)
- return false if lists.empty?
-
- search(ip)
- final_score >= threshold
- end
-
- def test_strict(ip)
- return false if lists.empty?
-
- search(ip)
- @score > 0
- end
-
- def use_threshold=(value)
- @use_threshold = value == true
- end
-
- def use_threshold?
- @use_threshold &&= true
- end
-
- def threshold=(threshold)
- raise ArgumentError, "'threshold' value must be grather than 0 and less than or equal to 1" unless threshold > 0 && threshold <= 1
- @threshold = threshold
- end
-
- def threshold
- @threshold ||= DEFAULT_THRESHOLD
- end
-
- def add_list(domain, weight)
- @lists ||= []
- @lists << { domain: domain, weight: weight }
- end
-
- def lists
- @lists ||= []
- end
-
- private
-
- def search(ip)
- raise ArgumentError, "'ip' value must be in #{IP_REGEXP} format" unless ip.match(IP_REGEXP)
-
- @score = 0
-
- reversed = reverse_ip(ip)
- search_in_rbls(reversed)
- end
-
- def reverse_ip(ip)
- ip.split('.').reverse.join('.')
- end
-
- def search_in_rbls(reversed_ip)
- lists.each do |rbl|
- query = "#{reversed_ip}.#{rbl[:domain]}"
- @score += rbl[:weight] if Resolver.search(query)
- end
- end
-
- def final_score
- weights = lists.map{ |rbl| rbl[:weight] }.reduce(:+).to_i
- return 0 if weights == 0
-
- (@score.to_f / weights.to_f).round(2)
- end
-end
diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb
new file mode 100644
index 00000000000..b366c89889e
--- /dev/null
+++ b/lib/gitlab/akismet_helper.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module AkismetHelper
+ def akismet_enabled?
+ current_application_settings.akismet_enabled
+ end
+
+ def akismet_client
+ @akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
+ Gitlab.config.gitlab.url)
+ end
+
+ def check_for_spam?(project, user)
+ akismet_enabled? && !project.team.member?(user)
+ end
+
+ def is_spam?(environment, user, text)
+ client = akismet_client
+ ip_address = environment['REMOTE_ADDR']
+ user_agent = environment['HTTP_USER_AGENT']
+
+ params = {
+ type: 'comment',
+ text: text,
+ created_at: DateTime.now,
+ author: user.name,
+ author_email: user.email,
+ referrer: environment['HTTP_REFERER'],
+ }
+
+ begin
+ is_spam, is_blatant = client.check(ip_address, user_agent, params)
+ is_spam || is_blatant
+ rescue => e
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index a6b2f14521c..8531c7e87e1 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -34,7 +34,8 @@ module Gitlab
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
- two_factor_grace_period: 48
+ two_factor_grace_period: 48,
+ akismet_enabled: false
)
end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index a7f925ce134..9429b3ff88d 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -21,13 +21,13 @@ module Gitlab
# ignore highlighting for "match" lines
next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline'
- rich_line = highlight_line(diff_line, i)
+ rich_line = highlight_line(diff_line) || diff_line.text
if line_inline_diffs = inline_diffs[i]
rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs)
end
- diff_line.text = rich_line.html_safe
+ diff_line.text = rich_line
diff_line
end
@@ -35,8 +35,8 @@ module Gitlab
private
- def highlight_line(diff_line, index)
- return html_escape(diff_line.text) unless diff_file && diff_file.diff_refs
+ def highlight_line(diff_line)
+ return unless diff_file && diff_file.diff_refs
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
@@ -49,11 +49,11 @@ module Gitlab
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
- rich_line ? line_prefix + rich_line : html_escape(diff_line.text)
+ "#{line_prefix}#{rich_line}".html_safe if rich_line
end
def inline_diffs
- @inline_diffs ||= InlineDiff.new(@raw_lines).inline_diffs
+ @inline_diffs ||= InlineDiff.for_lines(@raw_lines)
end
def old_lines
@@ -72,11 +72,6 @@ module Gitlab
[ref.project.repository, ref.id, path]
end
-
- def html_escape(str)
- replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
- str.gsub(/[&"'><]/, replacements)
- end
end
end
end
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index b8a61ad6115..789c14518b0 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -1,43 +1,58 @@
module Gitlab
module Diff
class InlineDiff
- attr_accessor :lines
+ attr_accessor :old_line, :new_line, :offset
- def initialize(lines)
- @lines = lines
- end
+ def self.for_lines(lines)
+ local_edit_indexes = self.find_local_edits(lines)
- def inline_diffs
inline_diffs = []
local_edit_indexes.each do |index|
old_index = index
new_index = index + 1
- old_line = @lines[old_index]
- new_line = @lines[new_index]
-
- # Skip inline diff if empty line was replaced with content
- next if old_line[1..-1] == ""
-
- # Add one, because this is based on the prefixless version
- lcp = longest_common_prefix(old_line[1..-1], new_line[1..-1]) + 1
- lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
+ old_line = lines[old_index]
+ new_line = lines[new_index]
- old_diff_range = lcp..(old_line.length - lcs - 1)
- new_diff_range = lcp..(new_line.length - lcs - 1)
+ old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
- inline_diffs[old_index] = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
- inline_diffs[new_index] = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
+ inline_diffs[old_index] = old_diffs
+ inline_diffs[new_index] = new_diffs
end
inline_diffs
end
+ def initialize(old_line, new_line, offset: 0)
+ @old_line = old_line[offset..-1]
+ @new_line = new_line[offset..-1]
+ @offset = offset
+ end
+
+ def inline_diffs
+ # Skip inline diff if empty line was replaced with content
+ return if old_line == ""
+
+ lcp = longest_common_prefix(old_line, new_line)
+ lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
+
+ lcp += offset
+ old_length = old_line.length + offset
+ new_length = new_line.length + offset
+
+ old_diff_range = lcp..(old_length - lcs - 1)
+ new_diff_range = lcp..(new_length - lcs - 1)
+
+ old_diffs = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
+ new_diffs = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
+
+ [old_diffs, new_diffs]
+ end
+
private
- # Find runs of single line edits
- def local_edit_indexes
- line_prefixes = @lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
+ def self.find_local_edits(lines)
+ line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
joined_line_prefixes = " #{line_prefixes.join} "
offset = 0
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index 1d7fa1bce06..dccb717e95d 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -5,10 +5,12 @@ module Gitlab
def initialize(raw_line, rich_line = raw_line)
@raw_line = raw_line
- @rich_line = rich_line
+ @rich_line = ERB::Util.html_escape(rich_line)
end
def mark(line_inline_diffs)
+ return rich_line unless line_inline_diffs
+
marker_ranges = []
line_inline_diffs.each do |inline_diff_range|
# Map the inline-diff range based on the raw line to character positions in the rich line
@@ -19,11 +21,15 @@ module Gitlab
offset = 0
# Mark each range
- marker_ranges.each do |range|
- offset = insert_around_range(rich_line, range, "<span class='idiff'>", "</span>", offset)
+ marker_ranges.each_with_index do |range, i|
+ class_names = ["idiff"]
+ class_names << "left" if i == 0
+ class_names << "right" if i == marker_ranges.length - 1
+
+ offset = insert_around_range(rich_line, range, "<span class='#{class_names.join(" ")}'>", "</span>", offset)
end
- rich_line
+ rich_line.html_safe
end
private
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 4ddb4fea977..cac76442321 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -8,6 +8,7 @@ module Gitlab
blob = repository.blob_at(ref, file_name)
return [] unless blob
+ blob.load_all_data!(repository)
highlight(file_name, blob.data).lines.map!(&:html_safe)
end
diff --git a/lib/gitlab/ip_check.rb b/lib/gitlab/ip_check.rb
deleted file mode 100644
index f2e9b50d225..00000000000
--- a/lib/gitlab/ip_check.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Gitlab
- class IpCheck
-
- def initialize(ip)
- @ip = ip
-
- application_settings = ApplicationSetting.current
- @ip_blocking_enabled = application_settings.ip_blocking_enabled
- @dnsbl_servers_list = application_settings.dnsbl_servers_list
- end
-
- def spam?
- @ip_blocking_enabled && blacklisted?
- end
-
- private
-
- def blacklisted?
- on_dns_blacklist?
- end
-
- def on_dns_blacklist?
- dnsbl_check = DNSXLCheck.new
- prepare_dnsbl_list(dnsbl_check)
- dnsbl_check.test(@ip)
- end
-
- def prepare_dnsbl_list(dnsbl_check)
- @dnsbl_servers_list.split(',').map(&:strip).reject(&:empty?).each do |domain|
- dnsbl_check.add_list(domain, 1)
- end
- end
- end
-end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 53ab2686b43..5c35c5b1450 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -44,19 +44,19 @@ module Gitlab
def file_name_regex
- @file_name_regex ||= /\A[a-zA-Z0-9_\-\.]*\z/.freeze
+ @file_name_regex ||= /\A[a-zA-Z0-9_\-\.\@]*\z/.freeze
end
def file_name_regex_message
- "can contain only letters, digits, '_', '-' and '.'. "
+ "can contain only letters, digits, '_', '-', '@' and '.'. "
end
def file_path_regex
- @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/]*\z/.freeze
+ @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/\@]*\z/.freeze
end
def file_path_regex_message
- "can contain only letters, digits, '_', '-' and '.'. Separate directories with a '/'. "
+ "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. "
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
new file mode 100644
index 00000000000..a23120a4176
--- /dev/null
+++ b/lib/gitlab/workhorse.rb
@@ -0,0 +1,21 @@
+require 'base64'
+require 'json'
+
+module Gitlab
+ class Workhorse
+ class << self
+ def send_git_blob(repository, blob)
+ params_hash = {
+ 'RepoPath' => repository.path_to_repo,
+ 'BlobId' => blob.id,
+ }
+ params = Base64.urlsafe_encode64(JSON.dump(params_hash))
+
+ [
+ 'Gitlab-Workhorse-Send-Data',
+ "git-blob:#{params}",
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index 1633891c8a0..9e90a99f15b 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -219,7 +219,7 @@ start_gitlab() {
echo "The Unicorn web server already running with pid $wpid, not restarting."
else
# Remove old socket if it exists
- rm -f "$socket_path"/gitlab.socket 2>/dev/null
+ rm -f "$rails_socket" 2>/dev/null
# Start the web server
RAILS_ENV=$RAILS_ENV bin/web start
fi
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
new file mode 100644
index 00000000000..b51b303a714
--- /dev/null
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Admin::SpamLogsController do
+ let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
+ let!(:first_spam) { create(:spam_log, user: user) }
+ let!(:second_spam) { create(:spam_log, user: user) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe '#index' do
+ it 'lists all spam logs' do
+ get :index
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe '#destroy' do
+ it 'removes only the spam log when removing log' do
+ expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1)
+ expect(User.find(user.id)).to be_truthy
+ expect(response.status).to eq(200)
+ end
+
+ it 'removes user and his spam logs when removing the user' do
+ delete :destroy, id: first_spam.id, remove_user: true
+
+ expect(flash[:notice]).to eq "User #{user.username} was successfully removed."
+ expect(response.status).to eq(302)
+ expect(SpamLog.count).to eq(0)
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+end
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
new file mode 100644
index 00000000000..d90e5d6bf26
--- /dev/null
+++ b/spec/factories/spam_logs.rb
@@ -0,0 +1,11 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :spam_log do
+ user
+ source_ip { FFaker::Internet.ip_v4_address }
+ noteable_type 'Issue'
+ title { FFaker::Lorem.sentence }
+ description { FFaker::Lorem.paragraph(5) }
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 9a01c89ae2a..ed97b6cb577 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -82,7 +82,26 @@ feature 'Project', feature: true do
it 'click project-settings and find leave project' do
find('#project-settings-button').click
- expect(page).to have_link('Leave Project')
+ expect(page).to have_link('Leave Project')
+ end
+ end
+
+ describe 'project title' do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ login_with(user)
+ project.team.add_user(user, Gitlab::Access::MASTER)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ it 'click toggle and show dropdown', js: true do
+ find('.js-projects-dropdown-toggle').click
+ wait_for_ajax
+ expect(page).to have_css('.select2-results li', count: 1)
end
end
diff --git a/spec/fixtures/logo_sample.svg b/spec/fixtures/logo_sample.svg
new file mode 100644
index 00000000000..883e7e6cf92
--- /dev/null
+++ b/spec/fixtures/logo_sample.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Slice 1</title>
+ <desc>Created with Sketch.</desc>
+ <script>alert('FAIL')</script>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
+ <g id="Page-1" sketch:type="MSShapeGroup">
+ <g id="Fill-1-+-Group-24">
+ <g id="Group-24">
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
index a326b651aad..a8b7907d4ba 100644
--- a/spec/fixtures/parallel_diff_result.yml
+++ b/spec/fixtures/parallel_diff_result.yml
@@ -55,7 +55,7 @@
:type: new
:number: 9
:text: |
- +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
+ +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
- :left:
:type:
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 955d2852cfd..14986a74c2e 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -104,8 +104,7 @@ describe DiffHelper do
end
end
- describe 'diff_line_content' do
-
+ describe '#diff_line_content' do
it 'should return non breaking space when line is empty' do
expect(diff_line_content(nil)).to eq(' &nbsp;')
end
@@ -116,9 +115,19 @@ describe DiffHelper do
expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
expect(diff_file.diff_lines.first.new_pos).to eq(6)
end
+ end
+
+ describe "#mark_inline_diffs" do
+ let(:old_line) { %{abc 'def'} }
+ let(:new_line) { %{abc "def"} }
+
+ it "returns strings with marked inline diffs" do
+ marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
- it 'should return safe HTML' do
- expect(diff_line_content(diff_file.diff_lines.first.text)).to be_html_safe
+ expect(marked_old_line).to eq("abc <span class='idiff left right'>&#39;def&#39;</span>")
+ expect(marked_old_line).to be_html_safe
+ expect(marked_new_line).to eq("abc <span class='idiff left right'>&quot;def&quot;</span>")
+ expect(marked_new_line).to be_html_safe
end
end
end
diff --git a/spec/javascripts/behaviors/autosize_spec.js.coffee b/spec/javascripts/behaviors/autosize_spec.js.coffee
new file mode 100644
index 00000000000..7fc1d19c35f
--- /dev/null
+++ b/spec/javascripts/behaviors/autosize_spec.js.coffee
@@ -0,0 +1,11 @@
+#= require behaviors/autosize
+
+describe 'Autosize behavior', ->
+ beforeEach ->
+ fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>')
+
+ it 'does not overwrite the resize property', ->
+ load()
+ expect($('textarea')).toHaveCss(resize: 'vertical')
+
+ load = -> $(document).trigger('page:load')
diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml
new file mode 100644
index 00000000000..4286d1be669
--- /dev/null
+++ b/spec/javascripts/fixtures/project_title.html.haml
@@ -0,0 +1,7 @@
+%h1.title
+ %a
+ GitLab Org
+ %a.project-item-select-holder.js-projects-dropdown-toggle{href: "/gitlab-org/gitlab-test"}
+ GitLab Test
+ %span.fa.fa-chevron-down.dropdown-toggle-caret
+ %input#project_path.project-item-select.js-projects-dropdown.ajax-project-select{type: "hidden", name: "project_path", "data-include-groups" => "false"}
diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json
new file mode 100644
index 00000000000..84e8d0ba1e4
--- /dev/null
+++ b/spec/javascripts/fixtures/projects.json
@@ -0,0 +1 @@
+[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/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,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/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,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/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,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}]
diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee
new file mode 100644
index 00000000000..47c7b7febe3
--- /dev/null
+++ b/spec/javascripts/project_title_spec.js.coffee
@@ -0,0 +1,46 @@
+#= require select2
+#= require api
+#= require project_select
+#= require project
+
+window.gon = {}
+window.gon.api_version = 'v3'
+
+describe 'Project Title', ->
+ fixture.preload('project_title.html')
+ fixture.preload('projects.json')
+
+ beforeEach ->
+ fixture.load('project_title.html')
+ @project = new Project()
+
+ spyOn(@project, 'changeProject').and.callFake (url) ->
+ window.current_project_url = url
+
+ describe 'project list', ->
+ beforeEach =>
+ @projects_data = fixture.load('projects.json')[0]
+
+ spyOn(jQuery, 'ajax').and.callFake (req) =>
+ expect(req.url).toBe('/api/v3/projects.json')
+ d = $.Deferred()
+ d.resolve @projects_data
+ d.promise()
+
+ it 'to show on toggle click', =>
+ $('.js-projects-dropdown-toggle').click()
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(true)
+ expect($('.ajax-project-dropdown li').length).toBe(@projects_data.length)
+
+ it 'hide dropdown', ->
+ $("#select2-drop-mask").click()
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
+
+ it 'change project when clicking item', ->
+ $('.js-projects-dropdown-toggle').click()
+ $('.ajax-project-dropdown li:nth-child(2)').trigger('mouseup')
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
+ expect(window.current_project_url).toBe('http://localhost:3000/h5bp/html5-boilerplate')
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 473534ba68a..63a32d9d455 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -21,7 +21,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
let(:reference) { commit.id }
# Let's test a variety of commit SHA sizes just to be paranoid
- [6, 8, 12, 18, 20, 32, 40].each do |size|
+ [7, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do
doc = reference_filter("See #{reference[0...size]}")
@@ -35,7 +35,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
doc = reference_filter("See #{commit.id}")
expect(doc.text).to eq "See #{commit.short_id}"
- doc = reference_filter("See #{commit.id[0...6]}")
+ doc = reference_filter("See #{commit.id[0...7]}")
expect(doc.text).to eq "See #{commit.short_id}"
end
diff --git a/spec/lib/dnsxl_check_spec.rb b/spec/lib/dnsxl_check_spec.rb
deleted file mode 100644
index a35a1be0c90..00000000000
--- a/spec/lib/dnsxl_check_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-require 'ostruct'
-
-describe 'DNSXLCheck', lib: true, no_db: true do
- let(:spam_ip) { '127.0.0.2' }
- let(:no_spam_ip) { '127.0.0.3' }
- let(:invalid_ip) { 'a.b.c.d' }
- let!(:dnsxl_check) { DNSXLCheck.create_from_list([OpenStruct.new({ domain: 'test', weight: 1 })]) }
-
- before(:context) do
- class DNSXLCheck::Resolver
- class << self
- alias_method :old_search, :search
- def search(query)
- return false if query.match(/always\.failing\.domain\z/)
- return true if query.match(/\A2\.0\.0\.127\./)
- return false if query.match(/\A3\.0\.0\.127\./)
- end
- end
- end
- end
-
- describe '#test' do
- before do
- dnsxl_check.threshold = 0.75
- dnsxl_check.add_list('always.failing.domain', 1)
- end
-
- context 'when threshold is used' do
- before { dnsxl_check.use_threshold= true }
-
- it { expect(dnsxl_check.test(spam_ip)).to be_falsey }
- end
-
- context 'when threshold is not used' do
- before { dnsxl_check.use_threshold= false }
-
- it { expect(dnsxl_check.test(spam_ip)).to be_truthy }
- end
- end
-
- describe '#test_with_threshold' do
- it { expect{ dnsxl_check.test_with_threshold(invalid_ip) }.to raise_error(ArgumentError) }
-
- it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_truthy }
- it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
- end
-
- describe '#test_strict' do
- before do
- dnsxl_check.threshold = 1
- dnsxl_check.add_list('always.failing.domain', 1)
- end
-
- it { expect{ dnsxl_check.test_strict(invalid_ip) }.to raise_error(ArgumentError) }
-
- it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_falsey }
- it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
- it { expect(dnsxl_check.test_strict(spam_ip)).to be_truthy }
- it { expect(dnsxl_check.test_strict(no_spam_ip)).to be_falsey }
- end
-
- describe '#threshold=' do
- it { expect{ dnsxl_check.threshold = 0 }.to raise_error(ArgumentError) }
- it { expect{ dnsxl_check.threshold = 1.1 }.to raise_error(ArgumentError) }
- it { expect{ dnsxl_check.threshold = 0.5 }.not_to raise_error }
- end
-end
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
new file mode 100644
index 00000000000..9858935180a
--- /dev/null
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::AkismetHelper, type: :helper do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
+ current_application_settings.akismet_enabled = true
+ current_application_settings.akismet_api_key = '12345'
+ end
+
+ describe '#check_for_spam?' do
+ it 'returns true for non-member' do
+ expect(helper.check_for_spam?(project, user)).to eq(true)
+ end
+
+ it 'returns false for member' do
+ project.team << [user, :guest]
+ expect(helper.check_for_spam?(project, user)).to eq(false)
+ end
+ end
+
+ describe '#is_spam?' do
+ it 'returns true for spam' do
+ environment = {
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'HTTP_USER_AGENT' => 'Test User Agent'
+ }
+
+ allow_any_instance_of(::Akismet::Client).to receive(:check).and_return([true, true])
+ expect(helper.is_spam?(environment, user, 'Is this spam?')).to eq(true)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index b84a57f357a..d19bf4ac84b 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -9,33 +9,69 @@ describe Gitlab::Diff::Highlight, lib: true do
let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
describe '#highlight' do
- let(:diff_lines) { Gitlab::Diff::Highlight.new(diff_file).highlight }
+ context "with a diff file" do
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file).highlight }
- it 'should return Gitlab::Diff::Line elements' do
- expect(diff_lines.first).to be_an_instance_of(Gitlab::Diff::Line)
- end
+ it 'should return Gitlab::Diff::Line elements' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+ end
- it 'should not modify "match" lines' do
- expect(diff_lines[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
- expect(diff_lines[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
- end
+ it 'should not modify "match" lines' do
+ expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+ expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+ end
- it 'should highlight unchanged lines' do
- code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
+ it 'highlights and marks unchanged lines' do
+ code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
- expect(diff_lines[2].text).to eq(code)
- end
+ expect(subject[2].text).to eq(code)
+ end
- it 'should highlight removed lines' do
- code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+ it 'highlights and marks removed lines' do
+ code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
- expect(diff_lines[4].text).to eq(code)
+ expect(subject[4].text).to eq(code)
+ end
+
+ it 'highlights and marks added lines' do
+ code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+
+ expect(subject[5].text).to eq(code)
+ end
end
- it 'should highlight added lines' do
- code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+ context "with diff lines" do
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines).highlight }
+
+ it 'should return Gitlab::Diff::Line elements' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+ end
+
+ it 'should not modify "match" lines' do
+ expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+ expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+ end
+
+ it 'marks unchanged lines' do
+ code = %Q{ def popen(cmd, path=nil)}
+
+ expect(subject[2].text).to eq(code)
+ expect(subject[2].text).not_to be_html_safe
+ end
+
+ it 'marks removed lines' do
+ code = %Q{- raise "System commands must be given as an array of strings"}
+
+ expect(subject[4].text).to eq(code)
+ expect(subject[4].text).not_to be_html_safe
+ end
+
+ it 'marks added lines' do
+ code = %Q{+ raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
- expect(diff_lines[5].text).to eq(code)
+ expect(subject[5].text).to eq(code)
+ expect(subject[5].text).to be_html_safe
+ end
end
end
end
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 6f3276a8b53..ea5c31011f0 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -2,14 +2,28 @@ require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker, lib: true do
describe '#inline_diffs' do
- let(:raw) { "abc 'def'" }
- let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>} }
- let(:inline_diffs) { [2..5] }
- let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
+ context "when the rich text is html safe" do
+ let(:raw) { "abc 'def'" }
+ let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
- it 'marks the inline diffs' do
- expect(subject).to eq(%{<span class="abc">ab<span class='idiff'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff'>&#39;d</span>ef&#39;</span>})
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&#39;d</span>ef&#39;</span>})
+ expect(subject).to be_html_safe
+ end
+ end
+
+ context "when the text text is not html safe" do
+ let(:raw) { "abc 'def'" }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{ab<span class='idiff left right'>c &#39;d</span>ef&#39;})
+ expect(subject).to be_html_safe
+ end
end
end
end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 056917df893..95a993d26cf 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Diff::InlineDiff, lib: true do
- describe '#inline_diffs' do
+ describe '.for_lines' do
let(:diff) do
<<eos
class Test
@@ -13,7 +13,7 @@ describe Gitlab::Diff::InlineDiff, lib: true do
eos
end
- let(:subject) { Gitlab::Diff::InlineDiff.new(diff.lines).inline_diffs }
+ let(:subject) { described_class.for_lines(diff.lines) }
it 'finds all inline diffs' do
expect(subject[0]).to be_nil
@@ -24,4 +24,17 @@ eos
expect(subject[5]).to be_nil
end
end
+
+ describe "#inline_diffs" do
+ let(:old_line) { "XXX def initialize(test = true)" }
+ let(:new_line) { "YYY def initialize(test = false)" }
+ let(:subject) { described_class.new(old_line, new_line, offset: 3).inline_diffs }
+
+ it "finds the inline diff" do
+ old_diffs, new_diffs = subject
+
+ expect(old_diffs).to eq([26..28])
+ expect(new_diffs).to eq([26..29])
+ end
+ end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index d67ee423b9b..c51b10bdc69 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do
it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
end
+
+ describe 'file name regex' do
+ it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
+ end
+
+ describe 'file path regex' do
+ it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
+ end
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 138b87a9a06..fd1513cab1b 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -36,7 +36,7 @@ describe SystemHook, models: true do
it "project_destroy hook" do
user = create(:user)
project = create(:empty_project, namespace: user.namespace)
- Projects::DestroyService.new(project, user, {}).execute
+ Projects::DestroyService.new(project, user, {}).pending_delete!
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /project_destroy/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
@@ -65,7 +65,7 @@ describe SystemHook, models: true do
project = create(:project)
project.team << [user, :master]
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_add_to_team/,
+ body: /user_add_to_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -76,7 +76,7 @@ describe SystemHook, models: true do
project.team << [user, :master]
project.project_members.destroy_all
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_remove_from_team/,
+ body: /user_remove_from_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 291e6200a5b..46f2f20b986 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -137,9 +137,10 @@ describe MergeRequest, models: true do
describe 'detection of issues to be closed' do
let(:issue0) { create :issue, project: subject.project }
let(:issue1) { create :issue, project: subject.project }
- let(:commit0) { double('commit0', closes_issues: [issue0]) }
- let(:commit1) { double('commit1', closes_issues: [issue0]) }
- let(:commit2) { double('commit2', closes_issues: [issue1]) }
+
+ let(:commit0) { double('commit0', safe_message: "Fixes #{issue0.to_reference}") }
+ let(:commit1) { double('commit1', safe_message: "Fixes #{issue0.to_reference}") }
+ let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
before do
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
@@ -149,7 +150,9 @@ describe MergeRequest, models: true do
allow(subject.project).to receive(:default_branch).
and_return(subject.target_branch)
- expect(subject.closes_issues).to eq([issue0, issue1].sort_by(&:id))
+ closed = subject.closes_issues
+
+ expect(closed).to include(issue0, issue1)
end
it 'only lists issues as to be closed if it targets the default branch' do
@@ -167,17 +170,6 @@ describe MergeRequest, models: true do
expect(subject.closes_issues).to include(issue2)
end
-
- context 'for a project with JIRA integration' do
- let(:issue0) { JiraIssue.new('JIRA-123', subject.project) }
- let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) }
-
- it 'returns sorted JiraIssues' do
- allow(subject.project).to receive_messages(default_branch: subject.target_branch)
-
- expect(subject.closes_issues).to eq([issue0, issue1])
- end
- end
end
describe "#work_in_progress?" do
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
new file mode 100644
index 00000000000..c4ec7625cb0
--- /dev/null
+++ b/spec/models/spam_log_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe SpamLog, models: true do
+ describe 'associations' do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:user) }
+ end
+
+ describe '#remove_user' do
+ it 'blocks the user' do
+ spam_log = build(:spam_log)
+
+ expect { spam_log.remove_user }.to change { spam_log.user.blocked? }.to(true)
+ end
+
+ it 'removes the user' do
+ spam_log = build(:spam_log)
+
+ expect { spam_log.remove_user }.to change { User.count }.by(-1)
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0bef68e2885..cee051f5732 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -91,6 +91,7 @@ describe User, models: true do
it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:identities).dependent(:destroy) }
it { is_expected.to have_one(:abuse_report) }
+ it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
end
describe 'validations' do
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index c1b03838aa9..ddc49495eda 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -189,6 +189,38 @@ describe WikiPage, models: true do
end
end
+ describe '#historical?' do
+ before do
+ create_page('Update', 'content')
+ @page = wiki.find_page('Update')
+ 3.times { |i| @page.update("content #{i}") }
+ end
+
+ after do
+ destroy_page('Update')
+ end
+
+ it 'returns true when requesting an old version' do
+ old_version = @page.versions.last.to_s
+ old_page = wiki.find_page('Update', old_version)
+
+ expect(old_page.historical?).to eq true
+ end
+
+ it 'returns false when requesting latest version' do
+ latest_version = @page.versions.first.to_s
+ latest_page = wiki.find_page('Update', latest_version)
+
+ expect(latest_page.historical?).to eq false
+ end
+
+ it 'returns false when version is nil' do
+ latest_page = wiki.find_page('Update', nil)
+
+ expect(latest_page.historical?).to eq false
+ end
+ end
+
private
def remove_temp_repo(path)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 5e65ad18c0e..571ea2dae4c 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -46,10 +46,10 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it "should add pagination headers" do
- get api("/issues?per_page=3", user)
+ it "should add pagination headers and keep query params" do
+ get api("/issues?state=closed&per_page=3", user)
expect(response.headers['Link']).to eq(
- '<http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="last"'
+ '<http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="last"' % [user.private_token, user.private_token]
)
end
@@ -241,6 +241,37 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/issues with spam filtering' do
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:check_for_spam?).and_return(true)
+ allow(endpoint).to receive(:is_spam?).and_return(true)
+ end
+ end
+
+ let(:params) do
+ {
+ title: 'new issue',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it "should not create a new project issue" do
+ expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('new issue')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ expect(spam_logs[0].project_id).to eq(project.id)
+ end
+ end
+
describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 22937226fce..538f44e4f3f 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -321,12 +321,12 @@ describe Projects::HooksController, 'routing' do
end
end
-# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/}
+# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
describe Projects::CommitController, 'routing' do
it 'to #show' do
- expect(get('/gitlab/gitlabhq/commit/4246fb')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb')
- expect(get('/gitlab/gitlabhq/commit/4246fb.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'diff')
- expect(get('/gitlab/gitlabhq/commit/4246fb.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'patch')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch')
expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index a797a2fe4aa..ff23f13e1cb 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -14,9 +14,7 @@ describe Notes::CreateService, services: true do
noteable_type: 'Issue',
noteable_id: issue.id
}
-
- expect(project).to receive(:execute_hooks)
- expect(project).to receive(:execute_services)
+
@note = Notes::CreateService.new(project, user, opts).execute
end
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
new file mode 100644
index 00000000000..1a3f339bd64
--- /dev/null
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Notes::PostProcessService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+
+ describe :execute do
+ before do
+ project.team << [user, :master]
+ note_opts = {
+ note: 'Awesome comment',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+
+ @note = Notes::CreateService.new(project, user, note_opts).execute
+ end
+
+ it do
+ expect(project).to receive(:execute_hooks)
+ expect(project).to receive(:execute_services)
+ Notes::PostProcessService.new(@note).execute
+ end
+ end
+end
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index d6e03cbef3d..ef5ea7d626e 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -67,9 +67,9 @@ module FilterSpecHelper
if reference =~ /\A(.+)?.\d+\z/
# Integer-based reference with optional project prefix
reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
- elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
+ elsif reference =~ /\A(.+@)?(\h{7,40}\z)/
# SHA-based reference with optional prefix
- reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
+ reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
else
reference.gsub(/\w+\z/) { |v| v.reverse }
end
diff --git a/vendor/assets/javascripts/jquery.ba-resize.js b/vendor/assets/javascripts/jquery.ba-resize.js
new file mode 100644
index 00000000000..1f41d379153
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.ba-resize.js
@@ -0,0 +1,246 @@
+/*!
+ * jQuery resize event - v1.1 - 3/14/2010
+ * http://benalman.com/projects/jquery-resize-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery resize event
+//
+// *Version: 1.1, Last updated: 3/14/2010*
+//
+// Project Home - http://benalman.com/projects/jquery-resize-plugin/
+// GitHub - http://github.com/cowboy/jquery-resize/
+// Source - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js
+// (Minified) - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb)
+//
+// About: License
+//
+// Copyright (c) 2010 "Cowboy" Ben Alman,
+// Dual licensed under the MIT and GPL licenses.
+// http://benalman.com/about/license/
+//
+// About: Examples
+//
+// This working example, complete with fully commented code, illustrates a few
+// ways in which this plugin can be used.
+//
+// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/
+//
+// About: Support and Testing
+//
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+//
+// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1.
+// Unit Tests - http://benalman.com/code/projects/jquery-resize/unit/
+//
+// About: Release History
+//
+// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger
+// immediately after bind in some circumstances. Also changed $.fn.data
+// to $.data to improve performance.
+// 1.0 - (2/10/2010) Initial release
+
+(function($,window,undefined){
+ '$:nomunge'; // Used by YUI compressor.
+
+ // A jQuery object containing all non-window elements to which the resize
+ // event is bound.
+ var elems = $([]),
+
+ // Extend $.resize if it already exists, otherwise create it.
+ jq_resize = $.resize = $.extend( $.resize, {} ),
+
+ timeout_id,
+
+ // Reused strings.
+ str_setTimeout = 'setTimeout',
+ str_resize = 'resize',
+ str_data = str_resize + '-special-event',
+ str_delay = 'delay',
+ str_throttle = 'throttleWindow';
+
+ // Property: jQuery.resize.delay
+ //
+ // The numeric interval (in milliseconds) at which the resize event polling
+ // loop executes. Defaults to 250.
+
+ jq_resize[ str_delay ] = 250;
+
+ // Property: jQuery.resize.throttleWindow
+ //
+ // Throttle the native window object resize event to fire no more than once
+ // every <jQuery.resize.delay> milliseconds. Defaults to true.
+ //
+ // Because the window object has its own resize event, it doesn't need to be
+ // provided by this plugin, and its execution can be left entirely up to the
+ // browser. However, since certain browsers fire the resize event continuously
+ // while others do not, enabling this will throttle the window resize event,
+ // making event behavior consistent across all elements in all browsers.
+ //
+ // While setting this property to false will disable window object resize
+ // event throttling, please note that this property must be changed before any
+ // window object resize event callbacks are bound.
+
+ jq_resize[ str_throttle ] = true;
+
+ // Event: resize event
+ //
+ // Fired when an element's width or height changes. Because browsers only
+ // provide this event for the window element, for other elements a polling
+ // loop is initialized, running every <jQuery.resize.delay> milliseconds
+ // to see if elements' dimensions have changed. You may bind with either
+ // .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ).
+ //
+ // Usage:
+ //
+ // > jQuery('selector').bind( 'resize', function(e) {
+ // > // element's width or height has changed!
+ // > ...
+ // > });
+ //
+ // Additional Notes:
+ //
+ // * The polling loop is not created until at least one callback is actually
+ // bound to the 'resize' event, and this single polling loop is shared
+ // across all elements.
+ //
+ // Double firing issue in jQuery 1.3.2:
+ //
+ // While this plugin works in jQuery 1.3.2, if an element's event callbacks
+ // are manually triggered via .trigger( 'resize' ) or .resize() those
+ // callbacks may double-fire, due to limitations in the jQuery 1.3.2 special
+ // events system. This is not an issue when using jQuery 1.4+.
+ //
+ // > // While this works in jQuery 1.4+
+ // > $(elem).css({ width: new_w, height: new_h }).resize();
+ // >
+ // > // In jQuery 1.3.2, you need to do this:
+ // > var elem = $(elem);
+ // > elem.css({ width: new_w, height: new_h });
+ // > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } );
+ // > elem.resize();
+
+ $.event.special[ str_resize ] = {
+
+ // Called only when the first 'resize' event callback is bound per element.
+ setup: function() {
+ // Since window has its own native 'resize' event, return false so that
+ // jQuery will bind the event using DOM methods. Since only 'window'
+ // objects have a .setTimeout method, this should be a sufficient test.
+ // Unless, of course, we're throttling the 'resize' event for window.
+ if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
+
+ var elem = $(this);
+
+ // Add this element to the list of internal elements to monitor.
+ elems = elems.add( elem );
+
+ // Initialize data store on the element.
+ $.data( this, str_data, { w: elem.width(), h: elem.height() } );
+
+ // If this is the first element added, start the polling loop.
+ if ( elems.length === 1 ) {
+ loopy();
+ }
+ },
+
+ // Called only when the last 'resize' event callback is unbound per element.
+ teardown: function() {
+ // Since window has its own native 'resize' event, return false so that
+ // jQuery will unbind the event using DOM methods. Since only 'window'
+ // objects have a .setTimeout method, this should be a sufficient test.
+ // Unless, of course, we're throttling the 'resize' event for window.
+ if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
+
+ var elem = $(this);
+
+ // Remove this element from the list of internal elements to monitor.
+ elems = elems.not( elem );
+
+ // Remove any data stored on the element.
+ elem.removeData( str_data );
+
+ // If this is the last element removed, stop the polling loop.
+ if ( !elems.length ) {
+ clearTimeout( timeout_id );
+ }
+ },
+
+ // Called every time a 'resize' event callback is bound per element (new in
+ // jQuery 1.4).
+ add: function( handleObj ) {
+ // Since window has its own native 'resize' event, return false so that
+ // jQuery doesn't modify the event object. Unless, of course, we're
+ // throttling the 'resize' event for window.
+ if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
+
+ var old_handler;
+
+ // The new_handler function is executed every time the event is triggered.
+ // This is used to update the internal element data store with the width
+ // and height when the event is triggered manually, to avoid double-firing
+ // of the event callback. See the "Double firing issue in jQuery 1.3.2"
+ // comments above for more information.
+
+ function new_handler( e, w, h ) {
+ var elem = $(this),
+ data = $.data( this, str_data );
+
+ // If called from the polling loop, w and h will be passed in as
+ // arguments. If called manually, via .trigger( 'resize' ) or .resize(),
+ // those values will need to be computed.
+ data.w = w !== undefined ? w : elem.width();
+ data.h = h !== undefined ? h : elem.height();
+
+ old_handler.apply( this, arguments );
+ };
+
+ // This may seem a little complicated, but it normalizes the special event
+ // .add method between jQuery 1.4/1.4.1 and 1.4.2+
+ if ( $.isFunction( handleObj ) ) {
+ // 1.4, 1.4.1
+ old_handler = handleObj;
+ return new_handler;
+ } else {
+ // 1.4.2+
+ old_handler = handleObj.handler;
+ handleObj.handler = new_handler;
+ }
+ }
+
+ };
+
+ function loopy() {
+
+ // Start the polling loop, asynchronously.
+ timeout_id = window[ str_setTimeout ](function(){
+
+ // Iterate over all elements to which the 'resize' event is bound.
+ elems.each(function(){
+ var elem = $(this),
+ width = elem.width(),
+ height = elem.height(),
+ data = $.data( this, str_data );
+
+ // If element size has changed since the last time, update the element
+ // data store and trigger the 'resize' event.
+ if ( width !== data.w || height !== data.h ) {
+ elem.trigger( str_resize, [ data.w = width, data.h = height ] );
+ }
+
+ });
+
+ // Loop.
+ loopy();
+
+ }, jq_resize[ str_delay ] );
+
+ };
+
+})(jQuery,this);