summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.flayignore1
-rw-r--r--.gitlab-ci.yml32
-rw-r--r--CHANGELOG41
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile21
-rw-r--r--Gemfile.lock39
-rw-r--r--README.md2
-rw-r--r--app/assets/javascripts/application.js2
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/cycle-analytics.js.es633
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js.es68
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es66
-rw-r--r--app/assets/javascripts/diff_notes/mixins/namespace.js.es69
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js.es626
-rw-r--r--app/assets/javascripts/labels_select.js17
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js2
-rw-r--r--app/assets/javascripts/notes.js11
-rw-r--r--app/assets/javascripts/protected_branch_edit.js.es61
-rw-r--r--app/assets/javascripts/search_autocomplete.js37
-rw-r--r--app/assets/stylesheets/framework/buttons.scss6
-rw-r--r--app/assets/stylesheets/framework/flash.scss2
-rw-r--r--app/assets/stylesheets/framework/lists.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss6
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss55
-rw-r--r--app/assets/stylesheets/pages/milestone.scss27
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss4
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/assets/stylesheets/pages/snippets.scss4
-rw-r--r--app/controllers/concerns/membership_actions.rb7
-rw-r--r--app/controllers/jwt_controller.rb21
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/controllers/projects/commit_controller.rb17
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/search_controller.rb4
-rw-r--r--app/controllers/users_controller.rb2
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/helpers/lfs_helper.rb8
-rw-r--r--app/helpers/milestones_helper.rb24
-rw-r--r--app/mailers/notify.rb2
-rw-r--r--app/models/board.rb8
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/cycle_analytics/summary.rb28
-rw-r--r--app/models/global_milestone.rb13
-rw-r--r--app/models/merge_request.rb12
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb4
-rw-r--r--app/services/auth/container_registry_authentication_service.rb11
-rw-r--r--app/services/issuable_base_service.rb1
-rw-r--r--app/services/members/approve_access_request_service.rb31
-rw-r--r--app/services/slash_commands/interpret_service.rb4
-rw-r--r--app/validators/namespace_validator.rb5
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml4
-rw-r--r--app/views/admin/labels/_form.html.haml2
-rw-r--r--app/views/dashboard/snippets/index.html.haml12
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml2
-rw-r--r--app/views/discussions/_resolve_all.html.haml5
-rw-r--r--app/views/doorkeeper/authorized_applications/_delete_form.html.haml2
-rw-r--r--app/views/explore/snippets/index.html.haml5
-rw-r--r--app/views/layouts/_search.html.haml39
-rw-r--r--app/views/profiles/show.html.haml3
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml19
-rw-r--r--app/views/projects/commit/_builds.html.haml2
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml5
-rw-r--r--app/views/projects/commit/_pipelines_list.haml12
-rw-r--r--app/views/projects/commit/pipelines.html.haml7
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml28
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/labels/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml10
-rw-r--r--app/views/projects/notes/_note.html.haml8
-rw-r--r--app/views/projects/pipelines/index.html.haml2
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml2
-rw-r--r--app/views/projects/snippets/_actions.html.haml8
-rw-r--r--app/views/projects/snippets/index.html.haml7
-rw-r--r--app/views/projects/wikis/_form.html.haml2
-rw-r--r--app/views/shared/_milestones_filter.html.haml15
-rw-r--r--app/views/shared/icons/_icon_fork.svg4
-rw-r--r--app/views/shared/milestones/_milestone.html.haml2
-rw-r--r--app/views/snippets/_actions.html.haml8
-rw-r--r--app/views/snippets/_snippets.html.haml4
-rw-r--r--app/views/users/show.html.haml6
-rw-r--r--config/application.rb4
-rw-r--r--config/initializers/connection_fix.rb2
-rw-r--r--config/routes.rb1
-rw-r--r--db/fixtures/development/04_project.rb11
-rw-r--r--db/migrate/20160920160832_add_index_to_labels_title.rb11
-rw-r--r--db/migrate/20160926145521_add_organization_to_user.rb12
-rw-r--r--db/schema.rb4
-rw-r--r--doc/README.md1
-rw-r--r--doc/administration/auth/ldap.md6
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/builds.md30
-rw-r--r--doc/api/oauth2.md16
-rw-r--r--doc/api/users.md9
-rw-r--r--doc/ci/examples/README.md2
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/university/README.md139
-rw-r--r--doc/university/glossary/README.md482
-rw-r--r--doc/university/high-availability/aws/README.md387
-rw-r--r--doc/university/high-availability/aws/img/auto-scaling-det.pngbin0 -> 106157 bytes
-rw-r--r--doc/university/high-availability/aws/img/db-subnet-group.pngbin0 -> 98632 bytes
-rw-r--r--doc/university/high-availability/aws/img/ec-subnet.pngbin0 -> 91922 bytes
-rw-r--r--doc/university/high-availability/aws/img/elastic-file-system.pngbin0 -> 109719 bytes
-rw-r--r--doc/university/high-availability/aws/img/ig-rt.pngbin0 -> 42022 bytes
-rw-r--r--doc/university/high-availability/aws/img/ig.pngbin0 -> 26220 bytes
-rw-r--r--doc/university/high-availability/aws/img/instance_specs.pngbin0 -> 40938 bytes
-rw-r--r--doc/university/high-availability/aws/img/new_vpc.pngbin0 -> 54072 bytes
-rw-r--r--doc/university/high-availability/aws/img/policies.pngbin0 -> 132366 bytes
-rw-r--r--doc/university/high-availability/aws/img/rds-net-opt.pngbin0 -> 54996 bytes
-rw-r--r--doc/university/high-availability/aws/img/rds-sec-group.pngbin0 -> 43950 bytes
-rw-r--r--doc/university/high-availability/aws/img/redis-cluster-det.pngbin0 -> 81524 bytes
-rw-r--r--doc/university/high-availability/aws/img/redis-net.pngbin0 -> 100700 bytes
-rw-r--r--doc/university/high-availability/aws/img/route_table.pngbin0 -> 39611 bytes
-rw-r--r--doc/university/high-availability/aws/img/subnet.pngbin0 -> 56466 bytes
-rw-r--r--doc/university/process/README.md30
-rw-r--r--doc/university/support/README.md188
-rw-r--r--doc/university/training/end-user/README.md420
-rw-r--r--doc/user/markdown.md10
-rw-r--r--features/project/snippets.feature2
-rw-r--r--features/steps/profile/profile.rb2
-rw-r--r--features/steps/project/snippets.rb4
-rw-r--r--lib/api/access_requests.rb7
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/api/entities.rb11
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/api/users.rb6
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb24
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb2
-rw-r--r--lib/banzai/filter/sanitization_filter.rb64
-rw-r--r--lib/banzai/reference_parser/base_parser.rb6
-rw-r--r--lib/ci/version_info.rb52
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/current_settings.rb4
-rw-r--r--lib/gitlab/github_import/client.rb11
-rw-r--r--lib/gitlab/github_import/importer.rb138
-rw-r--r--lib/gitlab/import_export/import_export.yml8
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb8
-rw-r--r--lib/gitlab/ldap/access.rb2
-rw-r--r--lib/gitlab/ldap/adapter.rb3
-rw-r--r--lib/gitlab/lfs_token.rb19
-rw-r--r--lib/gitlab/redis.rb31
-rw-r--r--lib/gitlab/workhorse.rb12
-rw-r--r--lib/tasks/flog.rake25
-rw-r--r--public/deploy.html7
-rw-r--r--public/robots.txt2
-rwxr-xr-xscripts/prepare_build.sh2
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb3
-rw-r--r--spec/controllers/projects/boards/lists_controller_spec.rb3
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb2
-rw-r--r--spec/factories/milestones.rb7
-rw-r--r--spec/factories/projects.rb2
-rw-r--r--spec/features/boards/boards_spec.rb60
-rw-r--r--spec/features/calendar_spec.rb109
-rw-r--r--spec/features/dashboard/snippets_spec.rb15
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb72
-rw-r--r--spec/features/issues_spec.rb18
-rw-r--r--spec/features/projects/snippets_spec.rb14
-rw-r--r--spec/features/search_spec.rb32
-rw-r--r--spec/features/snippets_spec.rb14
-rw-r--r--spec/features/unsubscribe_links_spec.rb2
-rw-r--r--spec/features/users/snippets_spec.rb22
-rw-r--r--spec/helpers/milestones_helper_spec.rb33
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js.es613
-rw-r--r--spec/lib/gitlab/auth_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb13
-rw-r--r--spec/lib/gitlab/import_export/project.json39
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb5
-rw-r--r--spec/lib/gitlab/ldap/adapter_spec.rb37
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb8
-rw-r--r--spec/lib/gitlab/redis_spec.rb52
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb40
-rw-r--r--spec/mailers/shared/notify.rb3
-rw-r--r--spec/models/cycle_analytics/summary_spec.rb6
-rw-r--r--spec/models/global_milestone_spec.rb5
-rw-r--r--spec/models/merge_request_spec.rb36
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb16
-rw-r--r--spec/requests/api/builds_spec.rb27
-rw-r--r--spec/requests/api/internal_spec.rb4
-rw-r--r--spec/requests/api/users_spec.rb9
-rw-r--r--spec/requests/git_http_spec.rb657
-rw-r--r--spec/requests/jwt_controller_spec.rb4
-rw-r--r--spec/requests/lfs_http_spec.rb29
-rw-r--r--spec/services/boards/issues/list_service_spec.rb4
-rw-r--r--spec/services/boards/issues/move_service_spec.rb4
-rw-r--r--spec/services/boards/lists/create_service_spec.rb8
-rw-r--r--spec/services/boards/lists/destroy_service_spec.rb8
-rw-r--r--spec/services/boards/lists/move_service_spec.rb4
-rw-r--r--spec/services/issues/create_service_spec.rb36
-rw-r--r--spec/services/issues/update_service_spec.rb97
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb96
-rw-r--r--spec/services/slash_commands/interpret_service_spec.rb71
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/cycle_analytics_helpers.rb26
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb (renamed from spec/support/issuable_slash_commands_shared_examples.rb)0
-rw-r--r--spec/support/git_http_helpers.rb48
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb (renamed from spec/support/issuable_create_service_slash_commands_shared_examples.rb)0
-rw-r--r--spec/support/snippets_shared_examples.rb18
-rw-r--r--spec/views/admin/dashboard/index.html.haml_spec.rb2
-rw-r--r--spec/views/projects/builds/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/issues/_related_branches.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/_heading.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/notes/_form.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pipelines/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb2
209 files changed, 3862 insertions, 1078 deletions
diff --git a/.flayignore b/.flayignore
index f120de527bd..44df2ba2371 100644
--- a/.flayignore
+++ b/.flayignore
@@ -1,2 +1,3 @@
*.erb
lib/gitlab/sanitizers/svg/whitelist.rb
+lib/gitlab/diff/position_tracer.rb
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b167fc74996..a11c4705e82 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,7 +12,7 @@ variables:
RSPEC_RETRY_RETRY_COUNT: "3"
RAILS_ENV: "test"
SIMPLECOV: "true"
- USE_DB: "true"
+ SETUP_DB: "true"
USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1"
@@ -23,7 +23,7 @@ before_script:
- bundle --version
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
- retry gem install knapsack
- - '[ "$USE_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
+ - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
stages:
- prepare
@@ -35,7 +35,7 @@ stages:
.knapsack-state: &knapsack-state
services: []
variables:
- USE_DB: "false"
+ SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
cache:
key: "knapsack"
@@ -196,7 +196,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
.ruby-static-analysis: &ruby-static-analysis
variables:
SIMPLECOV: "false"
- USE_DB: "false"
+ SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
.exec: &exec
@@ -209,9 +209,6 @@ rubocop: *exec
rake haml_lint: *exec
rake scss_lint: *exec
rake brakeman: *exec
-rake flog:
- <<: *exec
- allow_failure: yes
rake flay:
<<: *exec
allow_failure: yes
@@ -224,6 +221,23 @@ rake db:migrate:reset:
script:
- rake db:migrate:reset
+rake db:seed_fu:
+ stage: test
+ <<: *use-db
+ variables:
+ SIZE: "1"
+ SETUP_DB: "false"
+ RAILS_ENV: "development"
+ script:
+ - git clone https://gitlab.com/gitlab-org/gitlab-test.git
+ /home/git/repositories/gitlab-org/gitlab-test.git
+ - bundle exec rake db:setup db:seed_fu
+ artifacts:
+ when: on_failure
+ expire_in: 1d
+ paths:
+ - log/development.log
+
teaspoon:
stage: test
<<: *use-db
@@ -272,7 +286,7 @@ coverage:
stage: post-test
services: []
variables:
- USE_DB: "false"
+ SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
script:
- bundle exec scripts/merge-simplecov
@@ -288,7 +302,7 @@ coverage:
notify:slack:
stage: post-test
variables:
- USE_DB: "false"
+ SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
diff --git a/CHANGELOG b/CHANGELOG
index b539e20f5d9..e1b9cbf0b1d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,7 +2,41 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.13.0 (unreleased)
- Expose expires_at field when sharing project on API
-v 8.12.0 (unreleased)
+v 8.13.0 (unreleased)
+ - Use gitlab-shell v3.6.2 (GIT TRACE logging)
+ - AbstractReferenceFilter caches project_refs on RequestStore when active
+ - Speed-up group milestones show page
+ - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
+ - Add more tests for calendar contribution (ClemMakesApps)
+ - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
+ - Fix permission for setting an issue's due date
+ - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
+ - Use a ConnectionPool for Rails.cache on Sidekiq servers
+ - Only update issuable labels if they have been changed
+ - Revoke button in Applications Settings underlines on hover.
+ - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe)
+ - Add organization field to user profile
+ - Fix resolved discussion display in side-by-side diff view !6575
+ - Optimize GitHub importing for speed and memory
+ - API: expose pipeline data in builds API (!6502, Guilherme Salazar)
+
+v 8.12.2 (unreleased)
+ - Added University content to doc/university
+ - Fix Import/Export not recognising correctly the imported services.
+ - Fix snippets pagination
+ - Fix List-Unsubscribe header in emails
+ - Fix IssuesController#show degradation including project on loaded notes
+ - Fix an issue with the "Commits" section of the cycle analytics summary. !6513
+ - Fix errors importing project feature and milestone models using GitLab project import
+ - Make JWT messages Docker-compatible
+ - Fix duplicate branch entry in the merge request version compare dropdown
+
+v 8.12.1
+ - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
+ - Fix issue with search filter labels not displaying
+ - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv)
+
+v 8.12.0
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
- Only check :can_resolve permission if the note is resolvable
- Bump fog-aws to v0.11.0 to support ap-south-1 region
@@ -10,12 +44,14 @@ v 8.12.0 (unreleased)
- Allow to set request_access_enabled for groups and projects
- Cleanup misalignments in Issue list view !6206
- Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist
+ - Add Pipelines for Commit
- Prune events older than 12 months. (ritave)
- Prepend blank line to `Closes` message on merge request linked to issue (lukehowell)
- Fix issues/merge-request templates dropdown for forked projects
- Filter tags by name !6121
- Update gitlab shell secret file also when it is empty. !3774 (glensc)
- Give project selection dropdowns responsive width, make non-wrapping.
+ - Fix resolve discussion buttons endpoint path
- Fix note form hint showing slash commands supported for commits.
- Make push events have equal vertical spacing.
- API: Ensure invitees are not returned in Members API.
@@ -48,6 +84,7 @@ v 8.12.0 (unreleased)
- Fix file permissions change when updating a file on the Gitlab UI !5979
- Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev)
- Change merge_error column from string to text type
+ - Fix issue with search filter labels not displaying
- Reduce contributions calendar data payload (ClemMakesApps)
- Show all pipelines for merge requests even from discarded commits !6414
- Replace contributions calendar timezone payload with dates (ClemMakesApps)
@@ -82,6 +119,7 @@ v 8.12.0 (unreleased)
- Fix markdown anchor icon interaction (ClemMakesApps)
- Test migration paths from 8.5 until current release !4874
- Replace animateEmoji timeout with eventListener (ClemMakesApps)
+ - Show badges in Milestone tabs. !5946 (Dan Rowden)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
@@ -99,6 +137,7 @@ v 8.12.0 (unreleased)
- Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084
- Show queued time when showing a pipeline !6084
- Remove unused mixins (ClemMakesApps)
+ - Fix issue board label filtering appending already filtered labels
- Add search to all issue board lists
- Scroll active tab into view on mobile
- Fix groups sort dropdown alignment (ClemMakesApps)
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 40c341bdcdb..b72762837ea 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.6.0
+3.6.2
diff --git a/Gemfile b/Gemfile
index 2ac0f8af755..d2677d69556 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,10 +6,8 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
gem 'responders', '~> 2.0'
-# Specify a sprockets version due to increased performance
-# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
-gem 'sprockets', '~> 3.6.0'
-gem 'sprockets-es6'
+gem 'sprockets', '~> 3.7.0'
+gem 'sprockets-es6', '~> 0.9.2'
# Default values for AR models
gem 'default_value_for', '~> 3.0.0'
@@ -19,7 +17,7 @@ gem 'mysql2', '~> 0.3.16', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres
# Authentication libraries
-gem 'devise', '~> 4.0'
+gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
@@ -122,8 +120,8 @@ gem 'diffy', '~> 3.0.3'
# Application server
group :unicorn do
- gem 'unicorn', '~> 4.9.0'
- gem 'unicorn-worker-killer', '~> 0.4.2'
+ gem 'unicorn', '~> 5.1.0'
+ gem 'unicorn-worker-killer', '~> 0.4.4'
end
# State machine
@@ -212,7 +210,7 @@ gem 'oj', '~> 2.17.4'
gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6'
-gem 'sass-rails', '~> 5.0.0'
+gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
@@ -302,7 +300,6 @@ group :development, :test do
gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.18.2', require: false
gem 'simplecov', '0.12.0', require: false
- gem 'flog', '~> 4.3.2', require: false
gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
@@ -322,10 +319,6 @@ group :test do
gem 'timecop', '~> 0.8.0'
end
-group :production do
- gem 'gitlab_meta', '7.0'
-end
-
gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.3.0'
@@ -334,7 +327,7 @@ gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8'
-gem 'ruby-prof', '~> 0.15.9'
+gem 'ruby-prof', '~> 0.16.2'
## CI
gem 'activerecord-session_store', '~> 1.0.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index a5a2e5785ac..6f6667b51e9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -161,7 +161,7 @@ GEM
activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
- devise (4.1.1)
+ devise (4.2.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.1)
@@ -209,9 +209,6 @@ GEM
flay (2.6.1)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
- flog (4.3.2)
- ruby_parser (~> 3.1, > 3.1.0)
- sexp_processor (~> 4.4)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
@@ -284,7 +281,6 @@ GEM
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
rugged (~> 0.24.0)
- gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9)
omniauth (~> 1.0)
@@ -554,7 +550,7 @@ GEM
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
- raindrops (0.15.0)
+ raindrops (0.17.0)
rake (10.5.0)
rb-fsevent (0.9.6)
rb-inotify (0.9.5)
@@ -588,7 +584,7 @@ GEM
request_store (1.3.1)
rerun (0.11.0)
listen (~> 3.0)
- responders (2.1.1)
+ responders (2.3.0)
railties (>= 4.2.0, < 5.1)
rinku (2.0.0)
rotp (2.1.2)
@@ -630,7 +626,7 @@ GEM
rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
- ruby-prof (0.15.9)
+ ruby-prof (0.16.2)
ruby-progressbar (1.8.1)
ruby-saml (1.3.0)
nokogiri (>= 1.5.10)
@@ -645,7 +641,7 @@ GEM
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.22)
- sass-rails (5.0.5)
+ sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
@@ -706,10 +702,10 @@ GEM
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
- sprockets (3.6.3)
+ sprockets (3.7.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-es6 (0.9.0)
+ sprockets-es6 (0.9.2)
babel-source (>= 5.8.11)
babel-transpiler
sprockets (>= 3.0.0)
@@ -760,9 +756,8 @@ GEM
unf_ext
unf_ext (0.0.7.2)
unicode-display_width (1.1.1)
- unicorn (4.9.0)
+ unicorn (5.1.0)
kgio (~> 2.6)
- rack
raindrops (~> 0.7)
unicorn-worker-killer (0.4.4)
get_process_mem (~> 0)
@@ -837,7 +832,7 @@ DEPENDENCIES
d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0)
default_value_for (~> 3.0.0)
- devise (~> 4.0)
+ devise (~> 4.2)
devise-two-factor (~> 3.0.0)
diffy (~> 3.0.3)
doorkeeper (~> 4.2.0)
@@ -847,7 +842,6 @@ DEPENDENCIES
factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0)
flay (~> 2.6.1)
- flog (~> 4.3.2)
fog-aws (~> 0.9)
fog-azure (~> 0.0)
fog-core (~> 1.40)
@@ -864,7 +858,6 @@ DEPENDENCIES
github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_git (~> 10.6.6)
- gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2)
@@ -945,9 +938,9 @@ DEPENDENCIES
rubocop (~> 0.42.0)
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
- ruby-prof (~> 0.15.9)
+ ruby-prof (~> 0.16.2)
sanitize (~> 2.0)
- sass-rails (~> 5.0.0)
+ sass-rails (~> 5.0.6)
scss_lint (~> 0.47.0)
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
@@ -966,8 +959,8 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2)
- sprockets (~> 3.6.0)
- sprockets-es6
+ sprockets (~> 3.7.0)
+ sprockets-es6 (~> 0.9.2)
state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6)
task_list (~> 1.0.2)
@@ -981,8 +974,8 @@ DEPENDENCIES
uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0)
unf (~> 0.1.4)
- unicorn (~> 4.9.0)
- unicorn-worker-killer (~> 0.4.2)
+ unicorn (~> 5.1.0)
+ unicorn-worker-killer (~> 0.4.4)
version_sorter (~> 2.1.0)
virtus (~> 1.0.1)
vmstat (~> 2.2)
@@ -991,4 +984,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.13.0
+ 1.13.1
diff --git a/README.md b/README.md
index 9661a554b9f..8236f986b56 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
## Canonical source
-The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
+The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
## Open source software to collaborate on code
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index c029bf3b5ca..8a61669822c 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -247,7 +247,7 @@
$this.toggleClass('active');
var notesHolders = $this.closest('.diff-file').find('.notes_holder');
if ($this.hasClass('active')) {
- notesHolders.show();
+ notesHolders.show().find('.hide').show();
} else {
notesHolders.hide();
}
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 0decc6d09e6..44af1c135a0 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -357,7 +357,7 @@
$('ul.emoji-menu-search, h5.emoji-search').remove();
if (term) {
// Generate a search result block
- h5 = $('<h5>').text('Search results');
+ h5 = $('<h5 class="emoji-search" />').text('Search results');
found_emojis = _this.searchEmojis(term).show();
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide();
diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle-analytics.js.es6
index afaed7c4f60..cd9886ba58d 100644
--- a/app/assets/javascripts/cycle-analytics.js.es6
+++ b/app/assets/javascripts/cycle-analytics.js.es6
@@ -1,17 +1,22 @@
((global) => {
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
+ const store = gl.cycleAnalyticsStore = {
+ isLoading: true,
+ hasError: false,
+ isHelpDismissed: $.cookie(COOKIE_NAME),
+ analytics: {}
+ };
gl.CycleAnalytics = class CycleAnalytics {
constructor() {
const that = this;
- this.isHelpDismissed = $.cookie(COOKIE_NAME);
this.vue = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
created: this.fetchData(),
- data: this.decorateData({ isLoading: true }),
+ data: store,
methods: {
dismissLanding() {
that.dismissLanding();
@@ -21,6 +26,7 @@
}
fetchData(options) {
+ store.isLoading = true;
options = options || { startDate: 30 };
$.ajax({
@@ -30,22 +36,20 @@
contentType: 'application/json',
data: { start_date: options.startDate }
}).done((data) => {
- this.vue.$data = this.decorateData(data);
+ this.decorateData(data);
this.initDropdown();
})
.error((data) => {
this.handleError(data);
})
.always(() => {
- this.vue.isLoading = false;
+ store.isLoading = false;
})
}
decorateData(data) {
data.summary = data.summary || [];
data.stats = data.stats || [];
- data.isHelpDismissed = this.isHelpDismissed;
- data.isLoading = data.isLoading || false;
data.summary.forEach((item) => {
item.value = item.value || '-';
@@ -53,23 +57,21 @@
data.stats.forEach((item) => {
item.value = item.value || '- - -';
- })
+ });
- return data;
+ store.analytics = data;
}
handleError(data) {
- this.vue.$data = {
- hasError: true,
- isHelpDismissed: this.isHelpDismissed
- };
-
+ store.hasError = true;
new Flash('There was an error while fetching cycle analytics data.', 'alert');
}
dismissLanding() {
- this.vue.isHelpDismissed = true;
- $.cookie(COOKIE_NAME, true);
+ store.isHelpDismissed = true;
+ $.cookie(COOKIE_NAME, true, {
+ path: gon.relative_url_root || '/'
+ });
}
initDropdown() {
@@ -82,7 +84,6 @@
const value = $target.data('value');
$label.text($target.text().trim());
- this.vue.isLoading = true;
this.fetchData({ startDate: value });
})
}
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
index be6ebc77947..cdedfd1af15 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
@@ -1,13 +1,9 @@
((w) => {
w.ResolveBtn = Vue.extend({
- mixins: [
- ButtonMixins
- ],
props: {
noteId: Number,
discussionId: String,
resolved: Boolean,
- namespacePath: String,
projectPath: String,
canResolve: Boolean,
resolvedBy: String
@@ -69,10 +65,10 @@
if (this.isResolved) {
promise = ResolveService
- .unresolve(this.namespace, this.noteId);
+ .unresolve(this.projectPath, this.noteId);
} else {
promise = ResolveService
- .resolve(this.namespace, this.noteId);
+ .resolve(this.projectPath, this.noteId);
}
promise.then((response) => {
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
index e373b06b1eb..0a617034502 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
@@ -1,12 +1,8 @@
((w) => {
w.ResolveDiscussionBtn = Vue.extend({
- mixins: [
- ButtonMixins
- ],
props: {
discussionId: String,
mergeRequestId: Number,
- namespacePath: String,
projectPath: String,
canResolve: Boolean,
},
@@ -50,7 +46,7 @@
},
methods: {
resolve: function () {
- ResolveService.toggleResolveForDiscussion(this.namespace, this.mergeRequestId, this.discussionId);
+ ResolveService.toggleResolveForDiscussion(this.projectPath, this.mergeRequestId, this.discussionId);
}
},
created: function () {
diff --git a/app/assets/javascripts/diff_notes/mixins/namespace.js.es6 b/app/assets/javascripts/diff_notes/mixins/namespace.js.es6
deleted file mode 100644
index d278678085b..00000000000
--- a/app/assets/javascripts/diff_notes/mixins/namespace.js.es6
+++ /dev/null
@@ -1,9 +0,0 @@
-((w) => {
- w.ButtonMixins = {
- computed: {
- namespace: function () {
- return `${this.namespacePath}/${this.projectPath}`;
- }
- }
- };
-})(window);
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js.es6
index de771ff814b..2a55f739b31 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js.es6
+++ b/app/assets/javascripts/diff_notes/services/resolve.js.es6
@@ -9,32 +9,32 @@
Vue.http.headers.common['X-CSRF-Token'] = $.rails.csrfToken();
}
- prepareRequest(namespace) {
+ prepareRequest(root) {
this.setCSRF();
- Vue.http.options.root = `/${namespace}`;
+ Vue.http.options.root = root;
}
- resolve(namespace, noteId) {
- this.prepareRequest(namespace);
+ resolve(projectPath, noteId) {
+ this.prepareRequest(projectPath);
return this.noteResource.save({ noteId }, {});
}
- unresolve(namespace, noteId) {
- this.prepareRequest(namespace);
+ unresolve(projectPath, noteId) {
+ this.prepareRequest(projectPath);
return this.noteResource.delete({ noteId }, {});
}
- toggleResolveForDiscussion(namespace, mergeRequestId, discussionId) {
+ toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId],
isResolved = discussion.isResolved();
let promise;
if (isResolved) {
- promise = this.unResolveAll(namespace, mergeRequestId, discussionId);
+ promise = this.unResolveAll(projectPath, mergeRequestId, discussionId);
} else {
- promise = this.resolveAll(namespace, mergeRequestId, discussionId);
+ promise = this.resolveAll(projectPath, mergeRequestId, discussionId);
}
promise.then((response) => {
@@ -57,10 +57,10 @@
})
}
- resolveAll(namespace, mergeRequestId, discussionId) {
+ resolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
- this.prepareRequest(namespace);
+ this.prepareRequest(projectPath);
discussion.loading = true;
@@ -70,10 +70,10 @@
}, {});
}
- unResolveAll(namespace, mergeRequestId, discussionId) {
+ unResolveAll(projectPath, mergeRequestId, discussionId) {
const discussion = CommentsStore.state[discussionId];
- this.prepareRequest(namespace);
+ this.prepareRequest(projectPath);
discussion.loading = true;
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 3f15a117ca8..ce79e2e348a 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -4,7 +4,7 @@
var _this;
_this = this;
$('.js-label-select').each(function(i, dropdown) {
- var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip;
+ var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected;
$dropdown = $(dropdown);
projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels');
@@ -24,6 +24,11 @@
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
+ initialSelected = $selectbox
+ .find('input[name="' + $dropdown.data('field-name') + '"]')
+ .map(function () {
+ return this.value;
+ }).get();
if (issueUpdateURL != null) {
issueURLSplit = issueUpdateURL.split('/');
}
@@ -43,6 +48,10 @@
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() {
return this.value;
}).get();
+
+ if (_.isEqual(initialSelected, selected)) return;
+ initialSelected = selected;
+
data = {};
data[abilityName] = {};
data[abilityName].label_ids = selected;
@@ -280,12 +289,12 @@
if (page === 'projects:boards:show') {
if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
- } else if (label.title) {
+ } else if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title);
} else {
var filters = gl.issueBoards.BoardsStore.state.filters['label_name'];
- filters = filters.filter(function (label) {
- return label !== $el.text().trim();
+ filters = filters.filter(function (filteredLabel) {
+ return filteredLabel !== label.title;
});
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters;
}
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index f84a20cf0fe..b8d52becb3f 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -19,7 +19,7 @@
while (i < sURLVariables.length) {
sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) {
- values.push(sParameterName[1]);
+ values.push(sParameterName[1].replace(/\+/g, ' '));
}
i++;
}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index c6854f703fb..866a04d3e21 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -432,14 +432,12 @@
var $form = $(xhr.target);
if ($form.attr('data-resolve-all') != null) {
- var namespacePath = $form.attr('data-namespace-path'),
- projectPath = $form.attr('data-project-path')
- discussionId = $form.attr('data-discussion-id'),
- mergeRequestId = $form.attr('data-noteable-iid'),
- namespace = namespacePath + '/' + projectPath;
+ var projectPath = $form.data('project-path')
+ discussionId = $form.data('discussion-id'),
+ mergeRequestId = $form.data('noteable-iid');
if (ResolveService != null) {
- ResolveService.toggleResolveForDiscussion(namespace, mergeRequestId, discussionId);
+ ResolveService.toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId);
}
}
@@ -854,7 +852,6 @@
.closest('form')
.attr('data-discussion-id', discussionId)
.attr('data-resolve-all', 'true')
- .attr('data-namespace-path', $this.attr('data-namespace-path'))
.attr('data-project-path', $this.attr('data-project-path'));
};
diff --git a/app/assets/javascripts/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branch_edit.js.es6
index 40bc4adb71b..15a6dca2875 100644
--- a/app/assets/javascripts/protected_branch_edit.js.es6
+++ b/app/assets/javascripts/protected_branch_edit.js.es6
@@ -40,7 +40,6 @@
dataType: 'json',
data: {
_method: 'PATCH',
- id: this.$wrap.data('banchId'),
protected_branch: {
merge_access_levels_attributes: [{
id: this.$allowedToMergeDropdown.data('access-level-id'),
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 8abb09c626f..678d836f56f 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -389,4 +389,41 @@
})();
+ $(function() {
+ var $projectOptionsDataEl = $('.js-search-project-options');
+ var $groupOptionsDataEl = $('.js-search-group-options');
+ var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
+
+ if ($projectOptionsDataEl.length) {
+ gl.projectOptions = gl.projectOptions || {};
+
+ var projectPath = $projectOptionsDataEl.data('project-path');
+
+ gl.projectOptions[projectPath] = {
+ name: $projectOptionsDataEl.data('name'),
+ issuesPath: $projectOptionsDataEl.data('issues-path'),
+ mrPath: $projectOptionsDataEl.data('mr-path')
+ };
+ }
+
+ if ($groupOptionsDataEl.length) {
+ gl.groupOptions = gl.groupOptions || {};
+
+ var groupPath = $groupOptionsDataEl.data('group-path');
+
+ gl.groupOptions[groupPath] = {
+ name: $groupOptionsDataEl.data('name'),
+ issuesPath: $groupOptionsDataEl.data('issues-path'),
+ mrPath: $groupOptionsDataEl.data('mr-path')
+ };
+ }
+
+ if ($dashboardOptionsDataEl.length) {
+ gl.dashboardOptions = {
+ issuesPath: $dashboardOptionsDataEl.data('issues-path'),
+ mrPath: $dashboardOptionsDataEl.data('mr-path')
+ };
+ }
+ });
+
}).call(this);
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 4618687a4be..ce489f7c3de 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -336,3 +336,9 @@
box-shadow: inset 0 0 0 white;
}
}
+
+@media (max-width: $screen-xs-max) {
+ .btn-wide-on-xs {
+ width: 100%;
+ }
+}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 0c21d0240b3..7ae309ba103 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -3,7 +3,6 @@
margin: 0;
margin-bottom: $gl-padding;
font-size: 14px;
- z-index: 100;
.flash-notice {
@extend .alert;
@@ -41,4 +40,3 @@
}
}
}
-
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 46af18580d5..efc348214c2 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -164,7 +164,7 @@ ul.content-list {
}
.no-comments {
- opacity: 0.5;
+ opacity: .5;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 2f5e3ec8e44..14ec310de2d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -270,6 +270,12 @@ $calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: $gray-light;
/*
+ * Cycle Analytics
+ */
+$cycle-analytics-box-padding: 30px;
+$cycle-analytics-box-text-color: #8c8c8c;
+
+/*
* Personal Access Tokens
*/
$personal-access-tokens-disabled-label-color: #bbb;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 21e19c97632..778471a34d7 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -1,6 +1,6 @@
#cycle-analytics {
margin: 24px auto 0;
- width: 800px;
+ max-width: 800px;
position: relative;
.panel {
@@ -9,10 +9,18 @@
padding: 24px 0;
border-bottom: none;
position: relative;
+
+ @media (max-width: $screen-sm-min) {
+ padding: 6px 0 24px;
+ }
}
.column {
text-align: center;
+
+ @media (max-width: $screen-sm-min) {
+ padding: 15px 0;
+ }
.header {
font-size: 30px;
@@ -28,11 +36,14 @@
&:last-child {
text-align: right;
+
+ @media (max-width: $screen-sm-min) {
+ text-align: center;
+ }
}
}
.dropdown {
- position: relative;
top: 13px;
}
}
@@ -40,7 +51,7 @@
.bordered-box {
border: 1px solid $border-color;
@include border-radius($border-radius-default);
- position: relative;
+
}
.content-list {
@@ -60,9 +71,15 @@
line-height: 19px;
font-size: 15px;
font-weight: 600;
+ color: $gl-title-color;
}
- &:text {
- color: #8c8c8c;
+
+ &.text {
+ color: $layout-link-gray;
+
+ &.value-col {
+ color: $gl-title-color;
+ }
}
}
}
@@ -71,7 +88,9 @@
text-align: right;
span {
- line-height: 42px;
+ position: relative;
+ vertical-align: middle;
+ top: 3px;
}
}
}
@@ -82,21 +101,25 @@
.dismiss-icon {
position: absolute;
- right: $gl-padding;
+ right: $cycle-analytics-box-padding;
cursor: pointer;
color: #b2b2b2;
}
- svg {
- margin: 0 20px;
- float: left;
- width: 136px;
- height: 136px;
+ .svg-container {
+ text-align: center;
+
+ svg {
+ width: 136px;
+ height: 136px;
+ }
}
-
+
.inner-content {
- width: 480px;
- float: left;
+ @media (max-width: $screen-sm-min) {
+ padding: 0 28px;
+ text-align: center;
+ }
h4 {
color: $gl-text-color;
@@ -104,7 +127,7 @@
}
p {
- color: #8c8c8c;
+ color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding;
}
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index b94f524b513..6b865730487 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -2,13 +2,17 @@
max-width: 90%;
}
-li.milestone {
- h4 {
- font-weight: bold;
- }
+.milestones {
+ .milestone {
+ padding: 10px 16px;
+
+ h4 {
+ font-weight: bold;
+ }
- .progress {
- height: 6px;
+ .progress {
+ height: 6px;
+ }
}
}
@@ -64,3 +68,14 @@ li.milestone {
border-bottom: 1px solid $border-color;
padding: 20px 0;
}
+
+@media (max-width: $screen-sm-min) {
+ .milestone-actions {
+ @include clearfix();
+ padding-top: $gl-vert-padding;
+
+ .btn:first-child {
+ margin-left: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 1b4d12d3053..b035bfc9f3c 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -177,6 +177,10 @@
border-bottom: 2px solid $border-color;
}
}
+
+ a {
+ display: block;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 436fb00ba2e..e77f9816d8a 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -103,7 +103,7 @@
// Custom dropdown positioning
.dropdown-menu {
- top: 30px;
+ top: 37px;
left: -5px;
padding: 0;
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 4d5df566d9b..857eb76131a 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -36,3 +36,7 @@
float: right;
}
}
+
+.snippet-scope-menu .btn-new {
+ margin-top: 15px;
+}
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 52682ef9dc9..b8ed2c159a7 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -1,6 +1,5 @@
module MembershipActions
extend ActiveSupport::Concern
- include MembersHelper
def request_access
membershipable.request_access(current_user)
@@ -10,11 +9,7 @@ module MembershipActions
end
def approve_access_request
- @member = membershipable.requesters.find(params[:id])
-
- return render_403 unless can?(current_user, action_member_permission(:update, @member), @member)
-
- @member.accept_request
+ Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute
redirect_to polymorphic_url([membershipable, :members])
end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 34d5d99558e..7e4da73bc11 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -25,7 +25,7 @@ class JwtController < ApplicationController
authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
- render_403 unless @authentication_result.success? &&
+ render_unauthorized unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
end
rescue Gitlab::Auth::MissingPersonalTokenError
@@ -33,10 +33,21 @@ class JwtController < ApplicationController
end
def render_missing_personal_token
- render plain: "HTTP Basic: Access denied\n" \
- "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
- "You can generate one at #{profile_personal_access_tokens_url}",
- status: 401
+ render json: {
+ errors: [
+ { code: 'UNAUTHORIZED',
+ message: "HTTP Basic: Access denied\n" \
+ "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
+ "You can generate one at #{profile_personal_access_tokens_url}" }
+ ] }, status: 401
+ end
+
+ def render_unauthorized
+ render json: {
+ errors: [
+ { code: 'UNAUTHORIZED',
+ message: 'HTTP Basic: Access denied' }
+ ] }, status: 401
end
def auth_params
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index c5fa756d02b..f71e0a1302b 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -73,7 +73,8 @@ class ProfilesController < Profiles::ApplicationController
:skype,
:twitter,
:username,
- :website_url
+ :website_url,
+ :organization
)
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 02fb3f56890..cdfc1ba7b92 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -10,10 +10,11 @@ class Projects::CommitController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
+ before_action :authorize_read_pipeline!, only: [:pipelines]
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
- before_action :define_commit_vars, only: [:show, :diff_for_path, :builds]
- before_action :define_status_vars, only: [:show, :builds]
+ before_action :define_commit_vars, only: [:show, :diff_for_path, :builds, :pipelines]
+ before_action :define_status_vars, only: [:show, :builds, :pipelines]
before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
@@ -31,6 +32,9 @@ class Projects::CommitController < Projects::ApplicationController
render_diff_for_path(@commit.diffs(diff_options))
end
+ def pipelines
+ end
+
def builds
end
@@ -96,10 +100,6 @@ class Projects::CommitController < Projects::ApplicationController
@noteable = @commit ||= @project.commit(params[:id])
end
- def pipelines
- @pipelines ||= project.pipelines.where(sha: commit.sha)
- end
-
def ci_builds
@ci_builds ||= Ci::Build.where(pipeline: pipelines)
end
@@ -134,8 +134,9 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_status_vars
- @statuses = CommitStatus.where(pipeline: pipelines).relevant
- @builds = Ci::Build.where(pipeline: pipelines).relevant
+ @ci_pipelines = project.pipelines.where(sha: commit.sha)
+ @statuses = CommitStatus.where(pipeline: @ci_pipelines).relevant
+ @builds = Ci::Build.where(pipeline: @ci_pipelines).relevant
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 3eb13a121bf..ef13e0677d2 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -54,7 +54,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- raw_notes = @issue.notes_with_associations.fresh
+ raw_notes = @issue.notes.inc_relations_for_view.fresh
@notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 61517d21f9f..d01e0dedf52 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -6,8 +6,6 @@ class SearchController < ApplicationController
layout 'search'
def show
- return if params[:search].nil? || params[:search].blank?
-
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project)
@@ -18,6 +16,8 @@ class SearchController < ApplicationController
@group = nil unless can?(current_user, :read_group, @group)
end
+ return if params[:search].nil? || params[:search].blank?
+
@search_term = params[:search]
@scope = params[:scope]
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index a4bedb3bfe6..838ecc837e4 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -65,7 +65,7 @@ class UsersController < ApplicationController
format.html { render 'show' }
format.json do
render json: {
- html: view_to_html_string("snippets/_snippets", collection: @snippets)
+ html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true)
}
end
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 639deb7c521..b7f48630bd4 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -56,7 +56,7 @@ module CiStatusHelper
def render_commit_status(commit, tooltip_placement: 'auto left')
project = commit.project
- path = builds_namespace_project_commit_path(project.namespace, project, commit)
+ path = pipelines_namespace_project_commit_path(project.namespace, project, commit)
render_status_with_link('commit', commit.status, path, tooltip_placement: tooltip_placement)
end
diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb
index c15ecc8f86e..95b60aeab5f 100644
--- a/app/helpers/lfs_helper.rb
+++ b/app/helpers/lfs_helper.rb
@@ -1,11 +1,13 @@
module LfsHelper
+ include Gitlab::Routing.url_helpers
+
def require_lfs_enabled!
return if Gitlab.config.lfs.enabled
render(
json: {
message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
- documentation_url: "#{Gitlab.config.gitlab.url}/help",
+ documentation_url: help_url,
},
status: 501
)
@@ -46,7 +48,7 @@ module LfsHelper
render(
json: {
message: 'Access forbidden. Check your access level.',
- documentation_url: "#{Gitlab.config.gitlab.url}/help",
+ documentation_url: help_url,
},
content_type: "application/vnd.git-lfs+json",
status: 403
@@ -57,7 +59,7 @@ module LfsHelper
render(
json: {
message: 'Not found.',
- documentation_url: "#{Gitlab.config.gitlab.url}/help",
+ documentation_url: help_url,
},
content_type: "application/vnd.git-lfs+json",
status: 404
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index b3e6e468ecd..a11c313a6b8 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -35,6 +35,30 @@ module MilestonesHelper
milestone.issues.with_label(label.title).send(state).size
end
+ # Returns count of milestones for different states
+ # Uses explicit hash keys as the 'opened' state URL params differs from the db value
+ # and we need to add the total
+ def milestone_counts(milestones)
+ counts = milestones.reorder(nil).group(:state).count
+
+ {
+ opened: counts['active'] || 0,
+ closed: counts['closed'] || 0,
+ all: counts.values.sum || 0
+ }
+ end
+
+ # Show 'active' class if provided GET param matches check
+ # `or_blank` allows the function to return 'active' when given an empty param
+ # Could be refactored to be simpler but that may make it harder to read
+ def milestone_class_for_state(param, check, match_blank_param = false)
+ if match_blank_param
+ 'active' if param.blank? || param == check
+ else
+ 'active' if param == check
+ end
+ end
+
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 9799f1dc886..29f1c527776 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -109,7 +109,7 @@ class Notify < BaseMailer
headers['X-GitLab-Reply-Key'] = reply_key
if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
- headers['List-Unsubscribe'] = unsubscribe_sent_notification_url(@sent_notification, force: true)
+ headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>"
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
end
diff --git a/app/models/board.rb b/app/models/board.rb
index 3240c4bede3..c56422914a9 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -4,4 +4,12 @@ class Board < ActiveRecord::Base
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
validates :project, presence: true
+
+ def backlog_list
+ lists.merge(List.backlog).take
+ end
+
+ def done_list
+ lists.merge(List.done).take
+ end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index cb87b43f6be..522e2264bb8 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -91,7 +91,7 @@ module Ci
sha: build.sha,
ref: build.ref,
tag: build.tag,
- options: build.options[:environment],
+ options: build.options.to_h[:environment],
variables: build.variables)
service.execute(build)
end
diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb
index 53b2cacb131..b46db449bf3 100644
--- a/app/models/cycle_analytics/summary.rb
+++ b/app/models/cycle_analytics/summary.rb
@@ -10,15 +10,33 @@ class CycleAnalytics
end
def commits
- repository = @project.repository.raw_repository
-
- if @project.default_branch
- repository.log(ref: @project.default_branch, after: @from).count
- end
+ ref = @project.default_branch.presence
+ count_commits_for(ref)
end
def deploys
@project.deployments.where("created_at > ?", @from).count
end
+
+ private
+
+ # Don't use the `Gitlab::Git::Repository#log` method, because it enforces
+ # a limit. Since we need a commit count, we _can't_ enforce a limit, so
+ # the easiest way forward is to replicate the relevant portions of the
+ # `log` function here.
+ def count_commits_for(ref)
+ return unless ref
+
+ repository = @project.repository.raw_repository
+ sha = @project.repository.commit(ref).sha
+
+ cmd = %W(git --git-dir=#{repository.path} log)
+ cmd << '--format=%H'
+ cmd << "--after=#{@from.iso8601}"
+ cmd << sha
+
+ raw_output = IO.popen(cmd) { |io| io.read }
+ raw_output.lines.count
+ end
end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index da7c265a371..bda2b5c5d5d 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -8,7 +8,8 @@ class GlobalMilestone
milestones = milestones.group_by(&:title)
milestones.map do |title, milestones|
- new(title, milestones)
+ milestones_relation = Milestone.where(id: milestones.map(&:id))
+ new(title, milestones_relation)
end
end
@@ -31,7 +32,7 @@ class GlobalMilestone
end
def projects
- @projects ||= Project.for_milestones(milestones.map(&:id))
+ @projects ||= Project.for_milestones(milestones.select(:id))
end
def state
@@ -53,19 +54,19 @@ class GlobalMilestone
end
def issues
- @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
+ @issues ||= Issue.of_milestones(milestones.select(:id)).includes(:project, :assignee, :labels)
end
def merge_requests
- @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
+ @merge_requests ||= MergeRequest.of_milestones(milestones.select(:id)).includes(:target_project, :assignee, :labels)
end
def participants
- @participants ||= milestones.map(&:participants).flatten.compact.uniq
+ @participants ||= milestones.includes(:participants).map(&:participants).flatten.compact.uniq
end
def labels
- @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
+ @labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten)
.sort_by!(&:title)
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2dcf7f89bfc..aec555dcec0 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -590,13 +590,11 @@ class MergeRequest < ActiveRecord::Base
end
def merge_commit_message
- message = "Merge branch '#{source_branch}' into '#{target_branch}'"
- message << "\n\n"
- message << title.to_s
- message << "\n\n"
- message << description.to_s
- message << "\n\n"
- message << "See merge request !#{iid}"
+ message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n"
+ message << "#{title}\n\n"
+ message << "#{description}\n\n" if description.present?
+ message << "See merge request #{to_reference}"
+
message
end
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 63a5ed14484..d9fba3d4a41 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService
end
end
+ def title=(value)
+ self.properties['title'] = value if self.properties
+ end
+
def description
if self.properties && self.properties['description'].present?
self.properties['description']
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 38ac6631228..8ea88da8a53 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -7,10 +7,10 @@ module Auth
def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities
- return error('not found', 404) unless registry.enabled
+ return error('UNAVAILABLE', status: 404, message: 'registry not enabled') unless registry.enabled
unless current_user || project
- return error('forbidden', 403) unless scope
+ return error('DENIED', status: 403, message: 'access forbidden') unless scope
end
{ token: authorized_token(scope).encoded }
@@ -111,5 +111,12 @@ module Auth
@authentication_abilities.include?(:create_container_image) &&
can?(current_user, :create_container_image, requested_project)
end
+
+ def error(code, status:, message: '')
+ {
+ errors: [{ code: code, message: message }],
+ http_status: status
+ }
+ end
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index fbce46769f7..57d521f2fea 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -50,6 +50,7 @@ class IssuableBaseService < BaseService
params.delete(:remove_label_ids)
params.delete(:label_ids)
params.delete(:assignee_id)
+ params.delete(:due_date)
end
end
diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb
new file mode 100644
index 00000000000..416aee2ab51
--- /dev/null
+++ b/app/services/members/approve_access_request_service.rb
@@ -0,0 +1,31 @@
+module Members
+ class ApproveAccessRequestService < BaseService
+ include MembersHelper
+
+ attr_accessor :source
+
+ def initialize(source, current_user, params = {})
+ @source = source
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute
+ condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] }
+ access_requester = source.requesters.find_by!(condition)
+
+ raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester)
+
+ access_requester.access_level = params[:access_level] if params[:access_level]
+ access_requester.accept_request
+
+ access_requester
+ end
+
+ private
+
+ def can_update_access_requester?(access_requester)
+ access_requester && can?(current_user, action_member_permission(:update, access_requester), access_requester)
+ end
+ end
+end
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index 9ac1124abc1..ffcad5b3a87 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -195,7 +195,7 @@ module SlashCommands
params '<in 2 days | this Friday | December 31st>'
condition do
issuable.respond_to?(:due_date) &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
+ current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :due do |due_date_param|
due_date = Chronic.parse(due_date_param).try(:to_date)
@@ -208,7 +208,7 @@ module SlashCommands
issuable.persisted? &&
issuable.respond_to?(:due_date) &&
issuable.due_date? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
+ current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :remove_due_date do
@updates[:due_date] = nil
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
index 7a35958cc5f..4dc3b2ab9a0 100644
--- a/app/validators/namespace_validator.rb
+++ b/app/validators/namespace_validator.rb
@@ -5,7 +5,8 @@
# Values are checked for formatting and exclusion from a list of reserved path
# names.
class NamespaceValidator < ActiveModel::EachValidator
- RESERVED = %w(
+ RESERVED = %w[
+ .well-known
admin
all
assets
@@ -31,7 +32,7 @@ class NamespaceValidator < ActiveModel::EachValidator
u
unsubscribes
users
- ).freeze
+ ].freeze
def validate_each(record, attribute, value)
unless value =~ Gitlab::Regex.namespace_regex
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 6b157abf842..f952d2e9aa1 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -18,11 +18,11 @@
.form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
- = f.color_field :color, class: "form-control"
+ = f.text_field :color, class: "form-control"
.form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label'
.col-sm-10
- = f.color_field :font, class: "form-control"
+ = f.text_field :font, class: "form-control"
.form-group
= f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index 602cfa9b6fc..d5e6bede36a 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -14,7 +14,7 @@
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
- = f.color_field :color, class: "form-control"
+ = f.text_field :color, class: "form-control"
.help-block
Choose any color.
%br
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index d4e7862981c..b2af438ea57 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -4,10 +4,10 @@
= render 'dashboard/snippets_head'
.nav-block
- .controls
- = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
+ .controls.hidden-xs
+ = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do
= icon('plus')
- New Snippet
+ New snippet
.nav-links.snippet-scope-menu
%li{ class: ("active" unless params[:scope]) }
@@ -34,5 +34,9 @@
%span.badge
= current_user.snippets.are_public.count
-= render 'snippets/snippets'
+ .visible-xs
+ = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do
+ = icon('plus')
+ New snippet
+= render 'snippets/snippets'
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 689cd6ed665..2ef383960f4 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,4 +1,4 @@
-= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do
+= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- if devise_mapping.rememberable?
diff --git a/app/views/discussions/_resolve_all.html.haml b/app/views/discussions/_resolve_all.html.haml
index 7a8767ddba0..f0b61e0f7de 100644
--- a/app/views/discussions/_resolve_all.html.haml
+++ b/app/views/discussions/_resolve_all.html.haml
@@ -1,11 +1,10 @@
- if discussion.for_merge_request?
- %resolve-discussion-btn{ ":namespace-path" => "'#{discussion.project.namespace.path}'",
- ":project-path" => "'#{discussion.project.path}'",
+ %resolve-discussion-btn{ ":project-path" => "'#{project_path(discussion.project)}'",
":discussion-id" => "'#{discussion.id}'",
":merge-request-id" => discussion.noteable.iid,
":can-resolve" => discussion.can_resolve?(current_user),
"inline-template" => true }
.btn-group{ role: "group", "v-if" => "showButton" }
- %button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading" }
+ %button.btn.btn-default{ type: "button", "@click" => "resolve", ":disabled" => "loading", "v-cloak" => "true" }
= icon("spinner spin", "v-show" => "loading")
{{ buttonText }}
diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
index bfa95ce79a7..9f02a8d2ed9 100644
--- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
+++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
@@ -6,4 +6,4 @@
= form_tag path do
%input{:name => "_method", :type => "hidden", :value => "delete"}/
- = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm'
+ = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-remove btn-sm'
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 6306fe6d0bf..7def9eacdc9 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -8,9 +8,8 @@
.row-content-block
- if current_user
- .pull-right
- = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
- New Snippet
+ = link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do
+ New snippet
.oneline
Public snippets created by you and other users are listed here
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index f7580f00159..d7386105b7d 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -2,15 +2,18 @@
- label = 'This group'
- if controller.controller_path =~ /^projects/ && @project.persisted?
- label = 'This project'
-
+- if @group && @group.persisted? && @group.path
+ - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
+- if @project && @project.persisted?
+ - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) }
.search.search-form{class: "#{'has-location-badge' if label.present?}"}
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
- if label.present?
.location-badge= label
.search-input-wrap
- .dropdown{ data: {url: search_autocomplete_path } }
- = search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
+ .dropdown{ data: { url: search_autocomplete_path } }
+ = search_field_tag 'search', nil, placeholder: 'Search', class: 'search-input dropdown-menu-toggle js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { toggle: 'dropdown', issues_path: issues_dashboard_url, mr_path: merge_requests_dashboard_url }
.dropdown-menu.dropdown-select
= dropdown_content do
%ul
@@ -21,8 +24,9 @@
%i.search-icon
%i.clear-icon.js-clear-input
- = hidden_field_tag :group_id, @group.try(:id)
- = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
+ = hidden_field_tag :group_id, @group.try(:id), class: 'js-search-group-options', data: group_data_attrs
+
+ = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id', class: 'js-search-project-options', data: project_data_attrs
- if @project && @project.persisted?
- if current_controller?(:issues)
@@ -36,31 +40,6 @@
- else
= hidden_field_tag :search_code, true
- :javascript
- gl.projectOptions = gl.projectOptions || {};
- gl.projectOptions["#{j(@project.path)}"] = {
- issuesPath: "#{namespace_project_issues_path(@project.namespace, @project)}",
- mrPath: "#{namespace_project_merge_requests_path(@project.namespace, @project)}",
- name: "#{j(@project.name)}"
- };
-
- - if @group && @group.persisted? && @group.path
- :javascript
- gl.groupOptions = gl.groupOptions || {};
- gl.groupOptions["#{j(@group.path)}"] = {
- name: "#{j(@group.name)}",
- issuesPath: "#{issues_group_path(j(@group.path))}",
- mrPath: "#{merge_requests_group_path(j(@group.path))}"
- };
-
-
- :javascript
- gl.dashboardOptions = {
- issuesPath: "#{issues_dashboard_url}",
- mrPath: "#{merge_requests_dashboard_url}"
- };
-
-
- if @snippet || @snippets
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index d9fa74fad90..578af9fe98d 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -87,6 +87,9 @@
= f.label :location, 'Location', class: "label-light"
= f.text_field :location, class: "form-control"
.form-group
+ = f.label :organization, 'Organization', class: "label-light"
+ = f.text_field :organization, class: "form-control"
+ .form-group
= f.label :bio, class: "label-light"
= f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
%span.help-block Tell us about yourself in fewer than 250 characters.
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 6391c67021b..04e48a4dc17 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -1,4 +1,7 @@
- status = pipeline.status
+- show_commit = local_assigns.fetch(:show_commit, true)
+- show_branch = local_assigns.fetch(:show_branch, true)
+
%tr.commit
%td.commit-link
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
@@ -10,14 +13,14 @@
.branch-commit
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
%span ##{pipeline.id}
- - if pipeline.ref
- - unless defined?(hide_branch) && hide_branch
- .icon-container
- = pipeline.tag? ? icon('tag') : icon('code-fork')
- = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name"
- .icon-container
- = custom_icon("icon_commit")
- = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace"
+ - if pipeline.ref && show_branch
+ .icon-container
+ = pipeline.tag? ? icon('tag') : icon('code-fork')
+ = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name"
+ - if show_commit
+ .icon-container
+ = custom_icon("icon_commit")
+ = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace"
- if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered?
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index a508382578a..b7087749428 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,2 +1,2 @@
-- @pipelines.each do |pipeline|
+- @ci_pipelines.each do |pipeline|
= render "pipeline", pipeline: pipeline, pipeline_details: true
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index 935433306ea..cbfd99ca448 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -3,6 +3,11 @@
= link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Changes
%span.badge= @diffs.size
+ - if can?(current_user, :read_pipeline, @project)
+ = nav_link(path: 'commit#pipelines') do
+ = link_to pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ Pipelines
+ %span.badge= @ci_pipelines.count
= nav_link(path: 'commit#builds') do
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Builds
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index f41a11a056d..998812793a2 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -7,14 +7,8 @@
%table.table.builds
%tbody
%th Status
- %th Commit
- - pipelines.stages.each do |stage|
- %th.stage
- - if stage.titleize.length > 12
- %span.has-tooltip{ title: "#{stage.titleize}" }
- = stage.titleize
- - else
- = stage.titleize
+ %th Pipeline
+ %th Stages
%th
%th
- = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, hide_branch: true
+ = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, show_commit: false
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
new file mode 100644
index 00000000000..d85d6729a81
--- /dev/null
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -0,0 +1,7 @@
+- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits"
+
+.prepend-top-default
+ = render "commit_box"
+
+= render "ci_menu"
+= render "pipelines_list", pipelines: @ci_pipelines
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 5dcb2a17873..7f346df8797 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -2,18 +2,20 @@
- page_title "Cycle Analytics"
= render "projects/pipelines/head"
-#cycle-analytics{"v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
+#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
= icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
- = custom_icon('icon_cycle_analytics_splash')
- .inner-content
- %h4
- Introducing Cycle Analytics
- %p
- Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
+ .row
+ .col-sm-3.col-xs-12.svg-container
+ = custom_icon('icon_cycle_analytics_splash')
+ .col-sm-8.col-xs-12.inner-content
+ %h4
+ Introducing Cycle Analytics
+ %p
+ Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
- = link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
+ = link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
= icon("spinner spin", "v-show" => "isLoading")
@@ -25,11 +27,11 @@
.content-block
.container-fluid
.row
- .col-xs-3.column{"v-for" => "item in summary"}
+ .col-sm-3.col-xs-12.column{"v-for" => "item in analytics.summary"}
%h3.header {{item.value}}
%p.text {{item.title}}
- .col-xs-3.column
+ .col-sm-3.col-xs-12.column
.dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"}
%span.dropdown-label Last 30 days
@@ -44,14 +46,14 @@
.bordered-box
%ul.content-list
- %li{"v-for" => "item in stats"}
+ %li{"v-for" => "item in analytics.stats"}
.container-fluid
.row
- .col-xs-10.title-col
+ .col-xs-8.title-col
%p.title
{{item.title}}
%p.text
{{item.description}}
- .col-xs-2.value-col
+ .col-xs-4.value-col
%span
{{item.value}}
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 1a51ccd4c7d..d07de45fdde 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -5,7 +5,7 @@
- unless diff_file.submodule?
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
- = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this files", disabled: @diff_notes_disabled do
+ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
= icon('comment')
\
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index aa143e54ffe..6ab6ae50389 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -14,7 +14,7 @@
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
- = f.color_field :color, class: "form-control"
+ = f.text_field :color, class: "form-control"
.help-block
Choose any color.
%br
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 3900b4f6f17..cfb44bd206c 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -5,7 +5,7 @@
- if @merge_request.reopenable?
= link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
%comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" }
- %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } }
+ %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
{{ buttonText }}
#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 49819519759..904452fcc4f 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -58,11 +58,11 @@
.monospace #{short_sha(merge_request_diff.head_commit_sha)}
%small
= time_ago_with_tooltip(merge_request_diff.created_at)
- %li
- = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
- %strong
- #{@merge_request.target_branch} (base)
- .monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+ %li
+ = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
+ %strong
+ #{@merge_request.target_branch} (base)
+ .monospace #{short_sha(@merge_request_diff.base_commit_sha)}
- unless @merge_request_diff.latest? && !@start_sha
.comments-disabled-notif.content-block
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 9ec17cf6e76..788be4a0047 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -24,14 +24,12 @@
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
-
- %resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'",
- ":project-path" => "'#{note.project.path}'",
- ":discussion-id" => "'#{note.discussion_id}'",
+ %resolve-btn{ "project-path" => "#{project_path(note.project)}",
+ "discussion-id" => "#{note.discussion_id}",
":note-id" => note.id,
":resolved" => note.resolved?,
":can-resolve" => can_resolve,
- ":resolved-by" => "'#{note.resolved_by.try(:name)}'",
+ "resolved-by" => "#{note.resolved_by.try(:name)}",
"v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true,
"v-ref:note_#{note.id}" => true }
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index faf28db68d1..50c7e5044b2 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -46,7 +46,7 @@
%table.table.builds
%tbody
%th Status
- %th Commit
+ %th Pipeline
%th Stages
%th
%th
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 0628134b1bb..0193800dedf 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -1,4 +1,4 @@
-%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), branch_id: protected_branch.id } }
+%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
%td
= protected_branch.name
- if @project.root_ref?(protected_branch.name)
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 4aa4ab46a2f..9773b8438ec 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -1,7 +1,7 @@
.hidden-xs
- if can?(current_user, :create_project_snippet, @project)
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
- New Snippet
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do
+ New snippet
- if can?(current_user, :update_project_snippet, @snippet)
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete
@@ -17,8 +17,8 @@
%ul
- if can?(current_user, :create_project_snippet, @project)
%li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
- New Snippet
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do
+ New snippet
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 1646bcf4b8a..e77e1b026f6 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,10 +1,9 @@
- page_title "Snippets"
.sub-header-block
- .pull-right
- - if can?(current_user, :create_project_snippet, @project)
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
- New Snippet
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do
+ New snippet
.oneline
Share code pastes with others out of git repository
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 643f7c589e6..6624d5cb427 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -24,7 +24,7 @@
= succeed '.' do
More examples are in the
- = link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown")
+ = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown")
.form-group
= f.label :commit_message, class: 'control-label'
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
index cf16c203f9c..73d288e2236 100644
--- a/app/views/shared/_milestones_filter.html.haml
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -1,10 +1,19 @@
+- if @project
+ - counts = milestone_counts(@project.milestones)
+
%ul.nav-links
- %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
+ %li{class: milestone_class_for_state(params[:state], 'opened', true)}
= link_to milestones_filter_path(state: 'opened') do
Open
- %li{class: ("active" if params[:state] == 'closed')}
+ - if @project
+ %span.badge #{counts[:opened]}
+ %li{class: milestone_class_for_state(params[:state], 'closed')}
= link_to milestones_filter_path(state: 'closed') do
Closed
- %li{class: ("active" if params[:state] == 'all')}
+ - if @project
+ %span.badge #{counts[:closed]}
+ %li{class: milestone_class_for_state(params[:state], 'all')}
= link_to milestones_filter_path(state: 'all') do
All
+ - if @project
+ %span.badge #{counts[:all]}
diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg
index fc970e4ce50..ce22b6cdaea 100644
--- a/app/views/shared/icons/_icon_fork.svg
+++ b/app/views/shared/icons/_icon_fork.svg
@@ -1,3 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40">
- <path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40"><path fill="#7E7E7E" fill-rule="evenodd" d="M22 29.535V22.72c4.19-1.21 7.4-5.917 7.4-5.917.34-.446.6-1.247.6-1.795v-4.543C31.196 9.773 32 8.48 32 7c0-2.21-1.79-4-4-4s-4 1.79-4 4c0 1.48.804 2.773 2 3.465v4.243c0 .17-.094.39-.21.502 0 0-1.253 1.203-1.84 1.742C22.5 18.277 21.12 19 20.09 19c-1.042 0-2.473-.74-3.977-2.086-.61-.546-1.746-1.574-1.746-1.574-.2-.182-.367-.555-.367-.83v-4.045C15.196 9.773 16 8.48 16 7c0-2.21-1.79-4-4-4S8 4.79 8 7c0 1.48.804 2.773 2 3.465v4.543c0 .537.274 1.345.61 1.78 0 0 3.25 4.59 7.39 5.88v6.867c-1.196.692-2 1.984-2 3.465 0 2.21 1.79 4 4 4s4-1.79 4-4c0-1.48-.804-2.773-2-3.465zM14 7c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2zm16 0c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2zm-8 26c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2z"/></svg>
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index acc3ccf4dcf..3dccfb147bf 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -33,7 +33,7 @@
- if @project
.row
.col-sm-6= render('shared/milestone_expired', milestone: milestone)
- .col-sm-6
+ .col-sm-6.milestone-actions
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
Edit
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index fdaca199218..c446dc3bdc1 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -1,7 +1,7 @@
.hidden-xs
- if current_user
- = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
- New Snippet
+ = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do
+ New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete
@@ -16,8 +16,8 @@
.dropdown-menu.dropdown-menu-full-width
%ul
%li
- = link_to new_snippet_path, title: "New Snippet" do
- New Snippet
+ = link_to new_snippet_path, title: "New snippet" do
+ New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index 7be4a471579..77b66ca74b6 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -1,3 +1,5 @@
+- remote = local_assigns.fetch(:remote, false)
+
.snippets-list-holder
%ul.content-list
= render partial: 'shared/snippets/snippet', collection: @snippets
@@ -5,7 +7,7 @@
%li
.nothing-here-block Nothing here.
- = paginate @snippets, theme: 'gitlab', remote: true
+ = paginate @snippets, theme: 'gitlab', remote: remote
:javascript
gl.SnippetsList();
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 2a57ac90bab..60fc0c0daf6 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -36,7 +36,7 @@
.avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
-
+
.user-info
.cover-title
= @user.name
@@ -70,6 +70,10 @@
.profile-link-holder.middle-dot-divider
= icon('map-marker')
= @user.location
+ - unless @user.organization.blank?
+ .profile-link-holder.middle-dot-divider
+ = icon('building')
+ = @user.organization
- if @user.bio.present?
.cover-desc
diff --git a/config/application.rb b/config/application.rb
index 4792f6670a8..8166b6003f6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -116,6 +116,10 @@ module Gitlab
redis_config_hash = Gitlab::Redis.params
redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
+ if Sidekiq.server? # threaded context
+ redis_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
+ redis_config_hash[:pool_timeout] = 1
+ end
config.cache_store = :redis_store, redis_config_hash
config.active_record.raise_in_transactional_callbacks = true
diff --git a/config/initializers/connection_fix.rb b/config/initializers/connection_fix.rb
index d831a1838ed..d0b1444f607 100644
--- a/config/initializers/connection_fix.rb
+++ b/config/initializers/connection_fix.rb
@@ -20,7 +20,7 @@ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
execute_without_retry(*args)
rescue ActiveRecord::StatementInvalid => e
if e.message =~ /server has gone away/i
- warn "Server timed out, retrying"
+ warn "Lost connection to MySQL server during query"
reconnect!
retry
else
diff --git a/config/routes.rb b/config/routes.rb
index 4d6ec699cbd..ba3864b92be 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -635,6 +635,7 @@ Rails.application.routes.draw do
member do
get :branches
get :builds
+ get :pipelines
post :cancel_builds
post :retry_builds
post :revert
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index e3316ecdb6c..a984eda5ab5 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -3,11 +3,11 @@ require 'sidekiq/testing'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
project_urls = [
- 'https://github.com/documentcloud/underscore.git',
+ 'https://gitlab.com/gitlab-org/gitlab-test.git',
'https://gitlab.com/gitlab-org/gitlab-ce.git',
'https://gitlab.com/gitlab-org/gitlab-ci.git',
'https://gitlab.com/gitlab-org/gitlab-shell.git',
- 'https://gitlab.com/gitlab-org/gitlab-test.git',
+ 'https://github.com/documentcloud/underscore.git',
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
@@ -38,12 +38,7 @@ Sidekiq::Testing.inline! do
]
# You can specify how many projects you need during seed execution
- size = if ENV['SIZE'].present?
- ENV['SIZE'].to_i
- else
- 8
- end
-
+ size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
project_urls.first(size).each_with_index do |url, i|
group_path, project_path = url.split('/')[-2..-1]
diff --git a/db/migrate/20160920160832_add_index_to_labels_title.rb b/db/migrate/20160920160832_add_index_to_labels_title.rb
new file mode 100644
index 00000000000..b5de552b98c
--- /dev/null
+++ b/db/migrate/20160920160832_add_index_to_labels_title.rb
@@ -0,0 +1,11 @@
+class AddIndexToLabelsTitle < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :labels, :title
+ end
+end
diff --git a/db/migrate/20160926145521_add_organization_to_user.rb b/db/migrate/20160926145521_add_organization_to_user.rb
new file mode 100644
index 00000000000..e0bef6e7548
--- /dev/null
+++ b/db/migrate/20160926145521_add_organization_to_user.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddOrganizationToUser < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :users, :organization, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 59b3e237707..ad62c249b3f 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: 20160915042921) do
+ActiveRecord::Schema.define(version: 20160926145521) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -521,6 +521,7 @@ ActiveRecord::Schema.define(version: 20160915042921) do
add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
+ add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
create_table "lfs_objects", force: :cascade do |t|
t.string "oid", null: false
@@ -1131,6 +1132,7 @@ ActiveRecord::Schema.define(version: 20160915042921) do
t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false
+ t.string "organization"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index dd0eb97489e..4ff1a0582c8 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -19,6 +19,7 @@
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
+- [University](university/README.md) Learn Git and GitLab through videos and courses.
## Administrator documentation
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 7186f707ad6..bf7814875bf 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -275,3 +275,9 @@ If you are getting 'Connection Refused' errors when trying to connect to the
LDAP server please double-check the LDAP `port` and `method` settings used by
GitLab. Common combinations are `method: 'plain'` and `port: 389`, OR
`method: 'ssl'` and `port: 636`.
+
+### Login with valid credentials rejected
+
+If there is an unexpected error while authenticating the user with the LDAP
+backend, the login is rejected and details about the error are logged to
+`production.log`.
diff --git a/doc/api/README.md b/doc/api/README.md
index 6e3295e0e0c..8e4f7f12b4b 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -10,6 +10,7 @@ following locations:
- [Award Emoji](award_emoji.md)
- [Branches](branches.md)
+- [Broadcast Messages](broadcast_messages.md)
- [Builds](builds.md)
- [Build Triggers](build_triggers.md)
- [Build Variables](build_variables.md)
diff --git a/doc/api/builds.md b/doc/api/builds.md
index dce666445d0..e8a9e4743d3 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -40,6 +40,12 @@ Example of response
"finished_at": "2015-12-24T17:54:27.895Z",
"id": 7,
"name": "teaspoon",
+ "pipeline": {
+ "id": 6,
+ "ref": "master",
+ "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "status": "pending"
+ }
"ref": "master",
"runner": null,
"stage": "test",
@@ -78,6 +84,12 @@ Example of response
"finished_at": "2015-12-24T17:54:24.921Z",
"id": 6,
"name": "spinach:other",
+ "pipeline": {
+ "id": 6,
+ "ref": "master",
+ "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "status": "pending"
+ }
"ref": "master",
"runner": null,
"stage": "test",
@@ -146,6 +158,12 @@ Example of response
"finished_at": "2016-01-11T10:14:09.526Z",
"id": 69,
"name": "rubocop",
+ "pipeline": {
+ "id": 6,
+ "ref": "master",
+ "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "status": "pending"
+ }
"ref": "master",
"runner": null,
"stage": "test",
@@ -170,6 +188,12 @@ Example of response
"finished_at": "2015-12-24T17:54:33.913Z",
"id": 9,
"name": "brakeman",
+ "pipeline": {
+ "id": 6,
+ "ref": "master",
+ "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "status": "pending"
+ }
"ref": "master",
"runner": null,
"stage": "test",
@@ -231,6 +255,12 @@ Example of response
"finished_at": "2015-12-24T17:54:31.198Z",
"id": 8,
"name": "rubocop",
+ "pipeline": {
+ "id": 6,
+ "ref": "master",
+ "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "status": "pending"
+ }
"ref": "master",
"runner": null,
"stage": "test",
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 0b0fc39ec7e..b34e8075775 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -4,7 +4,7 @@ This document covers using the OAuth2 protocol to access GitLab.
If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md).
-OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password to a third-party.
+OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password to a third-party.
This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper)
@@ -22,7 +22,7 @@ In the following sections you will be introduced to the three steps needed for t
### 1. Registering the client
First, you should create an application (`/profile/applications`) in your user's account.
-Each application gets a unique App ID and App Secret parameters.
+Each application gets a unique App ID and App Secret parameters.
>**Note:**
**You should not share/leak your App ID or App Secret.**
@@ -46,10 +46,10 @@ http://myapp.com/oauth/redirect?code=1234567890&state=your_unique_state_hash
You should then use the `code` to request an access token.
>**Important:**
-It is highly recommended that you send a `state` value with the request to `/oauth/authorize` and
-validate that value is returned and matches in the redirect request.
-This is important to prevent [CSFR attacks](http://www.oauthsecurity.com/#user-content-authorization-code-flow),
-`state` really should have been a requirement in the standard!
+It is highly recommended that you send a `state` value with the request to `/oauth/authorize` and
+validate that value is returned and matches in the redirect request.
+This is important to prevent [CSRF attacks](http://www.oauthsecurity.com/#user-content-authorization-code-flow),
+`state` really should have been a requirement in the standard!
### 3. Requesting the access token
@@ -62,7 +62,7 @@ RestClient.post 'http://localhost:3000/oauth/token', parameters
# The response will be
{
"access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
- "token_type": "bearer",
+ "token_type": "bearer",
"expires_in": 7200,
"refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
}
@@ -95,7 +95,7 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/
---
-In this flow, a token is requested in exchange for the resource owner credentials (username and password).
+In this flow, a token is requested in exchange for the resource owner credentials (username and password).
The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
client is part of the device operating system or a highly privileged application), and when other authorization grant types are not
available (such as an authorization code).
diff --git a/doc/api/users.md b/doc/api/users.md
index 54f7a2a2ace..9be4f2e6ec3 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -57,6 +57,7 @@ GET /users
"linkedin": "",
"twitter": "",
"website_url": "",
+ "organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
@@ -89,6 +90,7 @@ GET /users
"linkedin": "",
"twitter": "",
"website_url": "",
+ "organization": "",
"last_sign_in_at": null,
"confirmed_at": "2012-05-30T16:53:06.148Z",
"theme_id": 1,
@@ -147,7 +149,8 @@ Parameters:
"skype": "",
"linkedin": "",
"twitter": "",
- "website_url": ""
+ "website_url": "",
+ "organization": ""
}
```
@@ -178,6 +181,7 @@ Parameters:
"linkedin": "",
"twitter": "",
"website_url": "",
+ "organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
@@ -214,6 +218,7 @@ Parameters:
- `linkedin` (optional) - LinkedIn
- `twitter` (optional) - Twitter account
- `website_url` (optional) - Website URL
+- `organization` (optional) - Organization name
- `projects_limit` (optional) - Number of projects user can create
- `extern_uid` (optional) - External UID
- `provider` (optional) - External provider name
@@ -242,6 +247,7 @@ Parameters:
- `linkedin` - LinkedIn
- `twitter` - Twitter account
- `website_url` - Website URL
+- `organization` - Organization name
- `projects_limit` - Limit projects each user can create
- `extern_uid` - External UID
- `provider` - External provider name
@@ -296,6 +302,7 @@ GET /user
"linkedin": "",
"twitter": "",
"website_url": "",
+ "organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 71670e6247c..40f0165deef 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -13,7 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
- [Test a Scala application](test-scala-application.md)
- [Using `dpl` as deployment tool](deployment/README.md)
- [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
-- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples)
+- [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 6a971c3ae87..22d67bd9964 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -44,7 +44,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the build is run |
| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
-| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returnes the address of the registry tied to the specific project |
+| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 3ac813aa914..cb4c1f4a091 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -381,7 +381,7 @@ sudo usermod -aG redis git
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production SKIP_STORAGE_VALIDATION=true
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
diff --git a/doc/university/README.md b/doc/university/README.md
new file mode 100644
index 00000000000..6ca1c20c9b2
--- /dev/null
+++ b/doc/university/README.md
@@ -0,0 +1,139 @@
+
+## What is GitLab University
+
+_GitLab University_ has as a goal to teach the fundamentals of **Version Control with Git and GitLab** through courses that cover topics which can be mastered in around 2 hours.
+
+_University materials don't replace our [Documentation](http://docs.gitlab.com) or [Blog Articles](https://about.gitlab.com/blog/)._
+
+---
+
+### On this page
+
++ [GITx] Git
++ [OPSx] DevOps
++ [GLBx] GitLab Basics
++ [INTx] GitLab Integrations
++ [GLFx] GitLab Workflows
++ [GLEx] GitLab Enterprise Edition extra features
++ [GCIx] GitLab CI
++ [ECO] Ecosystem
++ [COM] Competition comparison
++ [SPTx] Support Bootcamp
++ [SLSx] Sales Bootcamp
++ [TRAx] Trainings
+
+---
+
++ [GIT1] [Version Control Systems](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.g72f2e4906_2_29)
++ [GIT2] [Operating Systems and How Git Works](https://drive.google.com/a/gitlab.com/file/d/0B41DBToSSIG_OVYxVFJDOGI3Vzg/view?usp=sharing)
++ [GIT3] [Intro to Git](https://www.codeschool.com/account/courses/try-git)
+
+---
+
++ [OPS1] [What is Omnibus](https://www.youtube.com/watch?v=XTmpKudd-Oo)
++ [OPS2] [Installing GitLab](https://www.youtube.com/watch?v=Q69YaOjqNhg)
++ [OPS3] [Configuring an external PostgreSQL database](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#using-a-non-packaged-postgresql-database-management-server)
++ [OPS5] [Importing from Other Tools or SVN](http://doc.gitlab.com/ee/workflow/importing/)
++ [OPS6] [High Availability Documentation](https://about.gitlab.com/high-availability/)
++ [OPS7] [Managing LDAP, Active Directory](https://www.youtube.com/watch?v=HPMjM-14qa8)
++ [OPS8] [Scalability and High Availability](https://www.youtube.com/watch?v=cXRMJJb6sp4&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=2)
++ [OPS9] [High Availability on AWS](high-availability/aws/README.md)
+
+---
+
++ [GLB1] [Terminology](glossary/README.md)
++ [GLB2] [GitLab Basics](http://doc.gitlab.com/ce/gitlab-basics/README.html)
++ [GLB3] [Demo of GitLab.com](https://www.youtube.com/watch?v=WaiL5DGEMR4)
++ [GLB4] [Create and Add your SSH key to GitLab](https://www.youtube.com/watch?v=54mxyLo3Mqk)
++ [GLB5] [Repositories, Projects and Groups](https://www.youtube.com/watch?v=4TWfh1aKHHw&index=1&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
++ [GLB6] [Creating a Project in GitLab](https://www.youtube.com/watch?v=7p0hrpNaJ14)
++ [GLB7] [Issues and Merge Requests](https://www.youtube.com/watch?v=raXvuwet78M)
++ [GLB8] [Big files in Git (Git LFS, Annex)](https://gitlab.com/gitlab-org/University/blob/master/classes/git_lfs_and_annex.md)
+
+---
+
++ [INT1] [JIRA and Jenkins integrations in GitLab](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415)
++ [INT2] [Integrating JIRA with GitLab](http://doc.gitlab.com/ee/integration/jira.html)
++ [INT3] [Integrating Jenkins with GitLab](http://doc.gitlab.com/ee/integration/jenkins.html)
++ [INT4] [Integrating Bamboo with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/project_services/bamboo.md)
++ [INT5] [Documentation on Integrating Slack with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/slack.md)
+
+---
+
++ [GLF1] [GitLab Flow](https://www.youtube.com/watch?v=UGotqAUACZA)
+
+---
+
++ [GLE1] [Configuring an external MySQL database](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#using-a-mysql-database-management-server-enterprise-edition-only)
++ [GLE2] [Managing Permissions within EE](https://www.youtube.com/watch?v=DjUoIrkiNuM)
++ [GLE3] [Upcoming in EE and Big files in Git (Git LFS, Annex)](https://gitlab.com/gitlab-org/University/blob/master/classes/upcoming_in_ee.md)
+
+---
+
++ [GCI1] [GitLab CI product page](https://about.gitlab.com/gitlab-ci/)
++ [GCI2] [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
+
+---
+
++ [COM1] [GitLab compared to other tools](https://about.gitlab.com/comparison/)
++ [COM2] [Compare GitLab versions](https://about.gitlab.com/features/#compare)
++ [COM3] [Innersourcing article](https://about.gitlab.com/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/)
+
+---
+
++ [ECO1] [Ecosystem Overview](https://www.youtube.com/watch?v=sXlhgPK1NTY&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=6)
++ [ECO2] [Positioning FAQ](https://about.gitlab.com/handbook/positioning-faq)
++ [ECO3] [GitLab Ecosystem slides](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit)
++ [ECO4] [Customer Use-Cases](https://about.gitlab.com/handbook/use-cases/)
+
+---
+
++ [SPT1] [Support Path](support/README.md)
++ [SPT2] [End User Training Material](https://gitlab.com/gitlab-org/University/blob/master/training/user_training.md)
++ [SPT3] [Materials for Training Sessions](https://gitlab.com/gitlab-org/University/tree/master/training/topics)
+
+---
+
++ [SLS1] [Sales Path (redirect to sales handbook)](https://about.gitlab.com/handbook/sales-onboarding/)
++ [SLS2] [GitLab Direction](https://about.gitlab.com/direction/)
+
+---
+
++ [TRA1] [End User Training](training/end-user/README.md)
+
+---
+
+### External Resources
+
++ [DOC] GitLab Documentation
+ + [Set up and use GitLab Pages](http://doc.gitlab.com/ee/pages/README.html)
+ + [Markdown Reference](http://doc.gitlab.com/ce/markdown/markdown.html)
+
++ [GLW] GitLab Workshop (@ Platzi)
+ + [GitLab Workshop Part 1: Basics of Git and GitLab](https://courses.platzi.com/classes/git-gitlab/)
+ + [Create a GitLab Account](https://courses.platzi.com/classes/git-gitlab/concepto/first-steps/create-an-account-on-gitlab/material/)
+
++ [GLY] GitLab YouTube Videos
+ + [Making GitLab Great for Everyone, our response to the Dear GitHub letter](https://www.youtube.com/watch?v=GGC40y4vMx0)
+ + [Compared to Atlassian (Recorded on 2016-03-03) ](https://youtu.be/Nbzp1t45ERo)
+
++ [GLI] GitLab Team-Only Access
+ + [GitLab architecture for noobs](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/architecture.md)
+ + [Client Assessment of GitLab versus GitHub](https://docs.google.com/a/gitlab.com/spreadsheets/d/18cRF9Y5I6I7Z_ab6qhBEW55YpEMyU4PitZYjomVHM-M/edit?usp=sharing)
+
++ [KNT] Slides & Keynotes by GitLabbers & other individuals
+ + [Why Git and GitLab slide deck](https://docs.google.com/a/gitlab.com/presentation/d/1RcZhFmn5VPvoFu6UMxhMOy7lAsToeBZRjLRn0LIdaNc/)
+ + [Git Workshop](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/)
+
++ Others (not created by GitLab)
+ + [Dev Ops terminology](https://xebialabs.com/glossary/)
+ + [Continuous Delivery vs Continuous Deployment](https://www.youtube.com/watch?v=igwFj8PPSnw)
+ + [Periodic Table of DevOps Tools](https://xebialabs.com/periodic-table-of-devops-tools/)
+ + [State of Dev Ops 2015 Report by Puppet Labs](https://puppetlabs.com/sites/default/files/2015-state-of-devops-report.pdf) Insightful Chapters to understand the Impact of Continuous Delivery on Performance (Chapter 4), the Application Architecture (Chapter 5) and How IT Managers can help their teams win (Chapter 6).
+ + [2011 WSJ article by Mark Andreeson - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
+ + [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/)
+ + [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/)
+ + [Customer review of GitLab with talking points on why they prefer GitLab](https://www.enovate.co.uk/web-design-blog/2015/11/25/gitlab-review/)
+ + [3rd party tool comparison](http://technologyconversations.com/2015/10/16/github-vs-gitlabs-vs-bitbucket-server-formerly-stash/)
+ + [Amazon's transition to Continuous Delivery](https://www.youtube.com/watch?v=esEFaY0FDKc)
+ + [Article on Continuous Integration from ThoughtWorks](https://www.thoughtworks.com/continuous-integration)
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
new file mode 100644
index 00000000000..a86ff165f2e
--- /dev/null
+++ b/doc/university/glossary/README.md
@@ -0,0 +1,482 @@
+
+## What is the Glossary
+
+This contains a simplified list and definitions of some of the terms that you will encounter in your day to day activities when working with GitLab.
+Please add any terms that you discover that you think would be useful for others.
+
+### 2FA
+
+User authentication by combination of 2 different steps during login. This allows for more security.
+
+### Access Levels
+
+Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions.
+See, [GitLab's Permission Guidelines](http://doc.gitlab.com/ce/permissions/permissions.html)
+
+### Active Directory (AD)
+
+A Microsoft based directory service for windows domain networks. It uses LDAP technology under the hood
+
+### Agile
+
+Building and delivering software in phases/parts rather than trying to build everything at once then delivering to the user/client. The later is known as a WaterFall model
+
+### Application Lifecycle Management (ALM)
+
+Entire product lifecycle management process for an application. From requirements management, development and testing until deployment.
+
+### Artifactory
+
+Version control for binaries.
+
+### Artifacts
+
+objects (usually binary and large) created by a build process
+
+### Atlassian
+
+A company that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo. See [Atlassian] (https://www.atlassian.com)
+
+### Audit Log
+
+*** Needs definition here
+
+### Auto Defined User Group
+
+User groups are a way of centralizing control over important management tasks, particularly access control and password policies.
+A simple example of such groups are the users and the admins groups.
+In most of the cases these groups are auto defined in terms of access, rules of usage, conditions to be part of, etc...
+
+### Bamboo
+
+Atlassian's CI tool similar to GitLab CI and Jenkins
+
+### Basic Subscription
+
+Entry level subscription for GitLab EE currently available in packs of 10 see [Basic subscription](https://about.gitlab.com/pricing/)
+
+### Bitbucket
+
+Atlassian's web hosting service for Git and Mercurial Projects i.e. GitLab.com competitor
+
+### Branch
+
+A branch is a parallel version of a repository. Allows you to work on the repository without you affecting the "master" branch. Allows you to make changes without affecting the current "live" version. When you have made all your changes to your branch you can then merge to the master and to make the changes fo "live".
+
+### Branded Login
+
+Having your own logo on your GitLab instance login page instead of the GitLab logo.
+
+### CEPH
+
+is a distributed object store and file system designed to provide excellent performance, reliability and scalability.
+
+### Clone
+
+A copy of a repository stored on your machine that allows you to use your own editor without being online, but still tracks the changes made remotely.
+
+### Code Review
+
+Examination of a progam's code. The main aim is to maintain high standards quality of code that is being shipped.
+
+### Code Snippet
+
+A small amount of code. Usually for the purpose of showing other developers how
+to do something specific or reproduce a problem.
+
+### Collaborator
+
+Person with read and write access to a repository who has been invited by repository owner.
+
+### Commit
+
+Is a change (revision) to a file, and also creates an ID that allows you to see revision history and who made the changes.
+
+### Community
+
+Everyone who is using GitLab
+
+### Confluence
+
+Atlassian's product for collaboration of documents and projects.
+
+### Continuous Deivery
+
+Continuous delivery is a series of practices designed to ensure that code can be rapidly and safely deployed to production by delivering every change to a production-like environment and ensuring business applications and services function as expected through rigorous automated testing.
+
+### Continuous Deployment
+
+Continuous deployment is the next step of continuous delivery: Every change that passes the automated tests is deployed to production automatically.
+
+### Continuous Integration
+
+A process that involves adding new code commits to source code with the combined code being run on an automated test to ensure that the changes do not break the software.
+
+### Contributor
+
+Term used to a person contributing to an Open Source Project.
+
+### Data Centre
+
+Atlassian product for High Availability.
+
+### Deploy Keys
+
+An SSH key stored on the your server that grants access to a single GitLab repository. This is used by a GitLab runner to clone a project's code so that tests can be run against the checked out code.
+
+### Developer
+
+For us (GitLab) this means a software developer, i.e. someone who makes software. It is also one of the levels of access in our multi level approval system.
+
+### Diff
+
+Is the difference between two commits, or saved changes. This will also be shown visually after the changes.
+
+### Docker
+
+Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server.
+This guarantees that it will always run the same, regardless of the environment it is running in.
+
+### Fork
+
+Your own copy of a repository that allows you to make changes to the repository without affecting the original.
+
+### Gerrit
+
+A code review tool built on top of Git.
+
+### Git Hooks
+
+Are scripts you can use to trigger actions at certain points.
+
+### GitHost.io
+
+Is a single-tenant solution that provides GitLab CE or EE as a managed service. GitLab Inc. is responsible for
+installing, updating, hosting, and backing up customers own private and secure GitLab instance.
+
+### GitHub
+
+A web-based Git repository hosting service with an enterprise offering. Its main features are: issue tracking, pull request with code review, abundancy of integrations and wiki. As of April 2016, the service has over 14 million users. It offers free public repos, private repos and enterprise services are paid.
+
+### GitLab CE
+
+Our free on Premise solution with >100,000 users
+
+### GitLab CI
+
+Our own Continuos Integration feature that is shipped with each instance
+
+### GitLab EE
+
+Our premium on premise solution that currently has Basic, Standard and Plus subscription packages with additional features and support.
+
+### GitLab.com
+
+Our free SaaS for public and private repositories.
+
+### Gitolite
+
+Is basically an access layer that sits on top of Git. Users are granted access to repos via a simple config file and you as an admin only needs the users public SSH key and a username from the user.
+
+### Gitorious
+
+A web based hosting service for projects using Git. It was acquired by GitLab and we discontinued the service. [Gitorious Acquisition Blog Post](https://about.gitlab.com/2015/03/03/gitlab-acquires-gitorious/)
+
+### HADR
+
+Sometimes written HA/DR. High Availability for Disaster Recovery. Usually refers to a strategy having a failover server in place in case the main server fails.
+
+### Hip Chat
+
+Atlassian's real time chat application for teams. Competitor to Slack, RocketChat and MatterMost.
+
+### High Availability
+
+Refers to a system or component that is continuously operational for a desirably long length of time. Availability can be measured relative to "100% operational" or "never failing."
+
+### Issue Tracker
+
+A tool used to manage, organize, and maintain a list of issues, making it easier for an organization to manage.
+
+### Jenkins
+
+An Open Source CI tool written using the Java programming language. Does the same job as GitLab CI, Bamboo, Travis CI. It is extremely popular. see [Jenkins](https://jenkins-ci.org/)
+
+### Jira
+
+Atlassian's project management software. i.e. a complex issue tracker. See[Jira](https://www.atlassian.com/software/jira)
+
+### Kerberos
+
+A network authentication protocol that uses secret-key cryptography for security.
+
+### Kubernetes
+
+An open source container cluster manager originally designed by Google. It's basically a platform for automating deployment, scaling, and operations of application containers over clusters of hosts.
+
+### Labels
+
+An identifier to describe a group of one or more specific file revisions
+
+### LDAP
+
+Lightweight Directory Access Protocol - basically its a directory (electronic address book) with user information e.g. name, phone_number etc
+
+### LDAP User Authentication
+
+Allowing GitLab to sign in people from an LDAP server i.e. Allow people whose names are on the electronic user directory server) to be able to use their LDAP accounts to login.
+
+### LDAP Group Sync
+
+Allows you to synchronize the members of a GitLab group with one or more LDAP groups.
+
+### Git LFS
+
+Git Large File Storage. A way to enable git to handle large binary files by using reference pointers within small text files to point to the large files.
+
+### Linux
+
+An operating system like Windows or OS X. It is mostly used by software developers and on servers.
+
+### Markdown
+
+Is a lightweight markup language with plain text formatting syntax designed so that it can be converted to HTML and many other formats using a tool by the same name.
+Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor.
+
+### Maria DB
+
+A community developed fork/variation of MySQL. MySQL is owned by Oracle.
+
+### Master
+
+Name of the default branch in every git repository.
+
+### Mercurial
+
+A free distributed version control system like Git. Think of it as a competitor to Git.
+
+### Merge
+
+Takes changes from one branch, and applies them into another branch.
+
+### Meteor
+
+A hip platform for building javascript apps.[Meteor] (https://www.meteor.com)
+
+### Milestones
+
+Allows you to track the progress on issues, and merge requests, which allows you to get a snapshot of the progress made.
+
+### Mirror Repositories
+
+You can set up a project to automatically have its branches, tags, and commits updated from an upstream repository. This is useful when a repository you're interested in is located on a different server, and you want to be able to browse its content and its activity using the familiar GitLab interface.
+
+### MIT License
+
+A type of software license. It lets people do anything with your code with proper attribution and without warranty. It is the most common license for open source applications written in Ruby on Rails. GitLab CE is issued under this license.
+This means, you can download the code, modify it as you want even build a new commercial product using the underlying code and its not illegal. The only condition is that there is no form of waranty provided by GitLab so whatever happens if you use the code is your own problem.
+
+### Mondo
+
+*** Needs definition here
+
+### Multi LDAP Server
+
+*** Needs definition here
+
+### My SQL
+
+A relational database. Currently only supported if you are using EE. It is owned by Oracle.
+
+### Namespace
+
+In computing, a namespace is a set of symbols that are used to organize objects of various kinds, so that these objects may be referred to by name.
+
+Prominent examples include:
+- file systems are namespaces that assign names to files;
+- programming languages organize their variables and subroutines in namespaces;
+- computer networks and distributed systems assign names to resources, such as computers, printers, websites, (remote) files, etc.
+
+### Nginx
+
+(pronounced "engine x") is a web server. It can act as a reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer and an HTTP cache.
+
+### oAuth
+
+Is an open standard for authorization, commonly used as a way for Internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password.
+
+### Omnibus Packages
+
+Omnibus is a way to package the different services and tools required to run GitLab, so that users can install it without as much work.
+
+### On Premise
+
+On your own server. In GitLab, this refers to the ability to download GitLab EE/GitLab CE and host it on your own server rather than using GitLab.com which is hosted by GitLab Inc's servers.
+
+### Open Source Software
+
+Software for which the original source code is freely available and may be redistributed and modified.
+
+### Owner
+
+This is the most powerful person on a GitLab project. He has the permissions of all the other users plus the additional permission of being able to destroy i.e. delete the project
+
+### PaaS
+
+Typically referred to in regards to application development, it is a model in which a cloud provider delivers hardware and software tools to its users as a service
+
+### Perforce
+
+The company that produces Helix. A commercial, proprietary, centralised VCS well known for it's ability to version files of any size and type. They OEM a re-branded version of GitLab called "GitSwarm" that is tightly integrated with their "GitFusion" product, which in turn represents a portion of a Helix repository (called a depot) as a git repo
+
+### Phabricator
+
+Is a suite of web-based software development collaboration tools, including the Differential code review tool, the Diffusion repository browser, the Herald change monitoring tool, the Maniphest bug tracker and the Phriction wiki. Phabricator integrates with Git, Mercurial, and Subversion.
+
+### Piwik Analytics
+
+An open source analytics software to help you analyze web traffic. It is similar to google analytics only that google analytics is not open source and information is stored by google while in Piwik the information is stored in your own server hence fully private.
+
+### Plus Subscription
+
+GitLab Premium EE subscription that includes training and dedicated Account Management and Service Engineer and complete support package [Plus subscription](https://about.gitlab.com/pricing/)
+
+### PostgreSQL
+
+A relational database. Touted as the most advanced open source database.
+
+### Protected Branches
+
+A feature that protects branches from unauthorized pushes, force pushing or deletion.
+
+### Pull
+
+Git command to synchronize the local repository with the remote repository, by fetching all remote changes and merging them into the local repository.
+
+### Puppet
+
+A popular devops automation tool
+
+### Push
+
+Git command to send commits from the local repository to the remote repository.
+
+### RE Read Only
+
+Permissions to see a file and it's contents, but not change it
+
+### Rebase
+
+Moves a branch from one commit to another. This allows you to re-write your project's history.
+
+### Git Repository
+
+Storage location of all files which are tracked by git.
+
+### Requirements management
+
+*** Needs definition here
+
+### Revision
+
+*** Needs definition here
+
+### Revision Control
+
+Also known as version control or source control, is the management of changes to documents, computer programs, large web sites, and other collections of information. Changes are usually identified by a number or letter code, termed the "revision number", "revision level", or simply "revision".
+
+### RocketChat
+
+An open source chat application for teams. Very similar to Slack only that is is open-source.
+
+### Runners
+
+Actual build machines/containers that run/execute tests you have specified to be run on GitLab CI
+
+### SaaS
+
+Software as a service. Software is hosted centrally and accessed on-demand i.e. when you want to. This refers to GitLab.com in our scenario
+
+### SCM
+
+Software Configuration Management. Often used by people when they mean Version Control
+
+## Scrum
+
+An Agile framework designed to help complete complex (typically) software projects. It's made up of several parts: product requirments backlog, sprint plannnig, sprint (development), sprint review, retrospec (analyzing the sprint). The goal is to end up with potentially shippable products.
+
+### Scrum Board
+
+The board used to track the status and progress of each of the sprint backlog items.
+
+### Slack
+
+Real time messaging app for teams. Used internally by GitLab
+
+### Slave Servers
+
+Also known as secondary servers. They help to spread the load over multiple machines, they also provide backups when the master/primary server crashes.
+
+### Source Code
+
+Program code as typed by a computer programmer. i.e. it has not yet been compiled/translated by the computer to machine language.
+
+### SSH Key
+
+A unique identifier of a computer. It is used to identify computers without the need for a password. e.g. On GitLab I have added the ssh key of all my work machines so that the GitLab instance knows that it can accept code pushes and pulls from this trusted machines whose keys are I have added.
+
+### SSO
+
+Single Sign On. An authentication process that allows you enter one username and password to access multiple applications.
+
+### Standard Subscription
+
+Our mid range EE subscription that includes 24/7 support, support for High Availability [Standard Subscription](https://about.gitlab.com/pricing/)
+
+### Stash
+
+Atlassian's Git On-Premises solution. Think of it as Atlassian's GitLab EE. It is now known as BitBucket Server.
+
+### Subversion
+
+Non-proprietary, centralized version control system.
+
+### Sudo
+
+A program that allows you to perform superuser/administrator actions on Unix Operating Systems e.g. Linux, OS X. It actually stands for 'superuser do'
+
+### SVN
+
+Abbreviation for Subversion.
+
+### Tag
+
+Represents a version of a particular branch at a moment in time.
+
+### Tool Stack
+
+Set of tools used in a process to achieve a common outcome. E.g. set of tools used in Application Lifecycle Management.
+
+### Trac
+
+An Open Source project management and bug tracking web application.
+
+### User
+
+Anyone interacting with the software.
+
+### VCS
+
+Version Control Software
+
+### Waterfall
+
+A model of building software that involves collecting all requirements from the customer, then building and refining all the requirements and finally delivering the COMPLETE software to the customer that meets all the requirements specified by the customer
+
+### Webhooks
+
+A way for for an app to provide other applications with real-time information. e.g. send a message to a slack channel when a commit is pushed
+
+### Wiki
+
+A website/system that allows for collaborative editing of its content by the users. In programming, they usually contain documentation of how to use the software
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
new file mode 100644
index 00000000000..088f1cd7290
--- /dev/null
+++ b/doc/university/high-availability/aws/README.md
@@ -0,0 +1,387 @@
+
+# High Availability on AWS
+
+GitLab on AWS can leverage many of the services that are already
+configurable with High Availability. These services have a lot of
+flexibility and are able to adopt to most companies, best of all is the
+ability to automate both vertical and horizontal scaling.
+
+In this article we'll go through a basic HA setup where we'll start by
+configuring our Virtual Private Cloud and subnets to later integrate
+services such as RDS for our database server and ElastiCache as a Redis
+cluster to finally manage them within an auto scaling group with custom
+scaling policies.
+
+***
+
+## Where to Start
+
+Login to your AWS account through the `My Account` dropdown on
+`https://aws.amazon.com` or through the URI assigned to your team such as
+`https://myteam.signin.aws.amazon.com/console/`. You'll start on the
+Amazon Web Services console from where we can choose all of the services
+we'll be using to configure our cloud infrastructure.
+
+***
+
+## Network
+
+We'll start by creating a VPC for our GitLab cloud infrastructure, then
+we can create subnets to have public and private instances in at least
+two AZs. Public subnets will require a Route Table keep an associated
+Internet Gateway.
+
+### VPC
+
+Start by looking for the VPC option on the web console. Now create a new
+VPC. We can use `10.0.0.0/16` for the CIDR block and leave tenancy as
+default if we don't require dedicated hardware.
+
+![New VPC](img/new_vpc.png)
+
+If you're setting up the Elastic File System service then select the VPC
+and from the Actions dropdown choose Edit DNS Hostnames and select Yes.
+
+### Subnet
+
+Now let's create some subnets in different Availability Zones. Make sure
+that each subnet is associated the the VPC we just created, that it has
+a distinct VPC and lastly that CIDR blocks don't overlap. This will also
+allow us to enable multi AZ for redundancy.
+
+We will create private and public subnets to match load balancers and
+RDS instances as well.
+
+![Subnet Creation](img/subnet.png)
+
+The subnets are listed with their name, AZ and CIDR block:
+
+* gitlab-public-10.0.0.0 - us-west-2a - 10.0.0.0
+* gitlab-private-10.0.1.0 - us-west-2a - 10.0.1.0
+* gitlab-public-10.0.2.0 - us-west-2b - 10.0.2.0
+* gitlab-private-10.0.3.0 - us-west-2b - 10.0.3.0
+
+### Route Table
+
+Up to now all our subnets are private. We need to create a Route Table
+to associate an Internet Gateway. On the same VPC dashboard choose
+Route Tables on the left column and give it a name and associate it to
+our newly created VPC.
+
+![Route Table](img/route_table.png)
+
+
+### Internet Gateway
+
+Now still on the same dashboard head over to Internet Gateways and
+create a new one. After its created pres on the `Attach to VPC` button and
+select our VPC.
+
+![Internet Gateway](img/ig.png)
+
+### Configure Subnets
+
+Go back to the Router Tables screen and select the newly created one,
+press the Routes tab on the bottom section and edit it. We need to add a
+new target which will be our Internet Gateway and have it receive
+traffic from any destination.
+
+![Subnet Config](img/ig-rt.png)
+
+Before leaving this screen select the next tab to the rgiht which is
+Subnet Associations and add our public subnets. If you followed our
+naming convention they should be easy to find.
+
+***
+
+## Database with RDS
+
+For our database server we will use Amazon RDS which offers Multi AZ
+for redundancy. Lets start by creating a subnet group and then we'll
+create the actual RDS instance.
+
+### Subnet Group
+
+From the RDS dashboard select Subnet Groups. Lets select our VPC from
+the VPC ID dropdown and at the bottom we can add our private subnets.
+
+![Subnet Group](img/db-subnet-group.png)
+
+### RDS
+
+Select the RDS service from the Database section and create a new
+PostgreSQL instance. After choosing between a Production or
+Development instance we'll start with the actual configuration. On the
+image bellow we have the settings for this article but note the
+following two options which are of particular interest for HA:
+
+1. Multi-AZ-Deployment is recommended as redundancy. Read more at
+[High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html)
+1. While we chose a General Purpose (SSD) for this article a Provisioned
+IOPS (SSD) is best suited for HA. Read more about it at
+[Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html)
+
+![RDS Instance Specs](img/instance_specs.png)
+
+The rest of the setting on this page request a DB identifier, username
+and a master password. We've chosen to use `gitlab-ha`, `gitlab` and a
+very secure password respectively. Keep these in hand for later.
+
+![Network and Security](img/rds-net-opt.png)
+
+Make sure to choose our gitlab VPC, our subnet group, not have it public,
+and to leave it to create a new security group. The only additional
+change which will be helpful is the database name for which we can use
+`gitlabhq_production`.
+
+***
+
+## ElastiCache
+
+EC is an in-memory hosted caching solution. Redis maintains its own
+persistance and is used for certain types of application.
+
+Let's choose the ElastiCache service in the Database section from our
+AWS console. Now lets create a cache subnet group which will be very
+similar to the RDS subnet group. Make sure to select our VPC and its
+private subnets.
+
+![ElastiCache](img/ec-subnet.png)
+
+Now press the Launch a Cache Cluster and choose Redis for our
+DB engine. You'll be able to configure details such as replication,
+Multi AZ and node types. The second section will allow us to choose our
+subnet and security group and
+
+![Redis Cluster details](img/redis-cluster-det.png)
+
+![Redis Network](img/redis-net.png)
+
+***
+
+## Elastic File System
+
+This new AWS offering allows us to create a file system accessible by

+EC2 instances within a VPC. Choose our VPC and the subnets will be
+
automatically configured assuming we don't need to set explicit IPs.
+The
next section allows us to add tags and choose between General
+Purpose or
Max I/O which is a good option when being accessed by a
+large number of
EC2 instances.
+
+

![Elastic File System](img/elastic-file-system.png)
+
+To actually mount and install the NFS client we'll use the User Data
+section when adding our Launch Configuration.
+
+***
+
+## Initiate AMI
+
+We are going to launch an EC2 instance and bake an image so that we can
+later use it for auto scaling. We'll also take this opportunity to add an
+extension to our RDS through this temporary EC2 instance.
+
+### EC2 Instance
+
+Look for the EC2 option and choose to create an instance. We'll need at
+least a t2.medium type and for this article we'll choose an Ubuntu 14.04
+HVM 64-bit. In the Configure Instance section choose our GitLab VPC and
+a public subnet. I'd choose at least 10GB of storage.
+
+In the security group we'll create a new one considering that we need to
+SSH into the instance and also try it out through http. So let's add the
+http traffic from anywhere and name it something such as
+`gitlab-ec2-security-group`.
+
+While we wait for it to launch we can allocate an Elastic IP and
+associate it with our new EC2 instance.
+
+### RDS and Redis Security Group
+
+After the instance is being created we will navigate to our EC2 security
+groups and add a small change for our EC2 instances to be able to
+connect to RDS. First copy the security group name we just defined,
+namely `gitlab-ec2-security-group`, and edit select the RDS security
+group and edit the inbound rules. Choose the rule type to be PostgreSQL
+and paste the name under source.
+
+![RDS security group](img/rds-sec-group.png)
+
+Similar to the above we'll jump to the `gitlab-ec2-security-group` group
+and add a custom TCP rule for port 6379 accessible within itself.
+
+### Install GitLab
+
+To connect through SSH you will need to have the `pem` file which you
+chose available and with the correct permissions such as `400`.
+
+After accessing your server don't forget to update and upgrade your
+packages.
+
+ sudo apt-get update && sudo apt-get upgrade -y
+
+Then follow installation instructions from
+[GitLab](https://about.gitlab.com/downloads-ee/#ubuntu1404), but before
+running reconfigure we need to make sure all our services are tied down
+so just leave the reconfigure command until after we edit our gitlab.rb
+file.
+
+
+### Extension for PostgreSQL
+
+Connect to your new RDS instance to verify access and to install
+a required extension. We can find the host or endpoint by selecting the
+instance and we just created and after the details drop down we'll find
+it labeled as 'Endpoint'; do remember not to include the colon and port
+number.
+
+ sudo /opt/gitlab/embedded/bin/psql -U gitlab -h <rds-endpoint> -d gitlabhq_production
+ psql (9.4.7)
+ Type "help" for help.
+
+ gitlab=# CREATE EXTENSION pg_trgm;
+ gitlab=# \q
+
+### Configure GitLab
+
+While connected to your server edit the `gitlab.rb` file at `/etc/gitlab/gitlab.rb`
+find the `external_url 'http://gitlab.example.com'` option and change it
+to the domain you will be using or the public IP address of the current
+instance to test the configuration.
+
+For a more detailed description about configuring GitLab read [Configuring GitLab for HA](http://docs.gitlab.com/ee/administration/high_availability/gitlab.html)
+
+Now look for the GitLab database settings and uncomment as necessary. In
+our current case we'll specify the adapter, encoding, host, db name,
+username, and password.
+
+ gitlab_rails['db_adapter'] = "postgresql"
+ gitlab_rails['db_encoding'] = "unicode"
+ gitlab_rails['db_database'] = "gitlabhq_production"
+ gitlab_rails['db_username'] = "gitlab"
+ gitlab_rails['db_password'] = "mypassword"
+ gitlab_rails['db_host'] = "<rds-endpoint>"
+
+Next we only need to configure the Redis section by adding the host and
+uncommenting the port.
+
+
+
+The last configuration step is to [change the default file locations ](http://docs.gitlab.com/ee/administration/high_availability/nfs.html)
+to make the EFS integration easier to manage.
+
+ gitlab_rails['redis_host'] = "<redis-endpoint>"
+ gitlab_rails['redis_port'] = 6379
+
+Finally run reconfigure, you might find it useful to run a check and
+a service status to make sure everything has been setup correctly.
+
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-rake gitlab:check
+ sudo gitlab-ctl status
+
+If everything looks good copy the Elastic IP over to your browser and
+test the instance manually.
+
+### AMI
+
+After you finish testing your EC2 instance go back to its dashboard and
+while the instance is selected press on the Actions dropdown to choose
+Image -> Create an Image. Give it a name and description and confirm.
+
+***
+
+## Load Balancer
+
+On the same dashboard look for Load Balancer on the left column and press
+the Create button. Choose a classic Load Balancer, our gitlab VPC, not
+internal and make sure its listening for HTTP and HTTPS on port 80.
+
+Here is a tricky part though, when adding subnets we need to associate
+public subnets instead of the private ones where our instances will
+actually live.
+
+On the secruity group section let's create a new one named
+`gitlab-loadbalancer-sec-group` and allow both HTTP ad HTTPS traffic
+from anywhere.
+
+The Load Balancer Health will allow us to indicate where to ping and what
+makes up a healthy or unhealthy instance.
+
+We won't add the instance on the next session because we'll destroy it
+momentarily as we'll be using the image we where creating. We will keep
+the Enable Cross-Zone and Enable Connection Draining active.
+
+After we finish creating the Load Balancer we can re visit our Security
+Groups to improve access only through the ELB and any other requirement
+you might have.
+
+***
+
+## Auto Scaling Group
+
+Our AMI should be done by now so we can start working on our Auto
+Scaling Group.
+
+This option is also available through the EC2 dashboard on the left
+sidebar. Press on the create button. Select the new image on My AMIs and
+give it a `t2.medium` size. To be able to use Elastic File System we need
+to add a script to mount EFS automatically at launch. We'll do this at
+the Advanced Details section where we have a [User Data](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html)
+text area that allows us to add a lot of custom configurations which
+allows you to add a custom script for when launching an instance. Let's
+add the following script to the User Data section:
+
+
+ #cloud-config
+ package_upgrade: true
+ packages:
+ - nfs-common
+ runcmd:
+ - mkdir -p /gitlab-data
+ - chown ec2-user:ec2-user /gitlab-data
+ - echo "$(curl --silent http://169.254.169.254/latest/meta-data/placement/availability-zone).file-system-id.aws-region.amazonaws.com:/ /gitlab-data nfs defaults,vers=4.1 0 0" >> /etc/fstab
+ - mount -a -t nfs
+ - sudo gitlab-ctl reconfigure
+
+On the security group section we can chosse our existing
+`gitlab-ec2-security-group` group which has already been tested.
+
+After this is launched we are able to start creating our Auto Scaling
+Group. Start by giving it a name and assinging it our VPC and private
+subnets. We also want to always start with two instances and if you
+scroll down to Advanced Details we can choose to receive traffic from ELBs.
+Lets enable that option and select our ELB. We also want to use the ELB's
+health check.
+
+![Auto scaling](img/auto-scaling-det.png)
+
+### Policies
+
+This is the really great part of Auto Scaling, we get to choose when AWS
+launches new instances and when it removes them. For this group we'll
+scale between 2 and 4 instances where one instance will be added if CPU
+utilization is greater than 60% and one instance is removed if it falls
+to less than 45%. Here are the complete policies:
+
+![Policies](img/policies.png)
+
+You'll notice that after we save this AWS starts launching our two
+instances in different AZs and without a public IP which is exactly what
+we where aiming for.
+
+***
+
+## Final Thoughts
+
+After you're done with the policies section have some fun trying to break
+instances. You should be able to see how the Auto Scaling Group and the
+EC2 screen start bringing them up again.
+
+High Availability is a very big area, we went mostly through scaling and
+some redundancy options but it might also imply Geographic replication.
+There is a lot of ground yet to cover so have a read through these other
+resources and feel free to open an issue to request additional material.
+
+ * [GitLab High Availability](http://docs.gitlab.com/ce/administration/high_availability/README.html#sts=High Availability)
+ * [GitLab Geo](http://docs.gitlab.com/ee/gitlab-geo/README.html)
diff --git a/doc/university/high-availability/aws/img/auto-scaling-det.png b/doc/university/high-availability/aws/img/auto-scaling-det.png
new file mode 100644
index 00000000000..e9b65529495
--- /dev/null
+++ b/doc/university/high-availability/aws/img/auto-scaling-det.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/db-subnet-group.png b/doc/university/high-availability/aws/img/db-subnet-group.png
new file mode 100644
index 00000000000..0768aa73c45
--- /dev/null
+++ b/doc/university/high-availability/aws/img/db-subnet-group.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/ec-subnet.png b/doc/university/high-availability/aws/img/ec-subnet.png
new file mode 100644
index 00000000000..f41d78b271d
--- /dev/null
+++ b/doc/university/high-availability/aws/img/ec-subnet.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/elastic-file-system.png b/doc/university/high-availability/aws/img/elastic-file-system.png
new file mode 100644
index 00000000000..7de866d1e89
--- /dev/null
+++ b/doc/university/high-availability/aws/img/elastic-file-system.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/ig-rt.png b/doc/university/high-availability/aws/img/ig-rt.png
new file mode 100644
index 00000000000..93bb0c2ae02
--- /dev/null
+++ b/doc/university/high-availability/aws/img/ig-rt.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/ig.png b/doc/university/high-availability/aws/img/ig.png
new file mode 100644
index 00000000000..cc50456370f
--- /dev/null
+++ b/doc/university/high-availability/aws/img/ig.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/instance_specs.png b/doc/university/high-availability/aws/img/instance_specs.png
new file mode 100644
index 00000000000..ef31dc41dae
--- /dev/null
+++ b/doc/university/high-availability/aws/img/instance_specs.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/new_vpc.png b/doc/university/high-availability/aws/img/new_vpc.png
new file mode 100644
index 00000000000..4aac6af7c7a
--- /dev/null
+++ b/doc/university/high-availability/aws/img/new_vpc.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/policies.png b/doc/university/high-availability/aws/img/policies.png
new file mode 100644
index 00000000000..8c58117e4fa
--- /dev/null
+++ b/doc/university/high-availability/aws/img/policies.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/rds-net-opt.png b/doc/university/high-availability/aws/img/rds-net-opt.png
new file mode 100644
index 00000000000..bc204de2474
--- /dev/null
+++ b/doc/university/high-availability/aws/img/rds-net-opt.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/rds-sec-group.png b/doc/university/high-availability/aws/img/rds-sec-group.png
new file mode 100644
index 00000000000..8864dc3e463
--- /dev/null
+++ b/doc/university/high-availability/aws/img/rds-sec-group.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/redis-cluster-det.png b/doc/university/high-availability/aws/img/redis-cluster-det.png
new file mode 100644
index 00000000000..9e9a81283c5
--- /dev/null
+++ b/doc/university/high-availability/aws/img/redis-cluster-det.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/redis-net.png b/doc/university/high-availability/aws/img/redis-net.png
new file mode 100644
index 00000000000..037bd6d6897
--- /dev/null
+++ b/doc/university/high-availability/aws/img/redis-net.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/route_table.png b/doc/university/high-availability/aws/img/route_table.png
new file mode 100644
index 00000000000..1dea322474d
--- /dev/null
+++ b/doc/university/high-availability/aws/img/route_table.png
Binary files differ
diff --git a/doc/university/high-availability/aws/img/subnet.png b/doc/university/high-availability/aws/img/subnet.png
new file mode 100644
index 00000000000..dbc71201992
--- /dev/null
+++ b/doc/university/high-availability/aws/img/subnet.png
Binary files differ
diff --git a/doc/university/process/README.md b/doc/university/process/README.md
new file mode 100644
index 00000000000..7ff53c2cc3f
--- /dev/null
+++ b/doc/university/process/README.md
@@ -0,0 +1,30 @@
+---
+title: University | Process
+---
+
+## Suggesting improvements
+
+If you would like to teach a class or participate or help in any way please
+submit a merge request and assign it to [Job](https://gitlab.com/u/JobV).
+
+If you have suggestions for additional courses you would like to see,
+please submit a merge request to add an upcoming class, assign to
+[Chad](https://gitlab.com/u/chadmalchow) and /cc [Job](https://gitlab.com/u/JobV).
+
+## Adding classes
+
+1. All training materials of any kind should be added to [GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/)
+ to ensure they are available to a broad audience (don't use any other repo or
+ storage for training materials).
+1. Don't make materials that are needlessly specific to one group of people, try
+ to keep the wording broad and inclusive (don't make things for only GitLab Inc.
+ people, only interns, only customers, etc.).
+1. To allow people to contribute all content should be in git.
+1. The content can go in a subdirectory under `/doc/university/`.
+1. To make, view or modify the slides of the classes use [Deckset](http://www.decksetapp.com/)
+ or [RevealJS](http://lab.hakim.se/reveal-js/). Do not use Powerpoint or Google
+ Slides since this prevents everyone from contributing.
+1. Please upload any video recordings to our Youtube channel. We prefer them to
+ be public, if needed they can be unlisted but if so they should be linked from
+ this page.
+1. Please create a merge request and assign to [SeanPackham](https://gitlab.com/u/SeanPackham).
diff --git a/doc/university/support/README.md b/doc/university/support/README.md
new file mode 100644
index 00000000000..da991e56370
--- /dev/null
+++ b/doc/university/support/README.md
@@ -0,0 +1,188 @@
+
+## Support Boot Camp
+
+**Goal:** Prepare new Service Engineers at GitLab
+
+For each stage there are learning goals and content to support the learning of the engineer.
+The goal of this boot camp is to have every Service Engineer prepared to help our customers
+with whatever needs they might have and to also assist our awesome community with their
+questions.
+
+Always start with the [University Overview](../README.md) and then work
+your way here for more advanced and specific training. Once you feel comfortable
+with the topics of the current stage, move to the next.
+
+### Stage 1
+
+Follow the topics on the [University Overview](../README.md), concentrate on it
+during your first Stage, but also:
+
+- Perform the [first steps](https://about.gitlab.com/handbook/support/onboarding/#first-steps) of
+ the on-boarding process for new Service Engineers
+
+#### Goals
+
+Aim to have a good overview of the Product and main features, Git and the Company
+
+### Stage 2
+
+Continue to look over remaining portions of the [University Overview](../README.md) and continue on to these topics:
+
+#### Set up your development machine
+
+Get your development machine ready to familiarize yourself with the codebase, the components, and to be prepared to reproduce issues that our users encounter
+
+- Install the [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit)
+ - [Setup OpenLDAP as part of this](https://gitlab.com/gitlab-org/gitlab-development-kit#openldap)
+
+#### Become comfortable with the Installation processes that we support
+
+It's important to understand how to install GitLab in the same way that our users do. Try installing different versions and upgrading and downgrading between them. Installation from source will give you a greater understanding of the components that we employ and how everything fits together.
+
+Sometimes we need to upgrade customers from old versions of GitLab to latest, so it's good to get some experience of doing that now.
+
+- [Installation Methods](https://about.gitlab.com/installation/):
+ - [Omnibus](https://gitlab.com/gitlab-org/omnibus-gitlab/)
+ - [Docker](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/docker)
+ - [Source](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md)
+- Get yourself a Digital Ocean droplet, where you can install and maintain your own instance of GitLab
+ - Ask in #infrastructure about this
+ - Populate with some test data
+ - Keep this up-to-date as patch and version releases become available, just like our customers would
+- Try out the following installation path
+ - [Install GitLab 4.2 from source](https://gitlab.com/gitlab-org/gitlab-ce/blob/d67117b5a185cfb15a1d7e749588ff981ffbf779/doc/install/installation.md)
+ - External MySQL database
+ - External NGINX
+ - Create some test data
+ - Populated Repos
+ - Users
+ - Groups
+ - Projects
+ - [Backup using our Backup rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
+ - [Upgrade to 5.0 source using our Upgrade documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/4.2-to-5.0.md)
+ - [Upgrade to 5.1 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.0-to-5.1.md)
+ - [Upgrade to 6.0 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.1-to-6.0.md)
+ - [Upgrade to 7.14 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/6.x-or-7.x-to-7.14.md)
+ - [Backup using our Backup rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
+ - [Perform the MySQL to PostgreSQL migration to convert your backup](http://docs.gitlab.com/ce/update/mysql_to_postgresql.html#converting-a-gitlab-backup-file-from-mysql-to-postgres)
+ - [Upgrade to Omnibus 7.14](http://doc.gitlab.com/omnibus/update/README.html#upgrading-from-a-non-omnibus-installation-to-an-omnibus-installation)
+ - [Restore backup using our Restore rake task](http://docs.gitlab.com/ce/raketasks/backup_restore.html#restore-a-previously-created-backup)
+ - [Upgrade to latest EE](https://about.gitlab.com/downloads-ee)
+ - (GitLab inc. only) Acquire and apply a license for the Enterprise Edition product, ask in #support
+- Perform a downgrade from [EE to CE](http://doc.gitlab.com/ee/downgrade_ee_to_ce/README.html)
+
+#### Start to learn about some of the integrations that we support
+
+Our integrations add great value to GitLab. User questions often relate to integrating GitLab with existing external services and the configuration involved
+
+- Learn about our Integrations (specially, not only):
+ - [LDAP](http://doc.gitlab.com/ee/integration/ldap.html)
+ - [JIRA](http://doc.gitlab.com/ee/project_services/jira.html)
+ - [Jenkins](http://doc.gitlab.com/ee/integration/jenkins.html)
+ - [SAML](http://doc.gitlab.com/ce/integration/saml.html)
+
+#### Goals
+
+- Aim to be comfortable with installation of the GitLab product and configuration of some of the major integrations
+- Aim to have an installation available for reproducing customer reports
+
+### Stage 3
+
+#### Understand the gathering of diagnostics for GitLab instances
+
+- Learn about the GitLab checks that are available
+ - [Environment Information and maintenance checks](http://docs.gitlab.com/ce/raketasks/maintenance.html)
+ - [GitLab check](http://docs.gitlab.com/ce/raketasks/check.html)
+ - Omnibus commands
+ - [Status](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#get-service-status)
+ - [Starting and stopping services](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#starting-and-stopping)
+ - [Starting a rails console](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#invoking-rake-tasks)
+
+#### Learn about the Support process
+
+Zendesk is our Support Centre and our main communication line with our Customers. We communicate with customers through several other channels too
+
+- Familiarize yourself with ZenDesk
+ - [UI Overview](https://support.zendesk.com/hc/en-us/articles/203661806-Introduction-to-the-Zendesk-agent-interface)
+ - [Updating Tickets](https://support.zendesk.com/hc/en-us/articles/212530318-Updating-and-solving-tickets)
+ - [Working w/ Tickets](https://support.zendesk.com/hc/en-us/articles/203690856-Working-with-tickets) *Read: avoiding agent collision.*
+- Dive into our ZenDesk support process by reading how to [handle tickets](https://about.gitlab.com/handbook/support/onboarding/#handling-tickets)
+- Start getting real world experience by handling real tickets, all the while gaining further experience with the Product.
+ - First, learn about our [Support Channels](https://about.gitlab.com/handbook/support/#support-channels)
+ - Ask other Service Engineers for help, when necessary, and to review your responses
+ - Start with [StackOverflow](https://about.gitlab.com/handbook/support/#stack-overflowa-namestack-overflowa) and the [GitLab forum](https://about.gitlab.com/handbook/support/#foruma-namegitlab-foruma)
+ - Here you will find a large variety of queries mainly from our Users who are self hosting GitLab CE
+ - Understand the questions that are asked and dig in to try to find a solution
+ - [Proceed on to the GitLab.com Support Forum](https://about.gitlab.com/handbook/support/#gitlabcom-support-trackera-namesupp-foruma)
+ - Here you will find queries regarding our own GitLab.com
+ - Helping Users here will give you an understanding of our Admin interface and other tools
+ - [Proceed on to the Twitter tickets in Zendesk](https://about.gitlab.com/handbook/support/#twitter)
+ - Here you will gain a great insight into our userbase
+ - Learn from any complaints and problems and feed them back to the team
+ - Tweets can range from help needed with GitLab installations, the API and just general queries
+ - [Proceed on to Regular email Support tickets](https://about.gitlab.com/handbook/support/#regular-zendesk-tickets-a-nameregulara)
+ - Here you will find tickets from our GitLab EE Customers and GitLab CE Users
+ - Tickets here are extremely varied and often very technical
+ - You should be prepared for these tickets, given the knowledge gained from previous tiers and your training
+- Check out your colleagues' responses
+ - Hop on to the #support-live-feed channel in Slack and see the tickets as they come in and are updated
+ - Read through old tickets that your colleagues have worked on
+- Start arranging to pair on calls with other Service Engineers. Aim to cover a few of each type of call
+ - [Learn about Cisco WebEx](https://about.gitlab.com/handbook/support/onboarding/#webexa-namewebexa)
+ - Training calls
+ - Information gathering calls
+ - It's good to find out how new and prospective customers are going to be using the product and how they will set up their infrastructure
+ - Diagnosis calls
+ - When email isn't enough we may need to hop on a call and do some debugging along side the customer
+ - These paired calls are a great learning experience
+ - Upgrade calls
+ - Emergency calls
+
+#### Learn about the Escalation process for tickets
+
+Some tickets need specific knowledge or a deep understanding of a particular component and will need to be escalated to a Senior Service Engineer or Developer
+
+- Read about [Escalation](https://about.gitlab.com/handbook/support/onboarding/#create-issuesa-namecreate-issuea)
+- Find the macros in Zendesk for ticket escalations
+- Take a look at the [GitLab.com Team page](https://about.gitlab.com/team/) to find the resident experts in their fields
+
+#### Learn about raising issues and fielding feature proposals
+
+- Understand what's in the pipeline and proposed features at GitLab: [Direction Page](https://about.gitlab.com/direction/)
+- Practice searching issues and filtering using [labels](https://gitlab.com/gitlab-org/gitlab-ce/labels) to find existing feature proposals and bugs
+- If raising a new issue always provide a relevant label and a link to the relevant ticket in Zendesk
+- Add [customer labels](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=customer) for those issues relevant to our subscribers
+- Take a look at the [existing issue templates](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker) to see what is expected
+- Raise issues for bugs in a manner that would make the issue easily reproducible. A Developer or a contributor may work on your issue
+
+#### Goals
+
+- Aim to have a good understanding of the problems that customers are facing
+- Aim to have gained experience in scheduling and participating in calls with customers
+- Aim to have a good understanding of ticket flow through Zendesk and how to interat with our various channels
+
+### Stage 4
+
+#### Advanced GitLab topics
+
+Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
+
+- Set up and try [Git Annex](http://doc.gitlab.com/ee/workflow/git_annex.html)
+- Set up and try [Git LFS](http://doc.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
+- Get to know the [GitLab API](http://doc.gitlab.com/ee/api/README.html), its capabilities and shortcomings
+- Learn how to [migrate from SVN to Git](http://doc.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
+- Set up [GitLab CI](http://doc.gitlab.com/ee/ci/quick_start/README.html)
+- Create your first [GitLab Page](http://doc.gitlab.com/ee/pages/administration.html)
+- Get to know the GitLab Codebase by reading through the source code:
+ - Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
+ and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
+- Ask as many questions as you can think of on the `#support` chat channel
+
+#### Get initiated for on-call duty
+
+- Read over the [public run-books to understand common tasks](https://gitlab.com/gitlab-com/runbooks)
+- Create an issue on the internal Organization tracker to schedule time with the DevOps / Production team, so that you learn how to handle GitLab.com going down. Once you are trained for this, you are ready to be added to the on-call rotation.
+
+#### Goals
+
+- Aim to become a fully-fledged Service Engineer!
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
new file mode 100644
index 00000000000..03c62a81b10
--- /dev/null
+++ b/doc/university/training/end-user/README.md
@@ -0,0 +1,420 @@
+
+# Training
+
+This training material is the markdown used to generate training slides
+which can be found at [End User Slides](https://gitlab-org.gitlab.io/end-user-training-slides/#/)
+through it's [RevealJS](https://gitlab.com/gitlab-org/end-user-training-slides)
+project.
+
+---
+
+## Git Intro
+
+---
+
+### What is a Version Control System (VCS)
+
+- Records changes to a file
+- Maintains history of changes
+- Disaster Recovery
+- Types of VCS: Local, Centralized and Distributed
+
+---
+
+### Short Story of Git
+
+- 1991-2002: The Linux kernel was being maintaned by sharing archived files
+ and patches.
+- 2002: The Linux kernel project began using a DVCS called BitKeeper
+- 2005: BitKeeper revoked the free-of-charge status and Git was created
+
+---
+
+### What is Git
+
+- Distributed Version Control System
+- Great branching model that adapts well to most workflows
+- Fast and reliable
+- Keeps a complete history
+- Disaster recovery friendly
+- Open Source
+
+---
+
+### Getting Help
+
+- Use the tools at your disposal when you get stuck.
+ - Use `git help <command>` command
+ - Use Google (i.e. StackOverflow, Google groups)
+ - Read documentation at https://git-scm.com
+
+---
+
+## Git Setup
+Workshop Time!
+
+---
+
+### Setup
+
+- Windows: Install 'Git for Windows'
+ - https://git-for-windows.github.io
+- Mac: Type `git` in the Terminal application.
+ - If it's not installed, it will prompt you to install it.
+- Linux
+ - Debian: `sudo apt-get install git-all`
+ - Red Hat `sudo yum install git-all`
+
+---
+
+### Configure
+
+- One-time configuration of the Git client:
+
+```bash
+git config --global user.name "Your Name"
+git config --global user.email you@example.com
+```
+
+- If you don't use the global flag you can setup a different author for
+ each project
+- Check settings with:
+
+```bash
+git config --global --list
+```
+- You might want or be required to use an SSH key.
+ - Instructions: [SSH](http://doc.gitlab.com/ce/ssh/README.html)
+
+---
+
+### Workspace
+
+- Choose a directory on you machine easy to access
+- Create a workspace or development directory
+- This is where we'll be working and adding content
+
+---
+
+```bash
+mkdir ~/development
+cd ~/development
+
+-or-
+
+mkdir ~/workspace
+cd ~/workspace
+```
+
+---
+
+## Git Basics
+
+---
+
+### Git Workflow
+
+- Untracked files
+ - New files that Git has not been told to track previously.
+- Working area (Workspace)
+ - Files that have been modified but are not committed.
+- Staging area (Index)
+ - Modified files that have been marked to go in the next commit.
+- Upstream
+ - Hosted repository on a shared server
+
+---
+
+### GitLab
+
+- GitLab is an application to code, test and deploy.
+- Provides repository management with access controls, code reviews,
+ issue tracking, Merge Requests, and other features.
+- The hosted version of GitLab is gitlab.com
+
+---
+
+### New Project
+
+- Sign in into your gitlab.com account
+- Create a project
+- Choose to import from 'Any Repo by URL' and use https://gitlab.com/gitlab-org/training-examples.git
+- On your machine clone the `training-examples` project
+
+---
+
+### Git and GitLab basics
+
+1. Edit `edit_this_file.rb` in `training-examples`
+2. See it listed as a changed file (working area)
+3. View the differences
+4. Stage the file
+5. Commit
+6. Push the commit to the remote
+7. View the git log
+
+---
+
+```shell
+# Edit `edit_this_file.rb`
+git status
+git diff
+git add <file>
+git commit -m 'My change'
+git push origin master
+git log
+```
+
+---
+
+### Feature Branching
+
+1. Create a new feature branch called `squash_some_bugs`
+2. Edit `bugs.rb` and remove all the bugs.
+3. Commit
+4. Push
+
+---
+
+```shell
+git checkout -b squash_some_bugs
+# Edit `bugs.rb`
+git status
+git add bugs.rb
+git commit -m 'Fix some buggy code'
+git push origin squash_some_bugs
+```
+
+---
+
+## Merge Request
+
+---
+
+### Merge requests
+
+- When you want feedback create a merge request
+- Target is the ‘default’ branch (usually master)
+- Assign or mention the person you would like to review
+- Add `WIP` to the title if it's a work in progress
+- When accepting, always delete the branch
+- Anyone can comment, not just the assignee
+- Push corrections to the same branch
+
+
+---
+
+### Merge request example
+
+- Create your first merge request
+ - Use the blue button in the activity feed
+ - View the diff (changes) and leave a comment
+ - Push a new commit to the same branch
+ - Review the changes again and notice the update
+
+---
+
+### Feedback and Collaboration
+
+- Merge requests are a time for feedback and collaboration
+- Giving feedback is hard
+- Be as kind as possible
+- Receiving feedback is hard
+- Be as receptive as possible
+- Feedback is about the best code, not the person. You are not your code
+- Feedback and Collaboration
+
+---
+
+### Feedback and Collaboration
+
+- Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:[Thoughtbot](https://github.com/thoughtbot/guides/tree/master/code-review)
+- See GitLab merge requests for examples: [Merge Requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests)
+
+---
+
+## Merge Conflicts
+
+---
+
+### Merge Conflicts
+* Happen often
+* Learning to fix conflicts is hard
+* Practice makes perfect
+* Force push after fixing conflicts. Be careful!
+
+---
+
+### Example Plan
+1. Checkout a new branch and edit conflicts.rb. Add 'Line4' and 'Line5'.
+2. Commit and push
+3. Checkout master and edit conflicts.rb. Add 'Line6' and 'Line7' below 'Line3'.
+4. Commit and push to master
+5. Create a merge request and watch it fail
+6. Rebase our new branch with master
+7. Fix conflicts on the conflicts.rb file.
+8. Stage the file and continue rebasing
+9. Force push the changes
+10. Finally continue with the Merge Request
+
+---
+
+### Example 1/2
+
+ git checkout -b conflicts_branch
+
+ # vi conflicts.rb
+ # Add 'Line4' and 'Line5'
+
+ git commit -am "add line4 and line5"
+ git push origin conflicts_branch
+
+ git checkout master
+
+ # vi conflicts.rb
+ # Add 'Line6' and 'Line7'
+ git commit -am "add line6 and line7"
+ git push origin master
+
+---
+
+### Example 2/2
+
+Create a merge request on the GitLab web UI. You'll see a conflict warning.
+
+ git checkout conflicts_branch
+ git fetch
+ git rebase master
+
+ # Fix conflicts by editing the files.
+
+ git add conflicts.rb
+ # No need to commit this file
+
+ git rebase --continue
+
+ # Remember that we have rewritten our commit history so we
+ # need to force push so that our remote branch is restructured
+ git push origin conflicts_branch -f
+
+---
+
+### Notes
+
+* When to use `git merge` and when to use `git rebase`
+* Rebase when updating your branch with master
+* Merge when bringing changes from feature to master
+* Reference: https://www.atlassian.com/git/tutorials/merging-vs-rebasing/
+
+---
+
+## Revert and Unstage
+
+---
+
+### Unstage
+
+To remove files from stage use reset HEAD. Where HEAD is the last commit of the current branch:
+
+ git reset HEAD <file>
+
+This will unstage the file but maintain the modifications. To revert the file back to the state it was in before the changes we can use:
+
+ git checkout -- <file>
+
+To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag:
+
+ git rm '*.txt'
+ git rm -r <dirname>
+
+If we want to remove a file from the repository but keep it on disk, say we forgot to add it to our .gitignore file then use `--cache`:
+
+ git rm <filename> --cache
+
+---
+
+### Undo Commits
+
+Undo last commit putting everything back into the staging area:
+
+ git reset --soft HEAD^
+
+Add files and change message with:
+
+ git commit --amend -m "New Message"
+
+Undo last and remove changes
+
+ git reset --hard HEAD^
+
+Same as last one but for two commits back:
+
+ git reset --hard HEAD^^
+
+Don't reset after pushing
+
+---
+
+### Reset Workflow
+
+1. Edit file again 'edit_this_file.rb'
+2. Check status
+3. Add and commit with wrong message
+4. Check log
+5. Amend commit
+6. Check log
+7. Soft reset
+8. Check log
+9. Pull for updates
+10. Push changes
+
+----
+
+ # Change file edit_this_file.rb
+ git status
+ git commit -am "kjkfjkg"
+ git log
+ git commit --amend -m "New comment added"
+ git log
+ git reset --soft HEAD^
+ git log
+ git pull origin master
+ git push origin master
+
+---
+
+### Note
+
+git revert vs git reset
+Reset removes the commit while revert removes the changes but leaves the commit
+Revert is safer considering we can revert a revert
+
+
+ # Changed file
+ git commit -am "bug introduced"
+ git revert HEAD
+ # New commit created reverting changes
+ # Now we want to re apply the reverted commit
+ git log # take hash from the revert commit
+ git revert <rev commit hash>
+ # reverted commit is back (new commit created again)
+
+---
+
+## Questions
+
+---
+
+## Instructor Notes
+
+---
+
+### Version Control
+ - Local VCS was used with a filesystem or a simple db.
+ - Centralized VCS such as Subversion includes collaboration but
+ still is prone to data loss as the main server is the single point of
+ failure.
+ - Distributed VCS enables the team to have a complete copy of the project
+ and work with little dependency to the main server. In case of a main
+ server failing the project can be recovered by any of the latest copies
+ from the team
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index c7fda8a497f..56e5b802a52 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -27,6 +27,7 @@
* [Horizontal Rule](#horizontal-rule)
* [Line Breaks](#line-breaks)
* [Tables](#tables)
+* [Footnotes](#footnotes)
**[Wiki-Specific Markdown](#wiki-specific-markdown)**
@@ -699,6 +700,15 @@ By including colons in the header row, you can align the text within that column
| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
+## Footnotes
+
+You can add footnotes to your text as follows.[^1]
+[^1]: This is my awesome footnote.
+
+```
+You can add footnotes to your text as follows.[^1]
+[^1]: This is my awesome footnote.
+```
## Wiki-specific Markdown
diff --git a/features/project/snippets.feature b/features/project/snippets.feature
index 270557cbde7..3c51ea56585 100644
--- a/features/project/snippets.feature
+++ b/features/project/snippets.feature
@@ -12,7 +12,7 @@ Feature: Project Snippets
And I should not see "Snippet two" in snippets
Scenario: I create new project snippet
- Given I click link "New Snippet"
+ Given I click link "New snippet"
And I submit new snippet "Snippet three"
Then I should see snippet "Snippet three"
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 4ee6784a086..05ab2a7dc73 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -13,6 +13,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
+ fill_in 'user_organization', with: 'GitLab'
click_button 'Update profile settings'
@user.reload
end
@@ -23,6 +24,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
expect(@user.twitter).to eq 'testtwitter'
expect(@user.website_url).to eq 'testurl'
expect(@user.bio).to eq 'I <3 GitLab'
+ expect(@user.organization).to eq 'GitLab'
expect(find('#user_location').value).to eq 'Ukraine'
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index beb8ecfc799..5e7d539add6 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -21,8 +21,8 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
author: project.users.first)
end
- step 'I click link "New Snippet"' do
- click_link "New Snippet"
+ step 'I click link "New snippet"' do
+ click_link "New snippet"
end
step 'I click link "Snippet one"' do
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index 29a97ccbd75..9d1d9058996 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -55,13 +55,8 @@ module API
put ':id/access_requests/:user_id/approve' do
required_attributes! [:user_id]
source = find_source(source_type, params[:id])
- authorize_admin_source!(source_type, source)
- member = source.requesters.find_by!(user_id: params[:user_id])
- if params[:access_level]
- member.update(access_level: params[:access_level])
- end
- member.accept_request
+ member = ::Members::ApproveAccessRequestService.new(source, current_user, params).execute
status :created
present member.user, with: Entities::Member, member: member
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 74ca4728695..cb47ec8f33f 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -28,6 +28,7 @@ module API
helpers ::SentryHelper
helpers ::API::Helpers
+ # Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::AwardEmoji
mount ::API::Branches
@@ -48,6 +49,7 @@ module API
mount ::API::Lint
mount ::API::Members
mount ::API::MergeRequests
+ mount ::API::MergeRequestDiffs
mount ::API::Milestones
mount ::API::Namespaces
mount ::API::Notes
@@ -70,6 +72,5 @@ module API
mount ::API::Triggers
mount ::API::Users
mount ::API::Variables
- mount ::API::MergeRequestDiffs
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 29ad9f3565a..c5dc8b22f60 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -15,7 +15,7 @@ module API
class User < UserBasic
expose :created_at
expose :is_admin?, as: :is_admin
- expose :bio, :location, :skype, :linkedin, :twitter, :website_url
+ expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization
end
class Identity < Grape::Entity
@@ -545,6 +545,10 @@ module API
expose :filename, :size
end
+ class PipelineBasic < Grape::Entity
+ expose :id, :sha, :ref, :status
+ end
+
class Build < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
@@ -552,6 +556,7 @@ module API
expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
expose :commit, with: RepoCommit
expose :runner, with: Runner
+ expose :pipeline, with: PipelineBasic
end
class Trigger < Grape::Entity
@@ -562,8 +567,8 @@ module API
expose :key, :value
end
- class Pipeline < Grape::Entity
- expose :id, :status, :ref, :sha, :before_sha, :tag, :yaml_errors
+ class Pipeline < PipelineBasic
+ expose :before_sha, :tag, :yaml_errors
expose :user, with: Entities::UserBasic
expose :created_at, :updated_at, :started_at, :finished_at, :committed_at
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 090d04544da..9a5d1ece070 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -90,7 +90,7 @@ module API
{
username: token_handler.actor_name,
- lfs_token: token_handler.generate,
+ lfs_token: token_handler.token,
repository_http_path: project.http_url_to_repo
}
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index c440305ff0f..18c4cad09ae 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -60,6 +60,7 @@ module API
# linkedin - Linkedin
# twitter - Twitter account
# website_url - Website url
+ # organization - Organization
# projects_limit - Number of projects user can create
# extern_uid - External authentication provider UID
# provider - External provider
@@ -74,7 +75,7 @@ module API
post do
authenticated_as_admin!
required_attributes! [:email, :password, :name, :username]
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external, :organization]
admin = attrs.delete(:admin)
confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i)
user = User.build_user(attrs)
@@ -111,6 +112,7 @@ module API
# linkedin - Linkedin
# twitter - Twitter account
# website_url - Website url
+ # organization - Organization
# projects_limit - Limit projects each user can create
# bio - Bio
# location - Location of the user
@@ -122,7 +124,7 @@ module API
put ":id" do
authenticated_as_admin!
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external, :organization]
user = User.find(params[:id])
not_found!('User') unless user
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 16cd774c81a..affe34394c2 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -64,7 +64,7 @@ module Banzai
end
end
- def project_from_ref_cache(ref)
+ def project_from_ref_cached(ref)
if RequestStore.active?
cache = project_refs_cache
@@ -146,7 +146,7 @@ module Banzai
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text, pattern, link_text: nil)
references_in(text, pattern) do |match, id, project_ref, matches|
- project = project_from_ref_cache(project_ref)
+ project = project_from_ref_cached(project_ref)
if project && object = find_object_cached(project, id)
title = object_link_title(object)
@@ -243,11 +243,27 @@ module Banzai
end
end
- # Returns the projects for the given paths.
- def find_projects_for_paths(paths)
+ def projects_relation_for_paths(paths)
Project.where_paths_in(paths).includes(:namespace)
end
+ # Returns projects for the given paths.
+ def find_projects_for_paths(paths)
+ if RequestStore.active?
+ to_query = paths - project_refs_cache.keys
+
+ unless to_query.empty?
+ projects_relation_for_paths(to_query).each do |project|
+ get_or_set_cache(project_refs_cache, project.path_with_namespace) { project }
+ end
+ end
+
+ project_refs_cache.slice(*paths).values
+ else
+ projects_relation_for_paths(paths)
+ end
+ end
+
def current_project_path
@current_project_path ||= project.path_with_namespace
end
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 4042e9a4c25..54c5f9a71a4 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -66,7 +66,7 @@ module Banzai
end
end
- def find_projects_for_paths(paths)
+ def projects_relation_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 6e13282d5f4..2470362e019 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist
- whitelist = super.dup
+ whitelist = super
customize_whitelist(whitelist)
@@ -42,58 +42,58 @@ module Banzai
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
- whitelist[:transformers] = whitelist[:transformers].dup
-
# ...but then remove links with unsafe protocols
- whitelist[:transformers].push(remove_unsafe_links)
+ whitelist[:transformers].push(self.class.remove_unsafe_links)
# Remove `rel` attribute from `a` elements
- whitelist[:transformers].push(remove_rel)
+ whitelist[:transformers].push(self.class.remove_rel)
# Remove `class` attribute from non-highlight spans
- whitelist[:transformers].push(clean_spans)
+ whitelist[:transformers].push(self.class.clean_spans)
whitelist
end
- def remove_unsafe_links
- lambda do |env|
- node = env[:node]
+ class << self
+ def remove_unsafe_links
+ lambda do |env|
+ node = env[:node]
- return unless node.name == 'a'
- return unless node.has_attribute?('href')
+ return unless node.name == 'a'
+ return unless node.has_attribute?('href')
- begin
- uri = Addressable::URI.parse(node['href'])
- uri.scheme = uri.scheme.strip.downcase if uri.scheme
+ begin
+ uri = Addressable::URI.parse(node['href'])
+ uri.scheme = uri.scheme.strip.downcase if uri.scheme
- node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
- rescue Addressable::URI::InvalidURIError
- node.remove_attribute('href')
+ node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
+ rescue Addressable::URI::InvalidURIError
+ node.remove_attribute('href')
+ end
end
end
- end
- def remove_rel
- lambda do |env|
- if env[:node_name] == 'a'
- env[:node].remove_attribute('rel')
+ def remove_rel
+ lambda do |env|
+ if env[:node_name] == 'a'
+ env[:node].remove_attribute('rel')
+ end
end
end
- end
- def clean_spans
- lambda do |env|
- node = env[:node]
+ def clean_spans
+ lambda do |env|
+ node = env[:node]
- return unless node.name == 'span'
- return unless node.has_attribute?('class')
+ return unless node.name == 'span'
+ return unless node.has_attribute?('class')
- unless has_ancestor?(node, 'pre')
- node.remove_attribute('class')
- end
+ unless node.ancestors.any? { |n| n.name.casecmp('pre').zero? }
+ node.remove_attribute('class')
+ end
- { node_whitelist: [node] }
+ { node_whitelist: [node] }
+ end
end
end
end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index e8e03e4a98f..f5d110e987b 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -79,7 +79,11 @@ module Banzai
def referenced_by(nodes)
ids = unique_attribute_values(nodes, self.class.data_attribute)
- references_relation.where(id: ids)
+ if ids.empty?
+ references_relation.none
+ else
+ references_relation.where(id: ids)
+ end
end
# Returns the ActiveRecord::Relation to use for querying references in the
diff --git a/lib/ci/version_info.rb b/lib/ci/version_info.rb
deleted file mode 100644
index 2a87c91db5e..00000000000
--- a/lib/ci/version_info.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-class VersionInfo
- include Comparable
-
- attr_reader :major, :minor, :patch
-
- def self.parse(str)
- if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/)
- VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i)
- else
- VersionInfo.new
- end
- end
-
- def initialize(major = 0, minor = 0, patch = 0)
- @major = major
- @minor = minor
- @patch = patch
- end
-
- def <=>(other)
- return unless other.is_a? VersionInfo
- return unless valid? && other.valid?
-
- if other.major < @major
- 1
- elsif @major < other.major
- -1
- elsif other.minor < @minor
- 1
- elsif @minor < other.minor
- -1
- elsif other.patch < @patch
- 1
- elsif @patch < other.patch
- -1
- else
- 0
- end
- end
-
- def to_s
- if valid?
- "%d.%d.%d" % [@major, @minor, @patch]
- else
- "Unknown"
- end
- end
-
- def valid?
- @major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
- end
-end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 7c0f2115d43..aca5d0020cf 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -124,7 +124,7 @@ module Gitlab
read_authentication_abilities
end
- Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.value, password)
+ Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.token, password)
end
def build_access_token_check(login, password)
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 12fbb78c53e..ef9160d6437 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -59,10 +59,8 @@ module Gitlab
# When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
active_db_connection = ActiveRecord::Base.connection.active? rescue false
- ENV['USE_DB'] != 'false' &&
active_db_connection &&
- ActiveRecord::Base.connection.table_exists?('application_settings')
-
+ ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError
false
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 084e514492c..e33ac61f5ae 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -52,7 +52,7 @@ module Gitlab
def method_missing(method, *args, &block)
if api.respond_to?(method)
- request { api.send(method, *args, &block) }
+ request(method, *args, &block)
else
super(method, *args, &block)
end
@@ -99,20 +99,19 @@ module Gitlab
rate_limit.resets_in + GITHUB_SAFE_SLEEP_TIME
end
- def request
+ def request(method, *args, &block)
sleep rate_limit_sleep_time if rate_limit_exceed?
- data = yield
+ data = api.send(method, *args, &block)
+ yield data
last_response = api.last_response
while last_response.rels[:next]
sleep rate_limit_sleep_time if rate_limit_exceed?
last_response = last_response.rels[:next].get
- data.concat(last_response.data) if last_response.data.is_a?(Array)
+ yield last_response.data if last_response.data.is_a?(Array)
end
-
- data
end
end
end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index d35ee2a1c65..b8321244473 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -10,6 +10,7 @@ module Gitlab
@repo = project.import_source
@repo_url = project.import_url
@errors = []
+ @labels = {}
if credentials
@client = Client.new(credentials[:user])
@@ -23,6 +24,7 @@ module Gitlab
import_milestones
import_issues
import_pull_requests
+ import_comments
import_wiki
import_releases
handle_errors
@@ -46,66 +48,68 @@ module Gitlab
end
def import_labels
- labels = client.labels(repo, per_page: 100)
-
- labels.each do |raw|
- begin
- LabelFormatter.new(project, raw).create!
- rescue => e
- errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ client.labels(repo, per_page: 100) do |labels|
+ labels.each do |raw|
+ begin
+ label = LabelFormatter.new(project, raw).create!
+ @labels[label.title] = label.id
+ rescue => e
+ errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ end
end
end
end
def import_milestones
- milestones = client.milestones(repo, state: :all, per_page: 100)
-
- milestones.each do |raw|
- begin
- MilestoneFormatter.new(project, raw).create!
- rescue => e
- errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ client.milestones(repo, state: :all, per_page: 100) do |milestones|
+ milestones.each do |raw|
+ begin
+ MilestoneFormatter.new(project, raw).create!
+ rescue => e
+ errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ end
end
end
end
def import_issues
- issues = client.issues(repo, state: :all, sort: :created, direction: :asc, per_page: 100)
-
- issues.each do |raw|
- gh_issue = IssueFormatter.new(project, raw)
-
- if gh_issue.valid?
- begin
- issue = gh_issue.create!
- apply_labels(issue)
- import_comments(issue) if gh_issue.has_comments?
- rescue => e
- errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ client.issues(repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |issues|
+ issues.each do |raw|
+ gh_issue = IssueFormatter.new(project, raw)
+
+ if gh_issue.valid?
+ begin
+ issue = gh_issue.create!
+ apply_labels(issue, raw)
+ rescue => e
+ errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ end
end
end
end
end
def import_pull_requests
- pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc, per_page: 100)
- pull_requests = pull_requests.map { |raw| PullRequestFormatter.new(project, raw) }.select(&:valid?)
-
- pull_requests.each do |pull_request|
- begin
- restore_source_branch(pull_request) unless pull_request.source_branch_exists?
- restore_target_branch(pull_request) unless pull_request.target_branch_exists?
-
- merge_request = pull_request.create!
- apply_labels(merge_request)
- import_comments(merge_request)
- import_comments_on_diff(merge_request)
- rescue => e
- errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message }
- ensure
- clean_up_restored_branches(pull_request)
+ client.pull_requests(repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |pull_requests|
+ pull_requests.each do |raw|
+ pull_request = PullRequestFormatter.new(project, raw)
+ next unless pull_request.valid?
+
+ begin
+ restore_source_branch(pull_request) unless pull_request.source_branch_exists?
+ restore_target_branch(pull_request) unless pull_request.target_branch_exists?
+
+ merge_request = pull_request.create!
+ apply_labels(merge_request, raw)
+ rescue => e
+ errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message }
+ ensure
+ clean_up_restored_branches(pull_request)
+ end
end
end
+
+ project.repository.after_remove_branch
end
def restore_source_branch(pull_request)
@@ -125,37 +129,38 @@ module Gitlab
def clean_up_restored_branches(pull_request)
remove_branch(pull_request.source_branch_name) unless pull_request.source_branch_exists?
remove_branch(pull_request.target_branch_name) unless pull_request.target_branch_exists?
-
- project.repository.after_remove_branch
end
- def apply_labels(issuable)
- issue = client.issue(repo, issuable.iid)
-
- if issue.labels.count > 0
- label_ids = issue.labels
- .map { |attrs| project.labels.find_by(title: attrs.name).try(:id) }
+ def apply_labels(issuable, raw_issuable)
+ if raw_issuable.labels.count > 0
+ label_ids = raw_issuable.labels
+ .map { |attrs| @labels[attrs.name] }
.compact
issuable.update_attribute(:label_ids, label_ids)
end
end
- def import_comments(issuable)
- comments = client.issue_comments(repo, issuable.iid, per_page: 100)
- create_comments(issuable, comments)
- end
+ def import_comments
+ client.issues_comments(repo, per_page: 100) do |comments|
+ create_comments(comments, :issue)
+ end
- def import_comments_on_diff(merge_request)
- comments = client.pull_request_comments(repo, merge_request.iid, per_page: 100)
- create_comments(merge_request, comments)
+ client.pull_requests_comments(repo, per_page: 100) do |comments|
+ create_comments(comments, :pull_request)
+ end
end
- def create_comments(issuable, comments)
+ def create_comments(comments, issuable_type)
ActiveRecord::Base.no_touching do
comments.each do |raw|
begin
- comment = CommentFormatter.new(project, raw)
+ comment = CommentFormatter.new(project, raw)
+ issuable_class = issuable_type == :issue ? Issue : MergeRequest
+ iid = raw.send("#{issuable_type}_url").split('/').last # GH doesn't return parent ID directly
+ issuable = issuable_class.find_by_iid(iid)
+ next unless issuable
+
issuable.notes.create!(comment.attributes)
rescue => e
errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
@@ -180,13 +185,14 @@ module Gitlab
end
def import_releases
- releases = client.releases(repo, per_page: 100)
- releases.each do |raw|
- begin
- gh_release = ReleaseFormatter.new(project, raw)
- gh_release.create! if gh_release.valid?
- rescue => e
- errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ client.releases(repo, per_page: 100) do |releases|
+ releases.each do |raw|
+ begin
+ gh_release = ReleaseFormatter.new(project, raw)
+ gh_release.create! if gh_release.valid?
+ rescue => e
+ errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
+ end
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 88803d76623..bb9d1080330 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -1,5 +1,8 @@
# Model relationships to be included in the project import/export
project_tree:
+ - :labels
+ - milestones:
+ - :events
- issues:
- :events
- notes:
@@ -39,9 +42,6 @@ project_tree:
- protected_branches:
- :merge_access_levels
- :push_access_levels
- - :labels
- - milestones:
- - :events
- :project_feature
# Only include the following attributes for the models specified.
@@ -73,5 +73,7 @@ excluded_attributes:
methods:
statuses:
- :type
+ services:
+ - :type
merge_request_diff:
- :utf8_st_diffs
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index c7b3551b84c..35ff134ea19 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -61,11 +61,17 @@ module Gitlab
def restore_project
return @project unless @tree_hash
- project_params = @tree_hash.reject { |_key, value| value.is_a?(Array) }
@project.update(project_params)
@project
end
+ def project_params
+ @tree_hash.reject do |key, value|
+ # return params that are not 1 to many or 1 to 1 relations
+ value.is_a?(Array) || key == key.singularize
+ end
+ end
+
# Given a relation hash containing one or more models and its relationships,
# loops through each model and each object from a model type and
# and assigns its correspondent attributes hash from +tree_hash+
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 2f326d00a2f..7e06bd2b0fb 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -51,8 +51,6 @@ module Gitlab
user.ldap_block
false
end
- rescue
- false
end
def adapter
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index 82cb8cef754..8b38cfaefb6 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -62,6 +62,9 @@ module Gitlab
results
end
end
+ rescue Net::LDAP::Error => error
+ Rails.logger.warn("LDAP search raised exception #{error.class}: #{error.message}")
+ []
rescue Timeout::Error
Rails.logger.warn("LDAP search timed out after #{config.timeout} seconds")
[]
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index d089a2f9b0b..7b3bbcf6a32 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -17,19 +17,18 @@ module Gitlab
end
end
- def generate
- token = Devise.friendly_token(TOKEN_LENGTH)
-
+ def token
Gitlab::Redis.with do |redis|
- redis.set(redis_key, token, ex: EXPIRY_TIME)
- end
+ token = redis.get(redis_key)
- token
- end
+ if token
+ redis.expire(redis_key, EXPIRY_TIME)
+ else
+ token = Devise.friendly_token(TOKEN_LENGTH)
+ redis.set(redis_key, token, ex: EXPIRY_TIME)
+ end
- def value
- Gitlab::Redis.with do |redis|
- redis.get(redis_key)
+ token
end
end
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
index 9376b54f43b..69c4ef721d5 100644
--- a/lib/gitlab/redis.rb
+++ b/lib/gitlab/redis.rb
@@ -9,19 +9,22 @@ module Gitlab
SIDEKIQ_NAMESPACE = 'resque:gitlab'
MAILROOM_NAMESPACE = 'mail_room:gitlab'
DEFAULT_REDIS_URL = 'redis://localhost:6379'
+ CONFIG_FILE = File.expand_path('../../config/resque.yml', __dir__)
# To be thread-safe we must be careful when writing the class instance
- # variables @url and @pool. Because @pool depends on @url we need two
+ # variables @_raw_config and @pool. Because @pool depends on @_raw_config we need two
# mutexes to prevent deadlock.
- PARAMS_MUTEX = Mutex.new
+ RAW_CONFIG_MUTEX = Mutex.new
POOL_MUTEX = Mutex.new
- private_constant :PARAMS_MUTEX, :POOL_MUTEX
+ private_constant :RAW_CONFIG_MUTEX, :POOL_MUTEX
class << self
+ # Do NOT cache in an instance variable. Result may be mutated by caller.
def params
- @params || PARAMS_MUTEX.synchronize { @params = new.params }
+ new.params
end
+ # Do NOT cache in an instance variable. Result may be mutated by caller.
# @deprecated Use .params instead to get sentinel support
def url
new.url
@@ -36,8 +39,17 @@ module Gitlab
@pool.with { |redis| yield redis }
end
- def reset_params!
- @params = nil
+ def _raw_config
+ return @_raw_config if defined?(@_raw_config)
+
+ RAW_CONFIG_MUTEX.synchronize do
+ begin
+ @_raw_config = File.read(CONFIG_FILE).freeze
+ rescue Errno::ENOENT
+ @_raw_config = false
+ end
+ end
+ @_raw_config
end
end
@@ -83,12 +95,7 @@ module Gitlab
end
def fetch_config
- file = config_file
- File.exist?(file) ? YAML.load_file(file)[@rails_env] : false
- end
-
- def config_file
- File.expand_path('../../../config/resque.yml', __FILE__)
+ self.class._raw_config ? YAML.load(self.class._raw_config)[@rails_env] : false
end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 60aae541d46..5d33f98e89e 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -60,7 +60,7 @@ module Gitlab
def send_git_diff(repository, diff_refs)
params = {
'RepoPath' => repository.path_to_repo,
- 'ShaFrom' => diff_refs.start_sha,
+ 'ShaFrom' => diff_refs.base_sha,
'ShaTo' => diff_refs.head_sha
}
@@ -73,7 +73,7 @@ module Gitlab
def send_git_patch(repository, diff_refs)
params = {
'RepoPath' => repository.path_to_repo,
- 'ShaFrom' => diff_refs.start_sha,
+ 'ShaFrom' => diff_refs.base_sha,
'ShaTo' => diff_refs.head_sha
}
@@ -107,15 +107,15 @@ module Gitlab
bytes
end
end
-
+
def write_secret
bytes = SecureRandom.random_bytes(SECRET_LENGTH)
- File.open(secret_path, 'w:BINARY', 0600) do |f|
+ File.open(secret_path, 'w:BINARY', 0600) do |f|
f.chmod(0600)
f.write(Base64.strict_encode64(bytes))
end
end
-
+
def verify_api_request!(request_headers)
JWT.decode(
request_headers[INTERNAL_API_REQUEST_HEADER],
@@ -128,7 +128,7 @@ module Gitlab
def secret_path
Rails.root.join('.gitlab_workhorse_secret')
end
-
+
protected
def encode(hash)
diff --git a/lib/tasks/flog.rake b/lib/tasks/flog.rake
deleted file mode 100644
index 3bfe999ae74..00000000000
--- a/lib/tasks/flog.rake
+++ /dev/null
@@ -1,25 +0,0 @@
-desc 'Code complexity analyze via flog'
-task :flog do
- output = %x(bundle exec flog -m app/ lib/gitlab)
- exit_code = 0
- minimum_score = 70
- output = output.lines
-
- # Skip total complexity score
- output.shift
-
- # Skip some trash info
- output.shift
-
- output.each do |line|
- score, method = line.split(" ")
- score = score.to_i
-
- if score > minimum_score
- exit_code = 1
- puts "High complexity in #{method}. Score: #{score}"
- end
- end
-
- exit exit_code
-end
diff --git a/public/deploy.html b/public/deploy.html
index 142472b6c35..49ec4ac5ce1 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -2,6 +2,11 @@
<html>
<head>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
+ <meta name="refresh" content="60">
+ <meta name="retry-after" content="100">
+ <meta name="robots" content="noindex, nofollow, noarchive, nostore">
+ <meta name="cache-control" content="no-cache, no-store">
+ <meta name="pragma" content="no-cache">
<title>Deploy in progress</title>
<style>
body {
@@ -61,4 +66,4 @@
<p>Please contact your GitLab administrator if this problem persists.</p>
</div>
</body>
-</html>
+</html> \ No newline at end of file
diff --git a/public/robots.txt b/public/robots.txt
index 334f4c03533..7d69fad59d1 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -23,7 +23,7 @@ Disallow: /groups/*/edit
Disallow: /users
# Global snippets
-Disallow: /s
+Disallow: /s/
Disallow: /snippets/new
Disallow: /snippets/*/edit
Disallow: /snippets/*/raw
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 76b2178c79c..6e987e7d9c9 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -39,7 +39,7 @@ if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml
- export FLAGS=(--path vendor --retry 3)
+ export FLAGS=(--path vendor --retry 3 --quiet)
else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index c34475976c6..92b97bf3d0c 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -2,9 +2,10 @@ require 'spec_helper'
describe Groups::GroupMembersController do
let(:user) { create(:user) }
- let(:group) { create(:group) }
describe '#index' do
+ let(:group) { create(:group) }
+
before do
group.add_owner(user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb
index d687dea3c3b..709006a3601 100644
--- a/spec/controllers/projects/boards/lists_controller_spec.rb
+++ b/spec/controllers/projects/boards/lists_controller_spec.rb
@@ -20,10 +20,7 @@ describe Projects::Boards::ListsController do
end
it 'returns a list of board lists' do
- board = project.create_board
- create(:backlog_list, board: board)
create(:list, board: board)
- create(:done_list, board: board)
read_board_list user: user
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 2fe3c263524..38e02a46626 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -8,7 +8,7 @@ describe Projects::RepositoriesController do
it 'responds with redirect in correct format' do
get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip"
- expect(response.content_type).to start_with 'text/html'
+ expect(response.header["Content-Type"]).to start_with('text/html')
expect(response).to be_redirect
end
end
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index e9e85962fe4..84da71ed6dc 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -3,10 +3,15 @@ FactoryGirl.define do
title
project
+ trait :active do
+ state "active"
+ end
+
trait :closed do
- state :closed
+ state "closed"
end
+ factory :active_milestone, traits: [:active]
factory :closed_milestone, traits: [:closed]
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index fb84ba07d25..e61b1fd9647 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -106,6 +106,8 @@ FactoryGirl.define do
factory :project_with_board, parent: :empty_project do
after(:create) do |project|
project.create_board
+ project.board.lists.create(list_type: :backlog)
+ project.board.lists.create(list_type: :done)
end
end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 19941978c5f..26ea06e002b 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -4,15 +4,11 @@ describe 'Issue Boards', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
- let(:project) { create(:empty_project, :public) }
+ let(:project) { create(:project_with_board, :public) }
let(:user) { create(:user) }
let!(:user2) { create(:user) }
before do
- project.create_board
- project.board.lists.create(list_type: :backlog)
- project.board.lists.create(list_type: :done)
-
project.team << [user, :master]
project.team << [user2, :master]
@@ -62,6 +58,7 @@ describe 'Issue Boards', feature: true, js: true do
let(:bug) { create(:label, project: project, name: 'Bug') }
let!(:backlog) { create(:label, project: project, name: 'Backlog') }
let!(:done) { create(:label, project: project, name: 'Done') }
+ let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
let!(:list1) { create(:list, board: project.board, label: planning, position: 0) }
let!(:list2) { create(:list, board: project.board, label: development, position: 1) }
@@ -75,7 +72,7 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development]) }
let!(:issue7) { create(:labeled_issue, project: project, labels: [development]) }
let!(:issue8) { create(:closed_issue, project: project) }
- let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug]) }
+ let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug, accepting]) }
before do
visit namespace_project_board_path(project.namespace, project)
@@ -441,6 +438,57 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_empty_boards((2..4))
end
+ it 'filters by label with space after reload' do
+ page.within '.issues-filters' do
+ click_button('Label')
+ wait_for_ajax
+
+ page.within '.dropdown-menu-labels' do
+ click_link(accepting.title)
+ wait_for_vue_resource(spinner: false)
+ find('.dropdown-menu-close').click
+ end
+ end
+
+ # Test after reload
+ page.evaluate_script 'window.location.reload()'
+
+ wait_for_vue_resource
+
+ page.within(find('.board', match: :first)) do
+ expect(page.find('.board-header')).to have_content('1')
+ expect(page).to have_selector('.card', count: 1)
+ end
+
+ page.within(find('.board:nth-child(2)')) do
+ expect(page.find('.board-header')).to have_content('0')
+ expect(page).to have_selector('.card', count: 0)
+ end
+ end
+
+ it 'removes filtered labels' do
+ wait_for_vue_resource
+
+ page.within '.labels-filter' do
+ click_button('Label')
+ wait_for_ajax
+
+ page.within '.dropdown-menu-labels' do
+ click_link(testing.title)
+ wait_for_vue_resource(spinner: false)
+ end
+
+ expect(page).to have_css('input[name="label_name[]"]', visible: false)
+
+ page.within '.dropdown-menu-labels' do
+ click_link(testing.title)
+ wait_for_vue_resource(spinner: false)
+ end
+
+ expect(page).not_to have_css('input[name="label_name[]"]', visible: false)
+ end
+ end
+
it 'infinite scrolls list with label filter' do
50.times do
create(:labeled_issue, project: project, labels: [testing])
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index fd5fbaf2af4..f52682f229c 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -5,13 +5,41 @@ feature 'Contributions Calendar', js: true, feature: true do
let(:contributed_project) { create(:project, :public) }
- before do
- login_as :user
+ date_format = '%A %b %d, %Y'
+ issue_title = 'Bug in old browser'
+ issue_params = { title: issue_title }
+
+ def get_cell_color_selector(contributions)
+ contribution_cell = '.user-contrib-cell'
+ activity_colors = Array['#ededed', '#acd5f2', '#7fa8c9', '#527ba0', '#254e77']
+ activity_colors_index = 0
+
+ if contributions > 0 && contributions < 10
+ activity_colors_index = 1
+ elsif contributions >= 10 && contributions < 20
+ activity_colors_index = 2
+ elsif contributions >= 20 && contributions < 30
+ activity_colors_index = 3
+ elsif contributions >= 30
+ activity_colors_index = 4
+ end
+
+ "#{contribution_cell}[fill='#{activity_colors[activity_colors_index]}']"
+ end
+
+ def get_cell_date_selector(contributions, date)
+ contribution_text = 'No contributions'
- issue_params = { title: 'Bug in old browser' }
- Issues::CreateService.new(contributed_project, @user, issue_params).execute
+ if contributions === 1
+ contribution_text = '1 contribution'
+ elsif contributions > 1
+ contribution_text = "#{contributions} contributions"
+ end
- # Push code contribution
+ "#{get_cell_color_selector(contributions)}[data-original-title='#{contribution_text}<br />#{date}']"
+ end
+
+ def push_code_contribution
push_params = {
project: contributed_project,
action: Event::PUSHED,
@@ -20,7 +48,10 @@ feature 'Contributions Calendar', js: true, feature: true do
}
Event.create(push_params)
+ end
+ before do
+ login_as :user
visit @user.username
wait_for_ajax
end
@@ -29,11 +60,71 @@ feature 'Contributions Calendar', js: true, feature: true do
expect(page).to have_css('.js-contrib-calendar')
end
- it 'displays calendar activity log', js: true do
- expect(find('.content_list .event-note')).to have_content "Bug in old browser"
+ describe '1 calendar activity' do
+ before do
+ Issues::CreateService.new(contributed_project, @user, issue_params).execute
+ visit @user.username
+ wait_for_ajax
+ end
+
+ it 'displays calendar activity log', js: true do
+ expect(find('.content_list .event-note')).to have_content issue_title
+ end
+
+ it 'displays calendar activity square color for 1 contribution', js: true do
+ expect(page).to have_selector(get_cell_color_selector(1), count: 1)
+ end
+
+ it 'displays calendar activity square on the correct date', js: true do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ end
+ end
+
+ describe '10 calendar activities' do
+ before do
+ (0..9).each do |i|
+ push_code_contribution()
+ end
+
+ visit @user.username
+ wait_for_ajax
+ end
+
+ it 'displays calendar activity square color for 10 contributions', js: true do
+ expect(page).to have_selector(get_cell_color_selector(10), count: 1)
+ end
+
+ it 'displays calendar activity square on the correct date', js: true do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(10, today), count: 1)
+ end
end
- it 'displays calendar activity square color', js: true do
- expect(page).to have_selector('.user-contrib-cell[fill=\'#acd5f2\']', count: 1)
+ describe 'calendar activity on two days' do
+ before do
+ push_code_contribution()
+
+ Timecop.freeze(Date.yesterday)
+ Issues::CreateService.new(contributed_project, @user, issue_params).execute
+ Timecop.return
+
+ visit @user.username
+ wait_for_ajax
+ end
+
+ it 'displays calendar activity squares for both days', js: true do
+ expect(page).to have_selector(get_cell_color_selector(1), count: 2)
+ end
+
+ it 'displays calendar activity square for yesterday', js: true do
+ yesterday = Date.yesterday.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ end
+
+ it 'displays calendar activity square for today', js: true do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ end
end
end
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
new file mode 100644
index 00000000000..62937688c22
--- /dev/null
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'Dashboard snippets', feature: true do
+ context 'when the project has snippets' do
+ let(:project) { create(:empty_project, :public) }
+ let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
+ before do
+ allow(Snippet).to receive(:default_per_page).and_return(1)
+ login_as(project.owner)
+ visit dashboard_snippets_path
+ end
+
+ it_behaves_like 'paginated snippets'
+ end
+end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 105629c485a..bf2b93c92fb 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -25,32 +25,78 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
describe 'adding a due date from note' do
let(:issue) { create(:issue, project: project) }
- it 'does not create a note, and sets the due date accordingly' do
- write_note("/due 2016-08-28")
+ context 'when the current user can update the due date' do
+ it 'does not create a note, and sets the due date accordingly' do
+ write_note("/due 2016-08-28")
- expect(page).not_to have_content '/due 2016-08-28'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).not_to have_content '/due 2016-08-28'
+ expect(page).to have_content 'Your commands have been executed!'
- issue.reload
+ issue.reload
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
+ expect(issue.due_date).to eq Date.new(2016, 8, 28)
+ end
+ end
+
+ context 'when the current user cannot update the due date' do
+ let(:guest) { create(:user) }
+ before do
+ project.team << [guest, :guest]
+ logout
+ login_with(guest)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'does not create a note, and sets the due date accordingly' do
+ write_note("/due 2016-08-28")
+
+ expect(page).to have_content '/due 2016-08-28'
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ issue.reload
+
+ expect(issue.due_date).to be_nil
+ end
end
end
describe 'removing a due date from note' do
let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) }
- it 'does not create a note, and removes the due date accordingly' do
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
+ context 'when the current user can update the due date' do
+ it 'does not create a note, and removes the due date accordingly' do
+ expect(issue.due_date).to eq Date.new(2016, 8, 28)
+
+ write_note("/remove_due_date")
+
+ expect(page).not_to have_content '/remove_due_date'
+ expect(page).to have_content 'Your commands have been executed!'
+
+ issue.reload
+
+ expect(issue.due_date).to be_nil
+ end
+ end
+
+ context 'when the current user cannot update the due date' do
+ let(:guest) { create(:user) }
+ before do
+ project.team << [guest, :guest]
+ logout
+ login_with(guest)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
- write_note("/remove_due_date")
+ it 'does not create a note, and sets the due date accordingly' do
+ write_note("/remove_due_date")
- expect(page).not_to have_content '/remove_due_date'
- expect(page).to have_content 'Your commands have been executed!'
+ expect(page).to have_content '/remove_due_date'
+ expect(page).not_to have_content 'Your commands have been executed!'
- issue.reload
+ issue.reload
- expect(issue.due_date).to be_nil
+ expect(issue.due_date).to eq Date.new(2016, 8, 28)
+ end
end
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 22359c8f938..9fe40ea0892 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -369,6 +369,24 @@ describe 'Issues', feature: true do
end
end
+ describe 'update labels from issue#show', js: true do
+ let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
+ let!(:label) { create(:label, project: project) }
+
+ before do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'will not send ajax request when no data is changed' do
+ page.within '.labels' do
+ click_link 'Edit'
+ first('.dropdown-menu-close').click
+
+ expect(page).not_to have_selector('.block-loading')
+ end
+ end
+ end
+
describe 'update assignee from issue#show' do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb
new file mode 100644
index 00000000000..d37e8ed4699
--- /dev/null
+++ b/spec/features/projects/snippets_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe 'Project snippets', feature: true do
+ context 'when the project has snippets' do
+ let(:project) { create(:empty_project, :public) }
+ let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
+ before do
+ allow(Snippet).to receive(:default_per_page).and_return(1)
+ visit namespace_project_snippets_path(project.namespace, project)
+ end
+
+ it_behaves_like 'paginated snippets'
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index dcd3a2f17b0..1806200c82c 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe "Search", feature: true do
+ include WaitForAjax
+
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, assignee: user) }
@@ -16,6 +18,36 @@ describe "Search", feature: true do
expect(page).not_to have_selector('.search')
end
+ context 'search filters', js: true do
+ let(:group) { create(:group) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it 'shows group name after filtering' do
+ find('.js-search-group-dropdown').click
+ wait_for_ajax
+
+ page.within '.search-holder' do
+ click_link group.name
+ end
+
+ expect(find('.js-search-group-dropdown')).to have_content(group.name)
+ end
+
+ it 'shows project name after filtering' do
+ page.within('.project-filter') do
+ find('.js-search-project-dropdown').click
+ wait_for_ajax
+
+ click_link project.name_with_namespace
+ end
+
+ expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
+ end
+ end
+
describe 'searching for Projects' do
it 'finds a project' do
page.within '.search-holder' do
diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb
new file mode 100644
index 00000000000..70b16bfc810
--- /dev/null
+++ b/spec/features/snippets_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe 'Snippets', feature: true do
+ context 'when the project has snippets' do
+ let(:project) { create(:empty_project, :public) }
+ let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
+ before do
+ allow(Snippet).to receive(:default_per_page).and_return(1)
+ visit snippets_path(username: project.owner.username)
+ end
+
+ it_behaves_like 'paginated snippets'
+ end
+end
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
index cc40671787c..33b52d1547e 100644
--- a/spec/features/unsubscribe_links_spec.rb
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -11,7 +11,7 @@ describe 'Unsubscribe links', feature: true do
let(:mail) { ActionMailer::Base.deliveries.last }
let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) }
- let(:header_link) { mail.header['List-Unsubscribe'] }
+ let(:header_link) { mail.header['List-Unsubscribe'].to_s[1..-2] } # Strip angle brackets
let(:body_link) { body.find_link('unsubscribe')['href'] }
before do
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index f00abd82fea..ce7e809ec76 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -3,30 +3,16 @@ require 'spec_helper'
describe 'Snippets tab on a user profile', feature: true, js: true do
include WaitForAjax
- let(:user) { create(:user) }
-
context 'when the user has snippets' do
+ let(:user) { create(:user) }
+ let!(:snippets) { create_list(:snippet, 2, :public, author: user) }
before do
- create_list(:snippet, 25, :public, author: user)
-
+ allow(Snippet).to receive(:default_per_page).and_return(1)
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
wait_for_ajax
end
- it 'is limited to 20 items per page' do
- expect(page.all('.snippets-list-holder .snippet-row').count).to eq(20)
- end
-
- context 'clicking on the link to the second page' do
- before do
- click_link('2')
- wait_for_ajax
- end
-
- it 'shows the remaining snippets' do
- expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5)
- end
- end
+ it_behaves_like 'paginated snippets', remote: true
end
end
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
new file mode 100644
index 00000000000..28c2268f8d0
--- /dev/null
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe MilestonesHelper do
+ describe '#milestone_counts' do
+ let(:project) { FactoryGirl.create(:project) }
+ let(:counts) { helper.milestone_counts(project.milestones) }
+
+ context 'when there are milestones' do
+ let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
+ let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
+ let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) }
+
+ it 'returns the correct counts' do
+ expect(counts).to eq(opened: 2, closed: 1, all: 3)
+ end
+ end
+
+ context 'when there are only milestones of one type' do
+ let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
+ let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
+
+ it 'returns the correct counts' do
+ expect(counts).to eq(opened: 2, closed: 0, all: 2)
+ end
+ end
+
+ context 'when there are no milestones' do
+ it 'returns the correct counts' do
+ expect(counts).to eq(opened: 0, closed: 0, all: 0)
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js.es6
index 840c7b6d015..1ad6f612210 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js.es6
+++ b/spec/javascripts/labels_issue_sidebar_spec.js.es6
@@ -48,9 +48,9 @@
setTimeout(() => {
expect($('.dropdown-content a').length).toBe(10);
- $('.dropdow-content a').each((i, $link) => {
- if (i < 5) {
- $link.get(0).click();
+ $('.dropdown-content a').each(function (i) {
+ if (i < saveLabelCount) {
+ $(this).get(0).click();
}
});
@@ -70,9 +70,9 @@
setTimeout(() => {
expect($('.dropdown-content a').length).toBe(10);
- $('.dropdow-content a').each((i, $link) => {
- if (i < 5) {
- $link.get(0).click();
+ $('.dropdown-content a').each(function (i) {
+ if (i < saveLabelCount) {
+ $(this).get(0).click();
}
});
@@ -86,4 +86,3 @@
});
});
})();
-
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 745fbc0df45..c9d64e99f88 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -64,7 +64,7 @@ describe Gitlab::Auth, lib: true do
it 'recognizes user lfs tokens' do
user = create(:user)
ip = 'ip'
- token = Gitlab::LfsToken.new(user).generate
+ token = Gitlab::LfsToken.new(user).token
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
@@ -73,7 +73,7 @@ describe Gitlab::Auth, lib: true do
it 'recognizes deploy key lfs tokens' do
key = create(:deploy_key)
ip = 'ip'
- token = Gitlab::LfsToken.new(key).generate
+ token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}")
expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 613c47d55f1..e829b936343 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -66,6 +66,6 @@ describe Gitlab::GithubImport::Client, lib: true do
stub_request(:get, /api.github.com/)
allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound)
- expect { client.issues }.not_to raise_error
+ expect { client.issues {} }.not_to raise_error
end
end
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index 553c849c9b4..8854c8431b5 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -57,7 +57,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
created_at: created_at,
updated_at: updated_at,
closed_at: nil,
- url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347'
+ url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347',
+ labels: [double(name: 'Label #1')],
)
end
@@ -75,7 +76,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
created_at: created_at,
updated_at: updated_at,
closed_at: nil,
- url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348'
+ url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348',
+ labels: [double(name: 'Label #2')],
)
end
@@ -94,7 +96,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
updated_at: updated_at,
closed_at: nil,
merged_at: nil,
- url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347'
+ url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347',
+ labels: [double(name: 'Label #3')],
)
end
@@ -129,6 +132,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
+ allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_return([])
+ allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
@@ -148,9 +153,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
errors: [
{ type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
{ type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" },
- { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." },
{ type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" },
- { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." },
{ type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" },
{ type: :wiki, errors: "Gitlab::Shell::Error" },
{ type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 281f6cf1177..98323fe6be4 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2232,6 +2232,31 @@
],
"milestones": [
{
+ "id": 1,
+ "title": "test milestone",
+ "project_id": 8,
+ "description": "test milestone",
+ "due_date": null,
+ "created_at": "2016-06-14T15:02:04.415Z",
+ "updated_at": "2016-06-14T15:02:04.415Z",
+ "state": "active",
+ "iid": 1,
+ "events": [
+ {
+ "id": 487,
+ "target_type": "Milestone",
+ "target_id": 1,
+ "title": null,
+ "data": null,
+ "project_id": 46,
+ "created_at": "2016-06-14T15:02:04.418Z",
+ "updated_at": "2016-06-14T15:02:04.418Z",
+ "action": 1,
+ "author_id": 18
+ }
+ ]
+ },
+ {
"id": 20,
"title": "v4.0",
"project_id": 5,
@@ -6918,6 +6943,7 @@
"note_events": true,
"build_events": true,
"category": "issue_tracker",
+ "type": "CustomIssueTrackerService",
"default": true,
"wiki_page_events": true
},
@@ -7372,5 +7398,16 @@
}
]
}
- ]
+ ],
+ "project_feature": {
+ "builds_access_level": 0,
+ "created_at": "2014-12-26T09:26:45.000Z",
+ "id": 2,
+ "issues_access_level": 0,
+ "merge_requests_access_level": 20,
+ "project_id": 4,
+ "snippets_access_level": 20,
+ "updated_at": "2016-09-23T11:58:28.000Z",
+ "wiki_access_level": 20
+ }
} \ No newline at end of file
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index feacb295231..7582a732cdf 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -107,6 +107,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(Label.first.label_links.first.target).not_to be_nil
end
+ it 'has a project feature' do
+ restored_project_json
+
+ expect(project.project_feature).not_to be_nil
+ end
+
+ it 'restores the correct service' do
+ restored_project_json
+
+ expect(CustomIssueTrackerService.first).not_to be_nil
+ end
+
context 'Merge requests' do
before do
restored_project_json
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index d891c2d0cc6..cf8f2200c57 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -111,6 +111,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
end
+ it 'saves the correct service type' do
+ expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
+ end
+
it 'has project feature' do
project_feature = saved_project_json['project_feature']
expect(project_feature).not_to be_empty
@@ -161,6 +165,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
commit_id: ci_pipeline.sha)
create(:event, target: milestone, project: project, action: Event::CREATED, author: user)
+ create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index 0600893f4cf..563c074017a 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -73,17 +73,33 @@ describe Gitlab::LDAP::Adapter, lib: true do
describe '#dn_matches_filter?' do
subject { adapter.dn_matches_filter?(:dn, :filter) }
+ context "when the search result is non-empty" do
+ before { allow(adapter).to receive(:ldap_search).and_return([:foo]) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "when the search result is empty" do
+ before { allow(adapter).to receive(:ldap_search).and_return([]) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#ldap_search' do
+ subject { adapter.ldap_search(base: :dn, filter: :filter) }
+
context "when the search is successful" do
context "and the result is non-empty" do
before { allow(ldap).to receive(:search).and_return([:foo]) }
- it { is_expected.to be_truthy }
+ it { is_expected.to eq [:foo] }
end
context "and the result is empty" do
before { allow(ldap).to receive(:search).and_return([]) }
- it { is_expected.to be_falsey }
+ it { is_expected.to eq [] }
end
end
@@ -95,7 +111,22 @@ describe Gitlab::LDAP::Adapter, lib: true do
)
end
- it { is_expected.to be_falsey }
+ it { is_expected.to eq [] }
+ end
+
+ context "when the search raises an LDAP exception" do
+ before do
+ allow(ldap).to receive(:search) { raise Net::LDAP::Error, "some error" }
+ allow(Rails.logger).to receive(:warn)
+ end
+
+ it { is_expected.to eq [] }
+
+ it 'logs the error' do
+ subject
+ expect(Rails.logger).to have_received(:warn).with(
+ "LDAP search raised exception Net::LDAP::Error: some error")
+ end
end
end
end
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index 9f04f67e0a8..e9c1163e22a 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
describe Gitlab::LfsToken, lib: true do
- describe '#generate and #value' do
+ describe '#token' do
shared_examples 'an LFS token generator' do
it 'returns a randomly generated token' do
- token = handler.generate
+ token = handler.token
expect(token).not_to be_nil
expect(token).to be_a String
@@ -12,9 +12,9 @@ describe Gitlab::LfsToken, lib: true do
end
it 'returns the correct token based on the key' do
- token = handler.generate
+ token = handler.token
- expect(handler.value).to eq(token)
+ expect(handler.token).to eq(token)
end
end
diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb
index e54f5ffb312..cb54c020b31 100644
--- a/spec/lib/gitlab/redis_spec.rb
+++ b/spec/lib/gitlab/redis_spec.rb
@@ -3,19 +3,27 @@ require 'spec_helper'
describe Gitlab::Redis do
let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s }
- before(:each) { described_class.reset_params! }
- after(:each) { described_class.reset_params! }
+ before(:each) { clear_raw_config }
+ after(:each) { clear_raw_config }
describe '.params' do
subject { described_class.params }
+ it 'withstands mutation' do
+ params1 = described_class.params
+ params2 = described_class.params
+ params1[:foo] = :bar
+
+ expect(params2).not_to have_key(:foo)
+ end
+
context 'when url contains unix socket reference' do
let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s }
let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s }
context 'with old format' do
it 'returns path key instead' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_old }
+ stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(path: '/path/to/old/redis.sock')
is_expected.not_to have_key(:url)
@@ -24,7 +32,7 @@ describe Gitlab::Redis do
context 'with new format' do
it 'returns path key instead' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_new }
+ stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(path: '/path/to/redis.sock')
is_expected.not_to have_key(:url)
@@ -38,7 +46,7 @@ describe Gitlab::Redis do
context 'with old format' do
it 'returns hash with host, port, db, and password' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_old }
+ stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
@@ -47,7 +55,7 @@ describe Gitlab::Redis do
context 'with new format' do
it 'returns hash with host, port, db, and password' do
- expect_any_instance_of(described_class).to receive(:config_file) { config_new }
+ stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
is_expected.not_to have_key(:url)
@@ -56,6 +64,30 @@ describe Gitlab::Redis do
end
end
+ describe '.url' do
+ it 'withstands mutation' do
+ url1 = described_class.url
+ url2 = described_class.url
+ url1 << 'foobar'
+
+ expect(url2).not_to end_with('foobar')
+ end
+ end
+
+ describe '._raw_config' do
+ subject { described_class._raw_config }
+
+ it 'should be frozen' do
+ expect(subject).to be_frozen
+ end
+
+ it 'returns false when the file does not exist' do
+ stub_const("#{described_class}::CONFIG_FILE", '/var/empty/doesnotexist')
+
+ expect(subject).to eq(false)
+ end
+ end
+
describe '#raw_config_hash' do
it 'returns default redis url when no config file is present' do
expect(subject).to receive(:fetch_config) { false }
@@ -71,9 +103,15 @@ describe Gitlab::Redis do
describe '#fetch_config' do
it 'returns false when no config file is present' do
- allow(File).to receive(:exist?).with(redis_config) { false }
+ allow(described_class).to receive(:_raw_config) { false }
expect(subject.send(:fetch_config)).to be_falsey
end
end
+
+ def clear_raw_config
+ described_class.remove_instance_variable(:@_raw_config)
+ rescue NameError
+ # raised if @_raw_config was not set; ignore
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 6c7fa7e7c15..b5b685da904 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -1,8 +1,16 @@
require 'spec_helper'
describe Gitlab::Workhorse, lib: true do
- let(:project) { create(:project) }
- let(:subject) { Gitlab::Workhorse }
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+
+ def decode_workhorse_header(array)
+ key, value = array
+ command, encoded_params = value.split(":")
+ params = JSON.parse(Base64.urlsafe_decode64(encoded_params))
+
+ [key, command, params]
+ end
describe ".send_git_archive" do
context "when the repository doesn't have an archive file path" do
@@ -11,11 +19,37 @@ describe Gitlab::Workhorse, lib: true do
end
it "raises an error" do
- expect { subject.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
+ expect { described_class.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
end
end
end
+ describe '.send_git_patch' do
+ let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
+ subject { described_class.send_git_patch(repository, diff_refs) }
+
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-format-patch")
+ expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ end
+ end
+
+ describe '.send_git_diff' do
+ let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
+ subject { described_class.send_git_patch(repository, diff_refs) }
+
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq("Gitlab-Workhorse-Send-Data")
+ expect(command).to eq("git-format-patch")
+ expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
+ end
+ end
+
describe ".secret" do
subject { described_class.secret }
diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb
index 56872da9a8f..5c9851f14c7 100644
--- a/spec/mailers/shared/notify.rb
+++ b/spec/mailers/shared/notify.rb
@@ -169,8 +169,9 @@ shared_examples 'it should show Gmail Actions View Commit link' do
end
shared_examples 'an unsubscribeable thread' do
- it 'has a List-Unsubscribe header' do
+ it 'has a List-Unsubscribe header in the correct format' do
is_expected.to have_header 'List-Unsubscribe', /unsubscribe/
+ is_expected.to have_header 'List-Unsubscribe', /^<.+>$/
end
it { is_expected.to have_body_text /unsubscribe/ }
diff --git a/spec/models/cycle_analytics/summary_spec.rb b/spec/models/cycle_analytics/summary_spec.rb
index 743bc2da33f..9d67bc82cba 100644
--- a/spec/models/cycle_analytics/summary_spec.rb
+++ b/spec/models/cycle_analytics/summary_spec.rb
@@ -34,6 +34,12 @@ describe CycleAnalytics::Summary, models: true do
expect(subject.commits).to eq(0)
end
+
+ it "finds a large (> 100) snumber of commits if present" do
+ Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) }
+
+ expect(subject.commits).to eq(100)
+ end
end
describe "#deploys" do
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index 92e0f7f27ce..dd033480527 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -50,8 +50,9 @@ describe GlobalMilestone, models: true do
milestone1_project2,
milestone1_project3,
]
+ milestones_relation = Milestone.where(id: milestones.map(&:id))
- @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones)
+ @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones_relation)
end
it 'has exactly one group milestone' do
@@ -67,7 +68,7 @@ describe GlobalMilestone, models: true do
let(:milestone) { create(:milestone, title: "git / test", project: project1) }
it 'strips out slashes and spaces' do
- global_milestone = GlobalMilestone.new(milestone.title, [milestone])
+ global_milestone = GlobalMilestone.new(milestone.title, Milestone.where(id: milestone.id))
expect(global_milestone.safe_title).to eq('git-test')
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 12df6adde44..580a3235127 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -328,6 +328,42 @@ describe MergeRequest, models: true do
end
end
+ describe '#merge_commit_message' do
+ it 'includes merge information as the title' do
+ request = build(:merge_request, source_branch: 'source', target_branch: 'target')
+
+ expect(request.merge_commit_message)
+ .to match("Merge branch 'source' into 'target'\n\n")
+ end
+
+ it 'includes its title in the body' do
+ request = build(:merge_request, title: 'Remove all technical debt')
+
+ expect(request.merge_commit_message)
+ .to match("Remove all technical debt\n\n")
+ end
+
+ it 'includes its description in the body' do
+ request = build(:merge_request, description: 'By removing all code')
+
+ expect(request.merge_commit_message)
+ .to match("By removing all code\n\n")
+ end
+
+ it 'includes its reference in the body' do
+ request = build_stubbed(:merge_request)
+
+ expect(request.merge_commit_message)
+ .to match("See merge request #{request.to_reference}")
+ end
+
+ it 'excludes multiple linebreak runs when description is blank' do
+ request = build(:merge_request, title: 'Title', description: nil)
+
+ expect(request.merge_commit_message).not_to match("Title\n\n\n\n")
+ end
+ end
+
describe "#reset_merge_when_build_succeeds" do
let(:merge_if_green) do
create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user),
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index 7020667ea58..63320931e76 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -25,5 +25,21 @@ describe CustomIssueTrackerService, models: true do
it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) }
end
+
+ context 'title' do
+ let(:issue_tracker) { described_class.new(properties: {}) }
+
+ it 'sets a default title' do
+ issue_tracker.title = nil
+
+ expect(issue_tracker.title).to eq('Custom Issue Tracker')
+ end
+
+ it 'sets the custom title' do
+ issue_tracker.title = 'test title'
+
+ expect(issue_tracker.title).to eq('test title')
+ end
+ end
end
end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index ee0b61e2ca4..95c7bbf99c9 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -30,6 +30,15 @@ describe API::API, api: true do
expect(json_response.first['commit']['id']).to eq project.commit.id
end
+ it 'returns pipeline data' do
+ json_build = json_response.first
+ expect(json_build['pipeline']).not_to be_empty
+ expect(json_build['pipeline']['id']).to eq build.pipeline.id
+ expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
+ expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
+ expect(json_build['pipeline']['status']).to eq build.pipeline.status
+ end
+
context 'filter project with one scope element' do
let(:query) { 'scope=pending' }
@@ -91,6 +100,15 @@ describe API::API, api: true do
expect(json_response).to be_an Array
expect(json_response.size).to eq 2
end
+
+ it 'returns pipeline data' do
+ json_build = json_response.first
+ expect(json_build['pipeline']).not_to be_empty
+ expect(json_build['pipeline']['id']).to eq build.pipeline.id
+ expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
+ expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
+ expect(json_build['pipeline']['status']).to eq build.pipeline.status
+ end
end
context 'when pipeline has no builds' do
@@ -133,6 +151,15 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
expect(json_response['name']).to eq('test')
end
+
+ it 'returns pipeline data' do
+ json_build = json_response
+ expect(json_build['pipeline']).not_to be_empty
+ expect(json_build['pipeline']['id']).to eq build.pipeline.id
+ expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
+ expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
+ expect(json_build['pipeline']['status']).to eq build.pipeline.status
+ end
end
context 'unauthorized user' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 46e8e6f1169..f0f590b0331 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -111,7 +111,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
- expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
+ expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token)
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
end
@@ -131,7 +131,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}")
- expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
+ expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token)
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index ef73778efa9..f4ea3bebb4c 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -62,6 +62,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first.keys).to include 'email'
+ expect(json_response.first.keys).to include 'organization'
expect(json_response.first.keys).to include 'identities'
expect(json_response.first.keys).to include 'can_create_project'
expect(json_response.first.keys).to include 'two_factor_enabled'
@@ -265,6 +266,14 @@ describe API::API, api: true do
expect(user.reload.bio).to eq('new test bio')
end
+ it "updates user with organization" do
+ put api("/users/#{user.id}", admin), { organization: 'GitLab' }
+
+ expect(response).to have_http_status(200)
+ expect(json_response['organization']).to eq('GitLab')
+ expect(user.reload.organization).to eq('GitLab')
+ end
+
it 'updates user with his own email' do
put api("/users/#{user.id}", admin), email: user.email
expect(response).to have_http_status(200)
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 74516686921..c0c1e62e910 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -1,508 +1,517 @@
require "spec_helper"
describe 'Git HTTP requests', lib: true do
+ include GitHttpHelpers
include WorkhorseHelpers
- let(:user) { create(:user) }
- let(:project) { create(:project, path: 'project.git-project') }
-
it "gives WWW-Authenticate hints" do
clone_get('doesnt/exist.git')
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end
- context "when the project doesn't exist" do
- context "when no authentication is provided" do
- it "responds with status 401 (no project existence information leak)" do
- download('doesnt/exist.git') do |response|
- expect(response).to have_http_status(401)
- end
- end
- end
+ describe "User with no identities" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, path: 'project.git-project') }
- context "when username and password are provided" do
- context "when authentication fails" do
- it "responds with status 401" do
- download('doesnt/exist.git', user: user.username, password: "nope") do |response|
+ context "when the project doesn't exist" do
+ context "when no authentication is provided" do
+ it "responds with status 401 (no project existence information leak)" do
+ download('doesnt/exist.git') do |response|
expect(response).to have_http_status(401)
end
end
end
- context "when authentication succeeds" do
- it "responds with status 404" do
- download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(404)
+ context "when username and password are provided" do
+ context "when authentication fails" do
+ it "responds with status 401" do
+ download('doesnt/exist.git', user: user.username, password: "nope") do |response|
+ expect(response).to have_http_status(401)
+ end
end
end
- end
- end
- end
-
- context "when the Wiki for a project exists" do
- it "responds with the right project" do
- wiki = ProjectWiki.new(project)
- project.update_attribute(:visibility_level, Project::PUBLIC)
- download("/#{wiki.repository.path_with_namespace}.git") do |response|
- json_body = ActiveSupport::JSON.decode(response.body)
-
- expect(response).to have_http_status(200)
- expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ context "when authentication succeeds" do
+ it "responds with status 404" do
+ download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
end
- end
- context "when the project exists" do
- let(:path) { "#{project.path_with_namespace}.git" }
-
- context "when the project is public" do
- before do
+ context "when the Wiki for a project exists" do
+ it "responds with the right project" do
+ wiki = ProjectWiki.new(project)
project.update_attribute(:visibility_level, Project::PUBLIC)
- end
- it "downloads get status 200" do
- download(path, {}) do |response|
+ download("/#{wiki.repository.path_with_namespace}.git") do |response|
+ json_body = ActiveSupport::JSON.decode(response.body)
+
expect(response).to have_http_status(200)
+ expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
+ end
+
+ context "when the project exists" do
+ let(:path) { "#{project.path_with_namespace}.git" }
- it "uploads get status 401" do
- upload(path, {}) do |response|
- expect(response).to have_http_status(401)
+ context "when the project is public" do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
end
- end
- context "with correct credentials" do
- let(:env) { { user: user.username, password: user.password } }
+ it "downloads get status 200" do
+ download(path, {}) do |response|
+ expect(response).to have_http_status(200)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+ end
- it "uploads get status 403" do
- upload(path, env) do |response|
- expect(response).to have_http_status(403)
+ it "uploads get status 401" do
+ upload(path, {}) do |response|
+ expect(response).to have_http_status(401)
end
end
- context 'but git-receive-pack is disabled' do
- it "responds with status 404" do
- allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
+ context "with correct credentials" do
+ let(:env) { { user: user.username, password: user.password } }
+ it "uploads get status 403" do
upload(path, env) do |response|
expect(response).to have_http_status(403)
end
end
- end
- end
- context 'but git-upload-pack is disabled' do
- it "responds with status 404" do
- allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
+ context 'but git-receive-pack is disabled' do
+ it "responds with status 404" do
+ allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
- download(path, {}) do |response|
- expect(response).to have_http_status(404)
+ upload(path, env) do |response|
+ expect(response).to have_http_status(403)
+ end
+ end
end
end
- end
-
- context 'when the request is not from gitlab-workhorse' do
- it 'raises an exception' do
- expect do
- get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack")
- end.to raise_error(JWT::DecodeError)
- end
- end
- end
- context "when the project is private" do
- before do
- project.update_attribute(:visibility_level, Project::PRIVATE)
- end
+ context 'but git-upload-pack is disabled' do
+ it "responds with status 404" do
+ allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
- context "when no authentication is provided" do
- it "responds with status 401 to downloads" do
- download(path, {}) do |response|
- expect(response).to have_http_status(401)
+ download(path, {}) do |response|
+ expect(response).to have_http_status(404)
+ end
end
end
- it "responds with status 401 to uploads" do
- upload(path, {}) do |response|
- expect(response).to have_http_status(401)
+ context 'when the request is not from gitlab-workhorse' do
+ it 'raises an exception' do
+ expect do
+ get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack")
+ end.to raise_error(JWT::DecodeError)
end
end
end
- context "when username and password are provided" do
- let(:env) { { user: user.username, password: 'nope' } }
+ context "when the project is private" do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
- context "when authentication fails" do
- it "responds with status 401" do
- download(path, env) do |response|
+ context "when no authentication is provided" do
+ it "responds with status 401 to downloads" do
+ download(path, {}) do |response|
expect(response).to have_http_status(401)
end
end
- context "when the user is IP banned" do
- it "responds with status 401" do
- expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
-
- clone_get(path, env)
-
+ it "responds with status 401 to uploads" do
+ upload(path, {}) do |response|
expect(response).to have_http_status(401)
end
end
end
- context "when authentication succeeds" do
- let(:env) { { user: user.username, password: user.password } }
+ context "when username and password are provided" do
+ let(:env) { { user: user.username, password: 'nope' } }
- context "when the user has access to the project" do
- before do
- project.team << [user, :master]
+ context "when authentication fails" do
+ it "responds with status 401" do
+ download(path, env) do |response|
+ expect(response).to have_http_status(401)
+ end
end
- context "when the user is blocked" do
- it "responds with status 404" do
- user.block
- project.team << [user, :master]
+ context "when the user is IP banned" do
+ it "responds with status 401" do
+ expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
+ allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
- download(path, env) do |response|
- expect(response).to have_http_status(404)
- end
+ clone_get(path, env)
+
+ expect(response).to have_http_status(401)
end
end
+ end
- context "when the user isn't blocked" do
- it "downloads get status 200" do
- expect(Rack::Attack::Allow2Ban).to receive(:reset)
+ context "when authentication succeeds" do
+ let(:env) { { user: user.username, password: user.password } }
- clone_get(path, env)
+ context "when the user has access to the project" do
+ before do
+ project.team << [user, :master]
+ end
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ context "when the user is blocked" do
+ it "responds with status 404" do
+ user.block
+ project.team << [user, :master]
+
+ download(path, env) do |response|
+ expect(response).to have_http_status(404)
+ end
+ end
end
- it "uploads get status 200" do
- upload(path, env) do |response|
+ context "when the user isn't blocked" do
+ it "downloads get status 200" do
+ expect(Rack::Attack::Allow2Ban).to receive(:reset)
+
+ clone_get(path, env)
+
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
- end
- end
- context "when an oauth token is provided" do
- before do
- application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
- @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id)
+ it "uploads get status 200" do
+ upload(path, env) do |response|
+ expect(response).to have_http_status(200)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+ end
end
- it "downloads get status 200" do
- clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
+ context "when an oauth token is provided" do
+ before do
+ application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
+ @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id)
+ end
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- end
+ it "downloads get status 200" do
+ clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
- it "uploads get status 401 (no project existence information leak)" do
- push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
+ expect(response).to have_http_status(200)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
- expect(response).to have_http_status(401)
+ it "uploads get status 401 (no project existence information leak)" do
+ push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
+
+ expect(response).to have_http_status(401)
+ end
end
- end
- context 'when user has 2FA enabled' do
- let(:user) { create(:user, :two_factor) }
- let(:access_token) { create(:personal_access_token, user: user) }
+ context 'when user has 2FA enabled' do
+ let(:user) { create(:user, :two_factor) }
+ let(:access_token) { create(:personal_access_token, user: user) }
- before do
- project.team << [user, :master]
- end
+ before do
+ project.team << [user, :master]
+ end
- context 'when username and password are provided' do
- it 'rejects the clone attempt' do
- download("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(401)
- expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
+ context 'when username and password are provided' do
+ it 'rejects the clone attempt' do
+ download("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(401)
+ expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
+ end
end
- end
- it 'rejects the push attempt' do
- upload("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(401)
- expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
+ it 'rejects the push attempt' do
+ upload("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(401)
+ expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
+ end
end
end
- end
- context 'when username and personal access token are provided' do
- it 'allows clones' do
- download("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
- expect(response).to have_http_status(200)
+ context 'when username and personal access token are provided' do
+ it 'allows clones' do
+ download("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
+ expect(response).to have_http_status(200)
+ end
end
- end
- it 'allows pushes' do
- upload("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
- expect(response).to have_http_status(200)
+ it 'allows pushes' do
+ upload("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
+ expect(response).to have_http_status(200)
+ end
end
end
end
- end
- context "when blank password attempts follow a valid login" do
- def attempt_login(include_password)
- password = include_password ? user.password : ""
- clone_get path, user: user.username, password: password
- response.status
- end
+ context "when blank password attempts follow a valid login" do
+ def attempt_login(include_password)
+ password = include_password ? user.password : ""
+ clone_get path, user: user.username, password: password
+ response.status
+ end
- it "repeated attempts followed by successful attempt" do
- options = Gitlab.config.rack_attack.git_basic_auth
- maxretry = options[:maxretry] - 1
- ip = '1.2.3.4'
+ it "repeated attempts followed by successful attempt" do
+ options = Gitlab.config.rack_attack.git_basic_auth
+ maxretry = options[:maxretry] - 1
+ ip = '1.2.3.4'
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
- Rack::Attack::Allow2Ban.reset(ip, options)
+ allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
+ Rack::Attack::Allow2Ban.reset(ip, options)
- maxretry.times.each do
- expect(attempt_login(false)).to eq(401)
- end
+ maxretry.times.each do
+ expect(attempt_login(false)).to eq(401)
+ end
- expect(attempt_login(true)).to eq(200)
- expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
+ expect(attempt_login(true)).to eq(200)
+ expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
- maxretry.times.each do
- expect(attempt_login(false)).to eq(401)
- end
+ maxretry.times.each do
+ expect(attempt_login(false)).to eq(401)
+ end
- Rack::Attack::Allow2Ban.reset(ip, options)
+ Rack::Attack::Allow2Ban.reset(ip, options)
+ end
end
end
- end
- context "when the user doesn't have access to the project" do
- it "downloads get status 404" do
- download(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(404)
+ context "when the user doesn't have access to the project" do
+ it "downloads get status 404" do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(404)
+ end
end
- end
- it "uploads get status 404" do
- upload(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(404)
+ it "uploads get status 404" do
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(404)
+ end
end
end
end
end
- end
-
- context "when a gitlab ci token is provided" do
- let(:build) { create(:ci_build, :running) }
- let(:project) { build.project }
- let(:other_project) { create(:empty_project) }
-
- before do
- project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED)
- end
-
- context 'when build created by system is authenticated' do
- it "downloads get status 200" do
- clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
-
- expect(response).to have_http_status(200)
- expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- end
-
- it "uploads get status 401 (no project existence information leak)" do
- push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
-
- expect(response).to have_http_status(401)
- end
-
- it "downloads from other project get status 404" do
- clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
- expect(response).to have_http_status(404)
- end
- end
+ context "when a gitlab ci token is provided" do
+ let(:build) { create(:ci_build, :running) }
+ let(:project) { build.project }
+ let(:other_project) { create(:empty_project) }
- context 'and build created by' do
before do
- build.update(user: user)
- project.team << [user, :reporter]
+ project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED)
end
- shared_examples 'can download code only' do
- it 'downloads get status 200' do
+ context 'when build created by system is authenticated' do
+ it "downloads get status 200" do
clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
- it 'uploads get status 403' do
+ it "uploads get status 401 (no project existence information leak)" do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(401)
end
+
+ it "downloads from other project get status 404" do
+ clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+ expect(response).to have_http_status(404)
+ end
end
- context 'administrator' do
- let(:user) { create(:admin) }
+ context 'and build created by' do
+ before do
+ build.update(user: user)
+ project.team << [user, :reporter]
+ end
- it_behaves_like 'can download code only'
+ shared_examples 'can download code only' do
+ it 'downloads get status 200' do
+ clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
- it 'downloads from other project get status 403' do
- clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+ expect(response).to have_http_status(200)
+ expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
- expect(response).to have_http_status(403)
+ it 'uploads get status 403' do
+ push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+ expect(response).to have_http_status(401)
+ end
end
- end
- context 'regular user' do
- let(:user) { create(:user) }
+ context 'administrator' do
+ let(:user) { create(:admin) }
- it_behaves_like 'can download code only'
+ it_behaves_like 'can download code only'
- it 'downloads from other project get status 404' do
- clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+ it 'downloads from other project get status 403' do
+ clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'regular user' do
+ let(:user) { create(:user) }
+
+ it_behaves_like 'can download code only'
+
+ it 'downloads from other project get status 404' do
+ clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
+
+ expect(response).to have_http_status(404)
+ end
end
end
end
end
end
- end
- context "when the project path doesn't end in .git" do
- context "GET info/refs" do
- let(:path) { "/#{project.path_with_namespace}/info/refs" }
+ context "when the project path doesn't end in .git" do
+ context "GET info/refs" do
+ let(:path) { "/#{project.path_with_namespace}/info/refs" }
- context "when no params are added" do
- before { get path }
+ context "when no params are added" do
+ before { get path }
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
+ end
end
- end
- context "when the upload-pack service is requested" do
- let(:params) { { service: 'git-upload-pack' } }
- before { get path, params }
+ context "when the upload-pack service is requested" do
+ let(:params) { { service: 'git-upload-pack' } }
+ before { get path, params }
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ end
end
- end
- context "when the receive-pack service is requested" do
- let(:params) { { service: 'git-receive-pack' } }
- before { get path, params }
+ context "when the receive-pack service is requested" do
+ let(:params) { { service: 'git-receive-pack' } }
+ before { get path, params }
- it "redirects to the .git suffix version" do
- expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ end
end
- end
- context "when the params are anything else" do
- let(:params) { { service: 'git-implode-pack' } }
- before { get path, params }
+ context "when the params are anything else" do
+ let(:params) { { service: 'git-implode-pack' } }
+ before { get path, params }
- it "redirects to the sign-in page" do
- expect(response).to redirect_to(new_user_session_path)
+ it "redirects to the sign-in page" do
+ expect(response).to redirect_to(new_user_session_path)
+ end
end
end
- end
- context "POST git-upload-pack" do
- it "fails to find a route" do
- expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ context "POST git-upload-pack" do
+ it "fails to find a route" do
+ expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ end
end
- end
- context "POST git-receive-pack" do
- it "failes to find a route" do
- expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ context "POST git-receive-pack" do
+ it "failes to find a route" do
+ expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ end
end
end
- end
- context "retrieving an info/refs file" do
- before { project.update_attribute(:visibility_level, Project::PUBLIC) }
+ context "retrieving an info/refs file" do
+ before { project.update_attribute(:visibility_level, Project::PUBLIC) }
- context "when the file exists" do
- before do
- # Provide a dummy file in its place
- allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
- allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do
- Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore')
- end
+ context "when the file exists" do
+ before do
+ # Provide a dummy file in its place
+ allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
+ allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do
+ Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore')
+ end
- get "/#{project.path_with_namespace}/blob/master/info/refs"
- end
+ get "/#{project.path_with_namespace}/blob/master/info/refs"
+ end
- it "returns the file" do
- expect(response).to have_http_status(200)
+ it "returns the file" do
+ expect(response).to have_http_status(200)
+ end
end
- end
- context "when the file does not exist" do
- before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
+ context "when the file does not exist" do
+ before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
- it "returns not found" do
- expect(response).to have_http_status(404)
+ it "returns not found" do
+ expect(response).to have_http_status(404)
+ end
end
end
end
- def clone_get(project, options = {})
- get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
- end
-
- def clone_post(project, options = {})
- post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
- end
-
- def push_get(project, options = {})
- get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
- end
-
- def push_post(project, options = {})
- post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
- end
+ describe "User with LDAP identity" do
+ let(:user) { create(:omniauth_user, extern_uid: dn) }
+ let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
- def download(project, user: nil, password: nil, spnego_request_token: nil)
- args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }]
+ before do
+ allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
+ allow(Gitlab::LDAP::Authentication).to receive(:login).and_return(nil)
+ allow(Gitlab::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
+ end
- clone_get(*args)
- yield response
+ context "when authentication fails" do
+ context "when no authentication is provided" do
+ it "responds with status 401" do
+ download('doesnt/exist.git') do |response|
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
- clone_post(*args)
- yield response
- end
+ context "when username and invalid password are provided" do
+ it "responds with status 401" do
+ download('doesnt/exist.git', user: user.username, password: "nope") do |response|
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+ end
- def upload(project, user: nil, password: nil, spnego_request_token: nil)
- args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }]
+ context "when authentication succeeds" do
+ context "when the project doesn't exist" do
+ it "responds with status 404" do
+ download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
- push_get(*args)
- yield response
+ context "when the project exists" do
+ let(:project) { create(:project, path: 'project.git-project') }
- push_post(*args)
- yield response
- end
+ before do
+ project.team << [user, :master]
+ end
- def auth_env(user, password, spnego_request_token)
- env = workhorse_internal_api_request_header
- if user && password
- env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password)
- elsif spnego_request_token
- env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}"
+ it "responds with status 200" do
+ clone_get(path, user: user.username, password: user.password) do |response|
+ expect(response).to have_http_status(200)
+ end
+ end
+ end
end
-
- env
end
end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 6b956e63004..f0ef155bd7b 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -39,7 +39,7 @@ describe JwtController do
subject! { get '/jwt/auth', parameters, headers }
- it { expect(response).to have_http_status(403) }
+ it { expect(response).to have_http_status(401) }
end
end
@@ -77,7 +77,7 @@ describe JwtController do
subject! { get '/jwt/auth', parameters, headers }
- it { expect(response).to have_http_status(403) }
+ it { expect(response).to have_http_status(401) }
end
end
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 09e4e265dd1..dbdf83a0dff 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -257,6 +257,29 @@ describe 'Git LFS API and storage' do
it_behaves_like 'responds with a file'
end
+ describe 'when using a user key' do
+ let(:authorization) { authorize_user_key }
+
+ context 'when user allowed' do
+ let(:update_permissions) do
+ project.team << [user, :master]
+ project.lfs_objects << lfs_object
+ end
+
+ it_behaves_like 'responds with a file'
+ end
+
+ context 'when user not allowed' do
+ let(:update_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
context 'when build is authorized as' do
let(:authorization) { authorize_ci_project }
@@ -1110,7 +1133,11 @@ describe 'Git LFS API and storage' do
end
def authorize_deploy_key
- ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate)
+ ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).token)
+ end
+
+ def authorize_user_key
+ ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token)
end
def fork_project(project, user, object = nil)
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index cf4c5f13635..e65da15aca8 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -13,10 +13,10 @@ describe Boards::Issues::ListService, services: true do
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
- let!(:backlog) { create(:backlog_list, board: board) }
+ let!(:backlog) { project.board.backlog_list }
let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) }
- let!(:done) { create(:done_list, board: board) }
+ let!(:done) { project.board.done_list }
let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) }
let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) }
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 0122159cab8..180f1b08631 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -10,10 +10,10 @@ describe Boards::Issues::MoveService, services: true do
let(:development) { create(:label, project: project, name: 'Development') }
let(:testing) { create(:label, project: project, name: 'Testing') }
- let!(:backlog) { create(:backlog_list, board: board) }
+ let!(:backlog) { project.board.backlog_list }
let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) }
- let!(:done) { create(:done_list, board: board) }
+ let!(:done) { project.board.done_list }
before do
project.team << [user, :developer]
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index 90764b86b16..bff9c1fd1fe 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -17,17 +17,15 @@ describe Boards::Lists::CreateService, services: true do
end
end
- context 'when board lists has only a backlog list' do
+ context 'when board lists has backlog, and done lists' do
it 'creates a new list at beginning of the list' do
- create(:backlog_list, board: board)
-
list = service.execute
expect(list.position).to eq 0
end
end
- context 'when board lists has only labels lists' do
+ context 'when board lists has labels lists' do
it 'creates a new list at end of the lists' do
create(:list, board: board, position: 0)
create(:list, board: board, position: 1)
@@ -40,8 +38,6 @@ describe Boards::Lists::CreateService, services: true do
context 'when board lists has backlog, label and done lists' do
it 'creates a new list at end of the label lists' do
- create(:backlog_list, board: board)
- create(:done_list, board: board)
list1 = create(:list, board: board, position: 0)
list2 = service.execute
diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb
index 6eff445feee..474c4512471 100644
--- a/spec/services/boards/lists/destroy_service_spec.rb
+++ b/spec/services/boards/lists/destroy_service_spec.rb
@@ -15,11 +15,11 @@ describe Boards::Lists::DestroyService, services: true do
end
it 'decrements position of higher lists' do
- backlog = create(:backlog_list, board: board)
+ backlog = project.board.backlog_list
development = create(:list, board: board, position: 0)
review = create(:list, board: board, position: 1)
staging = create(:list, board: board, position: 2)
- done = create(:done_list, board: board)
+ done = project.board.done_list
described_class.new(project, user).execute(development)
@@ -31,14 +31,14 @@ describe Boards::Lists::DestroyService, services: true do
end
it 'does not remove list from board when list type is backlog' do
- list = create(:backlog_list, board: board)
+ list = project.board.backlog_list
service = described_class.new(project, user)
expect { service.execute(list) }.not_to change(board.lists, :count)
end
it 'does not remove list from board when list type is done' do
- list = create(:done_list, board: board)
+ list = project.board.done_list
service = described_class.new(project, user)
expect { service.execute(list) }.not_to change(board.lists, :count)
diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb
index 3e9b7d07fc6..102ed67449d 100644
--- a/spec/services/boards/lists/move_service_spec.rb
+++ b/spec/services/boards/lists/move_service_spec.rb
@@ -6,12 +6,12 @@ describe Boards::Lists::MoveService, services: true do
let(:board) { project.board }
let(:user) { create(:user) }
- let!(:backlog) { create(:backlog_list, board: board) }
+ let!(:backlog) { project.board.backlog_list }
let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) }
let!(:review) { create(:list, board: board, position: 2) }
let!(:staging) { create(:list, board: board, position: 3) }
- let!(:done) { create(:done_list, board: board) }
+ let!(:done) { project.board.done_list }
context 'when list type is set to label' do
it 'keeps position of lists when new position is nil' do
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 58569ba96c3..1050502fa19 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -20,16 +20,38 @@ describe Issues::CreateService, services: true do
let(:opts) do
{ title: 'Awesome issue',
description: 'please fix',
- assignee: assignee,
+ assignee_id: assignee.id,
label_ids: labels.map(&:id),
- milestone_id: milestone.id }
+ milestone_id: milestone.id,
+ due_date: Date.tomorrow }
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.labels).to match_array labels }
- it { expect(issue.milestone).to eq milestone }
+ it 'creates the issue with the given params' do
+ expect(issue).to be_persisted
+ expect(issue.title).to eq('Awesome issue')
+ expect(issue.assignee).to eq assignee
+ expect(issue.labels).to match_array labels
+ expect(issue.milestone).to eq milestone
+ expect(issue.due_date).to eq Date.tomorrow
+ end
+
+ context 'when current user cannot admin issues in the project' do
+ let(:guest) { create(:user) }
+ before do
+ project.team << [guest, :guest]
+ end
+
+ it 'filters out params that cannot be set without the :admin_issue permission' do
+ issue = described_class.new(project, guest, opts).execute
+
+ expect(issue).to be_persisted
+ expect(issue.title).to eq('Awesome issue')
+ expect(issue.assignee).to be_nil
+ expect(issue.labels).to be_empty
+ expect(issue.milestone).to be_nil
+ expect(issue.due_date).to be_nil
+ end
+ end
it 'creates a pending todo for new assignee' do
attributes = {
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 4f5375a3583..1638a46ed51 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -32,55 +32,84 @@ describe Issues::UpdateService, services: true do
described_class.new(project, user, opts).execute(issue)
end
- context "valid params" do
- before do
- opts = {
+ context 'valid params' do
+ let(:opts) do
+ {
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close',
- label_ids: [label.id]
+ label_ids: [label.id],
+ due_date: Date.tomorrow
}
-
- perform_enqueued_jobs do
- update_issue(opts)
- end
end
- it { expect(issue).to be_valid }
- it { expect(issue.title).to eq('New title') }
- it { expect(issue.assignee).to eq(user2) }
- it { expect(issue).to be_closed }
- it { expect(issue.labels.count).to eq(1) }
- it { expect(issue.labels.first.title).to eq(label.name) }
-
- it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
- deliveries = ActionMailer::Base.deliveries
- email = deliveries.last
- recipients = deliveries.last(2).map(&:to).flatten
- expect(recipients).to include(user2.email, user3.email)
- expect(email.subject).to include(issue.title)
+ it 'updates the issue with the given params' do
+ update_issue(opts)
+
+ expect(issue).to be_valid
+ expect(issue.title).to eq 'New title'
+ expect(issue.description).to eq 'Also please fix'
+ expect(issue.assignee).to eq user2
+ expect(issue).to be_closed
+ expect(issue.labels).to match_array [label]
+ expect(issue.due_date).to eq Date.tomorrow
end
- it 'creates system note about issue reassign' do
- note = find_note('Reassigned to')
+ context 'when current user cannot admin issues in the project' do
+ let(:guest) { create(:user) }
+ before do
+ project.team << [guest, :guest]
+ end
- expect(note).not_to be_nil
- expect(note.note).to include "Reassigned to \@#{user2.username}"
+ it 'filters out params that cannot be set without the :admin_issue permission' do
+ described_class.new(project, guest, opts).execute(issue)
+
+ expect(issue).to be_valid
+ expect(issue.title).to eq 'New title'
+ expect(issue.description).to eq 'Also please fix'
+ expect(issue.assignee).to eq user3
+ expect(issue.labels).to be_empty
+ expect(issue.milestone).to be_nil
+ expect(issue.due_date).to be_nil
+ end
end
- it 'creates system note about issue label edit' do
- note = find_note('Added ~')
+ context 'with background jobs processed' do
+ before do
+ perform_enqueued_jobs do
+ update_issue(opts)
+ end
+ end
+
+ it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
+ deliveries = ActionMailer::Base.deliveries
+ email = deliveries.last
+ recipients = deliveries.last(2).map(&:to).flatten
+ expect(recipients).to include(user2.email, user3.email)
+ expect(email.subject).to include(issue.title)
+ end
- expect(note).not_to be_nil
- expect(note.note).to include "Added ~#{label.id} label"
- end
+ it 'creates system note about issue reassign' do
+ note = find_note('Reassigned to')
- it 'creates system note about title change' do
- note = find_note('Changed title:')
+ expect(note).not_to be_nil
+ expect(note.note).to include "Reassigned to \@#{user2.username}"
+ end
- expect(note).not_to be_nil
- expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
+ it 'creates system note about issue label edit' do
+ note = find_note('Added ~')
+
+ expect(note).not_to be_nil
+ expect(note.note).to include "Added ~#{label.id} label"
+ end
+
+ it 'creates system note about title change' do
+ note = find_note('Changed title:')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
+ end
end
end
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
new file mode 100644
index 00000000000..6fca80b5613
--- /dev/null
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+describe Members::ApproveAccessRequestService, services: true do
+ let(:user) { create(:user) }
+ let(:access_requester) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:group) { create(:group, :public) }
+
+ shared_examples 'a service raising ActiveRecord::RecordNotFound' do
+ it 'raises ActiveRecord::RecordNotFound' do
+ expect { described_class.new(source, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
+ it 'raises Gitlab::Access::AccessDeniedError' do
+ expect { described_class.new(source, user, params).execute }.to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
+
+ shared_examples 'a service approving an access request' do
+ it 'succeeds' do
+ expect { described_class.new(source, user, params).execute }.to change { source.requesters.count }.by(-1)
+ end
+
+ it 'returns a <Source>Member' do
+ member = described_class.new(source, user, params).execute
+
+ expect(member).to be_a "#{source.class.to_s}Member".constantize
+ expect(member.requested_at).to be_nil
+ end
+
+ context 'with a custom access level' do
+ let(:params) { { user_id: access_requester.id, access_level: Gitlab::Access::MASTER } }
+
+ it 'returns a ProjectMember with the custom access level' do
+ member = described_class.new(source, user, params).execute
+
+ expect(member.access_level).to eq Gitlab::Access::MASTER
+ end
+ end
+ end
+
+ context 'when no access requester are found' do
+ let(:params) { { user_id: 42 } }
+
+ it_behaves_like 'a service raising ActiveRecord::RecordNotFound' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'a service raising ActiveRecord::RecordNotFound' do
+ let(:source) { group }
+ end
+ end
+
+ context 'when an access requester is found' do
+ before do
+ project.request_access(access_requester)
+ group.request_access(access_requester)
+ end
+ let(:params) { { user_id: access_requester.id } }
+
+ context 'when current user cannot approve access request to the project' do
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ let(:source) { group }
+ end
+ end
+
+ context 'when current user can approve access request to the project' do
+ before do
+ project.team << [user, :master]
+ group.add_owner(user)
+ end
+
+ it_behaves_like 'a service approving an access request' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'a service approving an access request' do
+ let(:source) { group }
+ end
+
+ context 'when given a :id' do
+ let(:params) { { id: project.requesters.find_by!(user_id: access_requester.id).id } }
+
+ it_behaves_like 'a service approving an access request' do
+ let(:source) { project }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index a616275e883..5b1edba87a1 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -1,19 +1,19 @@
require 'spec_helper'
describe SlashCommands::InterpretService, services: true do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :public) }
+ let(:developer) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project, title: '9.10') }
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
let(:bug) { create(:label, project: project, title: 'Bug') }
before do
- project.team << [user, :developer]
+ project.team << [developer, :developer]
end
describe '#execute' do
- let(:service) { described_class.new(project, user) }
+ let(:service) { described_class.new(project, developer) }
let(:merge_request) { create(:merge_request, source_project: project) }
shared_examples 'reopen command' do
@@ -45,13 +45,13 @@ describe SlashCommands::InterpretService, services: true do
it 'fetches assignee and populates assignee_id if content contains /assign' do
_, updates = service.execute(content, issuable)
- expect(updates).to eq(assignee_id: user.id)
+ expect(updates).to eq(assignee_id: developer.id)
end
end
shared_examples 'unassign command' do
it 'populates assignee_id: nil if content contains /unassign' do
- issuable.update(assignee_id: user.id)
+ issuable.update(assignee_id: developer.id)
_, updates = service.execute(content, issuable)
expect(updates).to eq(assignee_id: nil)
@@ -124,7 +124,7 @@ describe SlashCommands::InterpretService, services: true do
shared_examples 'done command' do
it 'populates todo_event: "done" if content contains /done' do
- TodoService.new.mark_todo(issuable, user)
+ TodoService.new.mark_todo(issuable, developer)
_, updates = service.execute(content, issuable)
expect(updates).to eq(todo_event: 'done')
@@ -141,7 +141,7 @@ describe SlashCommands::InterpretService, services: true do
shared_examples 'unsubscribe command' do
it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do
- issuable.subscribe(user)
+ issuable.subscribe(developer)
_, updates = service.execute(content, issuable)
expect(updates).to eq(subscription_event: 'unsubscribe')
@@ -209,12 +209,12 @@ describe SlashCommands::InterpretService, services: true do
end
it_behaves_like 'assign command' do
- let(:content) { "/assign @#{user.username}" }
+ let(:content) { "/assign @#{developer.username}" }
let(:issuable) { issue }
end
it_behaves_like 'assign command' do
- let(:content) { "/assign @#{user.username}" }
+ let(:content) { "/assign @#{developer.username}" }
let(:issuable) { merge_request }
end
@@ -380,5 +380,56 @@ describe SlashCommands::InterpretService, services: true do
let(:content) { '/remove_due_date' }
let(:issuable) { merge_request }
end
+
+ context 'when current_user cannot :admin_issue' do
+ let(:visitor) { create(:user) }
+ let(:issue) { create(:issue, project: project, author: visitor) }
+ let(:service) { described_class.new(project, visitor) }
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/assign @#{developer.username}" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/unassign' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/milestone %#{milestone.title}" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/remove_milestone' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { %(/unlabel ~"#{inprogress.title}") }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { %(/relabel ~"#{inprogress.title}") }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/due tomorrow' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/remove_due_date' }
+ let(:issuable) { issue }
+ end
+ end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 02b2b3ca101..b19f5824236 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -26,7 +26,7 @@ RSpec.configure do |config|
config.verbose_retry = true
config.display_try_failure_messages = true
- config.include Devise::TestHelpers, type: :controller
+ config.include Devise::Test::ControllerHelpers, type: :controller
config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :feature
config.include StubConfiguration
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index e8e760a6187..62a5b46d47b 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -4,24 +4,28 @@ module CycleAnalyticsHelpers
create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
end
- def create_commit(message, project, user, branch_name)
- filename = random_git_name
+ def create_commit(message, project, user, branch_name, count: 1)
oldrev = project.repository.commit(branch_name).sha
+ commit_shas = Array.new(count) do |index|
+ filename = random_git_name
- options = {
- committer: project.repository.user_to_committer(user),
- author: project.repository.user_to_committer(user),
- commit: { message: message, branch: branch_name, update_ref: true },
- file: { content: "content", path: filename, update: false }
- }
+ options = {
+ committer: project.repository.user_to_committer(user),
+ author: project.repository.user_to_committer(user),
+ commit: { message: message, branch: branch_name, update_ref: true },
+ file: { content: "content", path: filename, update: false }
+ }
+
+ commit_sha = Gitlab::Git::Blob.commit(project.repository, options)
+ project.repository.commit(commit_sha)
- commit_sha = Gitlab::Git::Blob.commit(project.repository, options)
- project.repository.commit(commit_sha)
+ commit_sha
+ end
GitPushService.new(project,
user,
oldrev: oldrev,
- newrev: commit_sha,
+ newrev: commit_shas.last,
ref: 'refs/heads/master').execute
end
diff --git a/spec/support/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 5e3b8f2b23e..5e3b8f2b23e 100644
--- a/spec/support/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
diff --git a/spec/support/git_http_helpers.rb b/spec/support/git_http_helpers.rb
new file mode 100644
index 00000000000..46b686fce94
--- /dev/null
+++ b/spec/support/git_http_helpers.rb
@@ -0,0 +1,48 @@
+module GitHttpHelpers
+ def clone_get(project, options = {})
+ get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ end
+
+ def clone_post(project, options = {})
+ post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ end
+
+ def push_get(project, options = {})
+ get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ end
+
+ def push_post(project, options = {})
+ post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ end
+
+ def download(project, user: nil, password: nil, spnego_request_token: nil)
+ args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }]
+
+ clone_get(*args)
+ yield response
+
+ clone_post(*args)
+ yield response
+ end
+
+ def upload(project, user: nil, password: nil, spnego_request_token: nil)
+ args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }]
+
+ push_get(*args)
+ yield response
+
+ push_post(*args)
+ yield response
+ end
+
+ def auth_env(user, password, spnego_request_token)
+ env = workhorse_internal_api_request_header
+ if user && password
+ env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password)
+ elsif spnego_request_token
+ env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}"
+ end
+
+ env
+ end
+end
diff --git a/spec/support/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index 5f9645ed44f..5f9645ed44f 100644
--- a/spec/support/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
diff --git a/spec/support/snippets_shared_examples.rb b/spec/support/snippets_shared_examples.rb
new file mode 100644
index 00000000000..57dfff3471f
--- /dev/null
+++ b/spec/support/snippets_shared_examples.rb
@@ -0,0 +1,18 @@
+# These shared examples expect a `snippets` array of snippets
+RSpec.shared_examples 'paginated snippets' do |remote: false|
+ it "is limited to #{Snippet.default_per_page} items per page" do
+ expect(page.all('.snippets-list-holder .snippet-row').count).to eq(Snippet.default_per_page)
+ end
+
+ context 'clicking on the link to the second page' do
+ before do
+ click_link('2')
+ wait_for_ajax if remote
+ end
+
+ it 'shows the remaining snippets' do
+ remaining_snippets_count = [snippets.size - Snippet.default_per_page, Snippet.default_per_page].min
+ expect(page).to have_selector('.snippets-list-holder .snippet-row', count: remaining_snippets_count)
+ end
+ end
+end
diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb
index dae858a52f6..68d2d72876e 100644
--- a/spec/views/admin/dashboard/index.html.haml_spec.rb
+++ b/spec/views/admin/dashboard/index.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'admin/dashboard/index.html.haml' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
before do
assign(:projects, create_list(:empty_project, 1))
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index 446ba3bfa14..da43622d3f9 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/builds/show' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
let(:project) { create(:project) }
let(:pipeline) do
diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
index 78af61f15a7..c8a3d02d8fd 100644
--- a/spec/views/projects/issues/_related_branches.html.haml_spec.rb
+++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/issues/_related_branches' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
let(:project) { create(:project) }
let(:branch) { project.repository.find_branch('feature') }
diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
index 21f49d396e7..86980f59cd8 100644
--- a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/merge_requests/widget/_heading' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
context 'when released to an environment' do
let(:project) { merge_request.target_project }
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 31bbb150698..26ea252fecb 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/merge_requests/edit.html.haml' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index fe0780e72df..68fbb4585c1 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/merge_requests/show.html.haml' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/views/projects/notes/_form.html.haml_spec.rb b/spec/views/projects/notes/_form.html.haml_spec.rb
index 932d6756ad2..b14b1ece2d0 100644
--- a/spec/views/projects/notes/_form.html.haml_spec.rb
+++ b/spec/views/projects/notes/_form.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/notes/_form' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb
index ac7f3ffb157..bf027499c94 100644
--- a/spec/views/projects/pipelines/show.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/show.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/pipelines/show' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 0f3fc1ee1ac..c381b1a86df 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'projects/tree/show' do
- include Devise::TestHelpers
+ include Devise::Test::ControllerHelpers
let(:project) { create(:project) }
let(:repository) { project.repository }