summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml5
-rw-r--r--.scss-lint.yml2
-rw-r--r--CHANGELOG27
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock23
-rw-r--r--PROCESS.md22
-rw-r--r--app/assets/javascripts/application.js.coffee1
-rw-r--r--app/assets/javascripts/awards_handler.coffee19
-rw-r--r--app/assets/javascripts/commits.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee7
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee4
-rw-r--r--app/assets/javascripts/labels_select.js.coffee72
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee12
-rw-r--r--app/assets/javascripts/raven_config.js.coffee44
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee62
-rw-r--r--app/assets/javascripts/search.js.coffee75
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee41
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee20
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee1
-rw-r--r--app/assets/javascripts/todos.js.coffee3
-rw-r--r--app/assets/stylesheets/framework/buttons.scss4
-rw-r--r--app/assets/stylesheets/framework/calendar.scss4
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/files.scss11
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss3
-rw-r--r--app/assets/stylesheets/framework/timeline.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss5
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss2
-rw-r--r--app/assets/stylesheets/highlight/white.scss18
-rw-r--r--app/assets/stylesheets/pages/confirmation.scss18
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss1
-rw-r--r--app/assets/stylesheets/pages/graph.scss3
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss7
-rw-r--r--app/assets/stylesheets/pages/search.scss89
-rw-r--r--app/controllers/concerns/filter_projects.rb2
-rw-r--r--app/controllers/confirmations_controller.rb9
-rw-r--r--app/controllers/projects/application_controller.rb3
-rw-r--r--app/controllers/projects/commit_controller.rb15
-rw-r--r--app/controllers/projects/issues_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb4
-rw-r--r--app/controllers/search_controller.rb6
-rw-r--r--app/finders/issuable_finder.rb7
-rw-r--r--app/helpers/ci_status_helper.rb15
-rw-r--r--app/helpers/diff_helper.rb8
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/page_layout_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb24
-rw-r--r--app/helpers/search_helper.rb10
-rw-r--r--app/helpers/tab_helper.rb8
-rw-r--r--app/models/ci/build.rb22
-rw-r--r--app/models/ci/commit.rb142
-rw-r--r--app/models/commit.rb7
-rw-r--r--app/models/commit_status.rb56
-rw-r--r--app/models/concerns/issuable.rb9
-rw-r--r--app/models/concerns/statuseable.rb81
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/project.rb14
-rw-r--r--app/models/repository.rb10
-rw-r--r--app/services/ci/create_builds_service.rb27
-rw-r--r--app/services/ci/create_trigger_request_service.rb4
-rw-r--r--app/services/ci/image_for_build_service.rb11
-rw-r--r--app/services/create_commit_builds_service.rb10
-rw-r--r--app/services/issuable_base_service.rb28
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/views/admin/users/_form.html.haml6
-rw-r--r--app/views/devise/confirmations/almost_there.haml10
-rw-r--r--app/views/doorkeeper/applications/index.html.haml1
-rw-r--r--app/views/events/_event.html.haml7
-rw-r--r--app/views/help/_shortcuts.html.haml14
-rw-r--r--app/views/layouts/_page.html.haml4
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise_empty.html.haml17
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml3
-rw-r--r--app/views/layouts/nav/_profile.html.haml14
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/layouts/profile.html.haml3
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/profiles/audit_log.html.haml1
-rw-r--r--app/views/profiles/emails/index.html.haml1
-rw-r--r--app/views/profiles/keys/index.html.haml1
-rw-r--r--app/views/profiles/notifications/show.html.haml1
-rw-r--r--app/views/profiles/passwords/edit.html.haml1
-rw-r--r--app/views/profiles/preferences/show.html.haml1
-rw-r--r--app/views/projects/_builds_settings.html.haml3
-rw-r--r--app/views/projects/_last_commit.html.haml9
-rw-r--r--app/views/projects/blob/diff.html.haml8
-rw-r--r--app/views/projects/builds/index.html.haml2
-rw-r--r--app/views/projects/builds/show.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml13
-rw-r--r--app/views/projects/commit/_builds.html.haml69
-rw-r--r--app/views/projects/commit/_ci_commit.html.haml69
-rw-r--r--app/views/projects/commit/_commit_box.html.haml8
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml7
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml13
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/issues/update.js.haml0
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_builds.html.haml3
-rw-r--r--app/views/projects/merge_requests/update.js.haml0
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml2
-rw-r--r--app/views/search/_category.html.haml105
-rw-r--r--app/views/search/_filter.html.haml78
-rw-r--r--app/views/search/_form.html.haml19
-rw-r--r--app/views/search/_results.html.haml15
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml36
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml17
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml20
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml25
-rw-r--r--app/views/shared/projects/_dropdown.html.haml11
-rw-r--r--app/views/shared/projects/_project.html.haml7
-rw-r--r--app/views/votes/_votes_block.html.haml4
-rwxr-xr-xbin/rails5
-rwxr-xr-xbin/rake5
-rwxr-xr-xbin/rspec5
-rwxr-xr-xbin/spinach5
-rwxr-xr-xbin/spring12
-rwxr-xr-xbin/teaspoon8
-rw-r--r--config/initializers/metrics.rb4
-rw-r--r--config/routes.rb1
-rw-r--r--db/fixtures/development/14_builds.rb2
-rw-r--r--db/migrate/20160226114608_add_trigram_indexes_for_searching.rb11
-rw-r--r--db/migrate/20160412173416_add_fields_to_ci_commit.rb8
-rw-r--r--db/migrate/20160412173417_update_ci_commit.rb35
-rw-r--r--db/migrate/20160412173418_add_ci_commit_indexes.rb19
-rw-r--r--db/migrate/20160421130527_disable_repository_checks.rb11
-rw-r--r--db/schema.rb11
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/high_availability/README.md35
-rw-r--r--doc/administration/high_availability/database.md116
-rw-r--r--doc/administration/high_availability/gitlab.md131
-rw-r--r--doc/administration/high_availability/load_balancer.md63
-rw-r--r--doc/administration/high_availability/nfs.md116
-rw-r--r--doc/administration/high_availability/redis.md62
-rw-r--r--doc/administration/repository_checks.md3
-rw-r--r--doc/administration/troubleshooting/sidekiq.md162
-rw-r--r--doc/ci/docker/using_docker_images.md4
-rw-r--r--doc/ci/examples/php.md2
-rw-r--r--doc/downgrade_ee_to_ce/README.md82
-rw-r--r--doc/monitoring/performance/grafana_configuration.md75
-rw-r--r--doc/update/8.6-to-8.7.md4
-rw-r--r--doc/update/README.md93
-rw-r--r--doc/workflow/cherry_pick_changes.md3
-rw-r--r--doc/workflow/importing/import_projects_from_github.md14
-rw-r--r--doc/workflow/shortcuts.pngbin25005 -> 90936 bytes
-rw-r--r--features/search.feature5
-rw-r--r--features/steps/profile/active_tab.rb4
-rw-r--r--features/steps/project/merge_requests.rb2
-rw-r--r--features/steps/search.rb1
-rw-r--r--features/steps/shared/builds.rb6
-rw-r--r--features/steps/shared/project.rb2
-rw-r--r--lib/api/commit_statuses.rb21
-rw-r--r--lib/api/project_hooks.rb4
-rw-r--r--lib/award_emoji.rb4
-rw-r--r--lib/banzai/filter/external_link_filter.rb5
-rw-r--r--lib/banzai/filter/label_reference_filter.rb36
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb26
-rw-r--r--lib/ci/status.rb19
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb4
-rw-r--r--lib/support/nginx/gitlab3
-rw-r--r--lib/support/nginx/gitlab-ssl3
-rw-r--r--public/503.html54
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb8
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb16
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb27
-rw-r--r--spec/features/issues/filter_by_labels_spec.rb51
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb79
-rw-r--r--spec/features/issues_spec.rb34
-rw-r--r--spec/features/markdown_spec.rb7
-rw-r--r--spec/features/project/shortcuts_spec.rb21
-rw-r--r--spec/features/projects/commit/builds_spec.rb27
-rw-r--r--spec/features/projects/commits/cherry_pick_spec.rb (renamed from spec/features/project/commits/cherry_pick_spec.rb)0
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb20
-rw-r--r--spec/features/security/project/internal_access_spec.rb10
-rw-r--r--spec/features/security/project/private_access_spec.rb6
-rw-r--r--spec/features/security/project/public_access_spec.rb12
-rw-r--r--spec/features/signup_spec.rb4
-rw-r--r--spec/features/todos/todos_spec.rb38
-rw-r--r--spec/helpers/ci_status_helper_spec.rb6
-rw-r--r--spec/helpers/diff_helper_spec.rb20
-rw-r--r--spec/helpers/projects_helper_spec.rb26
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb42
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb159
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb6
-rw-r--r--spec/models/ci/commit_spec.rb213
-rw-r--r--spec/models/commit_spec.rb8
-rw-r--r--spec/models/commit_status_spec.rb64
-rw-r--r--spec/models/concerns/issuable_spec.rb30
-rw-r--r--spec/models/concerns/statuseable_spec.rb (renamed from spec/lib/ci/status_spec.rb)15
-rw-r--r--spec/models/event_spec.rb65
-rw-r--r--spec/models/merge_request_spec.rb4
-rw-r--r--spec/models/project_spec.rb24
-rw-r--r--spec/models/repository_spec.rb46
-rw-r--r--spec/requests/api/builds_spec.rb2
-rw-r--r--spec/requests/api/commit_status_spec.rb19
-rw-r--r--spec/requests/api/commits_spec.rb6
-rw-r--r--spec/requests/api/project_hooks_spec.rb14
-rw-r--r--spec/requests/ci/api/builds_spec.rb20
-rw-r--r--spec/services/ci/create_builds_service_spec.rb4
-rw-r--r--spec/services/ci/image_for_build_service_spec.rb2
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb63
-rw-r--r--spec/services/issues/update_service_spec.rb11
-rw-r--r--spec/services/merge_requests/update_service_spec.rb11
-rw-r--r--vendor/assets/javascripts/raven.js2435
217 files changed, 3120 insertions, 3716 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1dc49ca336d..85730e1b687 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -115,6 +115,11 @@ bundler:audit:
script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
+db-migrate-reset:
+ stage: test
+ script:
+ - RAILS_ENV=test bundle exec rake db:migrate:reset
+
# Ruby 2.2 jobs
spec:feature:ruby22:
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 835a4a88c44..9bfc18b9698 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -65,7 +65,7 @@ linters:
# Reports when you have an empty rule set.
EmptyRule:
- enabled: false
+ enabled: true
# Reports when you have an @extend directive.
ExtendDirective:
diff --git a/CHANGELOG b/CHANGELOG
index 433799eee2f..558897ad892 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,24 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased)
-
-v 8.7.0 (unreleased)
+ - Remove future dates from contribution calendar graph.
+ - Fix error when visiting commit builds page before build was updated
+ - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
+ - Updated search UI
+
+v 8.7.1 (unreleased)
+ - Throttle the update of `project.last_activity_at` to 1 minute. !3848
+ - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849
+ - Fix license detection to detect all license files, not only known licenses. !3878
+ - Use the `can?` helper instead of `current_user.can?`. !3882
+ - Prevent users from deleting Webhooks via API they do not own
+ - Fix Error 500 due to stale cache when projects are renamed or transferred
+
+v 8.7.0
+ - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented
+ - Fix vulnerability that made it possible to gain access to private labels and milestones
- The number of InfluxDB points stored per UDP packet can now be configured
+ - Fix error when cross-project label reference used with non-existent project
- Transactions for /internal/allowed now have an "action" tag set
- Method instrumentation now uses Module#prepend instead of aliasing methods
- Repository.clean_old_archives is now instrumented
@@ -50,8 +65,9 @@ v 8.7.0 (unreleased)
- Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
+ - Display project members page to all members
- Handle nil descriptions in Slack issue messages (Stan Hu)
- - Add automated repository integrity checks
+ - Add automated repository integrity checks (OFF by default)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
- API: Ability to star and unstar a project (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
@@ -68,6 +84,7 @@ v 8.7.0 (unreleased)
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765
+ - Make Ci::Commit to group only similar builds and make it stateful (ref, tag)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
@@ -77,6 +94,7 @@ v 8.7.0 (unreleased)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications
+ - Update email confirmation interface
- API: Expose user location (Robert Schilling)
- API: Do not leak group existence via return code (Robert Schilling)
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
@@ -87,10 +105,10 @@ v 8.7.0 (unreleased)
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
- - Show JavaScript errors in sentry
- Diff design updates (colors, button styles, etc)
- Copying and pasting a diff no longer pastes the line numbers or +/-
- Add null check to formData when updating profile content to fix Firefox bug
+ - Disable spellcheck and autocorrect for username field in admin page
- Delete tags using Rugged for performance reasons (Robert Schilling)
- Add Slack notifications when Wiki is edited (Sebastian Klier)
- Diffs load at the correct point when linking from from number
@@ -104,6 +122,7 @@ v 8.7.0 (unreleased)
- Updated print style for issues
- Use GitHub Issue/PR number as iid to keep references
- Import GitHub labels
+ - Add option to filter by "Owned projects" on dashboard page
- Import GitHub milestones
- Fix emoji catgories in the emoji picker
- Execute system web hooks on push to the project
diff --git a/Gemfile b/Gemfile
index 67cc3f34b8c..6e15da8dc6a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -178,7 +178,7 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'd3_rails', '~> 3.5.0'
#cal-heatmap
-gem 'cal-heatmap-rails', '~> 3.5.0'
+gem 'cal-heatmap-rails', '~> 3.6.0'
# underscore-rails
gem "underscore-rails", "~> 1.8.0"
@@ -217,7 +217,7 @@ gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails', '~> 4.0.0'
+gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index b00d7b35c84..679c52eff25 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -103,7 +103,7 @@ GEM
bundler (~> 1.2)
thor (~> 0.18)
byebug (8.2.1)
- cal-heatmap-rails (3.5.1)
+ cal-heatmap-rails (3.6.0)
capybara (2.6.2)
addressable
mime-types (>= 1.16)
@@ -134,7 +134,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
- concurrent-ruby (1.0.0)
+ concurrent-ruby (1.0.1)
connection_pool (2.2.0)
coveralls (0.8.13)
json (~> 1.8)
@@ -346,10 +346,10 @@ GEM
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
- gitlab-grit (2.7.3)
+ gitlab-grit (2.8.1)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
- mime-types (~> 1.15)
+ mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
@@ -431,8 +431,8 @@ GEM
json
ipaddress (0.8.2)
jquery-atwho-rails (1.3.2)
- jquery-rails (4.0.5)
- rails-dom-testing (~> 1.0)
+ jquery-rails (4.1.1)
+ rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
@@ -465,7 +465,7 @@ GEM
mime-types (>= 1.16, < 4)
mail_room (0.6.1)
method_source (0.8.2)
- mime-types (1.25.1)
+ mime-types (2.99.1)
mimemagic (0.3.0)
mini_portile2 (2.0.0)
minitest (5.7.0)
@@ -629,7 +629,7 @@ GEM
recaptcha (1.0.2)
json
redcarpet (3.3.3)
- redis (3.2.2)
+ redis (3.3.0)
redis-actionpack (4.0.1)
actionpack (~> 4)
redis-rack (~> 1.5.0)
@@ -736,10 +736,9 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
- sidekiq (4.0.1)
+ sidekiq (4.1.1)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
- json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
sidekiq-cron (0.4.0)
redis-namespace (>= 1.5.2)
@@ -905,7 +904,7 @@ DEPENDENCIES
bullet
bundler-audit
byebug
- cal-heatmap-rails (~> 3.5.0)
+ cal-heatmap-rails (~> 3.6.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
@@ -953,7 +952,7 @@ DEPENDENCIES
httparty (~> 0.13.3)
influxdb (~> 0.2)
jquery-atwho-rails (~> 1.3.2)
- jquery-rails (~> 4.0.0)
+ jquery-rails (~> 4.1.0)
jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 5.0.0)
diff --git a/PROCESS.md b/PROCESS.md
index cad45d23df9..fe3a963110d 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -59,7 +59,7 @@ core team members will mention this person.
Workflow labels are purposely not very detailed since that would be hard to keep
updated as you would need to re-evaluate them after every comment. We optionally
-use functional labels on demand when want to group related issues to get an
+use functional labels on demand when we want to group related issues to get an
overview (for example all issues related to RVM, to tackle them in one go) and
to add details to the issue.
@@ -73,6 +73,7 @@ in support or comment for further detail. Do not use `feature request`.
- ~bug is an issue reporting undesirable or incorrect behavior.
- ~customer is an issue reported by enterprise subscribers. This label should
be accompanied by *bug* or *feature proposal* labels.
+
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
## Functional labels
@@ -105,6 +106,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide].
+## Feature Freeze
+
+5 working days before the 22nd the stable branches for the upcoming release will
+be frozen for major changes. Merge requests may still be merged into master
+during this period. By freezing the stable branches prior to a release there's
+no need to worry about last minute merge requests potentially breaking a lot of
+things.
+
+What is considered to be a major change is determined on a case by case basis as
+this definition depends very much on the context of changes. For example, a 5
+line change might have a big impact on the entire application. Ultimately the
+decision will be made by those reviewing a merge request and the release
+manager.
+
+During the feature freeze all merge requests that are meant to go into the next
+release should have the correct milestone assigned _and_ have the label
+~"Pick into Stable" set. Merge requests without a milestone and this label will
+not be merged into any stable branches.
+
## Copy & paste responses
### Improperly formatted issue
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 642e7429acf..5bac8eef1cb 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -55,7 +55,6 @@
#= require_tree .
#= require fuzzaldrin-plus
#= require cropper
-#= require raven
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index af4462ece38..fcba9818726 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,5 +1,5 @@
class @AwardsHandler
- constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
+ constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @unicodes) ->
$(".js-add-award").on "click", (event) =>
event.stopPropagation()
event.preventDefault()
@@ -31,6 +31,8 @@ class @AwardsHandler
awards_handler.addAward emoji
+ $(this).trigger 'blur'
+
didUserClickEmoji: (that, emoji) ->
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
$(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
@@ -55,7 +57,6 @@ class @AwardsHandler
, 200
addAward: (emoji) ->
- emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
@@ -64,7 +65,6 @@ class @AwardsHandler
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
- emoji = @normilizeEmojiName(emoji)
if @exist(emoji)
if @isActive(emoji)
@decrementCounter(emoji)
@@ -146,15 +146,7 @@ class @AwardsHandler
$('.award-control').tooltip()
resolveNameToCssClass: (emoji) ->
- emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
-
- if emoji_icon.length > 0
- unicodeName = emoji_icon.data("unicode-name")
- else
- # Find by alias
- unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name")
-
- "emoji-#{unicodeName}"
+ "emoji-#{@unicodes[emoji]}"
postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: {
@@ -173,9 +165,6 @@ class @AwardsHandler
scrollTop: $('.awards').offset().top - 80
}, 200)
- normilizeEmojiName: (emoji) ->
- @aliases[emoji] || emoji
-
addEmojiToFrequentlyUsedList: (emoji) ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
frequently_used_emojis.push(emoji)
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index ffd3627b1b0..0acb4c1955e 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,7 +1,7 @@
class @CommitsList
@timer = null
- @init: (ref, limit) ->
+ @init: (limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) ->
if event.target.nodeName != "A"
location.href = $(this).attr("url")
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 2fdb7562515..f91aa3c5ad7 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -108,6 +108,8 @@ class Dispatcher
new BuildArtifacts()
when 'projects:group_links:index'
new GroupsSelect()
+ when 'search:show'
+ new Search()
switch path.first()
when 'admin'
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index e5204f9dee9..29466e9f2ed 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -221,6 +221,9 @@ class GitLabDropdown
menu.toggleClass PAGE_TWO_CLASS
+ # Focus first visible input on active page
+ @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
+
parseData: (data) ->
@renderedData = data
@@ -240,7 +243,8 @@ class GitLabDropdown
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
- if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
+
+ if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
e.stopPropagation()
return false
else
@@ -375,7 +379,6 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 3a439b94c59..3c491ebfc4c 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -10,8 +10,8 @@ class @IssuableContext
$(this).submit()
$(document)
- .off 'click', '.dropdown-content a'
- .on 'click', '.dropdown-content a', (e) ->
+ .off 'click', '.issuable-sidebar .dropdown-content a'
+ .on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
e.preventDefault()
$(document)
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 021ade73d44..85517b18c5a 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -19,23 +19,19 @@ class @LabelsSelect
$form = $dropdown.closest('form')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
- $loading = $block.find('.block-loading').fadeOut()
-
- if newLabelField.length
- $newLabelCreateButton = $('.js-new-label-btn')
- $colorPreview = $('.js-dropdown-label-color-preview')
- $newLabelError = $dropdown.parent().find('.js-label-error')
- $newLabelError.hide()
+ $newLabelError = $('.js-label-error')
+ $colorPreview = $('.js-dropdown-label-color-preview')
+ $newLabelCreateButton = $('.js-new-label-btn')
- # Suggested colors in the dropdown to chose from pre-chosen colors
- $('.suggest-colors-dropdown a').on 'click', (e) ->
+ $newLabelError.hide()
+ $loading = $block.find('.block-loading').fadeOut()
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= _.escape(label.title) %>">
- <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>;">
+ <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;">
<%= _.escape(label.title) %>
</span>
</a>
@@ -43,7 +39,9 @@ class @LabelsSelect
)
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
- if newLabelField.length and $dropdown.hasClass 'js-extra-options'
+ if newLabelField.length
+
+ # Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
@@ -82,26 +80,25 @@ class @LabelsSelect
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
+ saveLabel = ->
+ # Create new label with API
+ Api.newLabel projectId, {
+ name: newLabelField.val()
+ color: newColorField.val()
+ }, (label) ->
+ $newLabelCreateButton.enable()
+
+ if label.message?
+ $newLabelError
+ .text label.message
+ .show()
+ else
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+
newLabelField.on 'keyup change', enableLabelCreateButton
newColorField.on 'keyup change', enableLabelCreateButton
@@ -112,24 +109,7 @@ class @LabelsSelect
.on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
-
- if newLabelField.val() isnt '' and newColorField.val() isnt ''
- $newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+ saveLabel()
saveLabelData = ->
selected = $dropdown
@@ -243,7 +223,7 @@ class @LabelsSelect
fieldName: $dropdown.data('field-name')
id: (label) ->
if $dropdown.hasClass("js-filter-submit") and not label.isAny?
- label.title
+ _.escape label.title
else
label.id
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index e8613cab72b..372732d0aac 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -87,8 +87,8 @@ class @MergeRequestTabs
if window.location.hash
navBarHeight = $('.navbar-gitlab').outerHeight()
- $el = $("#{container} #{window.location.hash}")
- $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
+ $el = $("#{container} #{window.location.hash}:not(.match)")
+ $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
@@ -176,12 +176,12 @@ class @MergeRequestTabs
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
- $diffLine = $(locationHash)
+ $diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
- if $diffLine.is ':not(tr)'
- $diffLine = $("td#{locationHash}, td#{hashClassString}")
+ if not $diffLine.is 'tr'
+ $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
else
- $diffLine = $('td', $diffLine)
+ $diffLine = $diffLine.find('td')
if $diffLine.length
$diffLine.addClass 'hll'
diff --git a/app/assets/javascripts/raven_config.js.coffee b/app/assets/javascripts/raven_config.js.coffee
deleted file mode 100644
index d031a655abf..00000000000
--- a/app/assets/javascripts/raven_config.js.coffee
+++ /dev/null
@@ -1,44 +0,0 @@
-@raven =
- init: ->
- if gon.sentry_dsn?
- Raven.config(gon.sentry_dsn, {
- includePaths: [/gon.relative_url_root/]
- ignoreErrors: [
- # Random plugins/extensions
- 'top.GLOBALS',
- # See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html
- 'originalCreateNotification',
- 'canvas.contentDocument',
- 'MyApp_RemoveAllHighlights',
- 'http://tt.epicplay.com',
- 'Can\'t find variable: ZiteReader',
- 'jigsaw is not defined',
- 'ComboSearch is not defined',
- 'http://loading.retry.widdit.com/',
- 'atomicFindClose',
- # ISP "optimizing" proxy - `Cache-Control: no-transform` seems to
- # reduce this. (thanks @acdha)
- # See http://stackoverflow.com/questions/4113268
- 'bmi_SafeAddOnload',
- 'EBCallBackMessageReceived',
- # See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
- 'conduitPage'
- ],
- ignoreUrls: [
- # Chrome extensions
- /extensions\//i,
- /^chrome:\/\//i,
- # Other plugins
- /127\.0\.0\.1:4001\/isrunning/i, # Cacaoweb
- /webappstoolbarba\.texthelp\.com\//i,
- /metrics\.itunes\.apple\.com\.edgesuite\.net\//i
- ]
- }).install()
-
- if gon.current_user_id
- Raven.setUserContext({
- id: gon.current_user_id
- })
-
-$ ->
- raven.init()
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
index 67403554340..2d084b76cfe 100644
--- a/app/assets/javascripts/right_sidebar.js.coffee
+++ b/app/assets/javascripts/right_sidebar.js.coffee
@@ -1,10 +1,12 @@
class @Sidebar
constructor: (currentUser) ->
+ @sidebar = $('aside')
+
@addEventListeners()
addEventListeners: ->
- $('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
- $('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
+ @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
+ $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
@@ -30,26 +32,56 @@ class @Sidebar
else
i.show()
-
sidebarCollapseClicked: (e) ->
+ sidebar = e.data
e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.openDropdown($block);
- $('aside')
- .find('.gutter-toggle')
- .trigger('click')
- $editLink = $block.find('.edit-link')
+ openDropdown: (blockOrName) ->
+ $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName
+
+ $block.find('.edit-link').trigger('click')
- if $editLink.length
- $editLink.trigger('click')
- $block.addClass('collapse-after-update')
- $('.page-with-sidebar').addClass('with-overlay')
+ if not @isOpen()
+ @setCollapseAfterUpdate($block)
+ @toggleSidebar('open')
- sidebarDropdownHidden: (e) ->
+ setCollapseAfterUpdate: ($block) ->
+ $block.addClass('collapse-after-update')
+ $('.page-with-sidebar').addClass('with-overlay')
+
+ onSidebarDropdownHidden: (e) ->
+ sidebar = e.data
+ e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.sidebarDropdownHidden($block)
+
+ sidebarDropdownHidden: ($block) ->
if $block.hasClass('collapse-after-update')
$block.removeClass('collapse-after-update')
$('.page-with-sidebar').removeClass('with-overlay')
- $('aside')
- .find('.gutter-toggle')
- .trigger('click') \ No newline at end of file
+ @toggleSidebar('hide')
+
+ triggerOpenSidebar: ->
+ @sidebar
+ .find('.js-sidebar-toggle')
+ .trigger('click')
+
+ toggleSidebar: (action = 'toggle') ->
+ if action is 'toggle'
+ @triggerOpenSidebar()
+
+ if action is 'open'
+ @triggerOpenSidebar() if not @isOpen()
+
+ if action is 'hide'
+ @triggerOpenSidebar() is @isOpen()
+
+ isOpen: ->
+ @sidebar.is('.right-sidebar-expanded')
+
+ getBlock: (name) ->
+ @sidebar.find(".block.#{name}")
+
+
diff --git a/app/assets/javascripts/search.js.coffee b/app/assets/javascripts/search.js.coffee
new file mode 100644
index 00000000000..661e1195f60
--- /dev/null
+++ b/app/assets/javascripts/search.js.coffee
@@ -0,0 +1,75 @@
+class @Search
+ constructor: ->
+ $groupDropdown = $('.js-search-group-dropdown')
+ $projectDropdown = $('.js-search-project-dropdown')
+ @eventListeners()
+
+ $groupDropdown.glDropdown(
+ selectable: true
+ filterable: true
+ fieldName: 'group_id'
+ data: (term, callback) ->
+ Api.groups term, null, (data) ->
+ data.unshift(
+ name: 'Any'
+ )
+ data.splice 1, 0, 'divider'
+
+ callback(data)
+ id: (obj) ->
+ obj.id
+ text: (obj) ->
+ obj.name
+ toggleLabel: (obj) ->
+ "#{$groupDropdown.data('default-label')} #{obj.name}"
+ clicked: =>
+ @submitSearch()
+ )
+
+ $projectDropdown.glDropdown(
+ selectable: true
+ filterable: true
+ fieldName: 'project_id'
+ data: (term, callback) ->
+ Api.projects term, 'id', (data) ->
+ data.unshift(
+ name_with_namespace: 'Any'
+ )
+ data.splice 1, 0, 'divider'
+
+ callback(data)
+ id: (obj) ->
+ obj.id
+ text: (obj) ->
+ obj.name_with_namespace
+ toggleLabel: (obj) ->
+ "#{$projectDropdown.data('default-label')} #{obj.name_with_namespace}"
+ clicked: =>
+ @submitSearch()
+ )
+
+ eventListeners: ->
+ $(document)
+ .off 'keyup', '.js-search-input'
+ .on 'keyup', '.js-search-input', @searchKeyUp
+
+ $(document)
+ .off 'click', '.js-search-clear'
+ .on 'click', '.js-search-clear', @clearSearchField
+
+ submitSearch: ->
+ $('.js-search-form').submit()
+
+ searchKeyUp: ->
+ $input = $(@)
+
+ if $input.val() is ''
+ $('.js-search-clear').addClass 'hidden'
+ else
+ $('.js-search-clear').removeClass 'hidden'
+
+ clearSearchField: ->
+ $('.js-search-input')
+ .val ''
+ .trigger 'keyup'
+ .focus()
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index 100e3aac535..f3d66004138 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -2,34 +2,35 @@ class @Shortcuts
constructor: ->
@enabledHelp = []
Mousetrap.reset()
- Mousetrap.bind('?', @selectiveHelp)
+ Mousetrap.bind('?', @onToggleHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
- selectiveHelp: (e) =>
- Shortcuts.showHelp(e, @enabledHelp)
+ onToggleHelp: (e) =>
+ e.preventDefault()
+ @toggleHelp(@enabledHelp)
toggleMarkdownPreview: (e) =>
$(document).triggerHandler('markdown-preview:toggle', [e])
- @showHelp: (e, location) ->
- if $('#modal-shortcuts').length > 0
- $('#modal-shortcuts').modal('show')
- else
- url = '/help/shortcuts'
- url = gon.relative_url_root + url if gon.relative_url_root?
- $.ajax(
- url: url,
- dataType: 'script',
- success: (e) ->
- if location and location.length > 0
- $(l).show() for l in location
- else
- $('.hidden-shortcut').show()
- $('.js-more-help-button').remove()
- )
- e.preventDefault()
+ toggleHelp: (location) ->
+ $modal = $('#modal-shortcuts')
+
+ if $modal.length
+ $modal.modal('toggle')
+ return
+
+ $.ajax(
+ url: gon.shortcuts_path,
+ dataType: 'script',
+ success: (e) ->
+ if location and location.length > 0
+ $(l).show() for l in location
+ else
+ $('.hidden-shortcut').show()
+ $('.js-more-help-button').remove()
+ )
@focusSearch: (e) ->
$('#search').focus()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index bbf02f1db24..ad9b3c1c6bf 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -4,18 +4,8 @@
class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
- Mousetrap.bind('a', ->
- $('.block.assignee .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('m', ->
- $('.block.milestone .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('r', =>
- @replyWithSelectedText()
- return false
- )
+ Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
+ Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
Mousetrap.bind('j', =>
@prevIssue()
return false
@@ -28,7 +18,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@editIssue()
return false
)
-
+ Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
@@ -71,3 +61,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
editIssue: ->
$editBtn = $('.issuable-edit')
Turbolinks.visit($editBtn.attr('href'))
+
+ openSidebarDropdown: (name) ->
+ sidebar.openDropdown(name)
+ return false
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 8decaedd87b..f39504e0645 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -14,6 +14,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
+ Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndFollowLink: (selector) ->
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index 10e698d6a54..10bef96f43d 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -102,7 +102,8 @@ class @Todos
todoLink = $(this).data('url')
return unless todoLink
- if e.metaKey
+ # Allow Meta-Click or Mouse3-click to open in a new tab
+ if e.metaKey or e.which is 2
e.preventDefault()
window.open(todoLink,'_blank')
else
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e8c0172680d..18a74fe21a0 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -144,6 +144,10 @@
}
}
+.btn-lg {
+ padding: 12px 20px;
+}
+
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 0b3af592d4a..11f39d583bd 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -54,6 +54,10 @@
fill: #254e77 !important;
}
+ .future {
+ visibility: hidden;
+ }
+
.domain-background {
fill: none;
shape-rendering: crispedges;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 2ade341c9dd..3386523dbf7 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -11,6 +11,7 @@
.prepend-top-10 { margin-top: 10px }
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px }
+.prepend-left-5 { margin-left: 5px }
.prepend-left-10 { margin-left: 10px }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index fd395041c3d..4bf3a050403 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -42,7 +42,7 @@
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
- border-radius: $dropdown-border-radius;
+ border-radius: $border-radius-base;
outline: 0;
text-overflow: ellipsis;
white-space: nowrap;
@@ -80,7 +80,7 @@
padding: 10px 0;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
- border-radius: $dropdown-border-radius;
+ border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading {
@@ -320,7 +320,7 @@
}
}
-.dropdown-input-field {
+.dropdown-input-field, .default-dropdown-input {
width: 100%;
padding: 0 7px;
color: $dropdown-input-color;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 789df42fb66..61d9954c6c8 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -38,12 +38,14 @@
.filename {
&.old {
+ display: inline-block;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
+ display: inline-block;
span.idiff {
background-color: #a6f3a6;
}
@@ -82,10 +84,6 @@
}
}
- &.blob_file {
-
- }
-
&.blob-no-preview {
background: #eee;
text-shadow: 0 1px 2px #fff;
@@ -129,6 +127,11 @@
td.line-numbers {
float: none;
border-left: 1px solid #ddd;
+
+ i {
+ float: none;
+ margin-right: 0;
+ }
}
td.lines {
padding: 0;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 704fa1ff800..fd885b38680 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -95,7 +95,7 @@
&.md-preview-holder {
code {
white-space: pre-wrap;
- word-break: break-all;
+ word-break: keep-all;
}
}
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b2fab387e17..eae5f062dda 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -121,9 +121,6 @@
}
}
-.select2-container-multi .select2-choices .select2-search-choice {
-}
-
.select2-drop-active {
margin-top: 6px;
font-size: 14px;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index f0ec250de2b..29501069d27 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -11,7 +11,7 @@
border-bottom: 1px solid $border-white-light;
&:target {
- background: $row-hover;
+ background: $line-target-blue;
}
.avatar {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 30ca27ab104..ecadbf32f6a 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -168,8 +168,12 @@ $line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
+$line-number-select: #fbf2da;
$match-line: #fafafa;
$table-border-gray: #f0f0f0;
+$line-target-blue: #eaf3fc;
+$line-select-yellow: #fcf8e7;
+$line-select-yellow-dark: #f0e2bd;
/*
* Fonts
*/
@@ -179,7 +183,6 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
/*
* Dropdowns
*/
-$dropdown-border-radius: 2px;
$dropdown-width: 300px;
$dropdown-bg: #fff;
$dropdown-link-color: #555;
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 28253d4ccb4..80a509a7c1a 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -111,8 +111,6 @@
.vg { color: #f8f8f2 } /* Name.Variable.Global */
.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
-
- .gh { } /* Generic Heading & Diff Header */
.gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
.gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
.gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 1ff6ad75e07..31a4e3deaac 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -21,11 +21,6 @@
// Diff line
.line_holder {
- td.diff-line-num.hll:not(.empty-cell),
- td.line_content.hll:not(.empty-cell) {
- background-color: #f8eec7;
- border-color: darken(#f8eec7, 15%);
- }
.diff-line-num {
&.old {
@@ -37,11 +32,16 @@
background-color: $line-number-new;
border-color: $line-added-dark;
}
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-number-select;
+ border-color: $line-select-yellow-dark;
+ }
}
.line_content {
&.old {
- background: $line-removed;
+ background-color: $line-removed;
span.idiff {
background-color: $line-removed-dark;
@@ -58,7 +58,11 @@
&.match {
color: $black-transparent;
- background: $match-line;
+ background-color: $match-line;
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-select-yellow;
}
}
}
diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss
new file mode 100644
index 00000000000..125f495d6d4
--- /dev/null
+++ b/app/assets/stylesheets/pages/confirmation.scss
@@ -0,0 +1,18 @@
+.well-confirmation {
+ margin-bottom: 20px;
+ border-bottom: 1px solid #eee;
+
+ > h1 {
+ font-weight: 400;
+ }
+
+ .lead {
+ margin-bottom: 20px;
+ }
+}
+
+.confirmation-content {
+ a {
+ color: $md-link-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 751a5ab4d92..3438dbe4958 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -40,6 +40,7 @@
.wiki {
code {
white-space: pre-wrap;
+ word-break: keep-all;
}
}
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 4e5c4ed84b6..f7f9a9bb770 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -18,9 +18,6 @@
}
.graphs {
- .graph-author-commits-count {
- }
-
.graph-author-email {
float: right;
color: #777;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 1d6190c8f18..1cf3023ecc9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -249,6 +249,10 @@
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
+
+ &.btn-primary {
+ @extend .btn-primary
+ }
}
a:not(.issuable-pager) {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 82f78e8d796..55b1ad97eb0 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -109,6 +109,10 @@ ul.notes {
border-color: darken(#f5f5f5, 8%);
margin: 10px 0;
}
+
+ code {
+ word-break: keep-all;
+ }
}
a {
@@ -183,6 +187,9 @@ ul.notes {
}
}
+ .author_link {
+ color: $gl-gray;
+ }
}
.note-headline-light,
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index f0f3744c6fa..7f642d9edbf 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -10,17 +10,6 @@
}
}
-.search-holder {
- max-width: 600px;
- margin: 0 auto;
- margin-bottom: 20px;
-
- input {
- border-color: #bbb;
- font-weight: bold;
- }
-}
-
.search {
margin-right: 10px;
margin-left: 10px;
@@ -163,3 +152,81 @@
}
}
}
+
+.search-holder {
+ @media (min-width: $screen-sm-min) {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ }
+
+ .search-field-holder {
+ -webkit-flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto;
+ position: relative;
+ margin-right: 0;
+
+ @media (min-width: $screen-sm-min) {
+ margin-right: 5px;
+ }
+ }
+
+ .search-icon {
+ position: absolute;
+ left: 10px;
+ top: 10px;
+ color: $gray-darkest;
+ pointer-events: none;
+ }
+
+ .search-text-input {
+ padding-left: $gl-padding + 15px;
+ padding-right: $gl-padding + 15px;
+ }
+
+ .btn-search {
+ width: 100%;
+ margin-top: 5px;
+
+ @media (min-width: $screen-sm-min) {
+ width: auto;
+ margin-top: 0;
+ margin-left: 5px;
+ }
+ }
+
+ .dropdown {
+ @media (min-width: $screen-sm-min) {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+ }
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ margin-top: 5px;
+
+ @media (min-width: $screen-sm-min) {
+ width: 160px;
+ margin-top: 0;
+ }
+ }
+}
+
+.search-clear {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ padding: 0;
+ color: $gray-darkest;
+ line-height: 0;
+ background: none;
+ border: 0;
+
+ &:hover,
+ &:focus {
+ color: $gl-link-color;
+ outline: none;
+ }
+}
diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb
index f63b703d101..586f97c5eb4 100644
--- a/app/controllers/concerns/filter_projects.rb
+++ b/app/controllers/concerns/filter_projects.rb
@@ -10,6 +10,8 @@ module FilterProjects
def filter_projects(projects)
projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
projects = projects.non_archived if params[:archived].blank?
+ projects = projects.personal(current_user) if params[:personal].present? && current_user
+
projects
end
end
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index af1faca93f6..7b66ad3f92c 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -1,7 +1,16 @@
class ConfirmationsController < Devise::ConfirmationsController
+ def almost_there
+ flash[:notice] = nil
+ render layout: "devise_empty"
+ end
+
protected
+ def after_resending_confirmation_instructions_path_for(resource)
+ users_almost_there_path
+ end
+
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
after_sign_in_path_for(resource)
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 74150ad606b..be872a93fee 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -83,8 +83,7 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
- view = params[:view] || cookies[:diff_view]
- cookies.permanent[:diff_view] = params[:view] = view if view
+ cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
def builds_enabled
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 4d64a2d9884..a202cb38692 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController
end
def cancel_builds
- ci_commit.builds.running_or_pending.each(&:cancel)
+ ci_builds.running_or_pending.each(&:cancel)
redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def retry_builds
- ci_commit.builds.latest.failed.each do |build|
+ ci_builds.latest.failed.each do |build|
if build.retryable?
Ci::Build.retry(build)
end
@@ -99,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id])
end
- def ci_commit
- @ci_commit ||= project.ci_commit(commit.sha)
+ def ci_commits
+ @ci_commits ||= project.ci_commits.where(sha: commit.sha)
+ end
+
+ def ci_builds
+ @ci_builds ||= Ci::Build.where(commit: ci_commits)
end
def define_show_vars
@@ -113,7 +117,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
- @statuses = ci_commit.statuses if ci_commit
+ @statuses = CommitStatus.where(commit: ci_commits)
+ @builds = Ci::Build.where(commit: ci_commits)
end
def assign_change_commit_vars(mr_source_branch)
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b96ab91c17d..7d4fc361ce2 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -101,7 +101,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
respond_to do |format|
- format.js
format.html do
if @issue.valid?
redirect_to issue_path(@issue)
@@ -110,7 +109,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
- render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 1388ea9d66c..9c147b3689e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -149,13 +149,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.valid?
respond_to do |format|
- format.js
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
@merge_request.target_project, @merge_request])
end
format.json do
- render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
else
@@ -321,6 +320,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars
@ci_commit = @merge_request.ci_commit
+ @ci_commits = [@ci_commit].compact
closes_issues
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index e457db2f0b7..33b2625c0ac 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -1,6 +1,6 @@
class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
- before_action :authorize_admin_project_member!, except: :leave
+ before_action :authorize_admin_project_member!, except: [:leave, :index]
def index
@project_members = @project.project_members
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index c48175a4c5a..059b88e2253 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -31,11 +31,11 @@ class RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
private
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index e42d2d73947..69c92d2bed2 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -8,8 +8,6 @@ class SearchController < ApplicationController
def show
return if params[:search].nil? || params[:search].blank?
- @search_term = params[:search]
-
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project)
@@ -20,6 +18,8 @@ class SearchController < ApplicationController
@group = nil unless can?(current_user, :read_group, @group)
end
+ @search_term = params[:search]
+
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
@@ -44,7 +44,7 @@ class SearchController < ApplicationController
Search::GlobalService.new(current_user, params).execute
end
- @objects = @search_results.objects(@scope, params[:page])
+ @search_objects = @search_results.objects(@scope, params[:page])
end
def autocomplete
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index a85c214e4c4..f00f3f709e9 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -272,16 +272,13 @@ class IssuableFinder
items = items.without_label
else
items = items.with_label(label_names)
-
if projects
items = items.where(labels: { project_id: projects })
end
end
end
- # When filtering by multiple labels we may end up duplicating issues (if one
- # has multiple labels). This ensures we only return unique issues.
- items.distinct
+ items
end
def by_due_date(items)
@@ -321,7 +318,7 @@ class IssuableFinder
end
def label_names
- params[:label_name].split(',')
+ params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
end
def current_user_related?
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8b1575d5e0c..417050b4132 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -4,14 +4,6 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
- def ci_status_icon(ci_commit)
- ci_icon_for_status(ci_commit.status)
- end
-
- def ci_status_label(ci_commit)
- ci_label_for_status(ci_commit.status)
- end
-
def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
klass = "ci-status ci-#{status}"
@@ -47,10 +39,13 @@ module CiStatusHelper
end
def render_ci_status(ci_commit, tooltip_placement: 'auto left')
- link_to ci_status_icon(ci_commit),
+ # TODO: split this method into
+ # - render_commit_status
+ # - render_pipeline_status
+ link_to ci_icon_for_status(ci_commit.status),
ci_status_path(ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
- title: "Build #{ci_status_label(ci_commit)}",
+ title: "Build #{ci_label_for_status(ci_commit.status)}",
data: { toggle: 'tooltip', placement: tooltip_placement }
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 6a3ec83b8c0..97466d532f4 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -9,7 +9,13 @@ module DiffHelper
end
def diff_view
- params[:view] == 'parallel' ? 'parallel' : 'inline'
+ diff_views = %w(inline parallel)
+
+ if diff_views.include?(cookies[:diff_view])
+ cookies[:diff_view]
+ else
+ diff_views.first
+ end
end
def diff_hard_limit_enabled?
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f3fddef01cb..f07eff3fb57 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
+ def project_pipelines_path(project, *args)
+ namespace_project_pipelines_path(project.namespace, project, *args)
+ end
+
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 82f805fa444..e4e8b934bc8 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -84,6 +84,14 @@ module PageLayoutHelper
end
end
+ def nav(name = nil)
+ if name
+ @nav = name
+ else
+ @nav
+ end
+ end
+
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = (current_user && current_user.layout == "fluid") || enabled
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 2f164da326c..3d5e61d2c18 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -123,6 +123,18 @@ module ProjectsHelper
end
end
+ def license_short_name(project)
+ no_license_key = project.repository.license_key.nil? ||
+ # Back-compat if cache contains 'no-license', can be removed in a few weeks
+ project.repository.license_key == 'no-license'
+
+ return 'LICENSE' if no_license_key
+
+ license = Licensee::License.new(project.repository.license_key)
+
+ license.nickname || license.name
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -144,6 +156,10 @@ module ProjectsHelper
nav_tabs << :settings
end
+ if can?(current_user, :read_project_member, project)
+ nav_tabs << :team
+ end
+
if can?(current_user, :read_issue, project)
nav_tabs << :issues
end
@@ -316,14 +332,6 @@ module ProjectsHelper
@ref || @repository.try(:root_ref)
end
- def license_short_name(project)
- license = Licensee::License.new(project.repository.license_key)
-
- license.nickname || license.name
- end
-
- private
-
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
namespace_project_blob_path(
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8a97a74ad73..24c4c098c65 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -19,6 +19,16 @@ module SearchHelper
end
end
+ def search_entries_info(collection, scope, term)
+ return unless collection.count > 0
+
+ from = collection.offset_value + 1
+ to = collection.offset_value + collection.length
+ count = collection.total_count
+
+ "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
+ end
+
private
# Autocomplete results for various settings pages
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 04e53fe7c61..96a83671009 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -110,4 +110,12 @@ module TabHelper
'active'
end
end
+
+ def profile_tab_class
+ if controller.controller_path =~ /\Aprofiles/
+ return 'active'
+ end
+
+ 'active' if current_controller?('oauth/applications')
+ end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d42a65620ff..553cd447971 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -37,8 +37,6 @@
module Ci
class Build < CommitStatus
- LAZY_ATTRIBUTES = ['trace']
-
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :erased_by, class_name: 'User'
@@ -50,25 +48,17 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
acts_as_taggable
- # To prevent db load megabytes of data from trace
- default_scope -> { select(Ci::Build.columns_without_lazy) }
-
before_destroy { project }
- class << self
- def columns_without_lazy
- (column_names - LAZY_ATTRIBUTES).map do |column_name|
- "#{table_name}.#{column_name}"
- end
- end
+ after_create :execute_hooks
+ class << self
def last_month
where('created_at > ?', Date.today - 1.month)
end
@@ -126,12 +116,16 @@ module Ci
end
def retried?
- !self.commit.latest_statuses_for_ref(self.ref).include?(self)
+ !self.commit.statuses.latest.include?(self)
+ end
+
+ def retry
+ Ci::Build.retry(self)
end
def depends_on_builds
# Get builds of the same type
- latest_builds = self.commit.builds.similar(self).latest
+ latest_builds = self.commit.builds.latest
# Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx)
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index f4cf7034b14..f2667e5476b 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -19,21 +19,28 @@
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
+ include Statuseable
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
+ delegate :stages, to: :statuses
+
validates_presence_of :sha
+ validates_presence_of :status
validate :valid_commit_sha
+ # Invalidate object and save if when touched
+ after_touch :update_state
+
def self.truncate_sha(sha)
sha[0...8]
end
- def to_param
- sha
+ def self.stages
+ CommitStatus.where(commit: all).stages
end
def project_id
@@ -68,15 +75,20 @@ module Ci
nil
end
- def stage
- running_or_pending = statuses.latest.running_or_pending.ordered
- running_or_pending.first.try(:stage)
+ def branch?
+ !tag?
+ end
+
+ def retryable?
+ builds.latest.any? do |build|
+ build.failed? && build.retryable?
+ end
end
- def create_builds(ref, tag, user, trigger_request = nil)
+ def create_builds(user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
- CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
+ CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
end
end
@@ -84,7 +96,7 @@ module Ci
return unless config_processor
# don't create other builds if this one is retried
- latest_builds = builds.similar(build).latest
+ latest_builds = builds.latest
return unless latest_builds.exists?(build.id)
# get list of stages after this build
@@ -92,88 +104,21 @@ module Ci
next_stages.delete(build.stage)
# get status for all prior builds
- prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
- status = Ci::Status.get_status(prior_builds)
+ prior_builds = latest_builds.where.not(stage: next_stages)
+ prior_status = prior_builds.status
# create builds for next stages based
next_stages.any? do |stage|
- CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
+ CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present?
end
end
- def refs
- statuses.order(:ref).pluck(:ref).uniq
- end
-
- def latest_statuses
- @latest_statuses ||= statuses.latest.to_a
- end
-
- def latest_statuses_for_ref(ref)
- latest_statuses.select { |status| status.ref == ref }
- end
-
- def matrix_builds(build = nil)
- matrix_builds = builds.latest.ordered
- matrix_builds = matrix_builds.similar(build) if build
- matrix_builds.to_a
- end
-
def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest)
end
- def status
- if yaml_errors.present?
- return 'failed'
- end
-
- @status ||= Ci::Status.get_status(latest_statuses)
- end
-
- def pending?
- status == 'pending'
- end
-
- def running?
- status == 'running'
- end
-
- def success?
- status == 'success'
- end
-
- def failed?
- status == 'failed'
- end
-
- def canceled?
- status == 'canceled'
- end
-
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
- end
-
- def duration
- duration_array = statuses.map(&:duration).compact
- duration_array.reduce(:+).to_i
- end
-
- def started_at
- @started_at ||= statuses.order('started_at ASC').first.try(:started_at)
- end
-
- def finished_at
- @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
- end
-
def coverage
- coverage_array = latest_statuses.map(&:coverage).compact
+ coverage_array = statuses.latest.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
@@ -181,23 +126,29 @@ module Ci
def config_processor
return nil unless ci_yaml_file
- @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
- rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
- save_yaml_error(e.message)
- nil
- rescue
- save_yaml_error("Undefined error")
- nil
+ return @config_processor if defined?(@config_processor)
+
+ @config_processor ||= begin
+ Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
+ rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
+ save_yaml_error(e.message)
+ nil
+ rescue
+ save_yaml_error("Undefined error")
+ nil
+ end
end
def ci_yaml_file
+ return @ci_yaml_file if defined?(@ci_yaml_file)
+
@ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
blob.data
+ rescue
+ nil
end
- rescue
- nil
end
def skip_ci?
@@ -206,10 +157,23 @@ module Ci
private
+ def update_state
+ statuses.reload
+ self.status = if yaml_errors.blank?
+ statuses.latest.status || 'skipped'
+ else
+ 'failed'
+ end
+ self.started_at = statuses.started_at
+ self.finished_at = statuses.finished_at
+ self.duration = statuses.latest.duration
+ save
+ end
+
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
- save
+ update_state
end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6bb018b086f..562c3ed15b2 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -207,12 +207,13 @@ class Commit
@raw.short_id(7)
end
- def ci_commit
- project.ci_commit(sha)
+ def ci_commits
+ @ci_commits ||= project.ci_commits.where(sha: sha)
end
def status
- ci_commit.try(:status) || :not_found
+ return @status if defined?(@status)
+ @status ||= ci_commits.status
end
def revert_branch_name
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 3377a85a55a..aa56314aa16 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -33,30 +33,23 @@
#
class CommitStatus < ActiveRecord::Base
+ include Statuseable
+
self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
- belongs_to :commit, class_name: 'Ci::Commit'
+ belongs_to :commit, class_name: 'Ci::Commit', touch: true
belongs_to :user
validates :commit, presence: true
- validates :status, inclusion: { in: %w(pending running failed success canceled) }
validates_presence_of :name
alias_attribute :author, :user
- scope :running, -> { where(status: 'running') }
- scope :pending, -> { where(status: 'pending') }
- scope :success, -> { where(status: 'success') }
- scope :failed, -> { where(status: 'failed') }
- scope :running_or_pending, -> { where(status: [:running, :pending]) }
- scope :finished, -> { where(status: [:success, :failed, :canceled]) }
- scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
+ scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
- scope :for_ref, ->(ref) { where(ref: ref) }
-
- AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled']
+ scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
event :run do
@@ -86,31 +79,24 @@ class CommitStatus < ActiveRecord::Base
after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end
-
- state :pending, value: 'pending'
- state :running, value: 'running'
- state :failed, value: 'failed'
- state :success, value: 'success'
- state :canceled, value: 'canceled'
end
- delegate :sha, :short_sha, to: :commit, prefix: false
+ delegate :sha, :short_sha, to: :commit
- # TODO: this should be removed with all references
def before_sha
- Gitlab::Git::BLANK_SHA
+ commit.before_sha || Gitlab::Git::BLANK_SHA
end
- def started?
- !pending? && !canceled? && started_at
+ def self.stages
+ order_by = 'max(stage_idx)'
+ group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact
end
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
+ def self.stages_status
+ all.stages.inject({}) do |h, stage|
+ h[stage] = all.where(stage: stage).status
+ h
+ end
end
def ignored?
@@ -118,11 +104,13 @@ class CommitStatus < ActiveRecord::Base
end
def duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.now - started_at
- end
+ duration =
+ if started_at && finished_at
+ finished_at - started_at
+ elsif started_at
+ Time.now - started_at
+ end
+ duration
end
def stuck?
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index afa2ca039ae..d5166e81474 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -37,7 +37,6 @@ module Issuable
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
- scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
@@ -122,6 +121,14 @@ module Issuable
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
+
+ def with_label(title)
+ if title.is_a?(Array) && title.count > 1
+ joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}")
+ else
+ joins(:labels).where(labels: { title: title })
+ end
+ end
end
def today?
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb
new file mode 100644
index 00000000000..8a293b7b76e
--- /dev/null
+++ b/app/models/concerns/statuseable.rb
@@ -0,0 +1,81 @@
+module Statuseable
+ extend ActiveSupport::Concern
+
+ AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
+
+ class_methods do
+ def status_sql
+ builds = all.select('count(*)').to_sql
+ success = all.success.select('count(*)').to_sql
+ ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
+ ignored ||= '0'
+ pending = all.pending.select('count(*)').to_sql
+ running = all.running.select('count(*)').to_sql
+ canceled = all.canceled.select('count(*)').to_sql
+ skipped = all.skipped.select('count(*)').to_sql
+
+ deduce_status = "(CASE
+ WHEN (#{builds})=0 THEN NULL
+ WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
+ WHEN (#{builds})=(#{pending}) THEN 'pending'
+ WHEN (#{builds})=(#{canceled}) THEN 'canceled'
+ WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+ WHEN (#{running})+(#{pending})>0 THEN 'running'
+ ELSE 'failed'
+ END)"
+
+ deduce_status
+ end
+
+ def status
+ all.pluck(self.status_sql).first
+ end
+
+ def duration
+ duration_array = all.map(&:duration).compact
+ duration_array.reduce(:+)
+ end
+
+ def started_at
+ all.minimum(:started_at)
+ end
+
+ def finished_at
+ all.maximum(:finished_at)
+ end
+ end
+
+ included do
+ validates :status, inclusion: { in: AVAILABLE_STATUSES }
+
+ state_machine :status, initial: :pending do
+ state :pending, value: 'pending'
+ state :running, value: 'running'
+ state :failed, value: 'failed'
+ state :success, value: 'success'
+ state :canceled, value: 'canceled'
+ state :skipped, value: 'skipped'
+ end
+
+ scope :running, -> { where(status: 'running') }
+ scope :pending, -> { where(status: 'pending') }
+ scope :success, -> { where(status: 'success') }
+ scope :failed, -> { where(status: 'failed') }
+ scope :canceled, -> { where(status: 'canceled') }
+ scope :skipped, -> { where(status: 'skipped') }
+ scope :running_or_pending, -> { where(status: [:running, :pending]) }
+ scope :finished, -> { where(status: [:success, :failed, :canceled]) }
+ end
+
+ def started?
+ !pending? && !canceled? && started_at
+ end
+
+ def active?
+ running? || pending?
+ end
+
+ def complete?
+ canceled? || success? || failed?
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 12183524b79..897518aadc7 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -345,7 +345,7 @@ class Event < ActiveRecord::Base
end
def reset_project_activity
- if project
+ if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain
project.update_column(:last_activity_at, self.created_at)
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index dbecc48485c..d00919c3b0c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -586,7 +586,7 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
- @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
+ @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project
end
def diff_refs
diff --git a/app/models/project.rb b/app/models/project.rb
index 8f0272d2ce0..0420c6a61ae 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -820,13 +820,11 @@ class Project < ActiveRecord::Base
wiki = Repository.new("#{old_path}.wiki", self)
if repo.exists?
- repo.expire_cache
- repo.expire_emptiness_caches
+ repo.before_delete
end
if wiki.exists?
- wiki.expire_cache
- wiki.expire_emptiness_caches
+ wiki.before_delete
end
end
@@ -963,12 +961,12 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
- def ci_commit(sha)
- ci_commits.find_by(sha: sha)
+ def ci_commit(sha, ref)
+ ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_ci_commit(sha)
- ci_commit(sha) || ci_commits.create(sha: sha)
+ def ensure_ci_commit(sha, ref)
+ ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
end
def enable_ci
diff --git a/app/models/repository.rb b/app/models/repository.rb
index da751591103..61c8dce6060 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -466,8 +466,8 @@ class Repository
return nil if !exists? || empty?
cache.fetch(:license_blob) do
- if licensee_project.license
- blob_at_branch(root_ref, licensee_project.matched_file.filename)
+ tree(:head).blobs.find do |file|
+ file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
end
end
end
@@ -476,7 +476,7 @@ class Repository
return nil if !exists? || empty?
cache.fetch(:license_key) do
- licensee_project.license.try(:key) || 'no-license'
+ Licensee.license(path).try(:key)
end
end
@@ -959,8 +959,4 @@ class Repository
def cache
@cache ||= RepositoryCache.new(path_with_namespace)
end
-
- def licensee_project
- @licensee_project ||= Licensee.project(path)
- end
end
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 2cd51a7610f..18274ce24e2 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -1,7 +1,11 @@
module Ci
class CreateBuildsService
- def execute(commit, stage, ref, tag, user, trigger_request, status)
- builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
+ def initialize(commit)
+ @commit = commit
+ end
+
+ def execute(stage, user, status, trigger_request = nil)
+ builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
@@ -17,7 +21,8 @@ module Ci
builds_attrs.map do |build_attrs|
# don't create the same build twice
- unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
+ unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag,
+ trigger_request: trigger_request, name: build_attrs[:name])
build_attrs.slice!(:name,
:commands,
:tag_list,
@@ -26,17 +31,21 @@ module Ci
:stage,
:stage_idx)
- build_attrs.merge!(ref: ref,
- tag: tag,
+ build_attrs.merge!(ref: @commit.ref,
+ tag: @commit.tag,
trigger_request: trigger_request,
user: user,
- project: commit.project)
+ project: @commit.project)
- build = commit.builds.create!(build_attrs)
- build.execute_hooks
- build
+ @commit.builds.create!(build_attrs)
end
end
end
+
+ private
+
+ def config_processor
+ @config_processor ||= @commit.config_processor
+ end
end
end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index b3dfc707221..993acf11db9 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -7,14 +7,14 @@ module Ci
# check if ref is tag
tag = project.repository.find_tag(ref).present?
- ci_commit = project.ensure_ci_commit(commit.sha)
+ ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
commit: ci_commit,
)
- if ci_commit.create_builds(ref, tag, nil, trigger_request)
+ if ci_commit.create_builds(nil, trigger_request)
trigger_request
end
end
diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb
index 50c95ced8a7..3018f27ec05 100644
--- a/app/services/ci/image_for_build_service.rb
+++ b/app/services/ci/image_for_build_service.rb
@@ -3,8 +3,9 @@ module Ci
def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref])
- commit = project.ci_commits.find_by(sha: sha)
- image_name = image_for_commit(commit)
+ ci_commits = project.ci_commits.where(sha: sha)
+ ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref]
+ image_name = image_for_status(ci_commits.status)
image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name)
@@ -16,9 +17,9 @@ module Ci
project.commit(ref).try(:sha) if ref
end
- def image_for_commit(commit)
- return 'build-unknown.svg' unless commit
- 'build-' + commit.status + ".svg"
+ def image_for_status(status)
+ status ||= 'unknown'
+ 'build-' + status + ".svg"
end
end
end
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index 69d5c42a877..0d2aa1ff03d 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -2,6 +2,7 @@ class CreateCommitBuildsService
def execute(project, user, params)
return false unless project.builds_enabled?
+ before_sha = params[:checkout_sha] || params[:before]
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
@@ -10,15 +11,16 @@ class CreateCommitBuildsService
end
ref = Gitlab::Git.ref_name(origin_ref)
+ tag = Gitlab::Git.tag_ref?(origin_ref)
# Skip branch removal
if sha == Gitlab::Git::BLANK_SHA
return false
end
- commit = project.ci_commit(sha)
+ commit = project.ci_commit(sha, ref)
unless commit
- commit = project.ci_commits.new(sha: sha)
+ commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
# Skip creating ci_commit when no gitlab-ci.yml is found
unless commit.ci_yaml_file
@@ -32,10 +34,10 @@ class CreateCommitBuildsService
# Skip creating builds for commits that have [ci skip]
unless commit.skip_ci?
# Create builds for commit
- tag = Gitlab::Git.tag_ref?(origin_ref)
- commit.create_builds(ref, tag, user)
+ commit.create_builds(user)
end
+ commit.touch
commit
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 18f76d3f650..2b16089df1b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -37,8 +37,9 @@ class IssuableBaseService < BaseService
end
def filter_params(issuable_ability_name = :issue)
- params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
- params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
+ filter_assignee
+ filter_milestone
+ filter_labels
ability = :"admin_#{issuable_ability_name}"
@@ -49,6 +50,29 @@ class IssuableBaseService < BaseService
end
end
+ def filter_assignee
+ if params[:assignee_id] == IssuableFinder::NONE
+ params[:assignee_id] = ''
+ end
+ end
+
+ def filter_milestone
+ milestone_id = params[:milestone_id]
+ return unless milestone_id
+
+ if milestone_id == IssuableFinder::NONE ||
+ project.milestones.find_by(id: milestone_id).nil?
+ params[:milestone_id] = ''
+ end
+ end
+
+ def filter_labels
+ return if params[:label_ids].to_a.empty?
+
+ params[:label_ids] =
+ project.labels.where(id: params[:label_ids]).pluck(:id)
+ end
+
def update(issuable)
change_state(issuable)
filter_params
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 79a27f4af7e..111b3ec05ea 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -34,6 +34,8 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
+ project.expire_caches_before_rename(old_path)
+
# Apply new namespace id and visibility level
project.namespace = new_namespace
project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index b05fdbd5552..fe0b9d3a491 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -7,17 +7,17 @@
.form-group
= f.label :name, class: 'control-label'
.col-sm-10
- = f.text_field :name, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group
= f.label :username, class: 'control-label'
.col-sm-10
- = f.text_field :username, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group
= f.label :email, class: 'control-label'
.col-sm-10
- = f.text_field :email, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
- if @user.new_record?
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
new file mode 100644
index 00000000000..3c3830a3f10
--- /dev/null
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -0,0 +1,10 @@
+.well-confirmation.text-center
+ %h1.prepend-top-0
+ Almost there...
+ %p.lead
+ Please check your email to confirm your account
+%p.confirmation-content.text-center
+ No confirmation email received? Please check your spam folder or
+.append-bottom-20.prepend-top-20.text-center
+ %a.btn.btn-lg.btn-success{ href: new_user_confirmation_path }
+ Request new confirmation email
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 0aff79749ef..79df17ba612 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Applications"
-- header_title page_title, applications_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 4d20dd5830e..5d622582088 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -4,7 +4,12 @@
#{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.2"] do
- = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - if event.author
+ = link_to user_path(event.author.username) do
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - else
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index da3c3711cdd..70e88da7aae 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -21,7 +21,7 @@
%tr
%td.shortcut
.key ?
- %td Show this dialog
+ %td Show/hide this dialog
%tr
%td.shortcut
- if browser.mac?
@@ -169,6 +169,10 @@
%td.shortcut
.key t
%td Go to finding file
+ %tr
+ %td.shortcut
+ .key i
+ %td New issue
.col-lg-4
%table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' }
@@ -241,6 +245,10 @@
%td.shortcut
.key e
%td Edit issue
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
%tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
%tr
%th
@@ -261,3 +269,7 @@
%td.shortcut
.key e
%td Edit merge request
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c799e9c588d..ca9c2a0bf2e 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -25,6 +25,10 @@
.content-wrapper
= render "layouts/flash"
= yield :flash_message
+ - if defined?(nav) && nav
+ .layout-nav
+ %div{ class: container_class }
+ = render "layouts/nav/#{nav}"
%div{ class: (container_class unless @no_container) }
.content
.clearfix
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index babfb032236..e4d1c773d03 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -6,6 +6,6 @@
= yield :scripts_body_top
= render "layouts/header/default", title: header_title
- = render 'layouts/page', sidebar: sidebar
+ = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
new file mode 100644
index 00000000000..7c061dd531f
--- /dev/null
+++ b/app/views/layouts/devise_empty.html.haml
@@ -0,0 +1,17 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head"
+ %body.ui_charcoal.login-page.application.navless
+ = render "layouts/header/empty"
+ = render "layouts/broadcast"
+ .container.navless-container
+ .content
+ = render "layouts/flash"
+ = yield
+
+ %hr
+ .container
+ .footer-links
+ = link_to "Explore", explore_root_path
+ = link_to "Help", help_path
+ = link_to "About GitLab", "https://about.gitlab.com/"
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5cef652da14..ca49c313ff7 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -48,8 +48,7 @@
%span
Help
- %li.separate-item
- = nav_link(controller: :profile) do
+ = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 3b9d31a6fc5..d730840d63a 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,17 +1,9 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
-
+%ul.nav-links
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
- Profile Settings
+ Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
@@ -27,7 +19,6 @@
= icon('envelope-o fw')
%span
Emails
- %span.count= number_with_delimiter(current_user.emails.count + 1)
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
@@ -45,7 +36,6 @@
= icon('key fw')
%span
SSH Keys
- %span.count= number_with_delimiter(current_user.keys.count)
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 86b46e8c75e..479bde33719 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -77,7 +77,7 @@
Merge Requests
%span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- - if project_nav_tab? :settings
+ - if project_nav_tab? :team
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
@@ -124,3 +124,8 @@
%li.hidden
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
Network
+
+ -# Shortcut to create a new issue
+ %li.hidden
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ Create a new issue
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index dfa6cc5702e..b77d3402a2e 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,6 @@
- page_title "Profile Settings"
- header_title "Profile Settings", profile_path unless header_title
-- sidebar "profile"
+- sidebar "dashboard"
+- nav "profile"
= render template: "layouts/application"
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 6efd119f260..afd3d79321f 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Account"
-- header_title page_title, profile_account_path
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index f630c03e5f6..9c404b6935f 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,5 +1,4 @@
- page_title "Audit Log"
-- header_title page_title, audit_log_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 3f328f96cea..57527361eb6 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Emails"
-- header_title page_title, profile_emails_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index e0f8c9a5733..6a067a03535 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,5 +1,4 @@
- page_title "SSH Keys"
-- header_title page_title, profile_keys_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index a2a505c082b..7696f112bb3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Notifications"
-- header_title page_title, profile_notifications_path
%div
- if @user.errors.any?
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 5ac8a8b9d09..243428b690e 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Password"
-- header_title page_title, edit_profile_password_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index f80211669fb..bfe53be6854 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,5 +1,4 @@
- page_title 'Preferences'
-- header_title page_title, profile_preferences_path
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index b6074373e2b..0de019983ca 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -55,6 +55,9 @@
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
+ %li
+ tap --coverage-report=text-summary (Node.js) -
+ %code ^Statements\s*:\s*([^%]+)
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 386d72e7787..66c30283c7a 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -1,9 +1,8 @@
.project-last-commit
- - ci_commit = project.ci_commit(commit.sha)
- - if ci_commit
- = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
- = ci_status_icon(ci_commit)
- = ci_status_label(ci_commit)
+ - if commit.status
+ = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
+ = ci_icon_for_status(commit.status)
+ = ci_label_for_status(commit.status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index 38e62c81fed..5926d181ba3 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -1,17 +1,17 @@
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
- %tr.line_holder{ id: @form.since }
+ %tr.line_holder
= render "projects/diffs/match_line", { line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
- %tr.line_holder
+ %tr.line_holder{ id: line_old }
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_old), "#"
+ = link_to raw(line_old), "##{line_old}"
%td.new_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_new) , "#"
+ = link_to raw(line_new) , "##{line_old}"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index aa85f495e39..0406fc21d77 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -58,6 +58,6 @@
%th Coverage
%th
- = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
+ = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= paginate @builds, theme: 'gitlab'
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 6d4505ebb60..99d72aa7935 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -13,7 +13,7 @@
= link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
#up-build-trace
- - builds = @build.commit.matrix_builds(@build)
+ - builds = @build.commit.builds.latest.to_a
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- builds.each do |build|
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 2cf9115e4dd..e123eb1cc7a 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -19,11 +19,12 @@
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
- %td
- - if build.ref
- = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- - else
- .light none
+ - if defined?(ref) && ref
+ %td
+ - if build.ref
+ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
+ - else
+ .light none
- if defined?(runner) && runner
%td
@@ -48,6 +49,8 @@
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
+ - if defined?(retried) && retried
+ %span.label.label-warning retried
%td.duration
- if build.duration
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index 003b7c18d0e..5c9a319edeb 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,67 +1,2 @@
-.gray-content-block.middle-block
- .pull-right
- - if can?(current_user, :update_build, @ci_commit.project)
- - if @ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
-
- - if @ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
-
- .oneline
- = pluralize @statuses.count(:id), "build"
- - if defined?(link_to_commit) && link_to_commit
- for commit
- = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
- - if @ci_commit.duration > 0
- in
- = time_interval_in_words @ci_commit.duration
-
-- if @ci_commit.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - @ci_commit.yaml_errors.split(",").each do |error|
- %li= error
- You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
-
-- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
- .bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
-
-.table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_commit.project.build_coverage_enabled?
- %th Coverage
- %th
- - @ci_commit.refs.each do |ref|
- - builds = @ci_commit.statuses.for_ref(ref).latest.ordered
- = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true
-
-- if @ci_commit.retried.any?
- .gray-content-block.second-block
- Retried builds
-
- .table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_commit.project.build_coverage_enabled?
- %th Coverage
- %th
- = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true
+- @ci_commits.each do |ci_commit|
+ = render "ci_commit", ci_commit: ci_commit
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
new file mode 100644
index 00000000000..d3acd33116c
--- /dev/null
+++ b/app/views/projects/commit/_ci_commit.html.haml
@@ -0,0 +1,69 @@
+.gray-content-block.middle-block
+ .pull-right
+ - if can?(current_user, :update_build, @project)
+ - if ci_commit.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+
+ - if ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+ .oneline
+ = pluralize ci_commit.statuses.count(:id), "build"
+ - if ci_commit.ref
+ for
+ %span.label.label-info
+ = ci_commit.ref
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace"
+ - if ci_commit.duration
+ in
+ = time_interval_in_words ci_commit.duration
+
+- if ci_commit.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - ci_commit.yaml_errors.split(",").each do |error|
+ %li= error
+ You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
+
+- if @project.builds_enabled? && !ci_commit.ci_yaml_file
+ .bs-callout.bs-callout-warning
+ \.gitlab-ci.yml not found in this commit
+
+.table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @project.build_coverage_enabled?
+ %th Coverage
+ %th
+ - builds = ci_commit.statuses.latest.ordered
+ = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
+
+- if ci_commit.retried.any?
+ .gray-content-block.second-block
+ Retried builds
+
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @project.build_coverage_enabled?
+ %th Coverage
+ %th
+ = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index d6c9e54e657..3d7c18a5f58 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -43,12 +43,12 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
-- if @ci_commit
+- if @commit.status
.pull-right
- = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
- = ci_status_icon(@ci_commit)
+ = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
+ = ci_icon_for_status(@commit.status)
build:
- = ci_status_label(@ci_commit)
+ = ci_label_for_status(@commit.status)
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index e550af7888a..e5e3d696035 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -5,7 +5,7 @@
.prepend-top-default
= render "commit_box"
-- if @ci_commit
+- if @commit.status
= render "ci_menu"
- else
%div.block-connector
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index d71f61466f1..c7d8c9a0d15 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -4,9 +4,8 @@
- notes = commit.notes
- note_count = notes.user.count
-- ci_commit = project.ci_commit(commit.sha)
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(commit.status) if commit.status
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
@@ -17,8 +16,8 @@
%a.text-expander.js-toggle-button ...
.pull-right
- - if ci_commit
- = render_ci_status(ci_commit)
+ - if commit.status
+ = render_ci_status(commit)
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index c52cf25d40a..bcdb09208aa 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -39,4 +39,4 @@
= spinner
:javascript
- CommitsList.init("#{@ref}", #{@limit});
+ CommitsList.init(#{@limit});
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index c15386b4883..f21c864e35c 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -15,12 +15,13 @@
- if defined?(commit_sha) && commit_sha
%td
= link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
-
- %td
- - if generic_commit_status.ref
- = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
- - else
- .light none
+
+ - if defined?(ref) && ref
+ %td
+ - if generic_commit_status.ref
+ = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+ - else
+ .light none
- if defined?(runner) && runner
%td
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index b10cd03515f..bdfa0c7009e 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- sha = @project.repository.find_branch(branch).target
- - ci_commit = @project.ci_commit(sha) if sha
+ - ci_commit = @project.ci_commit(sha, branch) if sha
- if ci_commit
%span.related-branch-ci-status
= render_ci_status(ci_commit)
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/views/projects/issues/update.js.haml
+++ /dev/null
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 8d05060f563..290753d57c6 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -3,7 +3,7 @@
- page_card_attributes @merge_request.card_attributes
- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
-- if params[:view] == 'parallel'
+- if diff_view == 'parallel'
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index 8ac653427c9..f5bf16ef3ad 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (#{merge_request.to_reference}", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title"
.merge-request
diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml
index 307a75d02ca..a116ffe2e15 100644
--- a/app/views/projects/merge_requests/show/_builds.html.haml
+++ b/app/views/projects/merge_requests/show/_builds.html.haml
@@ -1 +1,2 @@
-= render "projects/commit/builds", link_to_commit: true
+= render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true
+
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/views/projects/merge_requests/update.js.haml
+++ /dev/null
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 62888e41935..ae13f8428f0 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -8,7 +8,7 @@
group, members with
%strong #{group_links.human_access}
role (#{shared_group_users_count})
- - if current_user.can?(:admin_group, shared_group)
+ - if can?(current_user, :admin_group, shared_group)
.panel-head-actions
= link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index 2c3fca439f3..2c378231237 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -2,97 +2,70 @@
- if @project
%li{class: ("active" if @scope == 'blobs')}
= link_to search_filter_path(scope: 'blobs') do
- = icon('code fw')
- %span
- Code
- %span.badge
- = @search_results.blobs_count
+ Code
+ %span.badge
+ = @search_results.blobs_count
%li{class: ("active" if @scope == 'issues')}
= link_to search_filter_path(scope: 'issues') do
- = icon('exclamation-circle fw')
- %span
- Issues
- %span.badge
- = @search_results.issues_count
+ Issues
+ %span.badge
+ = @search_results.issues_count
%li{class: ("active" if @scope == 'merge_requests')}
= link_to search_filter_path(scope: 'merge_requests') do
- = icon('tasks fw')
- %span
- Merge requests
- %span.badge
- = @search_results.merge_requests_count
+ Merge requests
+ %span.badge
+ = @search_results.merge_requests_count
%li{class: ("active" if @scope == 'milestones')}
= link_to search_filter_path(scope: 'milestones') do
- = icon('clock-o fw')
- %span
- Milestones
- %span.badge
- = @search_results.milestones_count
+ Milestones
+ %span.badge
+ = @search_results.milestones_count
%li{class: ("active" if @scope == 'notes')}
= link_to search_filter_path(scope: 'notes') do
- = icon('comments fw')
- %span
- Comments
- %span.badge
- = @search_results.notes_count
+ Comments
+ %span.badge
+ = @search_results.notes_count
%li{class: ("active" if @scope == 'wiki_blobs')}
= link_to search_filter_path(scope: 'wiki_blobs') do
- = icon('book fw')
- %span
- Wiki
- %span.badge
- = @search_results.wiki_blobs_count
+ Wiki
+ %span.badge
+ = @search_results.wiki_blobs_count
%li{class: ("active" if @scope == 'commits')}
= link_to search_filter_path(scope: 'commits') do
- = icon('history fw')
- %span
- Commits
- %span.badge
- = @search_results.commits_count
+ Commits
+ %span.badge
+ = @search_results.commits_count
- elsif @show_snippets
%li{class: ("active" if @scope == 'snippet_blobs')}
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- = icon('code fw')
- %span
- Snippet Contents
- %span.badge
- = @search_results.snippet_blobs_count
+ Snippet Contents
+ %span.badge
+ = @search_results.snippet_blobs_count
%li{class: ("active" if @scope == 'snippet_titles')}
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- = icon('book fw')
- %span
- Titles and Filenames
- %span.badge
- = @search_results.snippet_titles_count
+ Titles and Filenames
+ %span.badge
+ = @search_results.snippet_titles_count
- else
%li{class: ("active" if @scope == 'projects')}
= link_to search_filter_path(scope: 'projects') do
- = icon('bookmark fw')
- %span
- Projects
- %span.badge
- = @search_results.projects_count
+ Projects
+ %span.badge
+ = @search_results.projects_count
%li{class: ("active" if @scope == 'issues')}
= link_to search_filter_path(scope: 'issues') do
- = icon('exclamation-circle fw')
- %span
- Issues
- %span.badge
- = @search_results.issues_count
+ Issues
+ %span.badge
+ = @search_results.issues_count
%li{class: ("active" if @scope == 'merge_requests')}
= link_to search_filter_path(scope: 'merge_requests') do
- = icon('tasks fw')
- %span
- Merge requests
- %span.badge
- = @search_results.merge_requests_count
+ Merge requests
+ %span.badge
+ = @search_results.merge_requests_count
%li{class: ("active" if @scope == 'milestones')}
= link_to search_filter_path(scope: 'milestones') do
- = icon('clock-o fw')
- %span
- Milestones
- %span.badge
- = @search_results.milestones_count
-
+ Milestones
+ %span.badge
+ = @search_results.milestones_count
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 4ef544136a8..ef1c0296d49 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,47 +1,33 @@
-.dropdown.inline
- %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light Group:
- - if @group.present?
- %strong= @group.name
- - else
- Any
- %b.caret
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Filter results by group
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
- = icon('times')
- .dropdown-content
- %ul
- %li
- = link_to search_filter_path(group_id: nil), class: ("is-active" if !params[:group_id].present?) do
- Any
- %li.divider
- - current_user.authorized_groups.sort_by(&:name).each do |group|
- %li
- = link_to search_filter_path(group_id: group.id, project_id: nil), class: ("is-active" if params[:group_id] == group.id.to_s) do
- = group.name
+- if params[:group_id].present?
+ = hidden_field_tag :group_id, params[:group_id]
+- if params[:project_id].present?
+ = hidden_field_tag :project_id, params[:project_id]
+.dropdown
+ %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } }
+ %span.dropdown-toggle-text
+ Group:
+ - if @group.present?
+ = @group.name
+ - else
+ Any
+ = icon("chevron-down")
+ .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right
+ = dropdown_title("Filter results by group")
+ = dropdown_filter("Search groups")
+ = dropdown_content
+ = dropdown_loading
-.dropdown.inline.prepend-left-10.project-filter
- %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light Project:
- - if @project.present?
- %strong= @project.name_with_namespace
- - else
- Any
- %b.caret
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Filter results by project
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
- = icon('times')
- .dropdown-content
- %ul
- %li
- = link_to search_filter_path(project_id: nil), class: ("is-active" if !params[:project_id].present?) do
- Any
- %li.divider
- - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
- %li
- = link_to search_filter_path(project_id: project.id, group_id: nil), class: ("is-active" if params[:project_id] == project.id.to_s) do
- = project.name_with_namespace
+.dropdown.project-filter
+ %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
+ %span.dropdown-toggle-text
+ Project:
+ - if @project.present?
+ = @project.name_with_namespace
+ - else
+ Any
+ = icon("chevron-down")
+ .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right
+ = dropdown_title("Filter results by project")
+ = dropdown_filter("Search projects")
+ = dropdown_content
+ = dropdown_loading
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index a9dbc84da29..3139be1cd37 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -1,14 +1,15 @@
-= form_tag search_path, method: :get do |f|
- = hidden_field_tag :project_id, params[:project_id]
- = hidden_field_tag :group_id, params[:group_id]
+= form_tag search_path, method: :get, class: 'js-search-form' do |f|
= hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope]
- .search-holder.clearfix
- .input-group
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true, spellcheck: false
- %span.input-group-btn
- = button_tag 'Search', class: "btn btn-primary"
+ .search-holder
+ .search-field-holder
+ = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
+ = icon("search", class: "search-icon")
+ %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
+ = icon("times-circle")
+ %span.sr-only
+ Clear search
- unless params[:snippets].eql? 'true'
- %br
= render 'filter' if current_user
+ = button_tag "Search", class: "btn btn-success btn-search"
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 60df348891c..711337f308e 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,10 +1,8 @@
-- if @search_results.empty?
+- if @search_objects.empty?
= render partial: "search/results/empty"
- else
.gray-content-block
- Search results for
- %code
- = @search_term
+ = search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
@@ -15,12 +13,9 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @objects
+ = render 'shared/projects/list', projects: @search_objects
- else
- = render partial: "search/results/#{@scope.singularize}", collection: @objects
+ = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
- if @scope != 'projects'
- = paginate @objects, theme: 'gitlab'
-
-:javascript
- $(".search-results .term").highlight("#{escape_javascript(params[:search])}");
+ = paginate(@search_objects, theme: 'gitlab')
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 710f5613c81..640890fbe92 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -7,7 +7,7 @@
- if issue.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(issue.description, { project: issue.project }))
+ = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project }))
%span.light
#{issue.project.name_with_namespace}
- if issue.closed?
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 3eaa45258f0..61fd1e9c335 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -8,39 +8,7 @@
= h(multi_label_name(params[:label_name], "Label"))
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Filter by label")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
+ = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" }
- if can? current_user, :admin_label, @project and @project
- .dropdown-page-two.dropdown-new-label
- = dropdown_title("Create new label", back: true)
- = dropdown_content do
- .dropdown-labels-error.js-label-error
- %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
- .suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
- .dropdown-label-color-input
- .dropdown-label-color-preview.js-dropdown-label-color-preview
- %input#new_label_color.dropdown-input-field{ type: "text" }
- .clearfix
- %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"}
- Create
- %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"}
- Cancel
+ = render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
new file mode 100644
index 00000000000..3bc57d3d2ac
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -0,0 +1,17 @@
+.dropdown-page-two.dropdown-new-label
+ = dropdown_title("Create new label", back: true)
+ = dropdown_content do
+ .dropdown-labels-error.js-label-error
+ %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" }
+ .suggest-colors.suggest-colors-dropdown
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp
+ .dropdown-label-color-input
+ .dropdown-label-color-preview.js-dropdown-label-color-preview
+ %input#new_label_color.default-dropdown-input{ type: "text" }
+ .clearfix
+ %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" }
+ Create
+ %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" }
+ Cancel
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
new file mode 100644
index 00000000000..7f4867417f7
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -0,0 +1,20 @@
+- title = local_assigns.fetch(:title, 'Assign labels')
+- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
+.dropdown-page-one
+ = dropdown_title(title)
+ = dropdown_filter(filter_placeholder)
+ = dropdown_content
+ - if @project
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_label, @project
+ %li
+ %a.dropdown-toggle-page{href: "#"}
+ Create new
+ %li
+ = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
+ = dropdown_loading \ No newline at end of file
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index a50b4b96693..ed1b8a8da2a 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -20,7 +20,7 @@
%a.btn.btn-default.issuable-pager.disabled{href: '#'}
Next
- = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
.sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
- if issuable.assignee
@@ -129,24 +129,9 @@
Label
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Assign labels")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
- = dropdown_loading
+ = render partial: "shared/issuable/label_page_default"
+ - if can? current_user, :admin_label, @project and @project
+ = render partial: "shared/issuable/label_page_create"
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
@@ -181,5 +166,5 @@
new LabelsSelect();
new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
new Subscription('.subscription')
- new Sidebar();
new DueDateSelect();
+ sidebar = new Sidebar();
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index e7e04621ff4..1169bed0382 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,4 +1,5 @@
- @sort ||= sort_value_recently_updated
+- personal = params[:personal]
- archived = params[:archived]
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -10,7 +11,7 @@
Sort by
- projects_sort_options_hash.each do |value, title|
%li
- = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
+ = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
= title
%li.divider
@@ -20,3 +21,11 @@
%li
= link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
Show archived projects
+ - if current_user
+ %li.divider
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do
+ Owned by anyone
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do
+ Owned by me
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 53ff8959bc8..ab8b022411d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -6,9 +6,8 @@
- css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
-- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(project.commit.status) if project.commit.try(:status)
%li.project-row{ class: css_class }
= cache(cache_key) do
@@ -16,9 +15,9 @@
- if project.main_language
%span
= project.main_language
- - if ci_commit
+ - if project.commit.try(:status)
%span
- = render_ci_status(ci_commit)
+ = render_ci_status(project.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 dc249155b92..59e12798691 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -19,12 +19,12 @@
var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
var noteable_type = "#{votable.class.name.underscore}";
var noteable_id = "#{votable.id}";
- var aliases = #{AwardEmoji.aliases.to_json};
+ var unicodes = #{AwardEmoji.unicode.to_json};
window.awards_handler = new AwardsHandler(
get_emojis_url,
post_emoji_url,
noteable_type,
noteable_id,
- aliases
+ unicodes
);
diff --git a/bin/rails b/bin/rails
index 5191e6927af..0138d79b751 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,4 +1,9 @@
#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index 17240489f64..d87d5f57810 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,4 +1,9 @@
#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
require_relative '../config/boot'
require 'rake'
Rake.application.run
diff --git a/bin/rspec b/bin/rspec
index 20060ebd79c..6e6709219af 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -1,7 +1,8 @@
#!/usr/bin/env ruby
begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('rspec-core', 'rspec')
diff --git a/bin/spinach b/bin/spinach
index a080e286cfe..474050e29d1 100755
--- a/bin/spinach
+++ b/bin/spinach
@@ -1,7 +1,8 @@
#!/usr/bin/env ruby
begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('spinach', 'spinach')
diff --git a/bin/spring b/bin/spring
index 7b45d374fcd..7fe232c3aae 100755
--- a/bin/spring
+++ b/bin/spring
@@ -4,12 +4,12 @@
# It gets overwritten when you run the `spring binstub` command.
unless defined?(Spring)
- require "rubygems"
- require "bundler"
+ require 'rubygems'
+ require 'bundler'
- if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)
- Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq }
- gem "spring", match[1]
- require "spring/binstub"
+ if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m))
+ Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) }
+ gem 'spring', match[1]
+ require 'spring/binstub'
end
end
diff --git a/bin/teaspoon b/bin/teaspoon
new file mode 100755
index 00000000000..7c3b8dfc4ed
--- /dev/null
+++ b/bin/teaspoon
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+require 'bundler/setup'
+load Gem.bin_path('teaspoon', 'teaspoon')
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 22fe51a4534..283936d0efc 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -107,6 +107,10 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
+
+ # Instrument the classes used for checking if somebody has push access.
+ config.instrument_instance_methods(Gitlab::GitAccess)
+ config.instrument_instance_methods(Gitlab::GitAccessWiki)
end
GC::Profiler.enable
diff --git a/config/routes.rb b/config/routes.rb
index 79b62a0b1bb..2f820aafed1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -418,6 +418,7 @@ Rails.application.routes.draw do
devise_scope :user do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
+ get '/users/almost_there' => 'confirmations#almost_there'
end
root to: "root#index"
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index e3ca2b4eea3..b99d24a03c9 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds
commits = @project.repository.commits('master', nil, 5)
commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha|
- @project.ensure_ci_commit(sha)
+ @project.ensure_ci_commit(sha, 'master')
end
rescue
[]
diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
index 003169c13c6..d7b00e3d6ed 100644
--- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
+++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
@@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
def up
return unless Gitlab::Database.postgresql?
+ create_trigrams_extension
+
unless trigrams_enabled?
raise 'You must enable the pg_trgm extension. You can do so by running ' \
'"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \
@@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
row && row['enabled'] == 't' ? true : false
end
+ def create_trigrams_extension
+ # This may not work if the user doesn't have permission. We attempt in
+ # case we do have permission, particularly for test/dev environments.
+ begin
+ enable_extension 'pg_trgm'
+ rescue
+ end
+ end
+
def to_index
{
ci_runners: [:token, :description],
diff --git a/db/migrate/20160412173416_add_fields_to_ci_commit.rb b/db/migrate/20160412173416_add_fields_to_ci_commit.rb
new file mode 100644
index 00000000000..125956a3ddd
--- /dev/null
+++ b/db/migrate/20160412173416_add_fields_to_ci_commit.rb
@@ -0,0 +1,8 @@
+class AddFieldsToCiCommit < ActiveRecord::Migration
+ def change
+ add_column :ci_commits, :status, :string
+ add_column :ci_commits, :started_at, :timestamp
+ add_column :ci_commits, :finished_at, :timestamp
+ add_column :ci_commits, :duration, :integer
+ end
+end
diff --git a/db/migrate/20160412173417_update_ci_commit.rb b/db/migrate/20160412173417_update_ci_commit.rb
new file mode 100644
index 00000000000..fd92444dbac
--- /dev/null
+++ b/db/migrate/20160412173417_update_ci_commit.rb
@@ -0,0 +1,35 @@
+class UpdateCiCommit < ActiveRecord::Migration
+ # This migration can be run online, but needs to be executed for the second time after restarting Unicorn workers
+ # Otherwise Offline migration should be used.
+ def change
+ execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL")
+ end
+
+ private
+
+ def status
+ builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)'
+ success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')"
+ ignored = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND (status='failed' OR status='canceled') AND allow_failure)"
+ pending = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='pending')"
+ running = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='running')"
+ canceled = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='canceled')"
+
+ "(CASE
+ WHEN #{builds}=0 THEN 'skipped'
+ WHEN #{builds}=#{success}+#{ignored} THEN 'success'
+ WHEN #{builds}=#{pending} THEN 'pending'
+ WHEN #{builds}=#{canceled} THEN 'canceled'
+ WHEN #{running}+#{pending}>0 THEN 'running'
+ ELSE 'failed'
+ END)"
+ end
+
+ def ref
+ '(SELECT ref FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
+ end
+
+ def tag
+ '(SELECT tag FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
+ end
+end
diff --git a/db/migrate/20160412173418_add_ci_commit_indexes.rb b/db/migrate/20160412173418_add_ci_commit_indexes.rb
new file mode 100644
index 00000000000..603d4a41610
--- /dev/null
+++ b/db/migrate/20160412173418_add_ci_commit_indexes.rb
@@ -0,0 +1,19 @@
+class AddCiCommitIndexes < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ def change
+ add_index :ci_commits, [:gl_project_id, :sha], index_options
+ add_index :ci_commits, [:gl_project_id, :status], index_options
+ add_index :ci_commits, [:status], index_options
+ end
+
+ private
+
+ def index_options
+ if Gitlab::Database.postgresql?
+ { algorithm: :concurrently }
+ else
+ { }
+ end
+ end
+end
diff --git a/db/migrate/20160421130527_disable_repository_checks.rb b/db/migrate/20160421130527_disable_repository_checks.rb
new file mode 100644
index 00000000000..808a4b93c7c
--- /dev/null
+++ b/db/migrate/20160421130527_disable_repository_checks.rb
@@ -0,0 +1,11 @@
+class DisableRepositoryChecks < ActiveRecord::Migration
+ def up
+ change_column_default :application_settings, :repository_checks_enabled, false
+ execute 'UPDATE application_settings SET repository_checks_enabled = false'
+ end
+
+ def down
+ change_column_default :application_settings, :repository_checks_enabled, true
+ execute 'UPDATE application_settings SET repository_checks_enabled = true'
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a93ba690730..42457d92353 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: 20160419120017) do
+ActiveRecord::Schema.define(version: 20160421130527) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -77,7 +77,7 @@ ActiveRecord::Schema.define(version: 20160419120017) do
t.string "akismet_api_key"
t.boolean "email_author_in_body", default: false
t.integer "default_group_visibility"
- t.boolean "repository_checks_enabled", default: true
+ t.boolean "repository_checks_enabled", default: false
t.integer "metrics_packet_size", default: 1
t.text "shared_runners_text"
end
@@ -171,14 +171,21 @@ ActiveRecord::Schema.define(version: 20160419120017) do
t.text "yaml_errors"
t.datetime "committed_at"
t.integer "gl_project_id"
+ t.string "status"
+ t.datetime "started_at"
+ t.datetime "finished_at"
+ t.integer "duration"
end
+ add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
+ add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree
add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree
add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree
add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
+ add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree
create_table "ci_events", force: :cascade do |t|
t.integer "project_id"
diff --git a/doc/README.md b/doc/README.md
index e6ac4794827..e358da1c424 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -41,6 +41,8 @@
- [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics
+- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs
+- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability
## Contributor documentation
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
new file mode 100644
index 00000000000..43d85ffb775
--- /dev/null
+++ b/doc/administration/high_availability/README.md
@@ -0,0 +1,35 @@
+# High Availability
+
+GitLab supports several different types of clustering and high-availability.
+The solution you choose will be based on the level of scalability and
+availability you require. The easiest solutions are scalable, but not necessarily
+highly available.
+
+## Architecture
+
+### Active/Passive
+
+For pure high-availability/failover with no scaling you can use an
+active/passive configuration. This utilizes DRBD (Distributed Replicated
+Block Device) to keep all data in sync. DRBD requires a low latency link to
+remain in sync. It is not advisable to attempt to run DRBD between data centers
+or in different cloud availability zones.
+
+Components/Servers Required:
+
+- 2 servers/virtual machines (one active/one passive)
+
+### Active/Active
+
+This architecture scales easily because all application servers handle
+user requests simultaneously. The database, Redis, and GitLab application are
+all deployed on separate servers. The configuration is **only** highly-available
+if the database, Redis and storage are also configured as such.
+
+**Steps to configure active/active:**
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
new file mode 100644
index 00000000000..538dada1bae
--- /dev/null
+++ b/doc/administration/high_availability/database.md
@@ -0,0 +1,116 @@
+# Configuring a Database for GitLab HA
+
+You can choose to install and manage a database server (PostgreSQL/MySQL)
+yourself, or you can use GitLab Omnibus packages to help. GitLab recommends
+PostgreSQL. This is the database that will be installed if you use the
+Omnibus package to manage your database.
+
+## Configure your own database server
+
+If you're hosting GitLab on a cloud provider, you can optionally use a
+managed service for PostgreSQL. For example, AWS offers a managed Relational
+Database Service (RDS) that runs PostgreSQL.
+
+If you use a cloud-managed service, or provide your own PostgreSQL:
+
+1. Set up a `gitlab` username with a password of your choice. The `gitlab` user
+ needs privileges to create the `gitlabhq_production` database.
+1. Configure the GitLab application servers with the appropriate details.
+ This step is covered in [Configuring GitLab for HA](gitlab.md)
+
+## Configure using Omnibus
+
+1. Download/install GitLab Omnibus using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ Be sure to change the `external_url` to match your eventual GitLab front-end
+ URL.
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Disable all components except PostgreSQL
+ postgresql['enable'] = true
+ bootstrap['enable'] = false
+ nginx['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ redis['enable'] = false
+ gitlab_workhorse['enable'] = false
+ mailroom['enable'] = false
+
+ # PostgreSQL configuration
+ postgresql['sql_password'] = 'DB password'
+ postgresql['md5_auth_cidr_addresses'] = ['0.0.0.0/0']
+ postgresql['listen_address'] = '0.0.0.0'
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure` to install and configure PostgreSQL.
+
+ > **Note**: This `reconfigure` step will result in some errors.
+ That's OK - don't be alarmed.
+
+1. Open a database prompt:
+
+ ```
+ su - gitlab-psql
+ /bin/bash
+ psql -h /var/opt/gitlab/postgresql -d template1
+
+ # Output:
+
+ psql (9.2.15)
+ Type "help" for help.
+
+ template1=#
+ ```
+
+1. Run the following command at the database prompt and you will be asked to
+ enter the new password for the PostgreSQL superuser.
+
+ ```
+ \password
+
+ # Output:
+
+ Enter new password:
+ Enter it again:
+ ```
+
+1. Similarly, set the password for the `gitlab` database user. Use the same
+ password that you specified in the `/etc/gitlab/gitlab.rb` file for
+ `postgresql['sql_password']`.
+
+ ```
+ \password gitlab
+
+ # Output:
+
+ Enter new password:
+ Enter it again:
+ ```
+
+1. Enable the `pg_trgm` extension:
+ ```
+ CREATE EXTENSION pg_trgm;
+
+ # Output:
+
+ CREATE EXTENSION
+ ```
+1. Exit the database prompt by typing `\q` and Enter.
+1. Exit the `gitlab-psql` user by running `exit` twice.
+1. Run `sudo gitlab-ctl reconfigure` a final time.
+1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
+ from running on upgrade. Only the primary GitLab application server should
+ handle migrations.
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
new file mode 100644
index 00000000000..8a881ce8863
--- /dev/null
+++ b/doc/administration/high_availability/gitlab.md
@@ -0,0 +1,131 @@
+# Configuring GitLab for HA
+
+Assuming you have already configured a database, Redis, and NFS, you can
+configure the GitLab application server(s) now. Complete the steps below
+for each GitLab application server in your environment.
+
+> **Note:** There is some additional configuration near the bottom for
+ secondary GitLab application servers. It's important to read and understand
+ these additional steps before proceeding with GitLab installation.
+
+1. If necessary, install the NFS client utility packages using the following
+ commands:
+
+ ```
+ # Ubuntu/Debian
+ apt-get install nfs-common
+
+ # CentOS/Red Hat
+ yum install nfs-utils nfs-utils-lib
+ ```
+
+1. Specify the necessary NFS shares. Mounts are specified in
+ `/etc/fstab`. The exact contents of `/etc/fstab` will depend on how you chose
+ to configure your NFS server. See [NFS documentation](nfs.md) for the various
+ options. Here is an example snippet to add to `/etc/fstab`:
+
+ ```
+ 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ ```
+
+1. Create the shared directories. These may be different depending on your NFS
+ mount locations.
+
+ ```
+ mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data
+ ```
+
+1. Download/install GitLab Omnibus using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ Be sure to change the `external_url` to match your eventual GitLab front-end
+ URL. Depending your the NFS configuration, you may need to change some GitLab
+ data locations. See [NFS documentation](nfs.md) for `/etc/gitlab/gitlab.rb`
+ configuration values for various scenarios. The example below assumes you've
+ added NFS mounts in the default data locations.
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Prevent GitLab from starting if NFS data mounts are not available
+ high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
+
+ # Disable components that will not be on the GitLab application server
+ postgresql['enable'] = false
+ redis['enable'] = false
+
+ # PostgreSQL connection details
+ gitlab_rails['db_adapter'] = 'postgresql'
+ gitlab_rails['db_encoding'] = 'unicode'
+ gitlab_rails['db_host'] = '10.1.0.5' # IP/hostname of database server
+ gitlab_rails['db_password'] = 'DB password'
+
+ # Redis connection details
+ gitlab_rails['redis_port'] = '6379'
+ gitlab_rails['redis_host'] = '10.1.0.6' # IP/hostname of Redis server
+ gitlab_rails['redis_password'] = 'Redis Password'
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
+
+## Primary GitLab application server
+
+As a final step, run the setup rake task on the first GitLab application server.
+It is not necessary to run this on additional application servers.
+
+1. Initialize the database by running `sudo gitlab-rake gitlab:setup`.
+
+> **WARNING:** Only run this setup task on **NEW** GitLab instances because it
+ will wipe any existing data.
+
+> **Note:** When you specify `https` in the `external_url`, as in the example
+ above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
+ certificates are not present, Nginx will fail to start. See
+ [Nginx documentation](http://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+ for more information.
+
+## Additional configuration for secondary GitLab application servers
+
+Secondary GitLab servers (servers configured **after** the first GitLab server)
+need some additional configuration.
+
+1. Configure shared secrets. These values can be obtained from the primary
+ GitLab server in `/etc/gitlab/gitlab-secrets.json`. Add these to
+ `/etc/gitlab/gitlab.rb` **prior to** running the first `reconfigure` in
+ the steps above.
+
+ ```ruby
+ gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860'
+ gitlab_rails['secret_token'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa'
+ gitlab_ci['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d'
+ gitlab_ci['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964'
+ ```
+
+1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
+ from running on upgrade. Only the primary GitLab application server should
+ handle migrations.
+
+## Troubleshooting
+
+- `mount: wrong fs type, bad option, bad superblock on`
+
+You have not installed the necessary NFS client utilities. See step 1 above.
+
+- `mount: mount point /var/opt/gitlab/... does not exist`
+
+This particular directory does not exist on the NFS server. Ensure
+the share is exported and exists on the NFS server and try to remount.
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
new file mode 100644
index 00000000000..b1fe34ed9a1
--- /dev/null
+++ b/doc/administration/high_availability/load_balancer.md
@@ -0,0 +1,63 @@
+# Load Balancer for GitLab HA
+
+In an active/active GitLab configuration, you will need a load balancer to route
+traffic to the application servers. The specifics on which load balancer to use
+or the exact configuration is beyond the scope of GitLab documentation. We hope
+that if you're managing HA systems like GitLab you have a load balancer of
+choice already. Some examples including HAProxy (open-source), F5 Big-IP LTM,
+and Citrix Net Scaler. This documentation will outline what ports and protocols
+you need to use with GitLab.
+
+## Basic ports
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 80 | 80 | HTTP |
+| 443 | 443 | HTTPS [^1] |
+| 22 | 22 | TCP |
+
+## GitLab Pages Ports
+
+If you're using GitLab Pages you will need some additional port configurations.
+GitLab Pages requires a separate VIP. Configure DNS to point the
+`pages_external_url` from `/etc/gitlab/gitlab.rb` at the new VIP. See the
+[GitLab Pages documentation][gitlab-pages] for more information.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 80 | Varies [^2] | HTTP |
+| 443 | Varies [^2] | TCP [^3] |
+
+## Alternate SSH Port
+
+Some organizations have policies against opening SSH port 22. In this case,
+it may be helpful to configure an alternate SSH hostname that allows users
+to use SSH on port 443. An alternate SSH hostname will require a new VIP
+compared to the other GitLab HTTP configuration above.
+
+Configure DNS for an alternate SSH hostname such as altssh.gitlab.example.com.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 443 | 22 | TCP |
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+
+[^1]: When using HTTPS protocol for port 443, you will need to add an SSL
+ certificate to the load balancers. If you wish to terminate SSL at the
+ GitLab application server instead, use TCP protocol.
+[^2]: The backend port for GitLab Pages depends on the
+ `gitlab_pages['external_http']` and `gitlab_pages['external_https']`
+ setting. See [GitLab Pages documentation][gitlab-pages] for more details.
+[^3]: Port 443 for GitLab Pages should always use the TCP protocol. Users can
+ configure custom domains with custom SSL, which would not be possible
+ if SSL was terminated at the load balancer.
+
+[gitlab-pages]: http://doc.gitlab.com/ee/pages/administration.html
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
new file mode 100644
index 00000000000..e4e124e200a
--- /dev/null
+++ b/doc/administration/high_availability/nfs.md
@@ -0,0 +1,116 @@
+# NFS
+
+## Required NFS Server features
+
+**File locking**: GitLab **requires** file locking which is only supported
+natively in NFS version 4. NFSv3 also supports locking as long as
+Linux Kernel 2.6.5+ is used. We recommend using version 4 and do not
+specifically test NFSv3.
+
+**no_root_squash**: NFS normally changes the `root` user to `nobody`. This is
+a good security measure when NFS shares will be accessed by many different
+users. However, in this case only GitLab will use the NFS share so it
+is safe. GitLab requires the `no_root_squash` setting because we need to
+manage file permissions automatically. Without the setting you will receive
+errors when the Omnibus package tries to alter permissions. Note that GitLab
+and other bundled components do **not** run as `root` but as non-privileged
+users. The requirement for `no_root_squash` is to allow the Omnibus package to
+set ownership and permissions on files, as needed.
+
+### Recommended options
+
+When you define your NFS exports, we recommend you also add the following
+options:
+
+- `sync` - Force synchronous behavior. Default is asynchronous and under certain
+ circumstances it could lead to data loss if a failure occurs before data has
+ synced.
+
+## Client mount options
+
+Below is an example of an NFS mount point we use on GitLab.com:
+
+```
+10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+```
+
+Notice several options that you should consider using:
+
+| Setting | Description |
+| ------- | ----------- |
+| `nobootwait` | Don't halt boot process waiting for this mount to become available
+| `lookupcache=positive` | Tells the NFS client to honor `positive` cache results but invalidates any `negative` cache results. Negative cache results cause problems with Git. Specifically, a `git push` can fail to register uniformly across all NFS clients. The negative cache causes the clients to 'remember' that the files did not exist previously.
+
+## Mount locations
+
+When using default Omnibus configuration you will need to share 5 data locations
+between all GitLab cluster nodes. No other locations should be shared. The
+following are the 5 locations you need to mount:
+
+| Location | Description |
+| -------- | ----------- |
+| `/var/opt/gitlab/git-data` | Git repository data. This will account for a large portion of your data
+| `/var/opt/gitlab/.ssh` | SSH `authorized_keys` file and keys used to import repositories from some other Git services
+| `/var/opt/gitlab/gitlab-rails/uploads` | User uploaded attachments
+| `/var/opt/gitlab/gitlab-rails/shared` | Build artifacts, GitLab Pages, LFS objects, temp files, etc. If you're using LFS this may also account for a large portion of your data
+| `/var/opt/gitlab/gitlab-ci/builds` | GitLab CI build traces
+
+Other GitLab directories should not be shared between nodes. They contain
+node-specific files and GitLab code that does not need to be shared. To ship
+logs to a central location consider using remote syslog. GitLab Omnibus packages
+provide configuration for [UDP log shipping][udp-log-shipping].
+
+### Consolidating mount points
+
+If you don't want to configure 5-6 different NFS mount points, you have a few
+alternative options.
+
+#### Change default file locations
+
+Omnibus allows you to configure the file locations. With custom configuration
+you can specify just one main mountpoint and have all of these locations
+as subdirectories. Mount `/gitlab-data` then use the following Omnibus
+configuration to move each data location to a subdirectory:
+
+```ruby
+user['home'] = '/gitlab-data/home'
+git_data_dir '/gitlab-data/git-data'
+gitlab_rails['shared_path'] = '/gitlab-data/shared'
+gitlab_rails['uploads_directory'] = "/gitlab-data/uploads"
+gitlab_ci['builds_directory'] = '/gitlab-data/builds'
+```
+
+To move the `git` home directory, all GitLab services must be stopped. Run
+`gitlab-ctl stop && initctl stop gitlab-runsvdir`. Then continue with the
+reconfigure.
+
+Run `sudo gitlab-ctl reconfigure` to start using the central location. Please
+be aware that if you had existing data you will need to manually copy/rsync it
+to these new locations and then restart GitLab.
+
+#### Bind mounts
+
+Bind mounts provide a way to specify just one NFS mount and then
+bind the default GitLab data locations to the NFS mount. Start by defining your
+single NFS mount point as you normally would in `/etc/fstab`. Let's assume your
+NFS mount point is `/gitlab-data`. Then, add the following bind mounts in
+`/etc/fstab`:
+
+```bash
+/gitlab-data/git-data /var/opt/gitlab/git-data none bind 0 0
+/gitlab-data/.ssh /var/opt/gitlab/.ssh none bind 0 0
+/gitlab-data/uploads /var/opt/gitlab/gitlab-rails/uploads none bind 0 0
+/gitlab-data/shared /var/opt/gitlab/gitlab-rails/shared none bind 0 0
+/gitlab-data/builds /var/opt/gitlab/gitlab-ci/builds none bind 0 0
+```
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
+
+[udp-log-shipping]: http://doc.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
new file mode 100644
index 00000000000..d89a1e582ca
--- /dev/null
+++ b/doc/administration/high_availability/redis.md
@@ -0,0 +1,62 @@
+# Configuring Redis for GitLab HA
+
+You can choose to install and manage Redis yourself, or you can use GitLab
+Omnibus packages to help.
+
+## Configure your own Redis server
+
+If you're hosting GitLab on a cloud provider, you can optionally use a
+managed service for Redis. For example, AWS offers a managed ElastiCache service
+that runs Redis.
+
+> **Note:** Redis does not require authentication by default. See
+ [Redis Security](http://redis.io/topics/security) documentation for more
+ information. We recommend using a combination of a Redis password and tight
+ firewall rules to secure your Redis service.
+
+## Configure using Omnibus
+
+1. Download/install GitLab Omnibus using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ Be sure to change the `external_url` to match your eventual GitLab front-end
+ URL.
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Disable all components except PostgreSQL
+ redis['enable'] = true
+ bootstrap['enable'] = false
+ nginx['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ postgresql['enable'] = false
+ gitlab_workhorse['enable'] = false
+ mailroom['enable'] = false
+
+ # Redis configuration
+ redis['port'] = 6379
+ redis['bind'] = '0.0.0.0'
+
+ # If you wish to use Redis authentication (recommended)
+ redis['password'] = 'Redis Password'
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure` to install and configure PostgreSQL.
+
+ > **Note**: This `reconfigure` step will result in some errors.
+ That's OK - don't be alarmed.
+1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
+ from running on upgrade. Only the primary GitLab application server should
+ handle migrations.
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 61bf8ce6161..3411e4af6a7 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -1,7 +1,8 @@
# Repository checks
>**Note:**
-This feature was [introduced][ce-3232] in GitLab 8.7.
+This feature was [introduced][ce-3232] in GitLab 8.7. It is OFF by
+default because it still causes too many false alarms.
Git has a built-in mechanism, [git fsck][git-fsck], to verify the
integrity of all data commited to a repository. GitLab administrators
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
new file mode 100644
index 00000000000..134a7583762
--- /dev/null
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -0,0 +1,162 @@
+# Troubleshooting Sidekiq
+
+Sidekiq is the background job processor GitLab uses to asynchronously run
+tasks. When things go wrong it can be difficult to troubleshoot. These
+situations also tend to be high-pressure because a production system job queue
+may be filling up. Users will notice when this happens because new branches
+may not show up and merge requests may not be updated. The following are some
+troubleshooting steps that will help you diagnose the bottleneck.
+
+> **Note:** GitLab administrators/users should consider working through these
+debug steps with GitLab Support so the backtraces can be analyzed by our team.
+It may reveal a bug or necessary improvement in GitLab.
+
+> **Note:** In any of the backtraces, be weary of suspecting cases where every
+ thread appears to be waiting in the database, Redis, or waiting to acquire
+ a mutex. This **may** mean there's contention in the database, for example,
+ but look for one thread that is different than the rest. This other thread
+ may be using all available CPU, or have a Ruby Global Interpreter Lock,
+ preventing other threads from continuing.
+
+## Thread dump
+
+Send the Sidekiq process ID the `TTIN` signal and it will output thread
+backtraces in the log file.
+
+```
+kill -TTIN <sidekiq_pid>
+```
+
+Check in `/var/log/gitlab/sidekiq/current` or `$GITLAB_HOME/log/sidekiq.log` for
+the backtrace output. The backtraces will be lengthy and generally start with
+several `WARN` level messages. Here's an example of a single thread's backtrace:
+
+```
+2016-04-13T06:21:20.022Z 31517 TID-orn4urby0 WARN: ActiveRecord::RecordNotFound: Couldn't find Note with 'id'=3375386
+2016-04-13T06:21:20.022Z 31517 TID-orn4urby0 WARN: /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/activerecord-4.2.5.2/lib/active_record/core.rb:155:in `find'
+/opt/gitlab/embedded/service/gitlab-rails/app/workers/new_note_worker.rb:7:in `perform'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/processor.rb:150:in `execute_job'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/processor.rb:132:in `block (2 levels) in process'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/middleware/chain.rb:127:in `block in invoke'
+/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/sidekiq_middleware/memory_killer.rb:17:in `call'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/middleware/chain.rb:129:in `block in invoke'
+/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/sidekiq_middleware/arguments_logger.rb:6:in `call'
+...
+```
+
+In some cases Sidekiq may be hung and unable to respond to the `TTIN` signal.
+Move on to other troubleshooting methods if this happens.
+
+## Process profiling with `perf`
+
+Linux has a process profiling tool called `perf` that is helpful when a certain
+process is eating up a lot of CPU. If you see high CPU usage and Sidekiq won't
+respond to the `TTIN` signal, this is a good next step.
+
+If `perf` is not installed on your system, install it with `apt-get` or `yum`:
+
+```
+# Debian
+sudo apt-get install linux-tools
+
+# Ubuntu (may require these additional Kernel packages)
+sudo apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r`
+
+# Red Hat/CentOS
+sudo yum install perf
+```
+
+Run perf against the Sidekiq PID:
+
+```
+sudo perf record -p <sidekiq_pid>
+```
+
+Let this run for 30-60 seconds and then press Ctrl-C. Then view the perf report:
+
+```
+sudo perf report
+
+# Sample output
+Samples: 348K of event 'cycles', Event count (approx.): 280908431073
+ 97.69% ruby nokogiri.so [.] xmlXPathNodeSetMergeAndClear
+ 0.18% ruby libruby.so.2.1.0 [.] objspace_malloc_increase
+ 0.12% ruby libc-2.12.so [.] _int_malloc
+ 0.10% ruby libc-2.12.so [.] _int_free
+```
+
+Above you see sample output from a perf report. It shows that 97% of the CPU is
+being spent inside Nokogiri and `xmlXPathNodeSetMergeAndClear`. For something
+this obvious you should then go investigate what job in GitLab would use
+Nokogiri and XPath. Combine with `TTIN` or `gdb` output to show the
+corresponding Ruby code where this is happening.
+
+## The GNU Project Debugger (gdb)
+
+`gdb` can be another effective tool for debugging Sidekiq. It gives you a little
+more interactive way to look at each thread and see what's causing problems.
+
+> **Note:** Attaching to a process with `gdb` will suspends the normal operation
+ of the process (Sidekiq will not process jobs while `gdb` is attached).
+
+Start by attaching to the Sidekiq PID:
+
+```
+gdb -p <sidekiq_pid>
+```
+
+Then gather information on all the threads:
+
+```
+info threads
+
+# Example output
+30 Thread 0x7fe5fbd63700 (LWP 26060) 0x0000003f7cadf113 in poll () from /lib64/libc.so.6
+29 Thread 0x7fe5f2b3b700 (LWP 26533) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+28 Thread 0x7fe5f2a3a700 (LWP 26534) 0x0000003f7ce0ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+27 Thread 0x7fe5f2939700 (LWP 26535) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+26 Thread 0x7fe5f2838700 (LWP 26537) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+25 Thread 0x7fe5f2737700 (LWP 26538) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+24 Thread 0x7fe5f2535700 (LWP 26540) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+23 Thread 0x7fe5f2434700 (LWP 26541) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+22 Thread 0x7fe5f2232700 (LWP 26543) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+21 Thread 0x7fe5f2131700 (LWP 26544) 0x00007fe5f7b570f0 in xmlXPathNodeSetMergeAndClear ()
+from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+...
+```
+
+If you see a suspicious thread, like the Nokogiri one above, you may want
+to get more information:
+
+```
+thread 21
+bt
+
+# Example output
+#0 0x00007ff0d6afe111 in xmlXPathNodeSetMergeAndClear () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#1 0x00007ff0d6b0b836 in xmlXPathNodeCollectAndTest () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#2 0x00007ff0d6b09037 in xmlXPathCompOpEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#3 0x00007ff0d6b09017 in xmlXPathCompOpEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#4 0x00007ff0d6b092e0 in xmlXPathCompOpEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#5 0x00007ff0d6b0bc37 in xmlXPathRunEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#6 0x00007ff0d6b0be5f in xmlXPathEvalExpression () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#7 0x00007ff0d6a97dc3 in evaluate (argc=2, argv=0x1022d058, self=<value optimized out>) at xml_xpath_context.c:221
+#8 0x00007ff0daeab0ea in vm_call_cfunc_with_frame (th=0x1022a4f0, reg_cfp=0x1032b810, ci=<value optimized out>) at vm_insnhelper.c:1510
+```
+
+To output a backtrace from all threads at once:
+
+```
+apply all thread bt
+```
+
+## Check for blocking queries
+
+Sometimes the speed at which Sidekiq processes jobs can be so fast that it can
+cause database contention. Check for blocking queries when backtraces above
+show that many threads are stuck in the database adapter.
+
+The PostgreSQL wiki has details on the query you can run to see blocking
+queries. The query is different based on PostgreSQL version. See
+[Lock Monitoring](https://wiki.postgresql.org/wiki/Lock_Monitoring) for
+the query details.
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index bd748f1b986..84212fb3c61 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -239,8 +239,8 @@ is specific to your project.
Then create some service containers:
```
-docker run -d -n service-mysql mysql:latest
-docker run -d -n service-postgres postgres:latest
+docker run -d --name service-mysql mysql:latest
+docker run -d --name service-postgres postgres:latest
```
This will create two service containers, named `service-mysql` and
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index aeadd6a448e..db077927126 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -40,7 +40,7 @@ repository with the following content:
#!/bin/bash
# We need to install dependencies only for Docker
-[[ ! -e /.dockerinit ]] && exit 0
+[[ ! -e /.dockerenv ]] && [[ ! -e /.dockerinit ]] && exit 0
set -xe
diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md
new file mode 100644
index 00000000000..3625c4191b8
--- /dev/null
+++ b/doc/downgrade_ee_to_ce/README.md
@@ -0,0 +1,82 @@
+# Downgrading from EE to CE
+
+If you ever decide to downgrade your Enterprise Edition back to the Community
+Edition, there are a few steps you need take before installing the CE package
+on top of the current EE package, or, if you are in an installation from source,
+before you change remotes and fetch the latest CE code.
+
+## Disable Enterprise-only features
+
+First thing to do is to disable the following features.
+
+### Authentication mechanisms
+
+Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so
+you should disable these mechanisms before downgrading and you should provide
+alternative authentication methods to your users.
+
+### Git Annex
+
+Git Annex is also only available on the Enterprise Edition. This means that if
+you have repositories that use Git Annex to store large files, these files will
+no longer be easily available via Git. You should consider migrating these
+repositories to use Git LFS before downgrading to the Community Edition.
+
+### Remove Jenkins CI Service entries from the database
+
+The `JenkinsService` class is only available on the Enterprise Edition codebase,
+so if you downgrade to the Community Edition, you'll come across the following
+error:
+
+```
+Completed 500 Internal Server Error in 497ms (ActiveRecord: 32.2ms)
+
+ActionView::Template::Error (The single-table inheritance mechanism failed to locate the subclass: 'JenkinsService'. This
+error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this
+column if you didn't intend it to be used for storing the inheritance class or overwrite Service.inheritance_column to
+use another column for that information.)
+```
+
+All services are created automatically for every project you have, so in order
+to avoid getting this error, you need to remove all instances of the
+`JenkinsService` from your database:
+
+**Omnibus Installation**
+
+```
+$ sudo gitlab-rails runner "Service.where(type: 'JenkinsService').delete_all"
+```
+
+**Source Installation**
+
+```
+$ bundle exec rails runner "Service.where(type: 'JenkinsService').delete_all" production
+```
+
+## Downgrade to CE
+
+After performing the above mentioned steps, you are now ready to downgrade your
+GitLab installation to the Community Edition.
+
+**Omnibus Installation**
+
+To downgrade an Omnibus installation, it is sufficient to install the Community
+Edition package on top of the currently installed one. You can do this manually,
+by directly [downloading the package](https://packages.gitlab.com/gitlab/gitlab-ce)
+you need, or by adding our CE package repository and following the
+[CE installation instructions](https://about.gitlab.com/downloads/).
+
+**Source Installation**
+
+To downgrade a source installation, you need to replace the current remote of
+your GitLab installation with the Community Edition's remote, fetch the latest
+changes, and checkout the latest stable branch:
+
+```
+$ git remote set-url origin git@gitlab.com:gitlab-org/gitlab-ce.git
+$ git fetch --all
+$ git checkout 8-x-stable
+```
+
+Remember to follow the correct [update guides](../update/README.md) to make
+sure all dependencies are up to date.
diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md
index a79c8d48d3b..168bd85c26a 100644
--- a/doc/monitoring/performance/grafana_configuration.md
+++ b/doc/monitoring/performance/grafana_configuration.md
@@ -59,34 +59,53 @@ This will drop you in to an InfluxDB interactive session. Copy the entire
contents below and paste it in to the interactive session:
```
-CREATE RETENTION POLICY gitlab_30d ON gitlab DURATION 30d REPLICATION 1 DEFAULT
-CREATE RETENTION POLICY seven_days ON gitlab DURATION 7d REPLICATION 1
-CREATE CONTINUOUS QUERY rails_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.rails_transaction_counts FROM rails_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.sidekiq_transaction_counts FROM sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.rails_method_call_timings FROM rails_method_calls GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.sidekiq_method_call_timings FROM sidekiq_method_calls GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.rails_method_call_timings_per_method FROM rails_method_calls GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.sidekiq_method_call_timings_per_method FROM sidekiq_method_calls GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY rails_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.rails_memory_usage_per_minute FROM rails_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.sidekiq_memory_usage_per_minute FROM sidekiq_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.sidekiq_file_descriptors_per_minute FROM sidekiq_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.rails_file_descriptors_per_minute FROM rails_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.rails_gc_counts_per_minute FROM rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.sidekiq_gc_counts_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.rails_gc_timings_per_minute FROM rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.sidekiq_gc_timings_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.rails_gc_major_minor_per_minute FROM rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.sidekiq_gc_major_minor_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_allowed_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_allowed_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_allowed_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_allowed_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_authorized_keys_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_authorized_keys_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.rails_transaction_timings FROM rails_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.sidekiq_transaction_timings FROM sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_transaction_counts FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.grape_transaction_timings FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END;
+CREATE RETENTION POLICY default ON gitlab DURATION 1h REPLICATION 1 DEFAULT
+CREATE RETENTION POLICY downsampled ON gitlab DURATION 7d REPLICATION 1
+CREATE CONTINUOUS QUERY grape_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY grape_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY grape_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method, action END;
+CREATE CONTINUOUS QUERY grape_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method END;
+CREATE CONTINUOUS QUERY grape_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY grape_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.rails_file_descriptor_counts FROM gitlab."default".rails_file_descriptors GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.rails_gc_counts FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.rails_gc_timings FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.rails_memory_usage_overall FROM gitlab."default".rails_memory_usage GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method, action END;
+CREATE CONTINUOUS QUERY rails_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method END;
+CREATE CONTINUOUS QUERY rails_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_overall FROM gitlab."default".rails_object_counts GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_per_type FROM gitlab."default".rails_object_counts GROUP BY time(1m), type END;
+CREATE CONTINUOUS QUERY rails_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_view_timings_per_action_and_view FROM gitlab."default".rails_views WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action, view END;
+CREATE CONTINUOUS QUERY sidekiq_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.sidekiq_file_descriptor_counts FROM gitlab."default".sidekiq_file_descriptors GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.sidekiq_gc_counts FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.sidekiq_gc_timings FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_git_timings_per_action FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_overall FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_per_action FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_markdown_timings_overall FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^Banzai/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.sidekiq_memory_usage_overall FROM gitlab."default".sidekiq_memory_usage GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_action_and_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method, action END;
+CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method END;
+CREATE CONTINUOUS QUERY sidekiq_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_overall FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_per_type FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m), type END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_view_timings_per_action_and_view FROM gitlab."default".sidekiq_views GROUP BY time(1m), action, view END;
+CREATE CONTINUOUS QUERY web_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.web_transaction_counts_overall FROM gitlab."default".rails_transactions GROUP BY time(1m) END;
```
## Import Dashboards
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
index 8599133a726..4a2c6ea91d2 100644
--- a/doc/update/8.6-to-8.7.md
+++ b/doc/update/8.6-to-8.7.md
@@ -45,8 +45,8 @@ sudo -u git -H git checkout 8-7-stable-ee
```bash
cd /home/git/gitlab-shell
-sudo -u git -H git fetch --all
-sudo -u git -H git checkout v2.7.0
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v2.7.2
```
### 5. Update gitlab-workhorse
diff --git a/doc/update/README.md b/doc/update/README.md
index 0241f036830..a770633c9b8 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -1,18 +1,95 @@
-Depending on the installation method and your GitLab version, there are multiple update guides. Choose one that fits your needs.
+# Updating GitLab
+
+Depending on the installation method and your GitLab version, there are multiple
+update guides.
+
+There are currently 3 official ways to install GitLab:
+
+- Omnibus packages
+- Source installation
+- Docker installation
+
+Based on your installation, choose a section below that fits your needs.
+
+---
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [Omnibus Packages](#omnibus-packages)
+- [Installation from source](#installation-from-source)
+- [Installation using Docker](#installation-using-docker)
+- [Upgrading between editions](#upgrading-between-editions)
+ - [Community to Enterprise Edition](#community-to-enterprise-edition)
+ - [Enterprise to Community Edition](#enterprise-to-community-edition)
+- [Miscellaneous](#miscellaneous)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Omnibus Packages
-- [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/).
+- The [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html)
+ contains the steps needed to update an Omnibus GitLab package.
## Installation from source
-- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source.
-- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status.
-- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for installations from source.
-- [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1.
+- [Upgrading Community Edition from source][source-ce] - The individual
+ upgrade guides are for those who have installed GitLab CE from source.
+- [Upgrading Enterprise Edition from source][source-ee] - The individual
+ upgrade guides are for those who have installed GitLab EE from source.
+- [Patch versions](patch_versions.md) guide includes the steps needed for a
+ patch version, eg. 6.2.0 to 6.2.1, and apply to both Community and Enterprise
+ Editions.
+
+## Installation using Docker
+
+GitLab provides official Docker images for both Community and Enterprise
+editions. They are based on the Omnibus package and instructions on how to
+update them are in [a separate document][omnidocker].
+
+## Upgrading between editions
+
+GitLab comes in two flavors: [Community Edition][ce] which is MIT licensed,
+and [Enterprise Edition][ee] which builds on top of the Community Edition and
+includes extra features mainly aimed at organizations with more than 100 users.
+
+Below you can find some guides to help you change editions easily.
+
+### Community to Enterprise Edition
+
+>**Note:**
+The following guides are for subscribers of the Enterprise Edition only.
+
+If you wish to upgrade your GitLab installation from Community to Enterprise
+Edition, follow the guides below based on the installation method:
+
+- [Source CE to EE update guides][source-ee] - Find your version, and follow the
+ `-ce-to-ee.md` guide. The steps are very similar to a version upgrade: stop
+ the server, get the code, update config files for the new functionality,
+ install libraries and do migrations, update the init script, start the
+ application and check its status.
+- [Omnibus CE to EE][omni-ce-ee] - Follow this guide to update your Omnibus
+ GitLab Community Edition to the Enterprise Edition.
+
+### Enterprise to Community Edition
+
+If you need to downgrade your Enterprise Edition installation back to Community
+Edition, you can follow [this guide][ee-ce] to make the process as smooth as
+possible.
## Miscellaneous
-- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL.
-- [MySQL installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/database_mysql.md) contains additional information about configuring GitLab to work with a MySQL database.
+- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating
+ your database from MySQL to PostgreSQL.
+- [MySQL installation guide](../install/database_mysql.md) contains additional
+ information about configuring GitLab to work with a MySQL database.
- [Restoring from backup after a failed upgrade](restore_after_failure.md)
+
+[omnidocker]: http://doc.gitlab.com/omnibus/docker/README.html
+[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update
+[source-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
+[ee-ce]: ../downgrade_ee_to_ce/README.md
+[ce]: https://about.gitlab.com/features/#community
+[ee]: https://about.gitlab.com/features/#enterprise
+[omni-ce-ee]: http://doc.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md
index b0ca0879643..4a499009842 100644
--- a/doc/workflow/cherry_pick_changes.md
+++ b/doc/workflow/cherry_pick_changes.md
@@ -1,6 +1,7 @@
# Cherry-pick changes
-_**Note:** This feature was [introduced][ce-3514] in GitLab 8.7._
+>**Note:**
+This feature was [introduced][ce-3514] in GitLab 8.7.
---
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index f693f430a42..e670e415c71 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -1,7 +1,8 @@
# Import your project from GitHub to GitLab
-_**Note:** In order to enable the GitHub import setting, you should first
-enable the [GitHub integration][gh-import] in your GitLab instance._
+>**Note:**
+In order to enable the GitHub import setting, you should first
+enable the [GitHub integration][gh-import] in your GitLab instance.
At its current state, GitHub importer can import:
@@ -10,10 +11,13 @@ At its current state, GitHub importer can import:
- the issues (introduced in GitLab 7.7)
- the pull requests (introduced in GitLab 8.4)
- the wiki pages (introduced in GitLab 8.4)
+- the milestones (introduced in GitLab 8.7)
+- the labels (introduced in GitLab 8.7)
-It is not yet possible to import your labels, milestones and cross-repository
-pull requests (those from forks). We are working on improving this in the near
-future.
+With GitLab 8.7+, references to pull requests and issues are preserved.
+
+It is not yet possible to import your cross-repository pull requests (those from
+forks). We are working on improving this in the near future.
The importer page is visible when you [create a new project][new-project].
Click on the **GitHub** link and you will be redirected to GitHub for
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
index 83e562d6929..beb6c53ec77 100644
--- a/doc/workflow/shortcuts.png
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/features/search.feature b/features/search.feature
index 3cd52810e59..a946a836525 100644
--- a/features/search.feature
+++ b/features/search.feature
@@ -30,11 +30,13 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see project code I am looking for
When I click project "Shop" link
And I search for "rspec"
Then I should see code results for project "Shop"
+ @javascript
Scenario: I should see project issues
And project has issues
When I click project "Shop" link
@@ -43,6 +45,7 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see project merge requests
And project has merge requests
When I click project "Shop" link
@@ -51,6 +54,7 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see project milestones
And project has milestones
When I click project "Shop" link
@@ -59,6 +63,7 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see Wiki blobs
And project has Wiki content
When I click project "Shop" link
diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb
index 4724a326277..3b59089a093 100644
--- a/features/steps/profile/active_tab.rb
+++ b/features/steps/profile/active_tab.rb
@@ -22,4 +22,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
step 'the active main tab should be Audit Log' do
ensure_active_main_tab('Audit Log')
end
+
+ def ensure_active_main_tab(content)
+ expect(find('.layout-nav li.active')).to have_content(content)
+ end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 4f883fe7c27..3b1a00f628a 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -519,7 +519,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step '"Bug NS-05" has CI status' do
project = merge_request.source_project
project.enable_ci
- ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id
+ ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch
create :ci_build, commit: ci_commit
end
diff --git a/features/steps/search.rb b/features/steps/search.rb
index 0ad837ebe1d..f885baf8453 100644
--- a/features/steps/search.rb
+++ b/features/steps/search.rb
@@ -35,6 +35,7 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end
step 'I click project "Shop" link' do
+ click_button 'Project'
page.within '.project-filter' do
click_link project.name_with_namespace
end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index c4c7672a432..cf30e23b6bd 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -10,16 +10,16 @@ module SharedBuilds
end
step 'project has a recent build' do
- @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha)
+ @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha, ref: 'master')
@build = create(:ci_build_with_coverage, commit: @ci_commit)
end
step 'recent build is successful' do
- @build.update_column(:status, 'success')
+ @build.update(status: 'success')
end
step 'recent build failed' do
- @build.update_column(:status, 'failed')
+ @build.update(status: 'failed')
end
step 'project has another build that is running' do
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index b13e82f276b..ea5f9580308 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -230,7 +230,7 @@ module SharedProject
step 'project "Shop" has CI build' do
project = Project.find_by(name: "Shop")
- create :ci_commit, project: project, sha: project.commit.sha
+ create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped'
end
step 'I should see last commit with CI status' do
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 8e74e177ea0..7388ed2f4ea 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -21,10 +21,9 @@ module API
authorize!(:read_commit_status, user_project)
not_found!('Commit') unless user_project.commit(params[:sha])
- ci_commit = user_project.ci_commit(params[:sha])
- return [] unless ci_commit
- statuses = ci_commit.statuses
+ ci_commits = user_project.ci_commits.where(sha: params[:sha])
+ statuses = ::CommitStatus.where(commit: ci_commits)
statuses = statuses.latest unless parse_boolean(params[:all])
statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
statuses = statuses.where(stage: params[:stage]) if params[:stage].present?
@@ -51,7 +50,21 @@ module API
commit = @project.commit(params[:sha])
not_found! 'Commit' unless commit
- ci_commit = @project.ensure_ci_commit(commit.sha)
+ # Since the CommitStatus is attached to Ci::Commit (in the future Pipeline)
+ # We need to always have the pipeline object
+ # To have a valid pipeline object that can be attached to specific MR
+ # Other CI service needs to send `ref`
+ # If we don't receive it, we will attach the CommitStatus to
+ # the first found branch on that commit
+
+ ref = params[:ref]
+ unless ref
+ branches = @project.repository.branch_names_contains(commit.sha)
+ not_found! 'References for commit' if branches.none?
+ ref = branches.first
+ end
+
+ ci_commit = @project.ensure_ci_commit(commit.sha, ref)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index cf9938d25a7..ccca65cbe1c 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -103,10 +103,10 @@ module API
required_attributes! [:hook_id]
begin
- @hook = ProjectHook.find(params[:hook_id])
- @hook.destroy
+ @hook = user_project.hooks.destroy(params[:hook_id])
rescue
# ProjectHook can raise Error if hook_id not found
+ not_found!("Error deleting hook #{params[:hook_id]}")
end
end
end
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
index 5f8ff01b0a9..b1aecc2e671 100644
--- a/lib/award_emoji.rb
+++ b/lib/award_emoji.rb
@@ -52,6 +52,10 @@ class AwardEmoji
end
end
+ def self.unicode
+ @unicode ||= emojis.map {|key, value| { key => emojis[key]["unicode"] } }.inject(:merge!)
+ end
+
def self.aliases
@aliases ||= begin
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index d179bea181e..38c4219518e 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -1,7 +1,6 @@
module Banzai
module Filter
- # HTML Filter to add a `rel="nofollow"` attribute to external links
- #
+ # HTML Filter to modify the attributes of external links
class ExternalLinkFilter < HTML::Pipeline::Filter
def call
doc.search('a').each do |node|
@@ -15,7 +14,7 @@ module Banzai
# Skip internal links
next if link.start_with?(internal_url)
- node.set_attribute('rel', 'nofollow')
+ node.set_attribute('rel', 'nofollow noreferrer')
end
doc
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index a2987850d03..8488a493b55 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -18,9 +18,7 @@ module Banzai
def references_in(text, pattern = Label.reference_pattern)
text.gsub(pattern) do |match|
- project = project_from_ref($~[:project])
- params = label_params($~[:label_id].to_i, $~[:label_name])
- label = project.labels.find_by(params)
+ label = find_label($~[:project], $~[:label_id], $~[:label_name])
if label
yield match, label.id, $~[:project], $~
@@ -30,18 +28,12 @@ module Banzai
end
end
- def url_for_object(label, project)
- h = Gitlab::Routing.url_helpers
- h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
- only_path: context[:only_path])
- end
+ def find_label(project_ref, label_id, label_name)
+ project = project_from_ref(project_ref)
+ return unless project
- def object_link_text(object, matches)
- if context[:project] == object.project
- LabelsHelper.render_colored_label(object)
- else
- LabelsHelper.render_colored_cross_project_label(object)
- end
+ label_params = label_params(label_id, label_name)
+ project.labels.find_by(label_params)
end
# Parameters to pass to `Label.find_by` based on the given arguments
@@ -55,7 +47,21 @@ module Banzai
if name
{ name: name.tr('"', '') }
else
- { id: id }
+ { id: id.to_i }
+ end
+ end
+
+ def url_for_object(label, project)
+ h = Gitlab::Routing.url_helpers
+ h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
+ only_path: context[:only_path])
+ end
+
+ def object_link_text(object, matches)
+ if context[:project] == object.project
+ LabelsHelper.render_colored_label(object)
+ else
+ LabelsHelper.render_colored_cross_project_label(object)
end
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index ff9887cba1e..504d3df9d34 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -61,23 +61,21 @@ module Ci
@stages = @config[:stages] || @config[:types]
@variables = @config[:variables] || {}
@cache = @config[:cache]
+ @jobs = {}
+
@config.except!(*ALLOWED_YAML_KEYS)
+ @config.each { |name, param| add_job(name, param) }
- # anything that doesn't have script is considered as unknown
- @config.each do |name, param|
- raise ValidationError, "Unknown parameter: #{name}" unless param.is_a?(Hash) && param.has_key?(:script)
- end
+ raise ValidationError, "Please define at least one job" if @jobs.none?
+ end
- unless @config.values.any?{|job| job.is_a?(Hash)}
- raise ValidationError, "Please define at least one job"
- end
+ def add_job(name, job)
+ return if name.to_s.start_with?('.')
- @jobs = {}
- @config.each do |key, job|
- next if key.to_s.start_with?('.')
- stage = job[:stage] || job[:type] || DEFAULT_STAGE
- @jobs[key] = { stage: stage }.merge(job)
- end
+ raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script)
+
+ stage = job[:stage] || job[:type] || DEFAULT_STAGE
+ @jobs[name] = { stage: stage }.merge(job)
end
def build_job(name, job)
@@ -112,8 +110,6 @@ module Ci
true
end
- private
-
def validate_global!
unless validate_array_of_strings(@before_script)
raise ValidationError, "before_script should be an array of strings"
diff --git a/lib/ci/status.rb b/lib/ci/status.rb
deleted file mode 100644
index 3fb1fe29494..00000000000
--- a/lib/ci/status.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Ci
- class Status
- def self.get_status(statuses)
- if statuses.none?
- 'skipped'
- elsif statuses.all? { |status| status.success? || status.ignored? }
- 'success'
- elsif statuses.all?(&:pending?)
- 'pending'
- elsif statuses.any?(&:running?) || statuses.any?(&:pending?)
- 'running'
- elsif statuses.all?(&:canceled?)
- 'canceled'
- else
- 'failed'
- end
- end
- end
-end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index eb27d82f110..ab900b641c4 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -6,8 +6,8 @@ module Gitlab
gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
+ gon.shortcuts_path = help_shortcuts_path
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
- gon.sentry_dsn = ApplicationSetting.current.sentry_dsn if Rails.env.production?
if current_user
gon.current_user_id = current_user.id
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 37232743325..ae85b294d31 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -29,8 +29,8 @@ module Gitlab
"in #{GRACE_TIME} seconds"
sleep(GRACE_TIME)
- Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
- Process.kill('SIGUSR1', Process.pid)
+ Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
+ Process.kill('SIGTERM', Process.pid)
Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
"#{SHUTDOWN_SIGNAL} to PID #{Process.pid}"
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 1324e4cd267..d521de28e8a 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -61,7 +61,8 @@ server {
error_page 422 /422.html;
error_page 500 /500.html;
error_page 502 /502.html;
- location ~ ^/(404|422|500|502)\.html$ {
+ error_page 503 /503.html;
+ location ~ ^/(404|422|500|502|503)\.html$ {
root /home/git/gitlab/public;
internal;
}
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index af6ea9ed706..bf014b56cf6 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -105,7 +105,8 @@ server {
error_page 422 /422.html;
error_page 500 /500.html;
error_page 502 /502.html;
- location ~ ^/(404|422|500|502)\.html$ {
+ error_page 503 /503.html;
+ location ~ ^/(404|422|500|502|503)\.html$ {
root /home/git/gitlab/public;
internal;
}
diff --git a/public/503.html b/public/503.html
new file mode 100644
index 00000000000..6ab1185658d
--- /dev/null
+++ b/public/503.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>GitLab is not responding (503)</title>
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ width: 800px;
+ margin: auto;
+ font-size: 14px;
+ }
+
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
+
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+ </style>
+</head>
+<body>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo"/><br />
+ 503
+ </h1>
+ <h3>Whoops, GitLab is currently unavailable.</h3>
+ <hr/>
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
+</body>
+</html>
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c54e83339a1..c0a1f45195f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -300,14 +300,6 @@ describe Projects::MergeRequestsController do
expect(response.cookies['diff_view']).to eq('parallel')
end
-
- it 'assigns :view param based on cookie' do
- request.cookies['diff_view'] = 'parallel'
-
- go
-
- expect(controller.params[:view]).to eq 'parallel'
- end
end
describe 'GET commits' do
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index d47e4ab9a4f..ed64e7cf9af 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -46,4 +46,20 @@ describe Projects::ProjectMembersController do
end
end
end
+
+ describe '#index' do
+ let(:project) { create(:project, :private) }
+
+ context 'when user is member' do
+ let(:member) { create(:user) }
+
+ before do
+ project.team << [member, :guest]
+ sign_in(member)
+ get :index, namespace_id: project.namespace.to_param, project_id: project.to_param
+ end
+
+ it { expect(response.status).to eq(200) }
+ end
+ end
end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
new file mode 100644
index 00000000000..cf86e2c85e9
--- /dev/null
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe "Dashboard > User filters projects", feature: true do
+
+ describe 'filtering personal projects' do
+ before do
+ user = create(:user)
+ project = create(:project, name: "Victorialand", namespace: user.namespace)
+ project.team << [user, :master]
+
+ user2 = create(:user)
+ project2 = create(:project, name: "Treasure", namespace: user2.namespace)
+ project2.team << [user, :developer]
+
+ login_as(user)
+ visit dashboard_projects_path
+ end
+
+ it 'filters by projects "Owned by me"' do
+ click_link "Owned by me"
+
+ expect(page).to have_css('.is-active', text: 'Owned by me')
+ expect(page).to have_content('Victorialand')
+ expect(page).not_to have_content('Treasure')
+ end
+ end
+end
diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb
index 7944403f874..7f654684143 100644
--- a/spec/features/issues/filter_by_labels_spec.rb
+++ b/spec/features/issues/filter_by_labels_spec.rb
@@ -1,26 +1,26 @@
require 'rails_helper'
feature 'Issue filtering by Labels', feature: true do
+ include WaitForAjax
+
let(:project) { create(:project, :public) }
let!(:user) { create(:user)}
let!(:label) { create(:label, project: project) }
before do
- ['bug', 'feature', 'enhancement'].each do |title|
- create(:label,
- project: project,
- title: title)
- end
+ bug = create(:label, project: project, title: 'bug')
+ feature = create(:label, project: project, title: 'feature')
+ enhancement = create(:label, project: project, title: 'enhancement')
issue1 = create(:issue, title: "Bugfix1", project: project)
- issue1.labels << project.labels.find_by(title: 'bug')
+ issue1.labels << bug
issue2 = create(:issue, title: "Bugfix2", project: project)
- issue2.labels << project.labels.find_by(title: 'bug')
- issue2.labels << project.labels.find_by(title: 'enhancement')
+ issue2.labels << bug
+ issue2.labels << enhancement
issue3 = create(:issue, title: "Feature1", project: project)
- issue3.labels << project.labels.find_by(title: 'feature')
+ issue3.labels << feature
project.team << [user, :master]
login_as(user)
@@ -31,10 +31,10 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label bug', js: true do
before do
page.find('.js-label-select').click
- sleep 0.5
+ wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- sleep 2
+ wait_for_ajax
end
it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do
@@ -59,10 +59,10 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label feature', js: true do
before do
page.find('.js-label-select').click
- sleep 0.5
+ wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- sleep 2
+ wait_for_ajax
end
it 'should show issue "Feature1" in issues list' do
@@ -87,10 +87,10 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label enhancement', js: true do
before do
page.find('.js-label-select').click
- sleep 0.5
+ wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- sleep 2
+ wait_for_ajax
end
it 'should show issue "Bugfix2" in issues list' do
@@ -115,20 +115,16 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label enhancement or feature', js: true do
before do
page.find('.js-label-select').click
- sleep 0.5
+ wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- sleep 2
+ wait_for_ajax
end
- it 'should show issue "Bugfix2" or "Feature1" in issues list' do
- expect(page).to have_content "Bugfix2"
- expect(page).to have_content "Feature1"
- end
-
- it 'should not show "Bugfix1" in issues list' do
+ it 'should not show "Bugfix1" or "Feature1" in issues list' do
expect(page).not_to have_content "Bugfix1"
+ expect(page).not_to have_content "Feature1"
end
it 'should show label "enhancement" and "feature" in filtered-labels' do
@@ -141,19 +137,18 @@ feature 'Issue filtering by Labels', feature: true do
end
end
- context 'filter by label enhancement or bug in issues list', js: true do
+ context 'filter by label enhancement and bug in issues list', js: true do
before do
page.find('.js-label-select').click
- sleep 0.5
+ wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
- sleep 2
+ wait_for_ajax
end
- it 'should show issue "Bugfix2" or "Bugfix1" in issues list' do
+ it 'should show issue "Bugfix2" in issues list' do
expect(page).to have_content "Bugfix2"
- expect(page).to have_content "Bugfix1"
end
it 'should not show "Feature1"' do
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
new file mode 100644
index 00000000000..5739bc64dfb
--- /dev/null
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -0,0 +1,79 @@
+require 'rails_helper'
+
+feature 'Issue Sidebar', feature: true do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let!(:user) { create(:user)}
+
+ before do
+ create(:label, project: project, title: 'bug')
+ login_as(user)
+ end
+
+ context 'as a allowed user' do
+ before do
+ project.team << [user, :developer]
+ visit_issue(project, issue)
+ end
+
+ describe 'when clicking on edit labels', js: true do
+ it 'dropdown has an option to create a new label' do
+ find('.block.labels .edit-link').click
+
+ page.within('.block.labels') do
+ expect(page).to have_content 'Create new'
+ end
+ end
+ end
+
+ context 'creating a new label', js: true do
+ it 'option to crate a new label is present' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+
+ expect(page).to have_content 'Create new'
+ end
+ end
+
+ it 'dropdown switches to "create label" section' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+ click_link 'Create new'
+
+ expect(page).to have_content 'Create new label'
+ end
+ end
+
+ it 'new label is added' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+ sleep 1
+ click_link 'Create new'
+
+ fill_in 'new_label_name', with: 'wontfix'
+ page.find(".suggest-colors a", match: :first).click
+ click_button 'Create'
+
+ page.within('.dropdown-page-one') do
+ expect(page).to have_content 'wontfix'
+ end
+ end
+ end
+ end
+ end
+
+ context 'as a guest' do
+ before do
+ project.team << [user, :guest]
+ visit_issue(project, issue)
+ end
+
+ it 'does not have a option to edit labels' do
+ expect(page).not_to have_selector('.block.labels .edit-link')
+ end
+ end
+
+ def visit_issue(project, issue)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 90476ab369b..b57131f68d5 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -178,6 +178,19 @@ describe 'Issues', feature: true do
expect(first_issue).to include('foo')
end
+
+ context 'with a filter on labels' do
+ let(:label) { create(:label, project: project) }
+ before { create(:label_link, label: label, target: foo) }
+
+ it 'sorts by least recently due date by excluding nil due dates' do
+ bar.update(due_date: nil)
+
+ visit namespace_project_issues_path(project.namespace, project, label_names: [label.name], sort: sort_value_due_date_later)
+
+ expect(first_issue).to include('foo')
+ end
+ end
end
describe 'filtering by due date' do
@@ -304,6 +317,27 @@ describe 'Issues', feature: true do
expect(issue.reload.assignee).to be_nil
end
+
+ it 'allows user to select an assignee', js: true do
+ issue2 = create(:issue, project: project, author: @user)
+ visit namespace_project_issue_path(project.namespace, project, issue2)
+
+ page.within('.assignee') do
+ expect(page).to have_content "No assignee"
+ end
+
+ page.within '.assignee' do
+ click_link 'Edit'
+ end
+
+ page.within '.dropdown-menu-user' do
+ click_link @user.name
+ end
+
+ page.within('.assignee') do
+ expect(page).to have_content @user.name
+ end
+ end
end
context 'by unauthorized user' do
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 3d0d0e59fd7..0148c87084a 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -165,7 +165,12 @@ describe 'GitLab Markdown', feature: true do
describe 'ExternalLinkFilter' do
it 'adds nofollow to external link' do
link = doc.at_css('a:contains("Google")')
- expect(link.attr('rel')).to match 'nofollow'
+ expect(link.attr('rel')).to include('nofollow')
+ end
+
+ it 'adds noreferrer to external link' do
+ link = doc.at_css('a:contains("Google")')
+ expect(link.attr('rel')).to include('noreferrer')
end
it 'ignores internal link' do
diff --git a/spec/features/project/shortcuts_spec.rb b/spec/features/project/shortcuts_spec.rb
new file mode 100644
index 00000000000..2595c4181e5
--- /dev/null
+++ b/spec/features/project/shortcuts_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Project shortcuts', feature: true do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ describe 'On a project', js: true do
+ before do
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ describe 'pressing "i"' do
+ it 'redirects to new issue page' do
+ find('body').native.send_key('i')
+ expect(page).to have_content('New Issue')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
new file mode 100644
index 00000000000..40ba0bdc115
--- /dev/null
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+feature 'project commit builds' do
+ given(:project) { create(:project) }
+
+ background do
+ user = create(:user)
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ context 'when no builds triggered yet' do
+ background do
+ create(:ci_commit, project: project,
+ sha: project.commit.sha,
+ ref: 'master')
+ end
+
+ scenario 'user views commit builds page' do
+ visit builds_namespace_project_commit_path(project.namespace,
+ project, project.commit.sha)
+
+
+ expect(page).to have_content('Builds')
+ end
+ end
+end
diff --git a/spec/features/project/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb
index 0559b02f321..0559b02f321 100644
--- a/spec/features/project/commits/cherry_pick_spec.rb
+++ b/spec/features/projects/commits/cherry_pick_spec.rb
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
new file mode 100644
index 00000000000..c5e3d143d91
--- /dev/null
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Anonymous user sees members', feature: true do
+ let(:user) { create(:user) }
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:empty_project, :public) }
+
+ background do
+ project.team << [user, :master]
+ create(:project_group_link, project: project, group: group)
+ end
+
+ scenario "anonymous user visits the project's members page and sees the list of members" do
+ visit namespace_project_project_members_path(project.namespace, project)
+
+ expect(current_path).to eq(
+ namespace_project_project_members_path(project.namespace, project))
+ expect(page).to have_content(user.name)
+ end
+end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 79d5bf4cf06..8625ea6bc10 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -101,12 +101,12 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
- it { is_expected.to be_denied_for developer }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_denied_for :external }
end
describe "GET /:project_path/blob" do
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 0a89193eb67..544270b4037 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -101,9 +101,9 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
- it { is_expected.to be_denied_for developer }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 40daac89d40..4def4f99bc0 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -101,12 +101,12 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
- it { is_expected.to be_denied_for developer }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :external }
- it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for :external }
end
describe "GET /:project_path/builds" do
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
index 01472743b2a..51b754ff85c 100644
--- a/spec/features/signup_spec.rb
+++ b/spec/features/signup_spec.rb
@@ -13,8 +13,8 @@ feature 'Signup', feature: true do
fill_in 'user_password_sign_up', with: user.password
click_button "Sign up"
- expect(current_path).to eq user_session_path
- expect(page).to have_content("A message with a confirmation link has been sent to your email address.")
+ expect(current_path).to eq users_almost_there_path
+ expect(page).to have_content("Please check your email to confirm your account")
end
end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 113d4c40cfc..248e004ba6e 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -1,12 +1,10 @@
require 'spec_helper'
describe 'Dashboard Todos', feature: true do
- let(:user){ create(:user) }
- let(:author){ create(:user) }
- let(:project){ create(:project) }
- let(:issue){ create(:issue) }
- let(:todos_per_page){ Todo.default_per_page }
- let(:todos_total){ todos_per_page + 1 }
+ let(:user) { create(:user) }
+ let(:author) { create(:user) }
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue) }
describe 'GET /dashboard/todos' do
context 'User does not have todos' do
@@ -46,31 +44,35 @@ describe 'Dashboard Todos', feature: true do
end
context 'User has multiple pages of Todos' do
- let(:todo_total_pages){ (todos_total.to_f/todos_per_page).ceil }
-
before do
- todos_total.times do
- create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
- end
+ allow(Todo).to receive(:default_per_page).and_return(1)
+
+ # Create just enough records to cause us to paginate
+ create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author)
login_as(user)
- visit dashboard_todos_path
end
it 'is paginated' do
+ visit dashboard_todos_path
+
expect(page).to have_selector('.gl-pagination')
end
it 'is has the right number of pages' do
- expect(page).to have_selector('.gl-pagination .page', count: todo_total_pages)
+ visit dashboard_todos_path
+
+ expect(page).to have_selector('.gl-pagination .page', count: 2)
end
- describe 'deleting last todo from last page', js: true do
+ describe 'completing last todo from last page', js: true do
it 'redirects to the previous page' do
- page.within('.gl-pagination') do
- click_link todo_total_pages.to_s
- end
- first('.done-todo').click
+ visit dashboard_todos_path(page: 2)
+ expect(page).to have_content(Todo.first.body)
+
+ click_link('Done')
+
+ expect(current_path).to eq dashboard_todos_path
expect(page).to have_content(Todo.last.body)
end
end
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 4f8d9c67262..f942695b6f0 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -6,8 +6,8 @@ describe CiStatusHelper do
let(:success_commit) { double("Ci::Commit", status: 'success') }
let(:failed_commit) { double("Ci::Commit", status: 'failed') }
- describe 'ci_status_icon' do
- it { expect(helper.ci_status_icon(success_commit)).to include('fa-check') }
- it { expect(helper.ci_status_icon(failed_commit)).to include('fa-close') }
+ describe 'ci_icon_for_status' do
+ it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
+ it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') }
end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 982c113e84b..b7810185d16 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -11,6 +11,26 @@ describe DiffHelper do
let(:diff_refs) { [commit.parent, commit] }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
+ describe 'diff_view' do
+ it 'returns a valid value when cookie is set' do
+ helper.request.cookies[:diff_view] = 'parallel'
+
+ expect(helper.diff_view).to eq 'parallel'
+ end
+
+ it 'returns a default value when cookie is invalid' do
+ helper.request.cookies[:diff_view] = 'invalid'
+
+ expect(helper.diff_view).to eq 'inline'
+ end
+
+ it 'returns a default value when cookie is nil' do
+ expect(helper.request.cookies).to be_empty
+
+ expect(helper.diff_view).to eq 'inline'
+ end
+ end
+
describe 'diff_hard_limit_enabled?' do
it 'should return true if param is provided' do
allow(controller).to receive(:params) { { force_show_diff: true } }
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index c258cfebd73..62389188d2c 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -105,4 +105,30 @@ describe ProjectsHelper do
end
end
end
+
+ describe '#license_short_name' do
+ let(:project) { create(:project) }
+
+ context 'when project.repository has a license_key' do
+ it 'returns the nickname of the license if present' do
+ allow(project.repository).to receive(:license_key).and_return('agpl-3.0')
+
+ expect(helper.license_short_name(project)).to eq('GNU AGPLv3')
+ end
+
+ it 'returns the name of the license if nickname is not present' do
+ allow(project.repository).to receive(:license_key).and_return('mit')
+
+ expect(helper.license_short_name(project)).to eq('MIT License')
+ end
+ end
+
+ context 'when project.repository has no license_key but a license_blob' do
+ it 'returns LICENSE' do
+ allow(project.repository).to receive(:license_key).and_return(nil)
+
+ expect(helper.license_short_name(project)).to eq('LICENSE')
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index e3a8e15330e..f4c5c621bd0 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -24,6 +24,14 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do
doc = filter(act)
expect(doc.at_css('a')).to have_attribute('rel')
- expect(doc.at_css('a')['rel']).to eq 'nofollow'
+ expect(doc.at_css('a')['rel']).to include 'nofollow'
+ end
+
+ it 'adds rel="noreferrer" to external links' do
+ act = %q(<a href="https://google.com/">Google</a>)
+ doc = filter(act)
+
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to include 'noreferrer'
end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 94468abcbb3..b0a38e7c251 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -178,27 +178,37 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
describe 'cross project label references' do
- let(:another_project) { create(:empty_project, :public) }
- let(:project_name) { another_project.name_with_namespace }
- let(:label) { create(:label, project: another_project, color: '#00ff00') }
- let(:reference) { label.to_reference(project) }
+ context 'valid project referenced' do
+ let(:another_project) { create(:empty_project, :public) }
+ let(:project_name) { another_project.name_with_namespace }
+ let(:label) { create(:label, project: another_project, color: '#00ff00') }
+ let(:reference) { label.to_reference(project) }
- let!(:result) { reference_filter("See #{reference}") }
+ let!(:result) { reference_filter("See #{reference}") }
- it 'points to referenced project issues page' do
- expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(another_project.namespace,
- another_project,
- label_name: label.name)
- end
+ it 'points to referenced project issues page' do
+ expect(result.css('a').first.attr('href'))
+ .to eq urls.namespace_project_issues_url(another_project.namespace,
+ another_project,
+ label_name: label.name)
+ end
+
+ it 'has valid color' do
+ expect(result.css('a span').first.attr('style'))
+ .to match /background-color: #00ff00/
+ end
- it 'has valid color' do
- expect(result.css('a span').first.attr('style'))
- .to match /background-color: #00ff00/
+ it 'contains cross project content' do
+ expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
+ end
end
- it 'contains cross project content' do
- expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
+ context 'project that does not exist referenced' do
+ let(:result) { reference_filter('aaa/bbb~ccc') }
+
+ it 'does not link reference' do
+ expect(result.to_html).to eq 'aaa/bbb~ccc'
+ end
end
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 643acf0303c..c7ab3185378 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -648,70 +648,131 @@ module Ci
end
describe "Hidden jobs" do
- let(:config) do
- YAML.dump({
- '.hidden_job' => { script: 'test' },
- 'normal_job' => { script: 'test' }
- })
+ let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+ subject { config_processor.builds_for_stage_and_ref("test", "master") }
+
+ shared_examples 'hidden_job_handling' do
+ it "doesn't create jobs that start with dot" do
+ expect(subject.size).to eq(1)
+ expect(subject.first).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :normal_job,
+ only: nil,
+ commands: "test",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
end
- let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+ context 'when hidden job have a script definition' do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { image: 'ruby:2.1', script: 'test' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
- subject { config_processor.builds_for_stage_and_ref("test", "master") }
+ it_behaves_like 'hidden_job_handling'
+ end
- it "doesn't create jobs that starts with dot" do
- expect(subject.size).to eq(1)
- expect(subject.first).to eq({
- except: nil,
- stage: "test",
- stage_idx: 1,
- name: :normal_job,
- only: nil,
- commands: "test",
- tag_list: [],
- options: {},
- when: "on_success",
- allow_failure: false
- })
+ context "when hidden job doesn't have a script definition" do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { image: 'ruby:2.1' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
+
+ it_behaves_like 'hidden_job_handling'
end
end
describe "YAML Alias/Anchor" do
- it "is correctly supported for jobs" do
- config = <<EOT
+ let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+ subject { config_processor.builds_for_stage_and_ref("build", "master") }
+
+ shared_examples 'job_templates_handling' do
+ it "is correctly supported for jobs" do
+ expect(subject.size).to eq(2)
+ expect(subject.first).to eq({
+ except: nil,
+ stage: "build",
+ stage_idx: 0,
+ name: :job1,
+ only: nil,
+ commands: "execute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ expect(subject.second).to eq({
+ except: nil,
+ stage: "build",
+ stage_idx: 0,
+ name: :job2,
+ only: nil,
+ commands: "execute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
+ end
+
+ context 'when template is a job' do
+ let(:config) do
+ <<EOT
job1: &JOBTMPL
+ stage: build
script: execute-script-for-job
job2: *JOBTMPL
EOT
+ end
- config_processor = GitlabCiYamlProcessor.new(config)
+ it_behaves_like 'job_templates_handling'
+ end
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(2)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- except: nil,
- stage: "test",
- stage_idx: 1,
- name: :job1,
- only: nil,
- commands: "execute-script-for-job",
- tag_list: [],
- options: {},
- when: "on_success",
- allow_failure: false
- })
- expect(config_processor.builds_for_stage_and_ref("test", "master").second).to eq({
- except: nil,
- stage: "test",
- stage_idx: 1,
- name: :job2,
- only: nil,
- commands: "execute-script-for-job",
- tag_list: [],
- options: {},
- when: "on_success",
- allow_failure: false
- })
+ context 'when template is a hidden job' do
+ let(:config) do
+ <<EOT
+.template: &JOBTMPL
+ stage: build
+ script: execute-script-for-job
+
+job1: *JOBTMPL
+
+job2: *JOBTMPL
+EOT
+ end
+
+ it_behaves_like 'job_templates_handling'
+ end
+
+ context 'when job adds its own keys to a template definition' do
+ let(:config) do
+ <<EOT
+.template: &JOBTMPL
+ stage: build
+
+job1:
+ <<: *JOBTMPL
+ script: execute-script-for-job
+
+job2:
+ <<: *JOBTMPL
+ script: execute-script-for-job
+EOT
+ end
+
+ it_behaves_like 'job_templates_handling'
end
end
diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
index 329792bb685..b6f7a2e7ec4 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::Badge::Build do
end
context 'build exists' do
- let(:ci_commit) { create(:ci_commit, project: project, sha: sha) }
+ let(:ci_commit) { create(:ci_commit, project: project, sha: sha, ref: branch) }
let!(:build) { create(:ci_build, commit: ci_commit) }
@@ -57,7 +57,7 @@ describe Gitlab::Badge::Build do
describe '#data' do
let(:data) { badge.data }
- it 'contains infromation about success' do
+ it 'contains information about success' do
expect(status_node(data, 'success')).to be_truthy
end
end
@@ -74,7 +74,7 @@ describe Gitlab::Badge::Build do
describe '#data' do
let(:data) { badge.data }
- it 'contains infromation about failure' do
+ it 'contains information about failure' do
expect(status_node(data, 'failed')).to be_truthy
end
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index 412842337ba..82c18aaa01a 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -27,6 +27,8 @@ describe Ci::Commit, models: true do
it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha }
+ it { is_expected.to validate_presence_of :status }
+ it { is_expected.to delegate_method(:stages).to(:statuses) }
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
@@ -52,57 +54,9 @@ describe Ci::Commit, models: true do
it { expect(commit.sha).to start_with(subject) }
end
- describe :stage do
- subject { commit.stage }
-
- before do
- @second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending'
- @first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending'
- end
-
- it 'returns first running stage' do
- is_expected.to eq('test')
- end
-
- context 'first build succeeded' do
- before do
- @first.success
- end
-
- it 'returns last running stage' do
- is_expected.to eq('deploy')
- end
- end
-
- context 'all builds succeeded' do
- before do
- @first.success
- @second.success
- end
-
- it 'returns nil' do
- is_expected.to be_nil
- end
- end
- end
-
describe :create_next_builds do
end
- describe :refs do
- subject { commit.refs }
-
- before do
- FactoryGirl.create :commit_status, commit: commit, name: 'deploy'
- FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop'
- FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master'
- end
-
- it 'returns all refs' do
- is_expected.to contain_exactly('master', 'develop', nil)
- end
- end
-
describe :retried do
subject { commit.retried }
@@ -117,10 +71,10 @@ describe Ci::Commit, models: true do
end
describe :create_builds do
- let!(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let!(:commit) { FactoryGirl.create :ci_commit, project: project, ref: 'master', tag: false }
def create_builds(trigger_request = nil)
- commit.create_builds('master', false, nil, trigger_request)
+ commit.create_builds(nil, trigger_request)
end
def create_next_builds
@@ -143,67 +97,6 @@ describe Ci::Commit, models: true do
expect(create_next_builds).to be_falsey
end
- context 'for different ref' do
- def create_develop_builds
- commit.create_builds('develop', false, nil, nil)
- end
-
- it 'creates builds' do
- expect(create_builds).to be_truthy
- commit.builds.update_all(status: "success")
- expect(commit.builds.count(:all)).to eq(2)
-
- expect(create_develop_builds).to be_truthy
- commit.builds.update_all(status: "success")
- expect(commit.builds.count(:all)).to eq(4)
- expect(commit.refs.size).to eq(2)
- expect(commit.builds.pluck(:name).uniq.size).to eq(2)
- end
- end
-
- context 'for build triggers' do
- let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
- let(:trigger_request) { FactoryGirl.create :ci_trigger_request, commit: commit, trigger: trigger }
-
- it 'creates builds' do
- expect(create_builds(trigger_request)).to be_truthy
- expect(commit.builds.count(:all)).to eq(2)
- end
-
- it 'rebuilds commit' do
- expect(create_builds).to be_truthy
- expect(commit.builds.count(:all)).to eq(2)
-
- expect(create_builds(trigger_request)).to be_truthy
- expect(commit.builds.count(:all)).to eq(4)
- end
-
- it 'creates next builds' do
- expect(create_builds(trigger_request)).to be_truthy
- expect(commit.builds.count(:all)).to eq(2)
- commit.builds.update_all(status: "success")
-
- expect(create_next_builds).to be_truthy
- expect(commit.builds.count(:all)).to eq(4)
- end
-
- context 'for [ci skip]' do
- before do
- allow(commit).to receive(:git_commit_message) { 'message [ci skip]' }
- end
-
- it 'rebuilds commit' do
- expect(commit.status).to eq('skipped')
- expect(create_builds).to be_truthy
-
- # since everything in Ci::Commit is cached we need to fetch a new object
- new_commit = Ci::Commit.find_by_id(commit.id)
- expect(new_commit.status).to eq('pending')
- end
- end
- end
-
-
context 'custom stage with first job allowed to fail' do
let(:yaml) do
{
@@ -284,6 +177,7 @@ describe Ci::Commit, models: true do
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
+ commit.reload
expect(commit.status).to eq('success')
end
@@ -306,6 +200,7 @@ describe Ci::Commit, models: true do
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
+ commit.reload
expect(commit.status).to eq('failed')
end
@@ -329,6 +224,7 @@ describe Ci::Commit, models: true do
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
+ commit.reload
expect(commit.status).to eq('failed')
end
@@ -351,6 +247,7 @@ describe Ci::Commit, models: true do
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
+ commit.reload
expect(commit.status).to eq('failed')
end
end
@@ -402,4 +299,98 @@ describe Ci::Commit, models: true do
expect(commit.coverage).to be_nil
end
end
+
+ describe '#retryable?' do
+ subject { commit.retryable? }
+
+ context 'no failed builds' do
+ before do
+ FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'success'
+ end
+
+ it 'be not retryable' do
+ is_expected.to be_falsey
+ end
+ end
+
+ context 'with failed builds' do
+ before do
+ FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'running'
+ FactoryGirl.create :ci_build, name: "rubocop", commit: commit, status: 'failed'
+ end
+
+ it 'be retryable' do
+ is_expected.to be_truthy
+ end
+ end
+ end
+
+ describe '#stages' do
+ let(:commit2) { FactoryGirl.create :ci_commit, project: project }
+ subject { CommitStatus.where(commit: [commit, commit2]).stages }
+
+ before do
+ FactoryGirl.create :ci_build, commit: commit2, stage: 'test', stage_idx: 1
+ FactoryGirl.create :ci_build, commit: commit, stage: 'build', stage_idx: 0
+ end
+
+ it 'return all stages' do
+ is_expected.to eq(%w(build test))
+ end
+ end
+
+ describe '#update_state' do
+ it 'execute update_state after touching object' do
+ expect(commit).to receive(:update_state).and_return(true)
+ commit.touch
+ end
+
+ context 'dependent objects' do
+ let(:commit_status) { build :commit_status, commit: commit }
+
+ it 'execute update_state after saving dependent object' do
+ expect(commit).to receive(:update_state).and_return(true)
+ commit_status.save
+ end
+ end
+
+ context 'update state' do
+ let(:current) { Time.now.change(usec: 0) }
+ let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 }
+
+ before do
+ build
+ end
+
+ [:status, :started_at, :finished_at, :duration].each do |param|
+ it "update #{param}" do
+ expect(commit.send(param)).to eq(build.send(param))
+ end
+ end
+ end
+ end
+
+ describe '#branch?' do
+ subject { commit.branch? }
+
+ context 'is not a tag' do
+ before do
+ commit.tag = false
+ end
+
+ it 'return true when tag is set to false' do
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'is not a tag' do
+ before do
+ commit.tag = true
+ end
+
+ it 'return false when tag is set to true' do
+ is_expected.to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 0e9111c8029..ad47e338a33 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -163,4 +163,12 @@ eos
it { expect(commit.reverts_commit?(another_commit)).to be_truthy }
end
end
+
+ describe '#ci_commits' do
+ # TODO: kamil
+ end
+
+ describe '#status' do
+ # TODO: kamil
+ end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 82c68ff6cb1..971e6750375 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -163,37 +163,73 @@ describe CommitStatus, models: true do
end
it 'return unique statuses' do
- is_expected.to eq([@commit2, @commit3, @commit4, @commit5])
+ is_expected.to eq([@commit4, @commit5])
end
end
- describe :for_ref do
- subject { CommitStatus.for_ref('bb').order(:id) }
+ describe :running_or_pending do
+ subject { CommitStatus.running_or_pending.order(:id) }
before do
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
+ @commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed'
+ @commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
end
- it 'return statuses with equal and nil ref set' do
- is_expected.to eq([@commit1])
+ it 'return statuses that are running or pending' do
+ is_expected.to eq([@commit1, @commit2])
end
end
- describe :running_or_pending do
- subject { CommitStatus.running_or_pending.order(:id) }
+ describe '#before_sha' do
+ subject { commit_status.before_sha }
+
+ context 'when no before_sha is set for ci::commit' do
+ before { commit.before_sha = nil }
+
+ it 'return blank sha' do
+ is_expected.to eq(Gitlab::Git::BLANK_SHA)
+ end
+ end
+
+ context 'for before_sha set for ci::commit' do
+ let(:value) { '1234' }
+ before { commit.before_sha = value }
+
+ it 'return the set value' do
+ is_expected.to eq(value)
+ end
+ end
+ end
+ describe '#stages' do
before do
- @commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
- @commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
- @commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
- @commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed'
- @commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'success'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'failed'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'deploy', stage_idx: 2, status: 'running'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'test', stage_idx: 1, status: 'success'
end
- it 'return statuses that are running or pending' do
- is_expected.to eq([@commit1, @commit2])
+ context 'stages list' do
+ subject { CommitStatus.where(commit: commit).stages }
+
+ it 'return ordered list of stages' do
+ is_expected.to eq(%w(build test deploy))
+ end
+ end
+
+ context 'stages with statuses' do
+ subject { CommitStatus.where(commit: commit).stages_status }
+
+ it 'return list of stages with statuses' do
+ is_expected.to eq({
+ 'build' => 'failed',
+ 'test' => 'success',
+ 'deploy' => 'running'
+ })
+ end
end
end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index b16ccc6e305..4a4cd093435 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -212,4 +212,34 @@ describe Issue, "Issuable" do
expect(issue.downvotes).to eq(1)
end
end
+
+ describe ".with_label" do
+ let(:project) { create(:project, :public) }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:feature) { create(:label, project: project, title: 'feature') }
+ let(:enhancement) { create(:label, project: project, title: 'enhancement') }
+ let(:issue1) { create(:issue, title: "Bugfix1", project: project) }
+ let(:issue2) { create(:issue, title: "Bugfix2", project: project) }
+ let(:issue3) { create(:issue, title: "Feature1", project: project) }
+
+ before(:each) do
+ issue1.labels << bug
+ issue1.labels << feature
+ issue2.labels << bug
+ issue2.labels << enhancement
+ issue3.labels << feature
+ end
+
+ it 'finds the correct issue containing just enhancement label' do
+ expect(Issue.with_label(enhancement.title)).to match_array([issue2])
+ end
+
+ it 'finds the correct issues containing the same label' do
+ expect(Issue.with_label(bug.title)).to match_array([issue1, issue2])
+ end
+
+ it 'finds the correct issues containing only both labels' do
+ expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2])
+ end
+ end
end
diff --git a/spec/lib/ci/status_spec.rb b/spec/models/concerns/statuseable_spec.rb
index 47f3df6e3ce..dacbd3034c0 100644
--- a/spec/lib/ci/status_spec.rb
+++ b/spec/models/concerns/statuseable_spec.rb
@@ -1,8 +1,17 @@
require 'spec_helper'
-describe Ci::Status do
- describe '.get_status' do
- subject { described_class.get_status(statuses) }
+describe Statuseable do
+ before do
+ @object = Object.new
+ @object.extend(Statuseable::ClassMethods)
+ end
+
+ describe '.status' do
+ before do
+ allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses))
+ end
+
+ subject { @object.status }
shared_examples 'build status summary' do
context 'all successful' do
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 89909c2bcd7..0c3cd13f399 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -30,32 +30,29 @@ describe Event, models: true do
it { is_expected.to respond_to(:commits) }
end
+ describe 'Callbacks' do
+ describe 'after_create :reset_project_activity' do
+ let(:project) { create(:project) }
+
+ context "project's last activity was less than 5 minutes ago" do
+ it 'does not update project.last_activity_at if it has been touched less than 5 minutes ago' do
+ create_event(project, project.owner)
+ project.update_column(:last_activity_at, 5.minutes.ago)
+ project_last_activity_at = project.last_activity_at
+
+ create_event(project, project.owner)
+
+ expect(project.last_activity_at).to eq(project_last_activity_at)
+ end
+ end
+ end
+ end
+
describe "Push event" do
before do
project = create(:project)
@user = project.owner
-
- data = {
- before: Gitlab::Git::BLANK_SHA,
- after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
- ref: "refs/heads/master",
- user_id: @user.id,
- user_name: @user.name,
- repository: {
- name: project.name,
- url: "localhost/rubinius",
- description: "",
- homepage: "localhost/rubinius",
- private: true
- }
- }
-
- @event = Event.create(
- project: project,
- action: Event::PUSHED,
- data: data,
- author_id: @user.id
- )
+ @event = create_event(project, @user)
end
it { expect(@event.push?).to be_truthy }
@@ -143,4 +140,28 @@ describe Event, models: true do
it { is_expected.to eq([event2]) }
end
end
+
+ def create_event(project, user, attrs = {})
+ data = {
+ before: Gitlab::Git::BLANK_SHA,
+ after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
+ ref: "refs/heads/master",
+ user_id: user.id,
+ user_name: user.name,
+ repository: {
+ name: project.name,
+ url: "localhost/rubinius",
+ description: "",
+ homepage: "localhost/rubinius",
+ private: true
+ }
+ }
+
+ Event.create({
+ project: project,
+ action: Event::PUSHED,
+ data: data,
+ author_id: user.id
+ }.merge(attrs))
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 6f5d912fe5d..d7884cea336 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -404,12 +404,12 @@ describe MergeRequest, models: true do
describe 'when the source project exists' do
it 'returns the latest commit' do
commit = double(:commit, id: '123abc')
- ci_commit = double(:ci_commit)
+ ci_commit = double(:ci_commit, ref: 'master')
allow(subject).to receive(:last_commit).and_return(commit)
expect(subject.source_project).to receive(:ci_commit).
- with('123abc').
+ with('123abc', 'master').
and_return(ci_commit)
expect(subject.ci_commit).to eq(ci_commit)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f29c389e094..e33c7d62ff4 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -441,9 +441,22 @@ describe Project, models: true do
describe :ci_commit do
let(:project) { create :project }
- let(:commit) { create :ci_commit, project: project }
+ let(:commit) { create :ci_commit, project: project, ref: 'master' }
- it { expect(project.ci_commit(commit.sha)).to eq(commit) }
+ subject { project.ci_commit(commit.sha, 'master') }
+
+ it { is_expected.to eq(commit) }
+
+ context 'return latest' do
+ let(:commit2) { create :ci_commit, project: project, ref: 'master' }
+
+ before do
+ commit
+ commit2
+ end
+
+ it { is_expected.to eq(commit2) }
+ end
end
describe :builds_enabled do
@@ -706,11 +719,8 @@ describe Project, models: true do
with('foo.wiki', project).
and_return(wiki)
- expect(repo).to receive(:expire_cache)
- expect(repo).to receive(:expire_emptiness_caches)
-
- expect(wiki).to receive(:expire_cache)
- expect(wiki).to receive(:expire_emptiness_caches)
+ expect(repo).to receive(:before_delete)
+ expect(wiki).to receive(:before_delete)
project.expire_caches_before_rename('foo')
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index b561aa663d1..c19524a01f8 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -132,7 +132,6 @@ describe Repository, models: true do
it { expect(subject.basename).to eq('a/b/c') }
end
end
-
end
describe '#license_blob' do
@@ -148,39 +147,18 @@ describe Repository, models: true do
expect(repository.license_blob).to be_nil
end
- it 'favors license file with no extension' do
- repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
- repository.commit_file(user, 'LICENSE.md', Licensee::License.new('mit').content, 'Add LICENSE.md', 'master', false)
-
- expect(repository.license_blob.name).to eq('LICENSE')
- end
-
- it 'favors .md file to .txt' do
- repository.commit_file(user, 'LICENSE.md', Licensee::License.new('mit').content, 'Add LICENSE.md', 'master', false)
- repository.commit_file(user, 'LICENSE.txt', Licensee::License.new('mit').content, 'Add LICENSE.txt', 'master', false)
-
- expect(repository.license_blob.name).to eq('LICENSE.md')
- end
-
- it 'favors LICENCE to LICENSE' do
- repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
- repository.commit_file(user, 'LICENCE', Licensee::License.new('mit').content, 'Add LICENCE', 'master', false)
-
- expect(repository.license_blob.name).to eq('LICENCE')
- end
-
- it 'favors LICENSE to COPYING' do
- repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
- repository.commit_file(user, 'COPYING', Licensee::License.new('mit').content, 'Add COPYING', 'master', false)
+ it 'detects license file with no recognizable open-source license content' do
+ repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
expect(repository.license_blob.name).to eq('LICENSE')
end
- it 'favors LICENCE to COPYING' do
- repository.commit_file(user, 'LICENCE', Licensee::License.new('mit').content, 'Add LICENCE', 'master', false)
- repository.commit_file(user, 'COPYING', Licensee::License.new('mit').content, 'Add COPYING', 'master', false)
+ %w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
+ it "detects '#{filename}'" do
+ repository.commit_file(user, filename, Licensee::License.new('mit').content, "Add #{filename}", 'master', false)
- expect(repository.license_blob.name).to eq('LICENCE')
+ expect(repository.license_blob.name).to eq(filename)
+ end
end
end
@@ -190,8 +168,14 @@ describe Repository, models: true do
repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
end
- it 'returns "no-license" when no license is detected' do
- expect(repository.license_key).to eq('no-license')
+ it 'returns nil when no license is detected' do
+ expect(repository.license_key).to be_nil
+ end
+
+ it 'detects license file with no recognizable open-source license content' do
+ repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+
+ expect(repository.license_key).to be_nil
end
it 'returns the license key' do
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 967c34800d0..5ead735be48 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -59,7 +59,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/repository/commits/:sha/builds' do
before do
- project.ensure_ci_commit(commit.sha)
+ project.ensure_ci_commit(commit.sha, 'master')
get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user)
end
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
index 429a24109fd..f3785b19362 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_status_spec.rb
@@ -16,7 +16,8 @@ describe API::CommitStatus, api: true do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
- let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
+ let!(:master) { project.ci_commits.create(sha: commit.id, ref: 'master') }
+ let!(:develop) { project.ci_commits.create(sha: commit.id, ref: 'develop') }
it_behaves_like 'a paginated resources' do
let(:request) { get api(get_url, reporter) }
@@ -25,16 +26,16 @@ describe API::CommitStatus, api: true do
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
- def create_status(opts = {})
- create(:commit_status, { commit: ci_commit }.merge(opts))
+ def create_status(commit, opts = {})
+ create(:commit_status, { commit: commit, ref: commit.ref }.merge(opts))
end
- let!(:status1) { create_status(status: 'running') }
- let!(:status2) { create_status(name: 'coverage', status: 'pending') }
- let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) }
- let!(:status4) { create_status(name: 'coverage', status: 'success') }
- let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') }
- let!(:status6) { create_status(status: 'success') }
+ let!(:status1) { create_status(master, status: 'running') }
+ let!(:status2) { create_status(master, name: 'coverage', status: 'pending') }
+ let!(:status3) { create_status(develop, status: 'running', allow_failure: true) }
+ let!(:status4) { create_status(master, name: 'coverage', status: 'success') }
+ let!(:status5) { create_status(develop, name: 'coverage', status: 'success') }
+ let!(:status6) { create_status(master, status: 'success') }
context 'latest commit statuses' do
before { get api(get_url, reporter) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 7ff21175c1b..e28998d51b5 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -48,14 +48,14 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
- it "should return not_found for CI status" do
+ it "should return nil for commit without CI" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200)
- expect(json_response['status']).to eq('not_found')
+ expect(json_response['status']).to be_nil
end
it "should return status for CI" do
- ci_commit = project.ensure_ci_commit(project.repository.commit.sha)
+ ci_commit = project.ensure_ci_commit(project.repository.commit.sha, 'master')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200)
expect(json_response['status']).to eq(ci_commit.status)
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 142b637d291..ffb93bbb120 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -148,14 +148,24 @@ describe API::API, 'ProjectHooks', api: true do
expect(response.status).to eq(200)
end
- it "should return success when deleting non existent hook" do
+ it "should return a 404 error when deleting non existent hook" do
delete api("/projects/#{project.id}/hooks/42", user)
- expect(response.status).to eq(200)
+ expect(response.status).to eq(404)
end
it "should return a 405 error if hook id not given" do
delete api("/projects/#{project.id}/hooks", user)
expect(response.status).to eq(405)
end
+
+ it "shold return a 404 if a user attempts to delete project hooks he/she does not own" do
+ test_user = create(:user)
+ other_project = create(:project)
+ other_project.team << [test_user, :master]
+
+ delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user)
+ expect(response.status).to eq(404)
+ expect(WebHook.exists?(hook.id)).to be_truthy
+ end
end
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index ebd16c7efbe..dfd361a2cdd 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -20,8 +20,8 @@ describe Ci::API::API do
describe "POST /builds/register" do
it "should start a build" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil)
build = commit.builds.first
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -56,8 +56,8 @@ describe Ci::API::API do
end
it "returns options" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -66,8 +66,8 @@ describe Ci::API::API do
end
it "returns variables" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -83,10 +83,10 @@ describe Ci::API::API do
it "returns variables for triggers" do
trigger = FactoryGirl.create(:ci_trigger, project: project)
- commit = FactoryGirl.create(:ci_commit, project: project)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
- commit.create_builds('master', false, nil, trigger_request)
+ commit.create_builds(nil, trigger_request)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -103,8 +103,8 @@ describe Ci::API::API do
end
it "returns dependent builds" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil, nil)
commit.builds.where(stage: 'test').each(&:success)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb
index 1fca3628686..ecc3a88a262 100644
--- a/spec/services/ci/create_builds_service_spec.rb
+++ b/spec/services/ci/create_builds_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Ci::CreateBuildsService, services: true do
- let(:commit) { create(:ci_commit) }
+ let(:commit) { create(:ci_commit, ref: 'master') }
let(:user) { create(:user) }
describe '#execute' do
@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do
#
subject do
- described_class.new.execute(commit, 'test', 'master', nil, user, nil, status)
+ described_class.new(commit).execute(commit, nil, user, status)
end
context 'next builds available' do
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index 870861ad20a..4cc4b3870d1 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' }
- let(:commit) { project.ensure_ci_commit(commit_sha) }
+ let(:commit) { project.ensure_ci_commit(commit_sha, 'master') }
let(:build) { FactoryGirl.create(:ci_build, commit: commit) }
describe :execute do
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index 6a7ea4b2f44..e91906d0d49 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -100,7 +100,7 @@ describe Issues::BulkUpdateService, services: true do
describe :update_milestone do
before do
- @milestone = create :milestone
+ @milestone = create(:milestone, project: @project)
@params = {
issues_ids: [issue.id],
milestone_id: @milestone.id
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 5e7915db7e1..ac28b6f71f9 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -3,40 +3,75 @@ require 'spec_helper'
describe Issues::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- let(:assignee) { create(:user) }
- describe :execute do
- context 'valid params' do
+ describe '#execute' do
+ let(:issue) { described_class.new(project, user, opts).execute }
+
+ context 'when params are valid' do
+ let(:assignee) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:labels) { create_pair(:label, project: project) }
+
before do
project.team << [user, :master]
project.team << [assignee, :master]
+ end
- opts = {
- title: 'Awesome issue',
+ let(:opts) do
+ { title: 'Awesome issue',
description: 'please fix',
- assignee: assignee
- }
-
- @issue = Issues::CreateService.new(project, user, opts).execute
+ assignee: assignee,
+ label_ids: labels.map(&:id),
+ milestone_id: milestone.id }
end
- it { expect(@issue).to be_valid }
- it { expect(@issue.title).to eq('Awesome issue') }
- it { expect(@issue.assignee).to eq assignee }
+ it { expect(issue).to be_valid }
+ it { expect(issue.title).to eq('Awesome issue') }
+ it { expect(issue.assignee).to eq assignee }
+ it { expect(issue.labels).to match_array labels }
+ it { expect(issue.milestone).to eq milestone }
it 'creates a pending todo for new assignee' do
attributes = {
project: project,
author: user,
user: assignee,
- target_id: @issue.id,
- target_type: @issue.class.name,
+ target_id: issue.id,
+ target_type: issue.class.name,
action: Todo::ASSIGNED,
state: :pending
}
expect(Todo.where(attributes).count).to eq 1
end
+
+ context 'when label belongs to different project' do
+ let(:label) { create(:label) }
+
+ let(:opts) do
+ { title: 'Title',
+ description: 'Description',
+ label_ids: [label.id] }
+ end
+
+ it 'does not assign label'do
+ expect(issue.labels).to_not include label
+ end
+ end
+
+ context 'when milestone belongs to different project' do
+ let(:milestone) { create(:milestone) }
+
+ let(:opts) do
+ { title: 'Title',
+ description: 'Description',
+ milestone_id: milestone.id }
+ end
+
+ it 'does not assign milestone' do
+ expect(issue.milestone).to_not eq milestone
+ end
+ end
end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 6b214a0d96b..52f69306994 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -4,10 +4,15 @@ describe Issues::UpdateService, services: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
- let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) }
- let(:label) { create(:label) }
+ let(:project) { create(:empty_project) }
+ let(:label) { create(:label, project: project) }
let(:label2) { create(:label) }
- let(:project) { issue.project }
+
+ let(:issue) do
+ create(:issue, title: 'Old title',
+ assignee_id: user3.id,
+ project: project)
+ end
before do
project.team << [user, :master]
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index cb8cff2fa8c..213e8c2eb3a 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,14 +1,19 @@
require 'spec_helper'
describe MergeRequests::UpdateService, services: true do
+ let(:project) { create(:project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
- let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) }
- let(:project) { merge_request.project }
- let(:label) { create(:label) }
+ let(:label) { create(:label, project: project) }
let(:label2) { create(:label) }
+ let(:merge_request) do
+ create(:merge_request, :simple, title: 'Old title',
+ assignee_id: user3.id,
+ source_project: project)
+ end
+
before do
project.team << [user, :master]
project.team << [user2, :developer]
diff --git a/vendor/assets/javascripts/raven.js b/vendor/assets/javascripts/raven.js
deleted file mode 100644
index d99c6f1c2c8..00000000000
--- a/vendor/assets/javascripts/raven.js
+++ /dev/null
@@ -1,2435 +0,0 @@
-/*! Raven.js 2.3.0 (b09d766) | github.com/getsentry/raven-js */
-
-/*
- * Includes TraceKit
- * https://github.com/getsentry/TraceKit
- *
- * Copyright 2016 Matt Robenolt and other contributors
- * Released under the BSD license
- * https://github.com/getsentry/raven-js/blob/master/LICENSE
- *
- */
-
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
-'use strict';
-
-function RavenConfigError(message) {
- this.name = 'RavenConfigError';
- this.message = message;
-}
-RavenConfigError.prototype = new Error();
-RavenConfigError.prototype.constructor = RavenConfigError;
-
-module.exports = RavenConfigError;
-
-},{}],2:[function(_dereq_,module,exports){
-/*global XDomainRequest:false*/
-'use strict';
-
-var TraceKit = _dereq_(5);
-var RavenConfigError = _dereq_(1);
-var utils = _dereq_(4);
-
-var isFunction = utils.isFunction;
-var isUndefined = utils.isUndefined;
-var isError = utils.isError;
-var isEmptyObject = utils.isEmptyObject;
-var hasKey = utils.hasKey;
-var joinRegExp = utils.joinRegExp;
-var each = utils.each;
-var objectMerge = utils.objectMerge;
-var truncate = utils.truncate;
-var urlencode = utils.urlencode;
-var uuid4 = utils.uuid4;
-
-var dsnKeys = 'source protocol user pass host port path'.split(' '),
- dsnPattern = /^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)/;
-
-function now() {
- return +new Date();
-}
-
-// First, check for JSON support
-// If there is no JSON, we no-op the core features of Raven
-// since JSON is required to encode the payload
-function Raven() {
- this._hasJSON = !!(typeof JSON === 'object' && JSON.stringify);
- // Raven can run in contexts where there's no document (react-native)
- this._hasDocument = typeof document !== 'undefined';
- this._lastCapturedException = null;
- this._lastEventId = null;
- this._globalServer = null;
- this._globalKey = null;
- this._globalProject = null;
- this._globalContext = {};
- this._globalOptions = {
- logger: 'javascript',
- ignoreErrors: [],
- ignoreUrls: [],
- whitelistUrls: [],
- includePaths: [],
- crossOrigin: 'anonymous',
- collectWindowErrors: true,
- maxMessageLength: 0,
- stackTraceLimit: 50
- };
- this._ignoreOnError = 0;
- this._isRavenInstalled = false;
- this._originalErrorStackTraceLimit = Error.stackTraceLimit;
- // capture references to window.console *and* all its methods first
- // before the console plugin has a chance to monkey patch
- this._originalConsole = window.console || {};
- this._originalConsoleMethods = {};
- this._plugins = [];
- this._startTime = now();
- this._wrappedBuiltIns = [];
-
- for (var method in this._originalConsole) { // eslint-disable-line guard-for-in
- this._originalConsoleMethods[method] = this._originalConsole[method];
- }
-}
-
-/*
- * The core Raven singleton
- *
- * @this {Raven}
- */
-
-Raven.prototype = {
- // Hardcode version string so that raven source can be loaded directly via
- // webpack (using a build step causes webpack #1617). Grunt verifies that
- // this value matches package.json during build.
- // See: https://github.com/getsentry/raven-js/issues/465
- VERSION: '2.3.0',
-
- debug: false,
-
- TraceKit: TraceKit, // alias to TraceKit
-
- /*
- * Configure Raven with a DSN and extra options
- *
- * @param {string} dsn The public Sentry DSN
- * @param {object} options Optional set of of global options [optional]
- * @return {Raven}
- */
- config: function(dsn, options) {
- var self = this;
-
- if (this._globalServer) {
- this._logDebug('error', 'Error: Raven has already been configured');
- return this;
- }
- if (!dsn) return this;
-
- // merge in options
- if (options) {
- each(options, function(key, value){
- // tags and extra are special and need to be put into context
- if (key === 'tags' || key === 'extra') {
- self._globalContext[key] = value;
- } else {
- self._globalOptions[key] = value;
- }
- });
- }
-
- var uri = this._parseDSN(dsn),
- lastSlash = uri.path.lastIndexOf('/'),
- path = uri.path.substr(1, lastSlash);
-
- this._dsn = dsn;
-
- // "Script error." is hard coded into browsers for errors that it can't read.
- // this is the result of a script being pulled in from an external domain and CORS.
- this._globalOptions.ignoreErrors.push(/^Script error\.?$/);
- this._globalOptions.ignoreErrors.push(/^Javascript error: Script error\.? on line 0$/);
-
- // join regexp rules into one big rule
- this._globalOptions.ignoreErrors = joinRegExp(this._globalOptions.ignoreErrors);
- this._globalOptions.ignoreUrls = this._globalOptions.ignoreUrls.length ? joinRegExp(this._globalOptions.ignoreUrls) : false;
- this._globalOptions.whitelistUrls = this._globalOptions.whitelistUrls.length ? joinRegExp(this._globalOptions.whitelistUrls) : false;
- this._globalOptions.includePaths = joinRegExp(this._globalOptions.includePaths);
-
- this._globalKey = uri.user;
- this._globalSecret = uri.pass && uri.pass.substr(1);
- this._globalProject = uri.path.substr(lastSlash + 1);
-
- this._globalServer = this._getGlobalServer(uri);
-
- this._globalEndpoint = this._globalServer +
- '/' + path + 'api/' + this._globalProject + '/store/';
-
- if (this._globalOptions.fetchContext) {
- TraceKit.remoteFetching = true;
- }
-
- if (this._globalOptions.linesOfContext) {
- TraceKit.linesOfContext = this._globalOptions.linesOfContext;
- }
-
- TraceKit.collectWindowErrors = !!this._globalOptions.collectWindowErrors;
-
- // return for chaining
- return this;
- },
-
- /*
- * Installs a global window.onerror error handler
- * to capture and report uncaught exceptions.
- * At this point, install() is required to be called due
- * to the way TraceKit is set up.
- *
- * @return {Raven}
- */
- install: function() {
- var self = this;
- if (this.isSetup() && !this._isRavenInstalled) {
- TraceKit.report.subscribe(function () {
- self._handleOnErrorStackInfo.apply(self, arguments);
- });
- this._wrapBuiltIns();
-
- // Install all of the plugins
- this._drainPlugins();
-
- this._isRavenInstalled = true;
- }
-
- Error.stackTraceLimit = this._globalOptions.stackTraceLimit;
- return this;
- },
-
- /*
- * Wrap code within a context so Raven can capture errors
- * reliably across domains that is executed immediately.
- *
- * @param {object} options A specific set of options for this context [optional]
- * @param {function} func The callback to be immediately executed within the context
- * @param {array} args An array of arguments to be called with the callback [optional]
- */
- context: function(options, func, args) {
- if (isFunction(options)) {
- args = func || [];
- func = options;
- options = undefined;
- }
-
- return this.wrap(options, func).apply(this, args);
- },
-
- /*
- * Wrap code within a context and returns back a new function to be executed
- *
- * @param {object} options A specific set of options for this context [optional]
- * @param {function} func The function to be wrapped in a new context
- * @return {function} The newly wrapped functions with a context
- */
- wrap: function(options, func) {
- var self = this;
-
- // 1 argument has been passed, and it's not a function
- // so just return it
- if (isUndefined(func) && !isFunction(options)) {
- return options;
- }
-
- // options is optional
- if (isFunction(options)) {
- func = options;
- options = undefined;
- }
-
- // At this point, we've passed along 2 arguments, and the second one
- // is not a function either, so we'll just return the second argument.
- if (!isFunction(func)) {
- return func;
- }
-
- // We don't wanna wrap it twice!
- try {
- if (func.__raven__) {
- return func;
- }
- } catch (e) {
- // Just accessing the __raven__ prop in some Selenium environments
- // can cause a "Permission denied" exception (see raven-js#495).
- // Bail on wrapping and return the function as-is (defers to window.onerror).
- return func;
- }
-
- // If this has already been wrapped in the past, return that
- if (func.__raven_wrapper__ ){
- return func.__raven_wrapper__ ;
- }
-
- function wrapped() {
- var args = [], i = arguments.length,
- deep = !options || options && options.deep !== false;
- // Recursively wrap all of a function's arguments that are
- // functions themselves.
-
- while(i--) args[i] = deep ? self.wrap(options, arguments[i]) : arguments[i];
-
- try {
- return func.apply(this, args);
- } catch(e) {
- self._ignoreNextOnError();
- self.captureException(e, options);
- throw e;
- }
- }
-
- // copy over properties of the old function
- for (var property in func) {
- if (hasKey(func, property)) {
- wrapped[property] = func[property];
- }
- }
- func.__raven_wrapper__ = wrapped;
-
- wrapped.prototype = func.prototype;
-
- // Signal that this function has been wrapped already
- // for both debugging and to prevent it to being wrapped twice
- wrapped.__raven__ = true;
- wrapped.__inner__ = func;
-
- return wrapped;
- },
-
- /*
- * Uninstalls the global error handler.
- *
- * @return {Raven}
- */
- uninstall: function() {
- TraceKit.report.uninstall();
-
- this._restoreBuiltIns();
-
- Error.stackTraceLimit = this._originalErrorStackTraceLimit;
- this._isRavenInstalled = false;
-
- return this;
- },
-
- /*
- * Manually capture an exception and send it over to Sentry
- *
- * @param {error} ex An exception to be logged
- * @param {object} options A specific set of options for this error [optional]
- * @return {Raven}
- */
- captureException: function(ex, options) {
- // If not an Error is passed through, recall as a message instead
- if (!isError(ex)) return this.captureMessage(ex, options);
-
- // Store the raw exception object for potential debugging and introspection
- this._lastCapturedException = ex;
-
- // TraceKit.report will re-raise any exception passed to it,
- // which means you have to wrap it in try/catch. Instead, we
- // can wrap it here and only re-raise if TraceKit.report
- // raises an exception different from the one we asked to
- // report on.
- try {
- var stack = TraceKit.computeStackTrace(ex);
- this._handleStackInfo(stack, options);
- } catch(ex1) {
- if(ex !== ex1) {
- throw ex1;
- }
- }
-
- return this;
- },
-
- /*
- * Manually send a message to Sentry
- *
- * @param {string} msg A plain message to be captured in Sentry
- * @param {object} options A specific set of options for this message [optional]
- * @return {Raven}
- */
- captureMessage: function(msg, options) {
- // config() automagically converts ignoreErrors from a list to a RegExp so we need to test for an
- // early call; we'll error on the side of logging anything called before configuration since it's
- // probably something you should see:
- if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(msg)) {
- return;
- }
-
- // Fire away!
- this._send(
- objectMerge({
- message: msg + '' // Make sure it's actually a string
- }, options)
- );
-
- return this;
- },
-
- addPlugin: function(plugin /*arg1, arg2, ... argN*/) {
- var pluginArgs = Array.prototype.slice.call(arguments, 1);
-
- this._plugins.push([plugin, pluginArgs]);
- if (this._isRavenInstalled) {
- this._drainPlugins();
- }
-
- return this;
- },
-
- /*
- * Set/clear a user to be sent along with the payload.
- *
- * @param {object} user An object representing user data [optional]
- * @return {Raven}
- */
- setUserContext: function(user) {
- // Intentionally do not merge here since that's an unexpected behavior.
- this._globalContext.user = user;
-
- return this;
- },
-
- /*
- * Merge extra attributes to be sent along with the payload.
- *
- * @param {object} extra An object representing extra data [optional]
- * @return {Raven}
- */
- setExtraContext: function(extra) {
- this._mergeContext('extra', extra);
-
- return this;
- },
-
- /*
- * Merge tags to be sent along with the payload.
- *
- * @param {object} tags An object representing tags [optional]
- * @return {Raven}
- */
- setTagsContext: function(tags) {
- this._mergeContext('tags', tags);
-
- return this;
- },
-
- /*
- * Clear all of the context.
- *
- * @return {Raven}
- */
- clearContext: function() {
- this._globalContext = {};
-
- return this;
- },
-
- /*
- * Get a copy of the current context. This cannot be mutated.
- *
- * @return {object} copy of context
- */
- getContext: function() {
- // lol javascript
- return JSON.parse(JSON.stringify(this._globalContext));
- },
-
- /*
- * Set release version of application
- *
- * @param {string} release Typically something like a git SHA to identify version
- * @return {Raven}
- */
- setRelease: function(release) {
- this._globalOptions.release = release;
-
- return this;
- },
-
- /*
- * Set the dataCallback option
- *
- * @param {function} callback The callback to run which allows the
- * data blob to be mutated before sending
- * @return {Raven}
- */
- setDataCallback: function(callback) {
- this._globalOptions.dataCallback = callback;
-
- return this;
- },
-
- /*
- * Set the shouldSendCallback option
- *
- * @param {function} callback The callback to run which allows
- * introspecting the blob before sending
- * @return {Raven}
- */
- setShouldSendCallback: function(callback) {
- this._globalOptions.shouldSendCallback = callback;
-
- return this;
- },
-
- /**
- * Override the default HTTP transport mechanism that transmits data
- * to the Sentry server.
- *
- * @param {function} transport Function invoked instead of the default
- * `makeRequest` handler.
- *
- * @return {Raven}
- */
- setTransport: function(transport) {
- this._globalOptions.transport = transport;
-
- return this;
- },
-
- /*
- * Get the latest raw exception that was captured by Raven.
- *
- * @return {error}
- */
- lastException: function() {
- return this._lastCapturedException;
- },
-
- /*
- * Get the last event id
- *
- * @return {string}
- */
- lastEventId: function() {
- return this._lastEventId;
- },
-
- /*
- * Determine if Raven is setup and ready to go.
- *
- * @return {boolean}
- */
- isSetup: function() {
- if (!this._hasJSON) return false; // needs JSON support
- if (!this._globalServer) {
- if (!this.ravenNotConfiguredError) {
- this.ravenNotConfiguredError = true;
- this._logDebug('error', 'Error: Raven has not been configured.');
- }
- return false;
- }
- return true;
- },
-
- afterLoad: function () {
- // TODO: remove window dependence?
-
- // Attempt to initialize Raven on load
- var RavenConfig = window.RavenConfig;
- if (RavenConfig) {
- this.config(RavenConfig.dsn, RavenConfig.config).install();
- }
- },
-
- showReportDialog: function (options) {
- if (!window.document) // doesn't work without a document (React native)
- return;
-
- options = options || {};
-
- var lastEventId = options.eventId || this.lastEventId();
- if (!lastEventId) {
- throw new RavenConfigError('Missing eventId');
- }
-
- var dsn = options.dsn || this._dsn;
- if (!dsn) {
- throw new RavenConfigError('Missing DSN');
- }
-
- var encode = encodeURIComponent;
- var qs = '';
- qs += '?eventId=' + encode(lastEventId);
- qs += '&dsn=' + encode(dsn);
-
- var user = options.user || this._globalContext.user;
- if (user) {
- if (user.name) qs += '&name=' + encode(user.name);
- if (user.email) qs += '&email=' + encode(user.email);
- }
-
- var globalServer = this._getGlobalServer(this._parseDSN(dsn));
-
- var script = document.createElement('script');
- script.async = true;
- script.src = globalServer + '/api/embed/error-page/' + qs;
- (document.head || document.body).appendChild(script);
- },
-
- /**** Private functions ****/
- _ignoreNextOnError: function () {
- var self = this;
- this._ignoreOnError += 1;
- setTimeout(function () {
- // onerror should trigger before setTimeout
- self._ignoreOnError -= 1;
- });
- },
-
- _triggerEvent: function(eventType, options) {
- // NOTE: `event` is a native browser thing, so let's avoid conflicting wiht it
- var evt, key;
-
- if (!this._hasDocument)
- return;
-
- options = options || {};
-
- eventType = 'raven' + eventType.substr(0,1).toUpperCase() + eventType.substr(1);
-
- if (document.createEvent) {
- evt = document.createEvent('HTMLEvents');
- evt.initEvent(eventType, true, true);
- } else {
- evt = document.createEventObject();
- evt.eventType = eventType;
- }
-
- for (key in options) if (hasKey(options, key)) {
- evt[key] = options[key];
- }
-
- if (document.createEvent) {
- // IE9 if standards
- document.dispatchEvent(evt);
- } else {
- // IE8 regardless of Quirks or Standards
- // IE9 if quirks
- try {
- document.fireEvent('on' + evt.eventType.toLowerCase(), evt);
- } catch(e) {
- // Do nothing
- }
- }
- },
-
- /**
- * Install any queued plugins
- */
- _wrapBuiltIns: function() {
- var self = this;
-
- function fill(obj, name, replacement, noUndo) {
- var orig = obj[name];
- obj[name] = replacement(orig);
- if (!noUndo) {
- self._wrappedBuiltIns.push([obj, name, orig]);
- }
- }
-
- function wrapTimeFn(orig) {
- return function (fn, t) { // preserve arity
- // Make a copy of the arguments
- var args = [].slice.call(arguments);
- var originalCallback = args[0];
- if (isFunction(originalCallback)) {
- args[0] = self.wrap(originalCallback);
- }
-
- // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
- // also supports only two arguments and doesn't care what this is, so we
- // can just call the original function directly.
- if (orig.apply) {
- return orig.apply(this, args);
- } else {
- return orig(args[0], args[1]);
- }
- };
- }
-
- fill(window, 'setTimeout', wrapTimeFn);
- fill(window, 'setInterval', wrapTimeFn);
- if (window.requestAnimationFrame) {
- fill(window, 'requestAnimationFrame', function (orig) {
- return function (cb) {
- return orig(self.wrap(cb));
- };
- });
- }
-
- // event targets borrowed from bugsnag-js:
- // https://github.com/bugsnag/bugsnag-js/blob/master/src/bugsnag.js#L666
- 'EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload'.replace(/\w+/g, function (global) {
- var proto = window[global] && window[global].prototype;
- if (proto && proto.hasOwnProperty && proto.hasOwnProperty('addEventListener')) {
- fill(proto, 'addEventListener', function(orig) {
- return function (evt, fn, capture, secure) { // preserve arity
- try {
- if (fn && fn.handleEvent) {
- fn.handleEvent = self.wrap(fn.handleEvent);
- }
- } catch (err) {
- // can sometimes get 'Permission denied to access property "handle Event'
- }
- return orig.call(this, evt, self.wrap(fn), capture, secure);
- };
- });
- fill(proto, 'removeEventListener', function (orig) {
- return function (evt, fn, capture, secure) {
- fn = fn && (fn.__raven_wrapper__ ? fn.__raven_wrapper__ : fn);
- return orig.call(this, evt, fn, capture, secure);
- };
- });
- }
- });
-
- if ('XMLHttpRequest' in window) {
- fill(XMLHttpRequest.prototype, 'send', function(origSend) {
- return function (data) { // preserve arity
- var xhr = this;
- 'onreadystatechange onload onerror onprogress'.replace(/\w+/g, function (prop) {
- if (prop in xhr && Object.prototype.toString.call(xhr[prop]) === '[object Function]') {
- fill(xhr, prop, function (orig) {
- return self.wrap(orig);
- }, true /* noUndo */); // don't track filled methods on XHR instances
- }
- });
- return origSend.apply(this, arguments);
- };
- });
- }
-
- var $ = window.jQuery || window.$;
- if ($ && $.fn && $.fn.ready) {
- fill($.fn, 'ready', function (orig) {
- return function (fn) {
- return orig.call(this, self.wrap(fn));
- };
- });
- }
- },
-
- _restoreBuiltIns: function () {
- // restore any wrapped builtins
- var builtin;
- while (this._wrappedBuiltIns.length) {
- builtin = this._wrappedBuiltIns.shift();
-
- var obj = builtin[0],
- name = builtin[1],
- orig = builtin[2];
-
- obj[name] = orig;
- }
- },
-
- _drainPlugins: function() {
- var self = this;
-
- // FIX ME TODO
- each(this._plugins, function(_, plugin) {
- var installer = plugin[0];
- var args = plugin[1];
- installer.apply(self, [self].concat(args));
- });
- },
-
- _parseDSN: function(str) {
- var m = dsnPattern.exec(str),
- dsn = {},
- i = 7;
-
- try {
- while (i--) dsn[dsnKeys[i]] = m[i] || '';
- } catch(e) {
- throw new RavenConfigError('Invalid DSN: ' + str);
- }
-
- if (dsn.pass && !this._globalOptions.allowSecretKey) {
- throw new RavenConfigError('Do not specify your secret key in the DSN. See: http://bit.ly/raven-secret-key');
- }
-
- return dsn;
- },
-
- _getGlobalServer: function(uri) {
- // assemble the endpoint from the uri pieces
- var globalServer = '//' + uri.host +
- (uri.port ? ':' + uri.port : '');
-
- if (uri.protocol) {
- globalServer = uri.protocol + ':' + globalServer;
- }
- return globalServer;
- },
-
- _handleOnErrorStackInfo: function() {
- // if we are intentionally ignoring errors via onerror, bail out
- if (!this._ignoreOnError) {
- this._handleStackInfo.apply(this, arguments);
- }
- },
-
- _handleStackInfo: function(stackInfo, options) {
- var self = this;
- var frames = [];
-
- if (stackInfo.stack && stackInfo.stack.length) {
- each(stackInfo.stack, function(i, stack) {
- var frame = self._normalizeFrame(stack);
- if (frame) {
- frames.push(frame);
- }
- });
- }
-
- this._triggerEvent('handle', {
- stackInfo: stackInfo,
- options: options
- });
-
- this._processException(
- stackInfo.name,
- stackInfo.message,
- stackInfo.url,
- stackInfo.lineno,
- frames.slice(0, this._globalOptions.stackTraceLimit),
- options
- );
- },
-
- _normalizeFrame: function(frame) {
- if (!frame.url) return;
-
- // normalize the frames data
- var normalized = {
- filename: frame.url,
- lineno: frame.line,
- colno: frame.column,
- 'function': frame.func || '?'
- }, context = this._extractContextFromFrame(frame), i;
-
- if (context) {
- var keys = ['pre_context', 'context_line', 'post_context'];
- i = 3;
- while (i--) normalized[keys[i]] = context[i];
- }
-
- normalized.in_app = !( // determine if an exception came from outside of our app
- // first we check the global includePaths list.
- !!this._globalOptions.includePaths.test && !this._globalOptions.includePaths.test(normalized.filename) ||
- // Now we check for fun, if the function name is Raven or TraceKit
- /(Raven|TraceKit)\./.test(normalized['function']) ||
- // finally, we do a last ditch effort and check for raven.min.js
- /raven\.(min\.)?js$/.test(normalized.filename)
- );
-
- return normalized;
- },
-
- _extractContextFromFrame: function(frame) {
- // immediately check if we should even attempt to parse a context
- if (!frame.context || !this._globalOptions.fetchContext) return;
-
- var context = frame.context,
- pivot = ~~(context.length / 2),
- i = context.length, isMinified = false;
-
- while (i--) {
- // We're making a guess to see if the source is minified or not.
- // To do that, we make the assumption if *any* of the lines passed
- // in are greater than 300 characters long, we bail.
- // Sentry will see that there isn't a context
- if (context[i].length > 300) {
- isMinified = true;
- break;
- }
- }
-
- if (isMinified) {
- // The source is minified and we don't know which column. Fuck it.
- if (isUndefined(frame.column)) return;
-
- // If the source is minified and has a frame column
- // we take a chunk of the offending line to hopefully shed some light
- return [
- [], // no pre_context
- context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column
- [] // no post_context
- ];
- }
-
- return [
- context.slice(0, pivot), // pre_context
- context[pivot], // context_line
- context.slice(pivot + 1) // post_context
- ];
- },
-
- _processException: function(type, message, fileurl, lineno, frames, options) {
- var stacktrace, fullMessage;
-
- if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return;
-
- message += '';
- message = truncate(message, this._globalOptions.maxMessageLength);
-
- fullMessage = (type ? type + ': ' : '') + message;
- fullMessage = truncate(fullMessage, this._globalOptions.maxMessageLength);
-
- if (frames && frames.length) {
- fileurl = frames[0].filename || fileurl;
- // Sentry expects frames oldest to newest
- // and JS sends them as newest to oldest
- frames.reverse();
- stacktrace = {frames: frames};
- } else if (fileurl) {
- stacktrace = {
- frames: [{
- filename: fileurl,
- lineno: lineno,
- in_app: true
- }]
- };
- }
-
- if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) return;
- if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) return;
-
- var data = objectMerge({
- // sentry.interfaces.Exception
- exception: {
- values: [{
- type: type,
- value: message,
- stacktrace: stacktrace
- }]
- },
- culprit: fileurl,
- message: fullMessage
- }, options);
-
- // Fire away!
- this._send(data);
- },
-
- _trimPacket: function(data) {
- // For now, we only want to truncate the two different messages
- // but this could/should be expanded to just trim everything
- var max = this._globalOptions.maxMessageLength;
- data.message = truncate(data.message, max);
- if (data.exception) {
- var exception = data.exception.values[0];
- exception.value = truncate(exception.value, max);
- }
-
- return data;
- },
-
- _getHttpData: function() {
- if (!this._hasDocument || !document.location || !document.location.href) {
- return;
- }
-
- var httpData = {
- headers: {
- 'User-Agent': navigator.userAgent
- }
- };
-
- httpData.url = document.location.href;
-
- if (document.referrer) {
- httpData.headers.Referer = document.referrer;
- }
-
- return httpData;
- },
-
-
- _send: function(data) {
- var self = this;
-
- var globalOptions = this._globalOptions;
-
- var baseData = {
- project: this._globalProject,
- logger: globalOptions.logger,
- platform: 'javascript'
- }, httpData = this._getHttpData();
-
- if (httpData) {
- baseData.request = httpData;
- }
-
- data = objectMerge(baseData, data);
-
- // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge
- data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags);
- data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra);
-
- // Send along our own collected metadata with extra
- data.extra['session:duration'] = now() - this._startTime;
-
- // If there are no tags/extra, strip the key from the payload alltogther.
- if (isEmptyObject(data.tags)) delete data.tags;
-
- if (this._globalContext.user) {
- // sentry.interfaces.User
- data.user = this._globalContext.user;
- }
-
- // Include the release if it's defined in globalOptions
- if (globalOptions.release) data.release = globalOptions.release;
-
- // Include server_name if it's defined in globalOptions
- if (globalOptions.serverName) data.server_name = globalOptions.serverName;
-
- if (isFunction(globalOptions.dataCallback)) {
- data = globalOptions.dataCallback(data) || data;
- }
-
- // Why??????????
- if (!data || isEmptyObject(data)) {
- return;
- }
-
- // Check if the request should be filtered or not
- if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) {
- return;
- }
-
- // Send along an event_id if not explicitly passed.
- // This event_id can be used to reference the error within Sentry itself.
- // Set lastEventId after we know the error should actually be sent
- this._lastEventId = data.event_id || (data.event_id = uuid4());
-
- // Try and clean up the packet before sending by truncating long values
- data = this._trimPacket(data);
-
- this._logDebug('debug', 'Raven about to send:', data);
-
- if (!this.isSetup()) return;
-
- var auth = {
- sentry_version: '7',
- sentry_client: 'raven-js/' + this.VERSION,
- sentry_key: this._globalKey
- };
- if (this._globalSecret) {
- auth.sentry_secret = this._globalSecret;
- }
-
- var url = this._globalEndpoint;
- (globalOptions.transport || this._makeRequest).call(this, {
- url: url,
- auth: auth,
- data: data,
- options: globalOptions,
- onSuccess: function success() {
- self._triggerEvent('success', {
- data: data,
- src: url
- });
- },
- onError: function failure() {
- self._triggerEvent('failure', {
- data: data,
- src: url
- });
- }
- });
- },
-
- _makeImageRequest: function(opts) {
- // Tack on sentry_data to auth options, which get urlencoded
- opts.auth.sentry_data = JSON.stringify(opts.data);
-
- var img = this._newImage(),
- src = opts.url + '?' + urlencode(opts.auth),
- crossOrigin = opts.options.crossOrigin;
-
- if (crossOrigin || crossOrigin === '') {
- img.crossOrigin = crossOrigin;
- }
- img.onload = opts.onSuccess;
- img.onerror = img.onabort = opts.onError;
- img.src = src;
- },
-
- _makeXhrRequest: function(opts) {
- var request;
-
- var url = opts.url;
- function handler() {
- if (request.status === 200) {
- if (opts.onSuccess) {
- opts.onSuccess();
- }
- } else if (opts.onError) {
- opts.onError();
- }
- }
-
- request = new XMLHttpRequest();
- if ('withCredentials' in request) {
- request.onreadystatechange = function () {
- if (request.readyState !== 4) {
- return;
- }
- handler();
- };
- } else {
- request = new XDomainRequest();
- // xdomainrequest cannot go http -> https (or vice versa),
- // so always use protocol relative
- url = url.replace(/^https?:/, '');
-
- // onreadystatechange not supported by XDomainRequest
- request.onload = handler;
- }
-
- // NOTE: auth is intentionally sent as part of query string (NOT as custom
- // HTTP header) so as to avoid preflight CORS requests
- request.open('POST', url + '?' + urlencode(opts.auth));
- request.send(JSON.stringify(opts.data));
- },
-
- _makeRequest: function(opts) {
- var hasCORS =
- 'withCredentials' in new XMLHttpRequest() ||
- typeof XDomainRequest !== 'undefined';
-
- return (hasCORS ? this._makeXhrRequest : this._makeImageRequest)(opts);
- },
-
- // Note: this is shitty, but I can't figure out how to get
- // sinon to stub document.createElement without breaking everything
- // so this wrapper is just so I can stub it for tests.
- _newImage: function() {
- return document.createElement('img');
- },
-
- _logDebug: function(level) {
- if (this._originalConsoleMethods[level] && this.debug) {
- // In IE<10 console methods do not have their own 'apply' method
- Function.prototype.apply.call(
- this._originalConsoleMethods[level],
- this._originalConsole,
- [].slice.call(arguments, 1)
- );
- }
- },
-
- _mergeContext: function(key, context) {
- if (isUndefined(context)) {
- delete this._globalContext[key];
- } else {
- this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context);
- }
- }
-};
-
-// Deprecations
-Raven.prototype.setUser = Raven.prototype.setUserContext;
-Raven.prototype.setReleaseContext = Raven.prototype.setRelease;
-
-module.exports = Raven;
-
-},{"1":1,"4":4,"5":5}],3:[function(_dereq_,module,exports){
-/**
- * Enforces a single instance of the Raven client, and the
- * main entry point for Raven. If you are a consumer of the
- * Raven library, you SHOULD load this file (vs raven.js).
- **/
-
-'use strict';
-
-var RavenConstructor = _dereq_(2);
-
-var _Raven = window.Raven;
-
-var Raven = new RavenConstructor();
-
-/*
- * Allow multiple versions of Raven to be installed.
- * Strip Raven from the global context and returns the instance.
- *
- * @return {Raven}
- */
-Raven.noConflict = function () {
- window.Raven = _Raven;
- return Raven;
-};
-
-Raven.afterLoad();
-
-module.exports = Raven;
-
-},{"2":2}],4:[function(_dereq_,module,exports){
-'use strict';
-
-var objectPrototype = Object.prototype;
-
-function isUndefined(what) {
- return what === void 0;
-}
-
-function isFunction(what) {
- return typeof what === 'function';
-}
-
-function isString(what) {
- return objectPrototype.toString.call(what) === '[object String]';
-}
-
-function isObject(what) {
- return typeof what === 'object' && what !== null;
-}
-
-function isEmptyObject(what) {
- for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars
- return true;
-}
-
-// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560
-// with some tiny modifications
-function isError(what) {
- var toString = objectPrototype.toString.call(what);
- return isObject(what) &&
- toString === '[object Error]' ||
- toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions
- what instanceof Error;
-}
-
-function each(obj, callback) {
- var i, j;
-
- if (isUndefined(obj.length)) {
- for (i in obj) {
- if (hasKey(obj, i)) {
- callback.call(null, i, obj[i]);
- }
- }
- } else {
- j = obj.length;
- if (j) {
- for (i = 0; i < j; i++) {
- callback.call(null, i, obj[i]);
- }
- }
- }
-}
-
-function objectMerge(obj1, obj2) {
- if (!obj2) {
- return obj1;
- }
- each(obj2, function(key, value){
- obj1[key] = value;
- });
- return obj1;
-}
-
-function truncate(str, max) {
- return !max || str.length <= max ? str : str.substr(0, max) + '\u2026';
-}
-
-/**
- * hasKey, a better form of hasOwnProperty
- * Example: hasKey(MainHostObject, property) === true/false
- *
- * @param {Object} host object to check property
- * @param {string} key to check
- */
-function hasKey(object, key) {
- return objectPrototype.hasOwnProperty.call(object, key);
-}
-
-function joinRegExp(patterns) {
- // Combine an array of regular expressions and strings into one large regexp
- // Be mad.
- var sources = [],
- i = 0, len = patterns.length,
- pattern;
-
- for (; i < len; i++) {
- pattern = patterns[i];
- if (isString(pattern)) {
- // If it's a string, we need to escape it
- // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
- sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
- } else if (pattern && pattern.source) {
- // If it's a regexp already, we want to extract the source
- sources.push(pattern.source);
- }
- // Intentionally skip other cases
- }
- return new RegExp(sources.join('|'), 'i');
-}
-
-function urlencode(o) {
- var pairs = [];
- each(o, function(key, value) {
- pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
- });
- return pairs.join('&');
-}
-
-function uuid4() {
- var crypto = window.crypto || window.msCrypto;
-
- if (!isUndefined(crypto) && crypto.getRandomValues) {
- // Use window.crypto API if available
- var arr = new Uint16Array(8);
- crypto.getRandomValues(arr);
-
- // set 4 in byte 7
- arr[3] = arr[3] & 0xFFF | 0x4000;
- // set 2 most significant bits of byte 9 to '10'
- arr[4] = arr[4] & 0x3FFF | 0x8000;
-
- var pad = function(num) {
- var v = num.toString(16);
- while (v.length < 4) {
- v = '0' + v;
- }
- return v;
- };
-
- return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) +
- pad(arr[5]) + pad(arr[6]) + pad(arr[7]);
- } else {
- // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
- return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- var r = Math.random()*16|0,
- v = c === 'x' ? r : r&0x3|0x8;
- return v.toString(16);
- });
- }
-}
-
-module.exports = {
- isUndefined: isUndefined,
- isFunction: isFunction,
- isString: isString,
- isObject: isObject,
- isEmptyObject: isEmptyObject,
- isError: isError,
- each: each,
- objectMerge: objectMerge,
- truncate: truncate,
- hasKey: hasKey,
- joinRegExp: joinRegExp,
- urlencode: urlencode,
- uuid4: uuid4
-};
-
-},{}],5:[function(_dereq_,module,exports){
-'use strict';
-
-var utils = _dereq_(4);
-
-var hasKey = utils.hasKey;
-var isString = utils.isString;
-var isUndefined = utils.isUndefined;
-
-/*
- TraceKit - Cross brower stack traces - github.com/occ/TraceKit
- MIT license
-*/
-
-var TraceKit = {
- remoteFetching: false,
- collectWindowErrors: true,
- // 3 lines before, the offending line, 3 lines after
- linesOfContext: 7,
- debug: false
-};
-
-// global reference to slice
-var _slice = [].slice;
-var UNKNOWN_FUNCTION = '?';
-
-// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
-var ERROR_TYPES_RE = /^(?:Uncaught )?((?:Eval|Internal|Range|Reference|Syntax|Type|URI)Error)\: ?(.*)$/;
-
-function getLocationHref() {
- if (typeof document === 'undefined')
- return '';
-
- return document.location.href;
-}
-
-/**
- * TraceKit.report: cross-browser processing of unhandled exceptions
- *
- * Syntax:
- * TraceKit.report.subscribe(function(stackInfo) { ... })
- * TraceKit.report.unsubscribe(function(stackInfo) { ... })
- * TraceKit.report(exception)
- * try { ...code... } catch(ex) { TraceKit.report(ex); }
- *
- * Supports:
- * - Firefox: full stack trace with line numbers, plus column number
- * on top frame; column number is not guaranteed
- * - Opera: full stack trace with line and column numbers
- * - Chrome: full stack trace with line and column numbers
- * - Safari: line and column number for the top frame only; some frames
- * may be missing, and column number is not guaranteed
- * - IE: line and column number for the top frame only; some frames
- * may be missing, and column number is not guaranteed
- *
- * In theory, TraceKit should work on all of the following versions:
- * - IE5.5+ (only 8.0 tested)
- * - Firefox 0.9+ (only 3.5+ tested)
- * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
- * Exceptions Have Stacktrace to be enabled in opera:config)
- * - Safari 3+ (only 4+ tested)
- * - Chrome 1+ (only 5+ tested)
- * - Konqueror 3.5+ (untested)
- *
- * Requires TraceKit.computeStackTrace.
- *
- * Tries to catch all unhandled exceptions and report them to the
- * subscribed handlers. Please note that TraceKit.report will rethrow the
- * exception. This is REQUIRED in order to get a useful stack trace in IE.
- * If the exception does not reach the top of the browser, you will only
- * get a stack trace from the point where TraceKit.report was called.
- *
- * Handlers receive a stackInfo object as described in the
- * TraceKit.computeStackTrace docs.
- */
-TraceKit.report = (function reportModuleWrapper() {
- var handlers = [],
- lastArgs = null,
- lastException = null,
- lastExceptionStack = null;
-
- /**
- * Add a crash handler.
- * @param {Function} handler
- */
- function subscribe(handler) {
- installGlobalHandler();
- handlers.push(handler);
- }
-
- /**
- * Remove a crash handler.
- * @param {Function} handler
- */
- function unsubscribe(handler) {
- for (var i = handlers.length - 1; i >= 0; --i) {
- if (handlers[i] === handler) {
- handlers.splice(i, 1);
- }
- }
- }
-
- /**
- * Remove all crash handlers.
- */
- function unsubscribeAll() {
- uninstallGlobalHandler();
- handlers = [];
- }
-
- /**
- * Dispatch stack information to all handlers.
- * @param {Object.<string, *>} stack
- */
- function notifyHandlers(stack, isWindowError) {
- var exception = null;
- if (isWindowError && !TraceKit.collectWindowErrors) {
- return;
- }
- for (var i in handlers) {
- if (hasKey(handlers, i)) {
- try {
- handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
- } catch (inner) {
- exception = inner;
- }
- }
- }
-
- if (exception) {
- throw exception;
- }
- }
-
- var _oldOnerrorHandler, _onErrorHandlerInstalled;
-
- /**
- * Ensures all global unhandled exceptions are recorded.
- * Supported by Gecko and IE.
- * @param {string} message Error message.
- * @param {string} url URL of script that generated the exception.
- * @param {(number|string)} lineNo The line number at which the error
- * occurred.
- * @param {?(number|string)} colNo The column number at which the error
- * occurred.
- * @param {?Error} ex The actual Error object.
- */
- function traceKitWindowOnError(message, url, lineNo, colNo, ex) {
- var stack = null;
-
- if (lastExceptionStack) {
- TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
- processLastException();
- } else if (ex) {
- // New chrome and blink send along a real error object
- // Let's just report that like a normal error.
- // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
- stack = TraceKit.computeStackTrace(ex);
- notifyHandlers(stack, true);
- } else {
- var location = {
- 'url': url,
- 'line': lineNo,
- 'column': colNo
- };
- location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
- location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
-
- var name = undefined;
- var msg = message; // must be new var or will modify original `arguments`
- var groups;
- if (isString(message)) {
- var groups = message.match(ERROR_TYPES_RE);
- if (groups) {
- name = groups[1];
- msg = groups[2];
- }
- }
-
- stack = {
- 'name': name,
- 'message': msg,
- 'url': getLocationHref(),
- 'stack': [location]
- };
- notifyHandlers(stack, true);
- }
-
- if (_oldOnerrorHandler) {
- return _oldOnerrorHandler.apply(this, arguments);
- }
-
- return false;
- }
-
- function installGlobalHandler ()
- {
- if (_onErrorHandlerInstalled) {
- return;
- }
- _oldOnerrorHandler = window.onerror;
- window.onerror = traceKitWindowOnError;
- _onErrorHandlerInstalled = true;
- }
-
- function uninstallGlobalHandler ()
- {
- if (!_onErrorHandlerInstalled) {
- return;
- }
- window.onerror = _oldOnerrorHandler;
- _onErrorHandlerInstalled = false;
- _oldOnerrorHandler = undefined;
- }
-
- function processLastException() {
- var _lastExceptionStack = lastExceptionStack,
- _lastArgs = lastArgs;
- lastArgs = null;
- lastExceptionStack = null;
- lastException = null;
- notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
- }
-
- /**
- * Reports an unhandled Error to TraceKit.
- * @param {Error} ex
- * @param {?boolean} rethrow If false, do not re-throw the exception.
- * Only used for window.onerror to not cause an infinite loop of
- * rethrowing.
- */
- function report(ex, rethrow) {
- var args = _slice.call(arguments, 1);
- if (lastExceptionStack) {
- if (lastException === ex) {
- return; // already caught by an inner catch block, ignore
- } else {
- processLastException();
- }
- }
-
- var stack = TraceKit.computeStackTrace(ex);
- lastExceptionStack = stack;
- lastException = ex;
- lastArgs = args;
-
- // If the stack trace is incomplete, wait for 2 seconds for
- // slow slow IE to see if onerror occurs or not before reporting
- // this exception; otherwise, we will end up with an incomplete
- // stack trace
- window.setTimeout(function () {
- if (lastException === ex) {
- processLastException();
- }
- }, (stack.incomplete ? 2000 : 0));
-
- if (rethrow !== false) {
- throw ex; // re-throw to propagate to the top level (and cause window.onerror)
- }
- }
-
- report.subscribe = subscribe;
- report.unsubscribe = unsubscribe;
- report.uninstall = unsubscribeAll;
- return report;
-}());
-
-/**
- * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
- *
- * Syntax:
- * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
- * Returns:
- * s.name - exception name
- * s.message - exception message
- * s.stack[i].url - JavaScript or HTML file URL
- * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
- * s.stack[i].args - arguments passed to the function, if known
- * s.stack[i].line - line number, if known
- * s.stack[i].column - column number, if known
- * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
- *
- * Supports:
- * - Firefox: full stack trace with line numbers and unreliable column
- * number on top frame
- * - Opera 10: full stack trace with line and column numbers
- * - Opera 9-: full stack trace with line numbers
- * - Chrome: full stack trace with line and column numbers
- * - Safari: line and column number for the topmost stacktrace element
- * only
- * - IE: no line numbers whatsoever
- *
- * Tries to guess names of anonymous functions by looking for assignments
- * in the source code. In IE and Safari, we have to guess source file names
- * by searching for function bodies inside all page scripts. This will not
- * work for scripts that are loaded cross-domain.
- * Here be dragons: some function names may be guessed incorrectly, and
- * duplicate functions may be mismatched.
- *
- * TraceKit.computeStackTrace should only be used for tracing purposes.
- * Logging of unhandled exceptions should be done with TraceKit.report,
- * which builds on top of TraceKit.computeStackTrace and provides better
- * IE support by utilizing the window.onerror event to retrieve information
- * about the top of the stack.
- *
- * Note: In IE and Safari, no stack trace is recorded on the Error object,
- * so computeStackTrace instead walks its *own* chain of callers.
- * This means that:
- * * in Safari, some methods may be missing from the stack trace;
- * * in IE, the topmost function in the stack trace will always be the
- * caller of computeStackTrace.
- *
- * This is okay for tracing (because you are likely to be calling
- * computeStackTrace from the function you want to be the topmost element
- * of the stack trace anyway), but not okay for logging unhandled
- * exceptions (because your catch block will likely be far away from the
- * inner function that actually caused the exception).
- *
- */
-TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
- var sourceCache = {};
-
- /**
- * Attempts to retrieve source code via XMLHttpRequest, which is used
- * to look up anonymous function names.
- * @param {string} url URL of source code.
- * @return {string} Source contents.
- */
- function loadSource(url) {
- if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.
- return '';
- }
- try {
- var getXHR = function() {
- try {
- return new window.XMLHttpRequest();
- } catch (e) {
- // explicitly bubble up the exception if not found
- return new window.ActiveXObject('Microsoft.XMLHTTP');
- }
- };
-
- var request = getXHR();
- request.open('GET', url, false);
- request.send('');
- return request.responseText;
- } catch (e) {
- return '';
- }
- }
-
- /**
- * Retrieves source code from the source code cache.
- * @param {string} url URL of source code.
- * @return {Array.<string>} Source contents.
- */
- function getSource(url) {
- if (!isString(url)) return [];
- if (!hasKey(sourceCache, url)) {
- // URL needs to be able to fetched within the acceptable domain. Otherwise,
- // cross-domain errors will be triggered.
- var source = '';
- var domain = '';
- try { domain = document.domain; } catch (e) {}
- if (url.indexOf(domain) !== -1) {
- source = loadSource(url);
- }
- sourceCache[url] = source ? source.split('\n') : [];
- }
-
- return sourceCache[url];
- }
-
- /**
- * Tries to use an externally loaded copy of source code to determine
- * the name of a function by looking at the name of the variable it was
- * assigned to, if any.
- * @param {string} url URL of source code.
- * @param {(string|number)} lineNo Line number in source code.
- * @return {string} The function name, if discoverable.
- */
- function guessFunctionName(url, lineNo) {
- var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
- reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
- line = '',
- maxLines = 10,
- source = getSource(url),
- m;
-
- if (!source.length) {
- return UNKNOWN_FUNCTION;
- }
-
- // Walk backwards from the first line in the function until we find the line which
- // matches the pattern above, which is the function definition
- for (var i = 0; i < maxLines; ++i) {
- line = source[lineNo - i] + line;
-
- if (!isUndefined(line)) {
- if ((m = reGuessFunction.exec(line))) {
- return m[1];
- } else if ((m = reFunctionArgNames.exec(line))) {
- return m[1];
- }
- }
- }
-
- return UNKNOWN_FUNCTION;
- }
-
- /**
- * Retrieves the surrounding lines from where an exception occurred.
- * @param {string} url URL of source code.
- * @param {(string|number)} line Line number in source code to centre
- * around for context.
- * @return {?Array.<string>} Lines of source code.
- */
- function gatherContext(url, line) {
- var source = getSource(url);
-
- if (!source.length) {
- return null;
- }
-
- var context = [],
- // linesBefore & linesAfter are inclusive with the offending line.
- // if linesOfContext is even, there will be one extra line
- // *before* the offending line.
- linesBefore = Math.floor(TraceKit.linesOfContext / 2),
- // Add one extra line if linesOfContext is odd
- linesAfter = linesBefore + (TraceKit.linesOfContext % 2),
- start = Math.max(0, line - linesBefore - 1),
- end = Math.min(source.length, line + linesAfter - 1);
-
- line -= 1; // convert to 0-based index
-
- for (var i = start; i < end; ++i) {
- if (!isUndefined(source[i])) {
- context.push(source[i]);
- }
- }
-
- return context.length > 0 ? context : null;
- }
-
- /**
- * Escapes special characters, except for whitespace, in a string to be
- * used inside a regular expression as a string literal.
- * @param {string} text The string.
- * @return {string} The escaped string literal.
- */
- function escapeRegExp(text) {
- return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
- }
-
- /**
- * Escapes special characters in a string to be used inside a regular
- * expression as a string literal. Also ensures that HTML entities will
- * be matched the same as their literal friends.
- * @param {string} body The string.
- * @return {string} The escaped string.
- */
- function escapeCodeAsRegExpForMatchingInsideHTML(body) {
- return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
- }
-
- /**
- * Determines where a code fragment occurs in the source code.
- * @param {RegExp} re The function definition.
- * @param {Array.<string>} urls A list of URLs to search.
- * @return {?Object.<string, (string|number)>} An object containing
- * the url, line, and column number of the defined function.
- */
- function findSourceInUrls(re, urls) {
- var source, m;
- for (var i = 0, j = urls.length; i < j; ++i) {
- // console.log('searching', urls[i]);
- if ((source = getSource(urls[i])).length) {
- source = source.join('\n');
- if ((m = re.exec(source))) {
- // console.log('Found function in ' + urls[i]);
-
- return {
- 'url': urls[i],
- 'line': source.substring(0, m.index).split('\n').length,
- 'column': m.index - source.lastIndexOf('\n', m.index) - 1
- };
- }
- }
- }
-
- // console.log('no match');
-
- return null;
- }
-
- /**
- * Determines at which column a code fragment occurs on a line of the
- * source code.
- * @param {string} fragment The code fragment.
- * @param {string} url The URL to search.
- * @param {(string|number)} line The line number to examine.
- * @return {?number} The column number.
- */
- function findSourceInLine(fragment, url, line) {
- var source = getSource(url),
- re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
- m;
-
- line -= 1;
-
- if (source && source.length > line && (m = re.exec(source[line]))) {
- return m.index;
- }
-
- return null;
- }
-
- /**
- * Determines where a function was defined within the source code.
- * @param {(Function|string)} func A function reference or serialized
- * function definition.
- * @return {?Object.<string, (string|number)>} An object containing
- * the url, line, and column number of the defined function.
- */
- function findSourceByFunctionBody(func) {
- if (typeof document === 'undefined')
- return;
-
- var urls = [window.location.href],
- scripts = document.getElementsByTagName('script'),
- body,
- code = '' + func,
- codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
- eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
- re,
- parts,
- result;
-
- for (var i = 0; i < scripts.length; ++i) {
- var script = scripts[i];
- if (script.src) {
- urls.push(script.src);
- }
- }
-
- if (!(parts = codeRE.exec(code))) {
- re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
- }
-
- // not sure if this is really necessary, but I don’t have a test
- // corpus large enough to confirm that and it was in the original.
- else {
- var name = parts[1] ? '\\s+' + parts[1] : '',
- args = parts[2].split(',').join('\\s*,\\s*');
-
- body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
- re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');
- }
-
- // look for a normal function definition
- if ((result = findSourceInUrls(re, urls))) {
- return result;
- }
-
- // look for an old-school event handler function
- if ((parts = eventRE.exec(code))) {
- var event = parts[1];
- body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
-
- // look for a function defined in HTML as an onXXX handler
- re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');
-
- if ((result = findSourceInUrls(re, urls[0]))) {
- return result;
- }
-
- // look for ???
- re = new RegExp(body);
-
- if ((result = findSourceInUrls(re, urls))) {
- return result;
- }
- }
-
- return null;
- }
-
- // Contents of Exception in various browsers.
- //
- // SAFARI:
- // ex.message = Can't find variable: qq
- // ex.line = 59
- // ex.sourceId = 580238192
- // ex.sourceURL = http://...
- // ex.expressionBeginOffset = 96
- // ex.expressionCaretOffset = 98
- // ex.expressionEndOffset = 98
- // ex.name = ReferenceError
- //
- // FIREFOX:
- // ex.message = qq is not defined
- // ex.fileName = http://...
- // ex.lineNumber = 59
- // ex.columnNumber = 69
- // ex.stack = ...stack trace... (see the example below)
- // ex.name = ReferenceError
- //
- // CHROME:
- // ex.message = qq is not defined
- // ex.name = ReferenceError
- // ex.type = not_defined
- // ex.arguments = ['aa']
- // ex.stack = ...stack trace...
- //
- // INTERNET EXPLORER:
- // ex.message = ...
- // ex.name = ReferenceError
- //
- // OPERA:
- // ex.message = ...message... (see the example below)
- // ex.name = ReferenceError
- // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
- // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
-
- /**
- * Computes stack trace information from the stack property.
- * Chrome and Gecko use this property.
- * @param {Error} ex
- * @return {?Object.<string, *>} Stack trace information.
- */
- function computeStackTraceFromStackProp(ex) {
- if (isUndefined(ex.stack) || !ex.stack) return;
-
- var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|<anonymous>).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,
- gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i,
- winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
- lines = ex.stack.split('\n'),
- stack = [],
- parts,
- element,
- reference = /^(.*) is undefined$/.exec(ex.message);
-
- for (var i = 0, j = lines.length; i < j; ++i) {
- if ((parts = chrome.exec(lines[i]))) {
- var isNative = parts[2] && parts[2].indexOf('native') !== -1;
- element = {
- 'url': !isNative ? parts[2] : null,
- 'func': parts[1] || UNKNOWN_FUNCTION,
- 'args': isNative ? [parts[2]] : [],
- 'line': parts[3] ? +parts[3] : null,
- 'column': parts[4] ? +parts[4] : null
- };
- } else if ( parts = winjs.exec(lines[i]) ) {
- element = {
- 'url': parts[2],
- 'func': parts[1] || UNKNOWN_FUNCTION,
- 'args': [],
- 'line': +parts[3],
- 'column': parts[4] ? +parts[4] : null
- };
- } else if ((parts = gecko.exec(lines[i]))) {
- element = {
- 'url': parts[3],
- 'func': parts[1] || UNKNOWN_FUNCTION,
- 'args': parts[2] ? parts[2].split(',') : [],
- 'line': parts[4] ? +parts[4] : null,
- 'column': parts[5] ? +parts[5] : null
- };
- } else {
- continue;
- }
-
- if (!element.func && element.line) {
- element.func = guessFunctionName(element.url, element.line);
- }
-
- if (element.line) {
- element.context = gatherContext(element.url, element.line);
- }
-
- stack.push(element);
- }
-
- if (!stack.length) {
- return null;
- }
-
- if (stack[0].line && !stack[0].column && reference) {
- stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
- } else if (!stack[0].column && !isUndefined(ex.columnNumber)) {
- // FireFox uses this awesome columnNumber property for its top frame
- // Also note, Firefox's column number is 0-based and everything else expects 1-based,
- // so adding 1
- stack[0].column = ex.columnNumber + 1;
- }
-
- return {
- 'name': ex.name,
- 'message': ex.message,
- 'url': getLocationHref(),
- 'stack': stack
- };
- }
-
- /**
- * Computes stack trace information from the stacktrace property.
- * Opera 10 uses this property.
- * @param {Error} ex
- * @return {?Object.<string, *>} Stack trace information.
- */
- function computeStackTraceFromStacktraceProp(ex) {
- // Access and store the stacktrace property before doing ANYTHING
- // else to it because Opera is not very good at providing it
- // reliably in other circumstances.
- var stacktrace = ex.stacktrace;
- if (isUndefined(ex.stacktrace) || !ex.stacktrace) return;
-
- var opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i,
- opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i,
- lines = stacktrace.split('\n'),
- stack = [],
- parts;
-
- for (var line = 0; line < lines.length; line += 2) {
- var element = null;
- if ((parts = opera10Regex.exec(lines[line]))) {
- element = {
- 'url': parts[2],
- 'line': +parts[1],
- 'column': null,
- 'func': parts[3],
- 'args':[]
- };
- } else if ((parts = opera11Regex.exec(lines[line]))) {
- element = {
- 'url': parts[6],
- 'line': +parts[1],
- 'column': +parts[2],
- 'func': parts[3] || parts[4],
- 'args': parts[5] ? parts[5].split(',') : []
- };
- }
-
- if (element) {
- if (!element.func && element.line) {
- element.func = guessFunctionName(element.url, element.line);
- }
- if (element.line) {
- try {
- element.context = gatherContext(element.url, element.line);
- } catch (exc) {}
- }
-
- if (!element.context) {
- element.context = [lines[line + 1]];
- }
-
- stack.push(element);
- }
- }
-
- if (!stack.length) {
- return null;
- }
-
- return {
- 'name': ex.name,
- 'message': ex.message,
- 'url': getLocationHref(),
- 'stack': stack
- };
- }
-
- /**
- * NOT TESTED.
- * Computes stack trace information from an error message that includes
- * the stack trace.
- * Opera 9 and earlier use this method if the option to show stack
- * traces is turned on in opera:config.
- * @param {Error} ex
- * @return {?Object.<string, *>} Stack information.
- */
- function computeStackTraceFromOperaMultiLineMessage(ex) {
- // Opera includes a stack trace into the exception message. An example is:
- //
- // Statement on line 3: Undefined variable: undefinedFunc
- // Backtrace:
- // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
- // undefinedFunc(a);
- // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
- // zzz(x, y, z);
- // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
- // yyy(a, a, a);
- // Line 1 of function script
- // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
- // ...
-
- var lines = ex.message.split('\n');
- if (lines.length < 4) {
- return null;
- }
-
- var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
- lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
- lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
- stack = [],
- scripts = document.getElementsByTagName('script'),
- inlineScriptBlocks = [],
- parts;
-
- for (var s in scripts) {
- if (hasKey(scripts, s) && !scripts[s].src) {
- inlineScriptBlocks.push(scripts[s]);
- }
- }
-
- for (var line = 2; line < lines.length; line += 2) {
- var item = null;
- if ((parts = lineRE1.exec(lines[line]))) {
- item = {
- 'url': parts[2],
- 'func': parts[3],
- 'args': [],
- 'line': +parts[1],
- 'column': null
- };
- } else if ((parts = lineRE2.exec(lines[line]))) {
- item = {
- 'url': parts[3],
- 'func': parts[4],
- 'args': [],
- 'line': +parts[1],
- 'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number.
- };
- var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
- var script = inlineScriptBlocks[parts[2] - 1];
- if (script) {
- var source = getSource(item.url);
- if (source) {
- source = source.join('\n');
- var pos = source.indexOf(script.innerText);
- if (pos >= 0) {
- item.line = relativeLine + source.substring(0, pos).split('\n').length;
- }
- }
- }
- } else if ((parts = lineRE3.exec(lines[line]))) {
- var url = window.location.href.replace(/#.*$/, '');
- var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[line + 1]));
- var src = findSourceInUrls(re, [url]);
- item = {
- 'url': url,
- 'func': '',
- 'args': [],
- 'line': src ? src.line : parts[1],
- 'column': null
- };
- }
-
- if (item) {
- if (!item.func) {
- item.func = guessFunctionName(item.url, item.line);
- }
- var context = gatherContext(item.url, item.line);
- var midline = (context ? context[Math.floor(context.length / 2)] : null);
- if (context && midline.replace(/^\s*/, '') === lines[line + 1].replace(/^\s*/, '')) {
- item.context = context;
- } else {
- // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
- item.context = [lines[line + 1]];
- }
- stack.push(item);
- }
- }
-
- if (!stack.length) {
- return null; // could not parse multiline exception message as Opera stack trace
- }
-
- return {
- 'name': ex.name,
- 'message': lines[0],
- 'url': getLocationHref(),
- 'stack': stack
- };
- }
-
- /**
- * Adds information about the first frame to incomplete stack traces.
- * Safari and IE require this to get complete data on the first frame.
- * @param {Object.<string, *>} stackInfo Stack trace information from
- * one of the compute* methods.
- * @param {string} url The URL of the script that caused an error.
- * @param {(number|string)} lineNo The line number of the script that
- * caused an error.
- * @param {string=} message The error generated by the browser, which
- * hopefully contains the name of the object that caused the error.
- * @return {boolean} Whether or not the stack information was
- * augmented.
- */
- function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
- var initial = {
- 'url': url,
- 'line': lineNo
- };
-
- if (initial.url && initial.line) {
- stackInfo.incomplete = false;
-
- if (!initial.func) {
- initial.func = guessFunctionName(initial.url, initial.line);
- }
-
- if (!initial.context) {
- initial.context = gatherContext(initial.url, initial.line);
- }
-
- var reference = / '([^']+)' /.exec(message);
- if (reference) {
- initial.column = findSourceInLine(reference[1], initial.url, initial.line);
- }
-
- if (stackInfo.stack.length > 0) {
- if (stackInfo.stack[0].url === initial.url) {
- if (stackInfo.stack[0].line === initial.line) {
- return false; // already in stack trace
- } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
- stackInfo.stack[0].line = initial.line;
- stackInfo.stack[0].context = initial.context;
- return false;
- }
- }
- }
-
- stackInfo.stack.unshift(initial);
- stackInfo.partial = true;
- return true;
- } else {
- stackInfo.incomplete = true;
- }
-
- return false;
- }
-
- /**
- * Computes stack trace information by walking the arguments.caller
- * chain at the time the exception occurred. This will cause earlier
- * frames to be missed but is the only way to get any stack trace in
- * Safari and IE. The top frame is restored by
- * {@link augmentStackTraceWithInitialElement}.
- * @param {Error} ex
- * @return {?Object.<string, *>} Stack trace information.
- */
- function computeStackTraceByWalkingCallerChain(ex, depth) {
- var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
- stack = [],
- funcs = {},
- recursion = false,
- parts,
- item,
- source;
-
- for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {
- if (curr === computeStackTrace || curr === TraceKit.report) {
- // console.log('skipping internal function');
- continue;
- }
-
- item = {
- 'url': null,
- 'func': UNKNOWN_FUNCTION,
- 'line': null,
- 'column': null
- };
-
- if (curr.name) {
- item.func = curr.name;
- } else if ((parts = functionName.exec(curr.toString()))) {
- item.func = parts[1];
- }
-
- if (typeof item.func === 'undefined') {
- try {
- item.func = parts.input.substring(0, parts.input.indexOf('{'));
- } catch (e) { }
- }
-
- if ((source = findSourceByFunctionBody(curr))) {
- item.url = source.url;
- item.line = source.line;
-
- if (item.func === UNKNOWN_FUNCTION) {
- item.func = guessFunctionName(item.url, item.line);
- }
-
- var reference = / '([^']+)' /.exec(ex.message || ex.description);
- if (reference) {
- item.column = findSourceInLine(reference[1], source.url, source.line);
- }
- }
-
- if (funcs['' + curr]) {
- recursion = true;
- }else{
- funcs['' + curr] = true;
- }
-
- stack.push(item);
- }
-
- if (depth) {
- // console.log('depth is ' + depth);
- // console.log('stack is ' + stack.length);
- stack.splice(0, depth);
- }
-
- var result = {
- 'name': ex.name,
- 'message': ex.message,
- 'url': getLocationHref(),
- 'stack': stack
- };
- augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
- return result;
- }
-
- /**
- * Computes a stack trace for an exception.
- * @param {Error} ex
- * @param {(string|number)=} depth
- */
- function computeStackTrace(ex, depth) {
- var stack = null;
- depth = (depth == null ? 0 : +depth);
-
- try {
- // This must be tried first because Opera 10 *destroys*
- // its stacktrace property if you try to access the stack
- // property first!!
- stack = computeStackTraceFromStacktraceProp(ex);
- if (stack) {
- return stack;
- }
- } catch (e) {
- if (TraceKit.debug) {
- throw e;
- }
- }
-
- try {
- stack = computeStackTraceFromStackProp(ex);
- if (stack) {
- return stack;
- }
- } catch (e) {
- if (TraceKit.debug) {
- throw e;
- }
- }
-
- try {
- stack = computeStackTraceFromOperaMultiLineMessage(ex);
- if (stack) {
- return stack;
- }
- } catch (e) {
- if (TraceKit.debug) {
- throw e;
- }
- }
-
- try {
- stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
- if (stack) {
- return stack;
- }
- } catch (e) {
- if (TraceKit.debug) {
- throw e;
- }
- }
-
- return {
- 'name': ex.name,
- 'message': ex.message,
- 'url': getLocationHref()
- };
- }
-
- computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
- computeStackTrace.computeStackTraceFromStackProp = computeStackTraceFromStackProp;
- computeStackTrace.guessFunctionName = guessFunctionName;
- computeStackTrace.gatherContext = gatherContext;
-
- return computeStackTrace;
-}());
-
-module.exports = TraceKit;
-
-},{"4":4}]},{},[3])(3)
-}); \ No newline at end of file