summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2016-11-04 02:41:01 +0800
committerLin Jen-Shin <godfat@godfat.org>2016-11-04 02:41:01 +0800
commit6a00cf1b9da3bc8bb1a9a270defd154bf7c93580 (patch)
treee82ee2a2bc25249f1a0f1a52344f277f87c3c010
parentf3c3d8e63ba078e55c0ce516e19ec11cea429e43 (diff)
parent3ac3106e585273b1d9c673c71f794ae018698f83 (diff)
downloadgitlab-ce-6a00cf1b9da3bc8bb1a9a270defd154bf7c93580.tar.gz
Merge remote-tracking branch 'upstream/master' into show-status-from-branch
* upstream/master: (74 commits) Clarify the author field for the changelog documentation Add and update .gitignore & .gitlab-ci.yml templates for 8.14 Update "Installation from source" guide for 8.14.0 Add CHANGELOG entries for latest patches Merge branch 'fix/import-export-symlink-vulnerability' into 'security' Merge branch 'fix/import-projectmember-security' into 'security' Use stubs instead of modifying global states Add changelog instructions to CHANGELOG.md Try not to include anything globally! Update help banner for bin/changelog Add a `--force` option to bin/changelog Update examples in changelog docs to use single quotes around title Use the server's base URL without relative URL part when creating links in JIRA Update docs and test description Update docs and unexpose token Make ESLint ignore instrumented files for coverage analysis (!7236) Initialize form validation on new group form. Check that JavaScript file names match convention (!7238) Unchange username_validator. Move snake_case to camelCase. ...
-rw-r--r--.eslintignore1
-rw-r--r--.eslintrc6
-rw-r--r--CHANGELOG.md40
-rw-r--r--CONTRIBUTING.md17
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js.es62
-rw-r--r--app/assets/javascripts/dispatcher.js.es62
-rw-r--r--app/assets/javascripts/gl_field_error.js.es6164
-rw-r--r--app/assets/javascripts/gl_field_errors.js.es6130
-rw-r--r--app/assets/javascripts/merge_request_tabs.js18
-rw-r--r--app/assets/javascripts/single_file_diff.js19
-rw-r--r--app/assets/javascripts/star.js2
-rw-r--r--app/assets/stylesheets/framework/avatar.scss63
-rw-r--r--app/assets/stylesheets/framework/forms.scss32
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss87
-rw-r--r--app/assets/stylesheets/framework/header.scss6
-rw-r--r--app/assets/stylesheets/framework/lists.scss4
-rw-r--r--app/assets/stylesheets/framework/pagination.scss62
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss5
-rw-r--r--app/assets/stylesheets/pages/boards.scss9
-rw-r--r--app/assets/stylesheets/pages/builds.scss26
-rw-r--r--app/assets/stylesheets/pages/dashboard.scss4
-rw-r--r--app/assets/stylesheets/pages/groups.scss9
-rw-r--r--app/assets/stylesheets/pages/login.scss30
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/helpers/events_helper.rb6
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/policies/project_policy.rb12
-rw-r--r--app/services/merge_requests/build_service.rb50
-rw-r--r--app/views/admin/appearances/preview.html.haml2
-rw-r--r--app/views/admin/groups/_group.html.haml3
-rw-r--r--app/views/admin/groups/show.html.haml3
-rw-r--r--app/views/admin/projects/index.html.haml3
-rw-r--r--app/views/devise/confirmations/new.html.haml2
-rw-r--r--app/views/devise/passwords/edit.html.haml2
-rw-r--r--app/views/devise/passwords/new.html.haml2
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml4
-rw-r--r--app/views/devise/unlocks/new.html.haml2
-rw-r--r--app/views/groups/edit.html.haml5
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/groups/show.html.haml3
-rw-r--r--app/views/kaminari/gitlab/_gap.html.haml4
-rw-r--r--app/views/kaminari/gitlab/_page.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml85
-rw-r--r--app/views/projects/_home_panel.html.haml3
-rw-r--r--app/views/projects/boards/components/_board.html.haml4
-rw-r--r--app/views/projects/builds/_header.html.haml29
-rw-r--r--app/views/projects/builds/_sidebar.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml4
-rw-r--r--app/views/projects/buttons/_star.html.haml2
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/wikis/_form.html.haml9
-rw-r--r--app/views/projects/wikis/_main_links.html.haml3
-rw-r--r--app/views/projects/wikis/edit.html.haml2
-rw-r--r--app/views/shared/_event_filter.html.haml9
-rw-r--r--app/views/shared/_group_form.html.haml6
-rw-r--r--app/views/shared/groups/_group.html.haml3
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml4
-rw-r--r--app/views/shared/projects/_project.html.haml9
-rw-r--r--app/views/users/_groups.html.haml3
-rw-r--r--app/workers/post_receive.rb6
-rwxr-xr-xbin/changelog170
-rw-r--r--config/gitlab.yml.example4
-rw-r--r--config/initializers/0_post_deployment_migrations.rb12
-rw-r--r--config/initializers/sidekiq.rb16
-rw-r--r--db/post_migrate/.gitkeep0
-rw-r--r--doc/administration/raketasks/maintenance.md220
-rw-r--r--doc/administration/troubleshooting/debug.md2
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/api/system_hooks.md30
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/changelog.md185
-rw-r--r--doc/development/post_deployment_migrations.md75
-rw-r--r--doc/development/testing.md36
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/integration/jira.md4
-rw-r--r--doc/integration/saml.md21
-rw-r--r--doc/raketasks/maintenance.md189
-rw-r--r--doc/update/8.13-to-8.14.md205
-rw-r--r--doc/update/README.md2
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md482
-rw-r--r--features/dashboard/active_tab.feature6
-rw-r--r--features/dashboard/dashboard.feature1
-rw-r--r--features/steps/dashboard/help.rb2
-rw-r--r--features/steps/shared/sidebar_active_tab.rb14
-rw-r--r--generator_templates/rails/post_deployment_migration/migration.rb22
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/api/project_hooks.rb6
-rw-r--r--lib/api/system_hooks.rb9
-rw-r--r--lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb15
-rw-r--r--lib/gitlab/import_export/attribute_cleaner.rb23
-rw-r--r--lib/gitlab/import_export/file_importer.rb8
-rw-r--r--lib/gitlab/import_export/members_mapper.rb7
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb10
-rw-r--r--lib/gitlab/import_export/relation_factory.rb11
-rw-r--r--lib/gitlab/import_export/version_checker.rb9
-rw-r--r--lib/tasks/gitlab/shell.rake15
-rw-r--r--package.json1
-rw-r--r--spec/bin/changelog_spec.rb77
-rw-r--r--spec/features/boards/boards_spec.rb8
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb6
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb8
-rw-r--r--spec/features/projects/builds_spec.rb4
-rw-r--r--spec/features/projects/features_visibility_spec.rb18
-rw-r--r--spec/javascripts/fixtures/gl_field_errors.html.haml4
-rw-r--r--spec/javascripts/gl_field_errors_spec.js.es64
-rw-r--r--spec/lib/gitlab/git_access_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb42
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb14
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb16
-rw-r--r--spec/models/project_services/jira_service_spec.rb25
-rw-r--r--spec/policies/project_policy_spec.rb14
-rw-r--r--spec/rake_helper.rb19
-rw-r--r--spec/requests/api/project_hooks_spec.rb32
-rw-r--r--spec/requests/api/system_hooks_spec.rb14
-rw-r--r--spec/requests/lfs_http_spec.rb12
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb28
-rw-r--r--spec/services/merge_requests/build_service_spec.rb49
-rw-r--r--spec/support/import_export/common_util.rb10
-rw-r--r--spec/support/rake_helpers.rb10
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb26
-rw-r--r--vendor/assets/javascripts/Sortable.js199
-rw-r--r--vendor/gitignore/Android.gitignore3
-rw-r--r--vendor/gitignore/C.gitignore15
-rw-r--r--vendor/gitignore/ExtJs.gitignore8
-rw-r--r--vendor/gitignore/Global/JetBrains.gitignore3
-rw-r--r--vendor/gitignore/Global/macOS.gitignore52
-rw-r--r--vendor/gitignore/LICENSE211
-rw-r--r--vendor/gitignore/Laravel.gitignore1
-rw-r--r--vendor/gitignore/Nanoc.gitignore4
-rw-r--r--vendor/gitignore/OpenCart.gitignore7
-rw-r--r--vendor/gitignore/Python.gitignore2
-rw-r--r--vendor/gitignore/Rust.gitignore3
-rw-r--r--vendor/gitignore/TeX.gitignore9
-rw-r--r--vendor/gitignore/UnrealEngine.gitignore3
-rw-r--r--vendor/gitignore/VisualStudio.gitignore7
-rw-r--r--vendor/gitlab-ci-yml/Clojure.gitlab-ci.yml22
-rw-r--r--vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml37
145 files changed, 3070 insertions, 862 deletions
diff --git a/.eslintignore b/.eslintignore
index 453747e14e1..a57137b4d70 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
+/coverage-javascript/
/public/
/tmp/
/vendor/
diff --git a/.eslintrc b/.eslintrc
index 16eb18ecba2..b58007d90a9 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,5 +1,11 @@
{
"extends": "airbnb",
+ "plugins": [
+ "filenames"
+ ],
+ "rules": {
+ "filenames/match-regex": [2, "^[a-z_]+$"]
+ },
"globals": {
"$": false,
"_": false,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67dafd4e422..edcfedece5b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,29 +1,42 @@
-Please view this file on the master branch, on stable branches it's out of date.
+**Note:** This file is automatically generated. Please see the [developer
+documentation](doc/development/changelog.md) for instructions on adding your own
+entry.
## 8.14.0 (2016-11-22)
+
+- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
- Backups do not fail anymore when using tar on annex and custom_hooks only. !5814
- Adds user project membership expired event to clarify why user was removed (Callum Dryden)
- Trim leading and trailing whitespace on project_path (Linus Thiel)
- Prevent award emoji via notes for issues/MRs authored by user (barthc)
+- Adds support for the `token` attribute in project hooks API (Gauvain Pocentek)
- Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO)
- Fix Markdown styling inside reference links (Jan Zdráhal)
- Fix extra space on Build sidebar on Firefox !7060
+- Fail gracefully when creating merge request with non-existing branch (alexsanford)
- Fix mobile layout issues in admin user overview page !7087
- Fix HipChat notifications rendering (airatshigapov, eisnerd)
+- Remove 'Edit' button from wiki edit view !7143 (Hiroyuki Sato)
- Refactor Jira service to use jira-ruby gem
- Improved todos empty state
- Add hover to trash icon in notes !7008 (blackst0ne)
+- Hides project activity tabs when features are disabled
- Only show one error message for an invalid email !5905 (lycoperdon)
+- Added guide describing how to upgrade PostgreSQL using Slony
- Fix sidekiq stats in admin area (blackst0ne)
+- Added label description as tooltip to issue board list title
- Created cycle analytics bundle JavaScript file
- API: Fix booleans not recognized as such when using the `to_boolean` helper
- Removed delete branch tooltip !6954
- Stop unauthorized users dragging on milestone page (blackst0ne)
- Restore issue boards welcome message when a project is created !6899
+- Check that JavaScript file names match convention !7238 (winniehell)
- Do not show tooltip for active element !7105 (winniehell)
- Escape ref and path for relative links !6050 (winniehell)
- Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose)
+- Fix broken issue/merge request links in JIRA comments. !6143 (Brian Kintz)
- Fix filtering of milestones with quotes in title (airatshigapov)
+- Fix issue boards dragging bug in Safari
- Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison)
- Fix showing pipeline status for a given commit from correct branch !7034
- Update mail_room and enable sentinel support to Reply By Email (!7101)
@@ -33,24 +46,28 @@ Please view this file on the master branch, on stable branches it's out of date.
- New issue board list dropdown stays open after adding a new list
- Fix: Backup restore doesn't clear cache
- Optimize Event queries by removing default order
+- Remove duplicate links from sidebar
- API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh)
+- Add Rake task to create/repair GitLab Shell hooks symlinks !5634
- Add job for removal of unreferenced LFS objects from both the database and the filesystem (Frank Groeneveld)
- Replace jquery.cookie plugin with js.cookie !7085
- Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method
- Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens
- Show full status link on MR & commit pipelines
- Fix documents and comments on Build API `scope`
+- Initialize Sidekiq with the list of queues used by GitLab
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
- Shortened merge request modal to let clipboard button not overlap
+- Adds JavaScript validation for group path editing field
- In all filterable drop downs, put input field in focus only after load is complete (Ido @leibo)
- Improve search query parameter naming in /admin/users !7115 (YarNayar)
+- Fix table pagination to be responsive
- Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar)
-## 8.13.3
+## 8.13.3 (2016-11-02)
-- Fix relative links in Markdown wiki when displayed in "Project" tab !7218
-- Reduce the overhead to calculate number of open/closed issues and merge requests within the group or project
-- Fix project features default values
+- Removes any symlinks before importing a project export file. CVE-2016-9086
+- Fixed Import/Export foreign key issue to do with project members.
## 8.13.2 (2016-10-31)
@@ -240,6 +257,11 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix broken Project API docs (Takuya Noguchi)
- Migrate invalid project members (owner -> master)
+## 8.12.8 (2016-11-02)
+
+- Removes any symlinks before importing a project export file. CVE-2016-9086
+- Fixed Import/Export foreign key issue to do with project members.
+
## 8.12.7
- Prevent running `GfmAutocomplete` setup for each diff note. !6569
@@ -499,6 +521,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix non-master branch readme display in tree view
- Add UX improvements for merge request version diffs
+## 8.11.10 (2016-11-02)
+
+- Removes any symlinks before importing a project export file. CVE-2016-9086
+
## 8.11.9
- Don't send Private-Token (API authentication) headers to Sentry
@@ -739,6 +765,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Update gitlab_git gem to 10.4.7
- Simplify SQL queries of marking a todo as done
+## 8.10.13 (2016-11-02)
+
+- Removes any symlinks before importing a project export file. CVE-2016-9086
+
## 8.10.12
- Don't send Private-Token (API authentication) headers to Sentry
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b4635e50c28..67c30c2424c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,7 +19,6 @@
- [Technical debt](#technical-debt)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- - [Merge request description format](#merge-request-description-format)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done)
@@ -247,13 +246,7 @@ request is as follows:
1. Fork the project into your personal space on GitLab.com
1. Create a feature branch, branch away from `master`
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
-1. Add your changes to the [CHANGELOG.md](CHANGELOG.md):
- 1. If you are fixing a ~regression issue, you can add your entry to the next
- patch release (e.g. `8.12.5` if current version is `8.12.4`)
- 1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if
- current version is `8.12.4`
- 1. Please add your entry at a random place among the entries of the targeted
- release
+1. [Generate a changelog entry with `bin/changelog`][changelog]
1. If you are writing documentation, make sure to follow the
[documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by
@@ -262,8 +255,11 @@ request is as follows:
1. Submit a merge request (MR) to the `master` branch
1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you
- used to achieve it, see the [merge request description format]
- (#merge-request-description-format)
+ used to achieve it.
+ 1. If you are contributing code, fill in the template already provided in the
+ "Description" field.
+ 1. If you are contributing documentation, choose `Documentation` from the
+ "Choose a template" menu and fill in the template.
1. If the MR changes the UI it should include *Before* and *After* screenshots
1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R`
@@ -469,6 +465,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[contributor-covenant]: http://contributor-covenant.org
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
+[changelog]: doc/development/changelog.md "Generate a changelog entry"
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
diff --git a/Gemfile b/Gemfile
index 7e94c4051da..af82ae16a56 100644
--- a/Gemfile
+++ b/Gemfile
@@ -117,7 +117,7 @@ gem 'truncato', '~> 0.7.8'
gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
# Diffs
-gem 'diffy', '~> 3.0.3'
+gem 'diffy', '~> 3.1.0'
# Application server
group :unicorn do
@@ -196,7 +196,7 @@ gem 'loofah', '~> 2.0.3'
gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing
-gem 'rack-attack', '~> 4.3.1'
+gem 'rack-attack', '~> 4.4.1'
# Ace editor
gem 'ace-rails-ap', '~> 4.1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 81af1ad2dac..888fa6b2bf5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -180,7 +180,7 @@ GEM
railties
rotp (~> 2.0)
diff-lcs (1.2.5)
- diffy (3.0.7)
+ diffy (3.1.0)
docile (1.1.5)
doorkeeper (4.2.0)
railties (>= 4.2)
@@ -520,7 +520,7 @@ GEM
rack (1.6.4)
rack-accept (0.4.5)
rack (>= 0.4)
- rack-attack (4.3.1)
+ rack-attack (4.4.1)
rack
rack-cors (0.4.0)
rack-mount (0.8.3)
@@ -847,7 +847,7 @@ DEPENDENCIES
default_value_for (~> 3.0.0)
devise (~> 4.2)
devise-two-factor (~> 3.0.0)
- diffy (~> 3.0.3)
+ diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
@@ -929,7 +929,7 @@ DEPENDENCIES
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
pry-rails (~> 0.3.4)
- rack-attack (~> 4.3.1)
+ rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rails (= 4.2.7.1)
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
index e520170ef74..db9a5a8e40a 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
@@ -22,7 +22,7 @@
fallbackClass: 'is-dragging',
fallbackOnBody: true,
ghostClass: 'is-ghost',
- filter: '.has-tooltip, .btn',
+ filter: '.board-delete, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 50,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20,
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index ff8b8f6d0ae..8e4fd1f19ba 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -299,7 +299,7 @@
};
Dispatcher.prototype.initFieldErrors = function() {
- $('.show-gl-field-errors').each((i, form) => {
+ $('.gl-show-field-errors').each((i, form) => {
new gl.GlFieldErrors(form);
});
};
diff --git a/app/assets/javascripts/gl_field_error.js.es6 b/app/assets/javascripts/gl_field_error.js.es6
new file mode 100644
index 00000000000..f7cbecc0385
--- /dev/null
+++ b/app/assets/javascripts/gl_field_error.js.es6
@@ -0,0 +1,164 @@
+/* eslint-disable no-param-reassign */
+((global) => {
+ /*
+ * This class overrides the browser's validation error bubbles, displaying custom
+ * error messages for invalid fields instead. To begin validating any form, add the
+ * class `gl-show-field-errors` to the form element, and ensure error messages are
+ * declared in each inputs' `title` attribute. If no title is declared for an invalid
+ * field the user attempts to submit, "This field is required." will be shown by default.
+ *
+ * Opt not to validate certain fields by adding the class `gl-field-error-ignore` to the input.
+ *
+ * Set a custom error anchor for error message to be injected after with the
+ * class `gl-field-error-anchor`
+ *
+ * Examples:
+ *
+ * Basic:
+ *
+ * <form class='gl-show-field-errors'>
+ * <input type='text' name='username' title='Username is required.'/>
+ * </form>
+ *
+ * Ignore specific inputs (e.g. UsernameValidator):
+ *
+ * <form class='gl-show-field-errors'>
+ * <div class="form-group>
+ * <input type='text' class='gl-field-errors-ignore' pattern='[a-zA-Z0-9-_]+'/>
+ * </div>
+ * <div class="form-group">
+ * <input type='text' name='username' title='Username is required.'/>
+ * </div>
+ * </form>
+ *
+ * Custom Error Anchor (allows error message to be injected after specified element):
+ *
+ * <form class='gl-show-field-errors'>
+ * <div class="form-group gl-field-error-anchor">
+ * <input type='text' name='username' title='Username is required.'/>
+ * // Error message typically injected here
+ * </div>
+ * // Error message now injected here
+ * </form>
+ *
+ * */
+
+ /*
+ * Regex Patterns in use:
+ *
+ * Only alphanumeric: : "[a-zA-Z0-9]+"
+ * No special characters : "[a-zA-Z0-9-_]+",
+ *
+ * */
+
+ const errorMessageClass = 'gl-field-error';
+ const inputErrorClass = 'gl-field-error-outline';
+ const errorAnchorSelector = '.gl-field-error-anchor';
+ const ignoreInputSelector = '.gl-field-error-ignore';
+
+ class GlFieldError {
+ constructor({ input, formErrors }) {
+ this.inputElement = $(input);
+ this.inputDomElement = this.inputElement.get(0);
+ this.form = formErrors;
+ this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
+ this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`);
+
+ this.state = {
+ valid: false,
+ empty: true,
+ };
+
+ this.initFieldValidation();
+ }
+
+ initFieldValidation() {
+ const customErrorAnchor = this.inputElement.parents(errorAnchorSelector);
+ const errorAnchor = customErrorAnchor.length ? customErrorAnchor : this.inputElement;
+
+ // hidden when injected into DOM
+ errorAnchor.after(this.fieldErrorElement);
+ this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
+ this.scopedSiblings = this.safelySelectSiblings();
+ }
+
+ safelySelectSiblings() {
+ // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled
+ const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreInputSelector})`);
+ const parentContainer = this.inputElement.parent('.form-group');
+
+ // Only select siblings when they're scoped within a form-group with one input
+ const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
+
+ return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
+ }
+
+ renderValidity() {
+ this.renderClear();
+
+ if (this.state.valid) {
+ this.renderValid();
+ } else if (this.state.empty) {
+ this.renderEmpty();
+ } else if (!this.state.valid) {
+ this.renderInvalid();
+ }
+ }
+
+ handleInvalidSubmit(event) {
+ event.preventDefault();
+ const currentValue = this.accessCurrentValue();
+ this.state.valid = false;
+ this.state.empty = currentValue === '';
+
+ this.renderValidity();
+ this.form.focusOnFirstInvalid.apply(this.form);
+ // For UX, wait til after first invalid submission to check each keyup
+ this.inputElement.off('keyup.fieldValidator')
+ .on('keyup.fieldValidator', this.updateValidity.bind(this));
+ }
+
+ /* Get or set current input value */
+ accessCurrentValue(newVal) {
+ return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
+ }
+
+ getInputValidity() {
+ return this.inputDomElement.validity.valid;
+ }
+
+ updateValidity() {
+ const inputVal = this.accessCurrentValue();
+ this.state.empty = !inputVal.length;
+ this.state.valid = this.getInputValidity();
+ this.renderValidity();
+ }
+
+ renderValid() {
+ return this.renderClear();
+ }
+
+ renderEmpty() {
+ return this.renderInvalid();
+ }
+
+ renderInvalid() {
+ this.inputElement.addClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ return this.fieldErrorElement.show();
+ }
+
+ renderClear() {
+ const inputVal = this.accessCurrentValue();
+ if (!inputVal.split(' ').length) {
+ const trimmedInput = inputVal.trim();
+ this.accessCurrentValue(trimmedInput);
+ }
+ this.inputElement.removeClass(inputErrorClass);
+ this.scopedSiblings.hide();
+ this.fieldErrorElement.hide();
+ }
+ }
+
+ global.GlFieldError = GlFieldError;
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6
index be6c3ec274f..6ce392d2a5b 100644
--- a/app/assets/javascripts/gl_field_errors.js.es6
+++ b/app/assets/javascripts/gl_field_errors.js.es6
@@ -1,131 +1,9 @@
/* eslint-disable */
-((global) => {
- /*
- * This class overrides the browser's validation error bubbles, displaying custom
- * error messages for invalid fields instead. To begin validating any form, add the
- * class `show-gl-field-errors` to the form element, and ensure error messages are
- * declared in each inputs' title attribute.
- *
- * Example:
- *
- * <form class='show-gl-field-errors'>
- * <input type='text' name='username' title='Username is required.'/>
- *</form>
- *
- * */
-
- const errorMessageClass = 'gl-field-error';
- const inputErrorClass = 'gl-field-error-outline';
-
- class GlFieldError {
- constructor({ input, formErrors }) {
- this.inputElement = $(input);
- this.inputDomElement = this.inputElement.get(0);
- this.form = formErrors;
- this.errorMessage = this.inputElement.attr('title') || 'This field is required.';
- this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${ this.errorMessage }</p>`);
-
- this.state = {
- valid: false,
- empty: true
- };
-
- this.initFieldValidation();
- }
-
- initFieldValidation() {
- // hidden when injected into DOM
- this.inputElement.after(this.fieldErrorElement);
- this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this));
- this.scopedSiblings = this.safelySelectSiblings();
- }
-
- safelySelectSiblings() {
- // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity
- const ignoreSelector = '.validation-ignore';
- const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`);
- const parentContainer = this.inputElement.parent('.form-group');
- // Only select siblings when they're scoped within a form-group with one input
- const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1;
-
- return safelyScoped ? unignoredSiblings : this.fieldErrorElement;
- }
-
- renderValidity() {
- this.renderClear();
-
- if (this.state.valid) {
- return this.renderValid();
- }
-
- if (this.state.empty) {
- return this.renderEmpty();
- }
-
- if (!this.state.valid) {
- return this.renderInvalid();
- }
-
- }
+//= require gl_field_error
- handleInvalidSubmit(event) {
- event.preventDefault();
- const currentValue = this.accessCurrentValue();
- this.state.valid = false;
- this.state.empty = currentValue === '';
-
- this.renderValidity();
- this.form.focusOnFirstInvalid.apply(this.form);
- // For UX, wait til after first invalid submission to check each keyup
- this.inputElement.off('keyup.field_validator')
- .on('keyup.field_validator', this.updateValidity.bind(this));
-
- }
-
- /* Get or set current input value */
- accessCurrentValue(newVal) {
- return newVal ? this.inputElement.val(newVal) : this.inputElement.val();
- }
-
- getInputValidity() {
- return this.inputDomElement.validity.valid;
- }
-
- updateValidity() {
- const inputVal = this.accessCurrentValue();
- this.state.empty = !inputVal.length;
- this.state.valid = this.getInputValidity();
- this.renderValidity();
- }
-
- renderValid() {
- return this.renderClear();
- }
-
- renderEmpty() {
- return this.renderInvalid();
- }
-
- renderInvalid() {
- this.inputElement.addClass(inputErrorClass);
- this.scopedSiblings.hide();
- return this.fieldErrorElement.show();
- }
-
- renderClear() {
- const inputVal = this.accessCurrentValue();
- if (!inputVal.split(' ').length) {
- const trimmedInput = inputVal.trim();
- this.accessCurrentValue(trimmedInput);
- }
- this.inputElement.removeClass(inputErrorClass);
- this.scopedSiblings.hide();
- this.fieldErrorElement.hide();
- }
- }
-
- const customValidationFlag = 'no-gl-field-errors';
+((global) => {
+ const customValidationFlag = 'gl-field-error-ignore';
class GlFieldErrors {
constructor(form) {
@@ -144,7 +22,7 @@
this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag))
- .map((input) => new GlFieldError({ input, formErrors: this }));
+ .map((input) => new global.GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit);
}
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 6658e4811ce..860ee5df57e 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -238,8 +238,11 @@
_this.expandViewContainer();
}
_this.diffsLoaded = true;
- _this.scrollToElement("#diffs");
- _this.highlighSelectedLine();
+ var anchoredDiff = gl.utils.getLocationHash();
+ if (anchoredDiff) _this.openAnchoredDiff(anchoredDiff, function() {
+ _this.scrollToElement("#diffs");
+ _this.highlighSelectedLine();
+ });
_this.filesCommentButton = $('.files .diff-file').filesCommentButton();
return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) {
e.preventDefault();
@@ -252,6 +255,17 @@
});
};
+ MergeRequestTabs.prototype.openAnchoredDiff = function(anchoredDiff, cb) {
+ var diffTitle = $('#file-path-' + anchoredDiff);
+ var diffFile = diffTitle.closest('.diff-file');
+ var nothingHereBlock = $('.nothing-here-block:visible', diffFile);
+ if (nothingHereBlock.length) {
+ diffFile.singleFileDiff(true, cb);
+ } else {
+ cb();
+ }
+ };
+
MergeRequestTabs.prototype.highlighSelectedLine = function() {
var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight;
$('.hll').removeClass('hll');
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index adca76ddd5f..8e54ca4f0dc 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -13,7 +13,7 @@
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
- function SingleFileDiff(file) {
+ function SingleFileDiff(file, forceLoad, cb) {
this.file = file;
this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file);
@@ -32,9 +32,12 @@
this.$toggleIcon.addClass('fa-caret-down');
}
$('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
+ if (forceLoad) {
+ this.toggleDiff(null, cb);
+ }
}
- SingleFileDiff.prototype.toggleDiff = function(e) {
+ SingleFileDiff.prototype.toggleDiff = function(e, cb) {
var $target = $(e.target);
if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen;
@@ -54,11 +57,11 @@
}
} else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
- return this.getContentHTML();
+ return this.getContentHTML(cb);
}
};
- SingleFileDiff.prototype.getContentHTML = function() {
+ SingleFileDiff.prototype.getContentHTML = function(cb) {
this.collapsedContent.hide();
this.loadingContent.show();
$.get(this.diffForPath, (function(_this) {
@@ -76,6 +79,8 @@
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
}
+
+ if (cb) cb();
};
})(this));
};
@@ -84,10 +89,10 @@
})();
- $.fn.singleFileDiff = function() {
+ $.fn.singleFileDiff = function(forceLoad, cb) {
return this.each(function() {
- if (!$.data(this, 'singleFileDiff')) {
- return $.data(this, 'singleFileDiff', new SingleFileDiff(this));
+ if (!$.data(this, 'singleFileDiff') || forceLoad) {
+ return $.data(this, 'singleFileDiff', new SingleFileDiff(this, forceLoad, cb));
}
});
};
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index a18d16ea46c..cfd1e2204d5 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -11,11 +11,9 @@
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
$starSpan.removeClass('starred').text('Star');
- gl.utils.updateTooltipTitle($this, 'Star project');
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
$starSpan.addClass('starred').text('Unstar');
- gl.utils.updateTooltipTitle($this, 'Unstar project');
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
};
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 98e301d3799..ce117c3fba5 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -1,11 +1,36 @@
-.avatar {
+@mixin avatar-size($size, $margin-right) {
+ width: $size;
+ height: $size;
+ margin-right: $margin-right;
+}
+
+.avatar-container {
float: left;
- margin-right: 12px;
+ margin-right: 15px;
+ border-radius: $avatar_radius;
+ border: 1px solid rgba(0, 0, 0, .1);
+ &.s16 { @include avatar-size(16px, 6px); }
+ &.s20 { @include avatar-size(20px, 7px); }
+ &.s24 { @include avatar-size(24px, 8px); }
+ &.s26 { @include avatar-size(26px, 8px); }
+ &.s32 { @include avatar-size(32px, 10px); }
+ &.s36 { @include avatar-size(36px, 10px); }
+ &.s40 { @include avatar-size(40px, 10px); }
+ &.s46 { @include avatar-size(46px, 15px); }
+ &.s48 { @include avatar-size(48px, 10px); }
+ &.s60 { @include avatar-size(60px, 12px); }
+ &.s70 { @include avatar-size(70px, 14px); }
+ &.s90 { @include avatar-size(90px, 15px); }
+ &.s110 { @include avatar-size(110px, 15px); }
+ &.s140 { @include avatar-size(140px, 15px); }
+ &.s160 { @include avatar-size(160px, 20px); }
+}
+
+.avatar {
+ @extend .avatar-container;
width: 40px;
height: 40px;
padding: 0;
- border-radius: $avatar_radius;
- border: 1px solid rgba(0, 0, 0, .1);
&.avatar-inline {
float: none;
@@ -20,22 +45,6 @@
border-radius: 0;
border: none;
}
-
- &.s16 { width: 16px; height: 16px; margin-right: 6px; }
- &.s20 { width: 20px; height: 20px; margin-right: 7px; }
- &.s24 { width: 24px; height: 24px; margin-right: 8px; }
- &.s26 { width: 26px; height: 26px; margin-right: 8px; }
- &.s32 { width: 32px; height: 32px; margin-right: 10px; }
- &.s36 { width: 36px; height: 36px; margin-right: 10px; }
- &.s40 { width: 40px; height: 40px; margin-right: 10px; }
- &.s46 { width: 46px; height: 46px; margin-right: 15px; }
- &.s48 { width: 48px; height: 48px; margin-right: 10px; }
- &.s60 { width: 60px; height: 60px; margin-right: 12px; }
- &.s70 { width: 70px; height: 70px; margin-right: 14px; }
- &.s90 { width: 90px; height: 90px; margin-right: 15px; }
- &.s110 { width: 110px; height: 110px; margin-right: 15px; }
- &.s140 { width: 140px; height: 140px; margin-right: 20px; }
- &.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
.identicon {
@@ -54,3 +63,17 @@
&.s140 { font-size: 72px; line-height: 138px; }
&.s160 { font-size: 96px; line-height: 158px; }
}
+
+.image-container {
+ @extend .avatar-container;
+ overflow: hidden;
+ display: flex;
+
+ .avatar {
+ border-radius: 0;
+ border: none;
+ height: auto;
+ margin: 0;
+ align-self: center;
+ }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 761c07384f4..f0727e9688a 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -136,3 +136,35 @@ label {
color: $red-normal;
}
+.gl-show-field-errors {
+ .gl-field-success-outline {
+ border: 1px solid $green-normal;
+
+ &:focus {
+ box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
+ border: 0 none;
+ }
+ }
+
+ .gl-field-error-outline {
+ border: 1px solid $red-normal;
+
+ &:focus {
+ box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
+ border: 0 none;
+ }
+ }
+
+ .gl-field-success-message {
+ color: $green-normal;
+ }
+
+ .gl-field-error-message {
+ color: $red-normal;
+ }
+
+ .gl-field-hint {
+ color: $gl-text-color;
+ }
+}
+
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 3f877d86a26..91ab1503439 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -21,57 +21,66 @@
background: $color-darker;
}
- .nav-sidebar li {
- a {
- color: $color-light;
-
- &:hover,
- &:focus,
- &:active {
- background: $color-dark;
- }
+ .sidebar-header,
+ .sidebar-action-buttons {
+ color: $color-light;
+ background-color: lighten($color-darker, 5%);
+ }
- i {
+ .nav-sidebar {
+ li {
+ a {
color: $color-light;
- }
-
- path,
- polygon {
- fill: $color-light;
- }
- .count {
- color: $color-light;
- background: $color-dark;
+ &:hover,
+ &:focus,
+ &:active {
+ background: $color-dark;
+ }
+
+ i {
+ color: $color-light;
+ }
+
+ path,
+ polygon {
+ fill: $color-light;
+ }
+
+ .count {
+ color: $color-light;
+ background: $color-dark;
+ }
+
+ svg {
+ position: relative;
+ top: 3px;
+ }
}
- svg {
- position: relative;
- top: 3px;
+ &.separate-item {
+ border-top: 1px solid $color;
}
- }
-
- &.separate-item {
- border-top: 1px solid $color;
- }
- &.active a {
- color: $white-light;
- background: $color-dark;
+ &.active a {
+ color: $white-light;
+ background: $color-dark;
- &.no-highlight {
- border: none;
- }
+ &.no-highlight {
+ border: none;
+ }
- i {
- color: $white-light;
- }
+ i {
+ color: $white-light;
+ }
- path,
- polygon {
- fill: $white-light;
+ path,
+ polygon {
+ fill: $white-light;
+ }
}
}
+
}
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 53ee1ed309e..4993ca7572a 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -49,12 +49,16 @@ header {
font-size: 18px;
padding: 0;
margin: ($header-height - 28) / 2 0;
- margin-left: 10px;
+ margin-left: 8px;
height: 28px;
min-width: 28px;
line-height: 28px;
text-align: center;
+ &.header-user-dropdown-toggle {
+ margin-left: 14px;
+ }
+
&:hover,
&:focus,
&:active {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 78464af94bd..bc0610cc417 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -142,10 +142,6 @@ ul.content-list {
}
}
- .avatar {
- margin-right: 15px;
- }
-
.controls {
float: right;
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index b6f21fd8c91..cb2c351c368 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -7,8 +7,70 @@
.pagination {
padding: 0;
}
+
+ .gap,
+ .gap:hover {
+ background-color: $gray-light;
+ padding: $gl-vert-padding;
+ cursor: default;
+ }
}
.panel > .gl-pagination {
margin: 0;
}
+
+/**
+ * Extra-small screen pagination.
+ */
+@media (max-width: 320px) {
+ .gl-pagination {
+ .first,
+ .last {
+ display: none;
+ }
+
+ .page {
+ display: none;
+
+ &.active {
+ display: inline;
+ }
+ }
+ }
+}
+
+/**
+ * Small screen pagination
+ */
+@media (max-width: $screen-xs) {
+ .gl-pagination {
+ .pagination li a {
+ padding: 6px 10px;
+ }
+
+ .page {
+ display: none;
+
+ &.active {
+ display: inline;
+ }
+ }
+ }
+}
+
+/**
+ * Medium screen pagination
+ */
+@media (min-width: $screen-xs) and (max-width: $screen-md-max) {
+ .gl-pagination {
+ .page {
+ display: none;
+
+ &.active,
+ &.sibling {
+ display: inline;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index c54f7b27575..d74c14ee2a4 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -59,6 +59,11 @@
padding: 0 !important;
}
+ .sidebar-header {
+ padding: 11px 22px 12px;
+ font-size: 20px;
+ }
+
li {
&.separate-item {
padding-top: 10px;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index ef6833c9845..47a7e84b5c6 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -12,6 +12,10 @@
opacity: 1!important;
* {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
// !important to make sure no style can override this when dragging
cursor: -webkit-grabbing!important;
cursor: grabbing!important;
@@ -45,11 +49,6 @@
.page-with-sidebar {
padding-bottom: 0;
}
-
- .issues-filters {
- position: relative;
- z-index: 999999;
- }
}
.boards-app {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index d6a55fbd464..6300ac9662f 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -52,10 +52,25 @@
.build-header {
position: relative;
- padding-right: 40px;
+ padding: 0;
+ display: flex;
+ min-height: 58px;
+ align-items: center;
- @media (min-width: $screen-sm-min) {
- padding-right: 0;
+ .btn-inverted {
+ @include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light);
+ }
+
+ @media (max-width: $screen-sm-max) {
+ padding-right: 40px;
+
+ .btn-inverted {
+ display: none;
+ }
+ }
+
+ .header-content {
+ flex: 1;
}
a {
@@ -137,10 +152,15 @@
.retry-link {
color: $gl-link-color;
+ display: none;
&:hover {
text-decoration: underline;
}
+
+ @media (max-width: $screen-sm-max) {
+ display: block;
+ }
}
.stage-item {
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index 76225ed8d06..016bab104eb 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -36,10 +36,6 @@
}
}
-.dash-project-avatar {
- float: left;
-}
-
.dash-project-access-icon {
float: left;
margin-right: 5px;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index ee2a398f031..4375e29c8db 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -3,12 +3,14 @@
}
.dashboard .side .panel .panel-heading .input-group {
+
.form-control {
height: 42px;
}
}
.group-row {
+
.stats {
float: right;
line-height: $list-text-height;
@@ -21,12 +23,14 @@
}
.ldap-group-links {
+
.form-actions {
margin-bottom: $gl-padding;
}
}
.groups-cover-block {
+
.container-fluid {
position: relative;
}
@@ -41,9 +45,14 @@
background-color: $background-color;
}
}
+
+ .group-avatar {
+ border: 0;
+ }
}
.groups-header {
+
@media (min-width: $screen-sm-min) {
.nav-links {
width: 35%;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index a2f5c6c6bd3..10f67b47998 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -75,43 +75,17 @@
.login-body {
font-size: 13px;
-
input + p {
margin-top: 5px;
}
- .gl-field-success-outline {
- border: 1px solid $green-normal;
-
- &:focus {
- box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal;
- border: 0 none;
- }
- }
-
- .gl-field-error-outline {
- border: 1px solid $red-normal;
-
- &:focus {
- box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6);
- border: 0 none;
- }
- }
-
- .username .validation-success,
- .gl-field-success-message {
+ .username .validation-success {
color: $green-normal;
}
- .username .validation-error,
- .gl-field-error-message {
+ .username .validation-error {
color: $red-normal;
}
-
- .gl-field-hint {
- color: $gl-text-color;
- }
-
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f0d39b353d2..f7d54564530 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -96,8 +96,8 @@
.project-avatar {
float: none;
- margin-left: auto;
- margin-right: auto;
+ margin: 0 auto;
+ border: none;
&.identicon {
border-radius: 50%;
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index f8ded05c31a..00e64076408 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -39,6 +39,12 @@ module EventsHelper
end
end
+ def event_filter_visible(feature_key)
+ return true unless @project
+
+ @project.feature_available?(feature_key, current_user)
+ end
+
def event_preposition(event)
if event.push? || event.commented? || event.target
"at"
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 5bcf199d468..0a493b7a12b 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -237,7 +237,7 @@ class JiraService < IssueTrackerService
end
def resource_url(resource)
- "#{Settings.gitlab['url'].chomp("/")}#{resource}"
+ "#{Settings.gitlab.base_url.chomp("/")}#{resource}"
end
def build_entity_url(entity_name, entity_id)
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index fbb3d4507d6..1ee31023e26 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -2,11 +2,11 @@ class ProjectPolicy < BasePolicy
def rules
team_access!(user)
- owner = user.admin? ||
- project.owner == user ||
+ owner = project.owner == user ||
(project.group && project.group.has_owner?(user))
- owner_access! if owner
+ owner_access! if user.admin? || owner
+ team_member_owner_access! if owner
if project.public? || (project.internal? && !user.external?)
guest_access!
@@ -16,7 +16,7 @@ class ProjectPolicy < BasePolicy
can! :read_build if project.public_builds?
if project.request_access_enabled &&
- !(owner || project.team.member?(user) || project_group_member?(user))
+ !(owner || user.admin? || project.team.member?(user) || project_group_member?(user))
can! :request_access
end
end
@@ -135,6 +135,10 @@ class ProjectPolicy < BasePolicy
can! :destroy_issue
end
+ def team_member_owner_access!
+ team_member_reporter_access!
+ end
+
# Push abilities on the users team role
def team_access!(user)
access = project.team.max_member_access(user.id)
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 404f75616b5..f415244068b 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -13,20 +13,8 @@ module MergeRequests
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
- if merge_request.target_branch.blank? || merge_request.source_branch.blank?
- message =
- if params[:source_branch] || params[:target_branch]
- "You must select source and target branch"
- end
-
- return build_failed(merge_request, message)
- end
-
- if merge_request.source_project == merge_request.target_project &&
- merge_request.target_branch == merge_request.source_branch
-
- return build_failed(merge_request, 'You must select different branches')
- end
+ messages = validate_branches(merge_request)
+ return build_failed(merge_request, messages) unless messages.empty?
compare = CompareService.new.execute(
merge_request.source_project,
@@ -43,6 +31,34 @@ module MergeRequests
private
+ def validate_branches(merge_request)
+ messages = []
+
+ if merge_request.target_branch.blank? || merge_request.source_branch.blank?
+ messages <<
+ if params[:source_branch] || params[:target_branch]
+ "You must select source and target branch"
+ end
+ end
+
+ if merge_request.source_project == merge_request.target_project &&
+ merge_request.target_branch == merge_request.source_branch
+
+ messages << 'You must select different branches'
+ end
+
+ # See if source and target branches exist
+ unless merge_request.source_project.commit(merge_request.source_branch)
+ messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
+ end
+
+ unless merge_request.target_project.commit(merge_request.target_branch)
+ messages << "Target branch \"#{merge_request.target_branch}\" does not exist"
+ end
+
+ messages
+ end
+
# When your branch name starts with an iid followed by a dash this pattern will be
# interpreted as the user wants to close that issue on this project.
#
@@ -91,8 +107,10 @@ module MergeRequests
merge_request
end
- def build_failed(merge_request, message)
- merge_request.errors.add(:base, message) unless message.nil?
+ def build_failed(merge_request, messages)
+ messages.compact.each do |message|
+ merge_request.errors.add(:base, message)
+ end
merge_request.compare_commits = []
merge_request.can_be_created = false
merge_request
diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml
index acbe17036f7..1af7dd5bb67 100644
--- a/app/views/admin/appearances/preview.html.haml
+++ b/app/views/admin/appearances/preview.html.haml
@@ -1,6 +1,6 @@
= render 'devise/shared/tab_single', tab_title: 'Sign in preview'
.login-box
- %form.show-gl-field-errors
+ %form.gl-show-field-errors
.form-group
= label_tag :login
= text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.'
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index adfa1eaafc9..05c88ca1cc8 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -16,7 +16,8 @@
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
- = image_tag group_icon(group), class: "avatar s40 hidden-xs"
+ .image-container.s40
+ = image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title
= link_to [:admin, group], class: 'group-name' do
= group.name
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 0188ed448ce..a7c1a4f5038 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -13,7 +13,8 @@
Group info:
%ul.well-list
%li
- = image_tag group_icon(@group), class: "avatar s60"
+ .image-container.s60
+ = image_tag group_icon(@group), class: "avatar s60"
%li
%span.light Name:
%strong= @group.name
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 339cfc613fe..10dce6f3d8f 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -76,7 +76,8 @@
.title
= link_to [:admin, project.namespace.becomes(Namespace), project] do
.dash-project-avatar
- = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+ .image-container.s40
+ = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
index 5d25dd398d6..73e70dc63e5 100644
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index b518fae7c95..5e189e6dc54 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
= f.hidden_field :reset_password_token
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index 1fcfd06419a..99ce13adf74 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title: 'Reset Password'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 5fd896f6835..21b89580818 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,4 +1,4 @@
-= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f|
+= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
%div.form-group
= f.label "Username or email", for: :login
= f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index 1d381ad7893..a6cadbcbdff 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -1,4 +1,4 @@
-= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do
+= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do
.form-group
= label_tag :username, 'Username or email'
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true }
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index c18bc2ac413..3ab5461f929 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(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do
+= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group
= label_tag :username, "#{server['label']} Username"
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index fd77cdbee2e..2cadc424668 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -7,7 +7,7 @@
.login-box
.login-body
- if @user.two_factor_otp_enabled?
- = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user gl-show-field-errors' }) do |f|
- resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index d0bbcf3115e..7c68e3266e5 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -1,6 +1,6 @@
#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' }
.login-body
- = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f|
+ = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= devise_error_messages!
%div.form-group
@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
%div.username.form-group
= f.label :username
- = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
index 49b2f77111f..b2f48a4e0bf 100644
--- a/app/views/devise/unlocks/new.html.haml
+++ b/app/views/devise/unlocks/new.html.haml
@@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions'
.login-box
.login-body
- = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f|
+ = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
.form-group.append-bottom-20
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index c766370d5a0..2f90c19d4b4 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -2,13 +2,14 @@
.panel-heading
Group settings
.panel-body
- = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
+ = form_for @group, html: { multipart: true, class: "form-horizontal gl-show-field-errors" }, authenticity_token: true do |f|
= form_errors(@group)
= render 'shared/group_form', f: f
.form-group
.col-sm-offset-2.col-sm-10
- = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
+ .image-container.s160
+ = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
%p.light
- if @group.avatar?
You can change your group avatar here
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 2b8bc269e64..d19eaa6add9 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -5,7 +5,7 @@
New Group
%hr
-= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
+= form_for @group, html: { class: 'group-form form-horizontal gl-show-field-errors' } do |f|
= form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index fab61f447c2..275581b3af8 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -6,7 +6,8 @@
.cover-block.groups-cover-block
%div{ class: container_class }
- = image_tag group_icon(@group), class: "avatar group-avatar s70 avatar-tile"
+ .image-container.s70.group-avatar
+ = image_tag group_icon(@group), class: "avatar s70 avatar-tile"
.group-info
.cover-title
%h1
diff --git a/app/views/kaminari/gitlab/_gap.html.haml b/app/views/kaminari/gitlab/_gap.html.haml
index 80ca30f36e6..889514c4755 100644
--- a/app/views/kaminari/gitlab/_gap.html.haml
+++ b/app/views/kaminari/gitlab/_gap.html.haml
@@ -4,6 +4,6 @@
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%li{class: "page"}
- %span.page.gap
+%li
+ %span.gap
= raw(t 'views.pagination.truncate')
diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml
index 522e4d1d05f..750aed8f329 100644
--- a/app/views/kaminari/gitlab/_page.html.haml
+++ b/app/views/kaminari/gitlab/_page.html.haml
@@ -6,5 +6,5 @@
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%li{class: "page#{' active' if page.current?}"}
+%li{class: "page#{' active' if page.current?}#{' sibling' if page.next? || page.prev?}"}
= link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil}
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 7faa8bded86..7a9859262f7 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -29,10 +29,6 @@
= icon('bell fw')
%span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) }
= todos_pending_count
- - if current_user.can_create_project?
- %li
- = link_to new_project_path, title: 'New project', aria: { label: "New project" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
@@ -48,6 +44,8 @@
= link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username }
%li
= link_to "Profile Settings", profile_path, aria: { label: "Profile Settings" }
+ %li
+ = link_to "Help", help_path, aria: { label: "Help" }
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" }
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 3b1295dc3c0..a0356feef95 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,49 +1,38 @@
-%ul.nav.nav-sidebar
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
- = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- %span
- Projects
- = nav_link(controller: :todos) do
- = link_to dashboard_todos_path, title: 'Todos' do
- %span
- Todos
- %span.count.js-todos-count= number_with_delimiter(todos_pending_count)
- = nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
- %span
- Activity
- - if koding_enabled?
- = nav_link(controller: :koding) do
- = link_to koding_path, title: 'Koding' do
+.nav-sidebar
+ .sidebar-header Across GitLab
+ %ul.nav
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
+ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
%span
- Koding
- = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
- = link_to dashboard_groups_path, title: 'Groups' do
- %span
- Groups
- = nav_link(controller: 'dashboard/milestones') do
- = link_to dashboard_milestones_path, title: 'Milestones' do
- %span
- Milestones
- = nav_link(path: 'dashboard#issues') do
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
- %span
- Issues
- %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
- = nav_link(path: 'dashboard#merge_requests') do
- = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
- %span
- Merge Requests
- %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
- = nav_link(controller: 'dashboard/snippets') do
- = link_to dashboard_snippets_path, title: 'Snippets' do
- %span
- Snippets
- = nav_link(controller: :help) do
- = link_to help_path, title: 'Help' do
- %span
- Help
- = nav_link(html_options: {class: profile_tab_class}) do
- = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
- %span
- Profile Settings
+ Projects
+ = nav_link(path: 'dashboard#activity') do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
+ %span
+ Activity
+ - if koding_enabled?
+ = nav_link(controller: :koding) do
+ = link_to koding_path, title: 'Koding' do
+ %span
+ Koding
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+ = link_to dashboard_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link(controller: 'dashboard/milestones') do
+ = link_to dashboard_milestones_path, title: 'Milestones' do
+ %span
+ Milestones
+ = nav_link(path: 'dashboard#issues') do
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
+ %span
+ Issues
+ %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
+ = nav_link(path: 'dashboard#merge_requests') do
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
+ %span
+ Merge Requests
+ %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
+ = nav_link(controller: 'dashboard/snippets') do
+ = link_to dashboard_snippets_path, title: 'Snippets' do
+ %span
+ Snippets
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index d3987fc9c4f..e67b66d1fff 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,7 +1,8 @@
- empty_repo = @project.empty_repo?
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
%div{ class: container_class }
- = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70 avatar-tile')
+ .image-container.s70.project-avatar
+ = project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
%h1.project-title
= @project.name
%span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml
index ba1502c97b6..f7071051efc 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/projects/boards/components/_board.html.haml
@@ -11,7 +11,9 @@
.board-inner
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
- {{ list.title }}
+ %span.has-tooltip{ ":title" => "(list.label ? list.label.description : '')",
+ data: { container: "body", placement: "bottom" } }
+ {{ list.title }}
.board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" }
%span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" }
{{ list.issuesSize }}
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index 51b5bd9db42..3f2ce7377fd 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,16 +1,19 @@
.content-block.build-header
- = ci_status_with_icon(@build.status)
- Build
- %strong ##{@build.id}
- for commit
- = link_to ci_status_path(@build.pipeline) do
- %strong= @build.pipeline.short_sha
- from
- = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do
- %code
- = @build.ref
- - if @build.user
- = render "user"
- = time_ago_with_tooltip(@build.created_at)
+ .header-content
+ = ci_status_with_icon(@build.status)
+ Build
+ %strong ##{@build.id}
+ for commit
+ = link_to ci_status_path(@build.pipeline) do
+ %strong= @build.pipeline.short_sha
+ from
+ = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do
+ %code
+ = @build.ref
+ - if @build.user
+ = render "user"
+ = time_ago_with_tooltip(@build.created_at)
+ - if can?(current_user, :update_build, @build) && @build.retryable?
+ = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted pull-right', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index b1053028279..28f519f11b2 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -44,7 +44,7 @@
.title
Build details
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
+ = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
- if @build.merge_request
%p.build-detail-row
%span.build-light-text Merge Request:
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 29d549a60f5..27da86b9efe 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -5,10 +5,10 @@
= custom_icon('icon_fork')
%span Fork
- else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn has-tooltip' do
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn' do
= custom_icon('icon_fork')
%span Fork
%div.count-with-arrow
%span.arrow
- = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count has-tooltip' do
+ = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count' do
= @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 311583037e5..12d35101770 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,5 +1,5 @@
- if current_user
- = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: current_user.starred?(@project) ? 'Unstar project' : 'Star project' } do
+ = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
- if current_user.starred?(@project)
= icon('star')
%span.starred Unstar
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 30473d14b9b..c40ad06969e 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -118,7 +118,8 @@
Project avatar
.form-group
- if @project.avatar?
- = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
+ .image-container.s160
+ = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
%p.light
- if @project.avatar_in_git
Project avatar in repository: #{ @project.avatar_in_git }
@@ -180,6 +181,7 @@
%ul
%li Build traces and artifacts
%li LFS objects
+ %li Container registry images
%hr
- if can? current_user, :archive_project, @project
.row.prepend-top-default
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 6624d5cb427..4e41a15d9f4 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -33,7 +33,12 @@
.form-actions
- if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel"
+ .pull-right
+ - if can?(current_user, :admin_wiki, @project)
+ = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger btn-grouped" do
+ Delete
+ = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped"
- else
= f.submit 'Create page', class: "btn-create btn"
- = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
+ .pull-right
+ = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 4ea75dbbf0c..763c2fea39b 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -7,6 +7,3 @@
- if can?(current_user, :create_wiki, @project)
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do
Edit
- - if can?(current_user, :admin_wiki, @project)
- = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
- Delete
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 233538bb488..679d6018bef 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -19,7 +19,5 @@
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New Page
- = render 'main_links'
-
= render 'form'
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 3480800369a..c367ae336db 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,6 +1,9 @@
%ul.nav-links.event-filter.scrolling-tabs
= event_filter_link EventFilter.all, 'All'
- = event_filter_link EventFilter.push, 'Push events'
- = event_filter_link EventFilter.merged, 'Merge events'
- = event_filter_link EventFilter.comments, 'Comments'
+ - if event_filter_visible(:repository)
+ = event_filter_link EventFilter.push, 'Push events'
+ - if event_filter_visible(:merge_requests)
+ = event_filter_link EventFilter.merged, 'Merge events'
+ - if event_filter_visible(:issues)
+ = event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 67072b9fc2a..ba25e09d638 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -9,11 +9,13 @@
= f.label :path, class: 'control-label' do
Group path
.col-sm-10
- .input-group
+ .input-group.gl-field-error-anchor
.input-group-addon
= root_url
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
- autofocus: local_assigns[:autofocus] || false
+ autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+",
+ required: true, title: 'Please choose a group name with no special characters.'
+
- if @group.persisted?
.alert.alert-warning.prepend-top-10
%ul
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index dc4ee3074d2..562291a61df 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -24,7 +24,8 @@
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
- = image_tag group_icon(group), class: "avatar s40 hidden-xs"
+ .image-container.s40
+ = image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title
= link_to group, class: 'group-name' do
= group.name
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index f27a9002ec2..40fe53e6a8d 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -1,10 +1,10 @@
- project = @target_project || @project
- extra_class = extra_class || ''
- show_menu_above = show_menu_above || false
-- selected_text = selected.try(:title)
+- selected_text = selected.try(:title) || params[:milestone_title]
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone")
- if selected.present?
- = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id)
+ = hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id)
= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- if project
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index e8668048703..3d2122a159c 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -32,10 +32,11 @@
= link_to project_path(project), class: dom_class(project) do
- if avatar
.dash-project-avatar
- - if use_creator_avatar
- = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- - else
- = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+ .image-container.s40
+ - if use_creator_avatar
+ = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
+ - else
+ = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace && !skip_namespace
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
index f360fbb3d5d..78f253f9023 100644
--- a/app/views/users/_groups.html.haml
+++ b/app/views/users/_groups.html.haml
@@ -1,4 +1,5 @@
.clearfix
- groups.each do |group|
= link_to group, class: 'profile-groups-avatars inline', title: group.name do
- = image_tag group_icon(group), class: 'avatar group-avatar s40'
+ .image-container.s40
+ = image_tag group_icon(group), class: 'avatar group-avatar s40'
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index eee0ca12af9..2fff6b0105d 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -16,7 +16,7 @@ class PostReceive
post_received = Gitlab::GitPostReceive.new(repo_path, identifier, changes)
if post_received.project.nil?
- log("Triggered hook for non-existing project with full path \"#{repo_path} \"")
+ log("Triggered hook for non-existing project with full path \"#{repo_path}\"")
return false
end
@@ -25,7 +25,7 @@ class PostReceive
elsif post_received.regular_project?
process_project_changes(post_received)
else
- log("Triggered hook for unidentifiable repository type with full path \"#{repo_path} \"")
+ log("Triggered hook for unidentifiable repository type with full path \"#{repo_path}\"")
false
end
end
@@ -37,7 +37,7 @@ class PostReceive
@user ||= post_received.identify(newrev)
unless @user
- log("Triggered hook for non-existing user \"#{post_received.identifier} \"")
+ log("Triggered hook for non-existing user \"#{post_received.identifier}\"")
return false
end
diff --git a/bin/changelog b/bin/changelog
new file mode 100755
index 00000000000..b6586ebb6aa
--- /dev/null
+++ b/bin/changelog
@@ -0,0 +1,170 @@
+#!/usr/bin/env ruby
+#
+# Generate a changelog entry file in the correct location.
+#
+# Automatically stages the file and amends the previous commit if the `--amend`
+# argument is used.
+
+require 'optparse'
+require 'yaml'
+
+Options = Struct.new(
+ :amend,
+ :author,
+ :dry_run,
+ :force,
+ :merge_request,
+ :title
+)
+
+class ChangelogOptionParser
+ def self.parse(argv)
+ options = Options.new
+
+ parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
+
+ # Note: We do not provide a shorthand for this in order to match the `git
+ # commit` interface
+ opts.on('--amend', 'Amend the previous commit') do |value|
+ options.amend = value
+ end
+
+ opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
+ options.force = value
+ end
+
+ opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
+ options.merge_request = value
+ end
+
+ opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
+ options.dry_run = value
+ end
+
+ opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
+ options.author = git_user_name if value
+ end
+
+ opts.on('-h', '--help', 'Print help message') do
+ $stdout.puts opts
+ exit
+ end
+ end
+
+ parser.parse!(argv)
+
+ # Title is everything that remains, but let's clean it up a bit
+ options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
+
+ options
+ end
+
+ def self.git_user_name
+ %x{git config user.name}.strip
+ end
+end
+
+class ChangelogEntry
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+
+ assert_feature_branch!
+ assert_new_file!
+ assert_title!
+
+ $stdout.puts "\e[32mcreate\e[0m #{file_path}"
+ $stdout.puts contents
+
+ unless options.dry_run
+ write
+ amend_commit if options.amend
+ end
+ end
+
+ def contents
+ YAML.dump(
+ 'title' => title,
+ 'merge_request' => options.merge_request,
+ 'author' => options.author
+ )
+ end
+
+ def write
+ File.write(file_path, contents)
+ end
+
+ def amend_commit
+ %x{git add #{file_path}}
+ exec("git commit --amend")
+ end
+
+ private
+
+ def fail_with(message)
+ $stderr.puts "\e[31merror\e[0m #{message}"
+ exit 1
+ end
+
+ def assert_feature_branch!
+ return unless branch_name == 'master'
+
+ fail_with "Create a branch first!"
+ end
+
+ def assert_new_file!
+ return unless File.exist?(file_path)
+ return if options.force
+
+ fail_with "#{file_path} already exists! Use `--force` to overwrite."
+ end
+
+ def assert_title!
+ return if options.title.length > 0 || options.amend
+
+ fail_with "Provide a title for the changelog entry or use `--amend`" \
+ " to use the title from the previous commit."
+ end
+
+ def title
+ if options.title.empty?
+ last_commit_subject
+ else
+ options.title
+ end
+ end
+
+ def last_commit_subject
+ %x{git log --format="%s" -1}.strip
+ end
+
+ def file_path
+ File.join(
+ unreleased_path,
+ branch_name.gsub(/[^\w-]/, '-') << '.yml'
+ )
+ end
+
+ def unreleased_path
+ File.join('changelogs', 'unreleased').tap do |path|
+ path << '-ee' if ee?
+ end
+ end
+
+ def ee?
+ @ee ||= File.exist?(File.expand_path('../CHANGELOG-EE.md', __dir__))
+ end
+
+ def branch_name
+ @branch_name ||= %x{git symbolic-ref HEAD}.strip.sub(%r{\Arefs/heads/}, '')
+ end
+end
+
+if $0 == __FILE__
+ options = ChangelogOptionParser.parse(ARGV)
+ ChangelogEntry.new(options)
+end
+
+# vim: ft=ruby
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 3451b68cea5..699ab6075b6 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -432,7 +432,9 @@ production: &base
## Repositories settings
repositories:
# Paths where repositories can be stored. Give the canonicalized absolute pathname.
- # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
+ # IMPORTANT: None of the path components may be symlink, because
+ # gitlab-shell invokes Dir.pwd inside the repository path and that results
+ # real path not the symlink.
storages: # You must have at least a `default` storage path.
default: /home/git/repositories/
diff --git a/config/initializers/0_post_deployment_migrations.rb b/config/initializers/0_post_deployment_migrations.rb
new file mode 100644
index 00000000000..0068a03d214
--- /dev/null
+++ b/config/initializers/0_post_deployment_migrations.rb
@@ -0,0 +1,12 @@
+# Post deployment migrations are included by default. This file must be loaded
+# before other initializers as Rails may otherwise memoize a list of migrations
+# excluding the post deployment migrations.
+unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS']
+ path = Rails.root.join('db', 'post_migrate').to_s
+
+ Rails.application.config.paths['db/migrate'] << path
+
+ # Rails memoizes migrations at certain points where it won't read the above
+ # path just yet. As such we must also update the following list of paths.
+ ActiveRecord::Migrator.migrations_paths << path
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index f7e714cd6bc..0455a98dbfe 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -42,3 +42,19 @@ end
Sidekiq.configure_client do |config|
config.redis = redis_config_hash
end
+
+# The Sidekiq client API always adds the queue to the Sidekiq queue
+# list, but mail_room and gitlab-shell do not. This is only necessary
+# for monitoring.
+config = YAML.load_file(Rails.root.join('config', 'sidekiq_queues.yml').to_s)
+
+begin
+ Sidekiq.redis do |conn|
+ conn.pipelined do
+ config[:queues].each do |queue|
+ conn.sadd('queues', queue[0])
+ end
+ end
+ end
+rescue Redis::BaseError, SocketError
+end
diff --git a/db/post_migrate/.gitkeep b/db/post_migrate/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/db/post_migrate/.gitkeep
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
new file mode 100644
index 00000000000..f3c2e72341f
--- /dev/null
+++ b/doc/administration/raketasks/maintenance.md
@@ -0,0 +1,220 @@
+# Maintenance Rake Tasks
+
+## Gather information about GitLab and the system it runs on
+
+This command gathers information about your GitLab installation and the System it runs on. These may be useful when asking for help or reporting issues.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:env:info
+```
+
+**Source Installation**
+
+```
+bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+Example output:
+
+```
+System information
+System: Debian 7.8
+Current User: git
+Using RVM: no
+Ruby Version: 2.1.5p273
+Gem Version: 2.4.3
+Bundler Version: 1.7.6
+Rake Version: 10.3.2
+Sidekiq Version: 2.17.8
+
+GitLab information
+Version: 7.7.1
+Revision: 41ab9e1
+Directory: /home/git/gitlab
+DB Adapter: postgresql
+URL: https://gitlab.example.com
+HTTP Clone URL: https://gitlab.example.com/some-project.git
+SSH Clone URL: git@gitlab.example.com:some-project.git
+Using LDAP: no
+Using Omniauth: no
+
+GitLab Shell
+Version: 2.4.1
+Repositories: /home/git/repositories/
+Hooks: /home/git/gitlab-shell/hooks/
+Git: /usr/bin/git
+```
+
+## Check GitLab configuration
+
+Runs the following rake tasks:
+
+- `gitlab:gitlab_shell:check`
+- `gitlab:sidekiq:check`
+- `gitlab:app:check`
+
+It will check that each component was setup according to the installation guide and suggest fixes for issues found.
+
+You may also have a look at our [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide).
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:check
+```
+
+**Source Installation**
+
+```
+bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+NOTE: Use SANITIZE=true for gitlab:check if you want to omit project names from the output.
+
+Example output:
+
+```
+Checking Environment ...
+
+Git configured for git user? ... yes
+Has python2? ... yes
+python2 is supported version? ... yes
+
+Checking Environment ... Finished
+
+Checking GitLab Shell ...
+
+GitLab Shell version? ... OK (1.2.0)
+Repo base directory exists? ... yes
+Repo base directory is a symlink? ... no
+Repo base owned by git:git? ... yes
+Repo base access is drwxrws---? ... yes
+post-receive hook up-to-date? ... yes
+post-receive hooks in repos are links: ... yes
+
+Checking GitLab Shell ... Finished
+
+Checking Sidekiq ...
+
+Running? ... yes
+
+Checking Sidekiq ... Finished
+
+Checking GitLab ...
+
+Database config exists? ... yes
+Database is SQLite ... no
+All migrations up? ... yes
+GitLab config exists? ... yes
+GitLab config outdated? ... no
+Log directory writable? ... yes
+Tmp directory writable? ... yes
+Init script exists? ... yes
+Init script up-to-date? ... yes
+Redis version >= 2.0.0? ... yes
+
+Checking GitLab ... Finished
+```
+
+## Rebuild authorized_keys file
+
+In some case it is necessary to rebuild the `authorized_keys` file.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:shell:setup
+```
+
+**Source Installation**
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
+```
+
+```
+This will rebuild an authorized_keys file.
+You will lose any data stored in authorized_keys file.
+Do you want to continue (yes/no)? yes
+```
+
+## Clear redis cache
+
+If for some reason the dashboard shows wrong information you might want to
+clear Redis' cache.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake cache:clear
+```
+
+**Source Installation**
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+## Precompile the assets
+
+Sometimes during version upgrades you might end up with some wrong CSS or
+missing some icons. In that case, try to precompile the assets again.
+
+Note that this only applies to source installations and does NOT apply to
+Omnibus packages.
+
+**Source Installation**
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
+
+For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at
+the release of upstream GitLab. The omnibus version includes optimized versions
+of those assets. Unless you are modifying the JavaScript / CSS code on your
+production machine after installing the package, there should be no reason to redo
+rake assets:precompile on the production machine. If you suspect that assets
+have been corrupted, you should reinstall the omnibus package.
+
+## Tracking Deployments
+
+GitLab provides a Rake task that lets you track deployments in GitLab
+Performance Monitoring. This Rake task simply stores the current GitLab version
+in the GitLab Performance Monitoring database.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:track_deployment
+```
+
+**Source Installation**
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
+```
+
+## Create or repair repository hooks symlink
+
+If the GitLab shell hooks directory location changes or another circumstance
+leads to the hooks symlink becoming missing or invalid, run this Rake task
+to create or repair the symlinks.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:shell:create_hooks
+```
+
+**Source Installation**
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:shell:create_hooks RAILS_ENV=production
+```
diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md
index d8dce4388e1..6f1356ddf8f 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -107,7 +107,7 @@ downtime. Otherwise skip to the next section.
1. To see the current threads, run:
```
- apply all thread bt
+ thread apply all bt
```
1. Once you're done debugging with `gdb`, be sure to detach from the process and exit:
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 8ebac57e612..4f4b20a1874 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1139,6 +1139,7 @@ Parameters:
| `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_events` | boolean | no | Trigger hook on wiki events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
+| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
### Edit project hook
@@ -1164,6 +1165,7 @@ Parameters:
| `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_events` | boolean | no | Trigger hook on wiki events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
+| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
### Delete project hook
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index 073e99b7147..efd23d514bc 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -27,11 +27,14 @@ Example response:
```json
[
- {
- "id" : 1,
- "url" : "https://gitlab.example.com/hook",
- "created_at" : "2015-11-04T20:07:35.874Z"
- }
+ {
+ "id":1,
+ "url":"https://gitlab.example.com/hook",
+ "created_at":"2016-10-31T12:32:15.192Z",
+ "push_events":true,
+ "tag_push_events":false,
+ "enable_ssl_verification":true
+ }
]
```
@@ -48,6 +51,10 @@ POST /hooks
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | yes | The hook URL |
+| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
+| `push_events` | boolean | no | When true, the hook will fire on push events |
+| `tag_push_events` | boolean | no | When true, the hook will fire on new tags being pushed |
+| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
Example request:
@@ -59,11 +66,14 @@ Example response:
```json
[
- {
- "id" : 2,
- "url" : "https://gitlab.example.com/hook",
- "created_at" : "2015-11-04T20:07:35.874Z"
- }
+ {
+ "id":1,
+ "url":"https://gitlab.example.com/hook",
+ "created_at":"2016-10-31T12:32:15.192Z",
+ "push_events":true,
+ "tag_push_events":false,
+ "enable_ssl_verification":true
+ }
]
```
diff --git a/doc/development/README.md b/doc/development/README.md
index 14d6f08e43a..bf1f054b7d5 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -21,6 +21,7 @@
## Process
+- [Generate a changelog entry with `bin/changelog`](changelog.md)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
- [Merge request performance guidelines](merge_request_performance_guidelines.md)
for ensuring merge requests do not negatively impact GitLab performance
@@ -41,6 +42,7 @@
- [What requires downtime?](what_requires_downtime.md)
- [Adding database indexes](adding_database_indexes.md)
+- [Post Deployment Migrations](post_deployment_migrations.md)
## Compliance
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
new file mode 100644
index 00000000000..6a97fae9cac
--- /dev/null
+++ b/doc/development/changelog.md
@@ -0,0 +1,185 @@
+# Generate a changelog entry
+
+This guide contains instructions for generating a changelog entry data file, as
+well as information and history about our changelog process.
+
+## Overview
+
+Each bullet point, or **entry**, in our [`CHANGELOG.md`][changelog.md] file is
+generated from a single data file in the [`changelogs/unreleased/`][unreleased]
+(or corresponding EE) folder. The file is expected to be a [YAML] file in the
+following format:
+
+```yaml
+---
+title: "Going through change[log]s"
+merge_request: 1972
+author: Ozzy Osbourne
+```
+
+The `merge_request` value is a reference to a merge request that adds this
+entry, and the `author` key is used to give attribution to community
+contributors. Both are optional.
+
+Community contributors and core team members are encouraged to add their name to
+the `author` field. GitLab team members should not.
+
+If you're working on the GitLab EE repository, the entry will be added to
+`changelogs/unreleased-ee/` instead.
+
+[changelog.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG.md
+[unreleased]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/changelogs/
+[YAML]: https://en.wikipedia.org/wiki/YAML
+
+## Instructions
+
+A `bin/changelog` script is available to generate the changelog entry file
+automatically.
+
+Its simplest usage is to provide the value for `title`:
+
+```text
+$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
+create changelogs/unreleased/my-feature.yml
+---
+title: Hey DZ, I added a feature to GitLab!
+merge_request:
+author:
+```
+
+The entry filename is based on the name of the current Git branch. If you run
+the command above on a branch called `feature/hey-dz`, it will generate a
+`changelogs/unreleased/feature-hey-dz.yml` file.
+
+### Arguments
+
+| Argument | Shorthand | Purpose |
+| ----------------- | --------- | --------------------------------------------- |
+| `--amend` | | Amend the previous commit |
+| `--force` | `-f` | Overwrite an existing entry |
+| `--merge-request` | `-m` | Merge Request ID |
+| `--dry-run` | `-n` | Don't actually write anything, just print |
+| `--git-username` | `-u` | Use Git user.name configuration as the author |
+| `--help` | `-h` | Print help message |
+
+#### `--amend`
+
+You can pass the **`--amend`** argument to automatically stage the generated
+file and amend it to the previous commit.
+
+If you use **`--amend`** and don't provide a title, it will automatically use
+the "subject" of the previous commit, which is the first line of the commit
+message:
+
+```text
+$ git show --oneline
+ab88683 Added an awesome new feature to GitLab
+
+$ bin/changelog --amend
+create changelogs/unreleased/feature-hey-dz.yml
+---
+title: Added an awesome new feature to GitLab
+merge_request:
+author:
+```
+
+#### `--force` or `-f`
+
+Use **`--force`** or **`-f`** to overwrite an existing changelog entry if it
+already exists.
+
+```text
+$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
+error changelogs/unreleased/feature-hey-dz.yml already exists! Use `--force` to overwrite.
+
+$ bin/changelog 'Hey DZ, I added a feature to GitLab!' --force
+create changelogs/unreleased/feature-hey-dz.yml
+---
+title: Hey DZ, I added a feature to GitLab!
+merge_request: 1983
+author:
+```
+
+#### `--merge-request` or `-m`
+
+Use the **`--merge-request`** or **`-m`** argument to provide the
+`merge_request` value:
+
+```text
+$ bin/changelog 'Hey DZ, I added a feature to GitLab!' -m 1983
+create changelogs/unreleased/feature-hey-dz.yml
+---
+title: Hey DZ, I added a feature to GitLab!
+merge_request: 1983
+author:
+```
+
+#### `--dry-run` or `-n`
+
+Use the **`--dry-run`** or **`-n`** argument to prevent actually writing or
+committing anything:
+
+```text
+$ bin/changelog --amend --dry-run
+create changelogs/unreleased/feature-hey-dz.yml
+---
+title: Added an awesome new feature to GitLab
+merge_request:
+author:
+
+$ ls changelogs/unreleased/
+```
+
+#### `--git-username` or `-u`
+
+Use the **`--git-username`** or **`-u`** argument to automatically fill in the
+`author` value with your configured Git `user.name` value:
+
+```text
+$ git config user.name
+Jane Doe
+
+$ bin/changelog --u 'Hey DZ, I added a feature to GitLab!'
+create changelogs/unreleased/feature-hey-dz.yml
+---
+title: Hey DZ, I added a feature to GitLab!
+merge_request:
+author: Jane Doe
+```
+
+## History and Reasoning
+
+Our `CHANGELOG` file was previously updated manually by each contributor that
+felt their change warranted an entry. When two merge requests added their own
+entries at the same spot in the list, it created a merge conflict in one as soon
+as the other was merged. When we had dozens of merge requests fighting for the
+same changelog entry location, this quickly became a major source of merge
+conflicts and delays in development.
+
+This led us to a [boring solution] of "add your entry in a random location in
+the list." This actually worked pretty well as we got further along in each
+monthly release cycle, but at the start of a new cycle, when a new version
+section was added and there were fewer places to "randomly" add an entry, the
+conflicts became a problem again until we had a sufficient number of entries.
+
+On top of all this, it created an entirely different headache for [release managers]
+when they cherry-picked a commit into a stable branch for a patch release. If
+the commit included an entry in the `CHANGELOG`, it would include the entire
+changelog for the latest version in `master`, so the release manager would have
+to manually remove the later entries. They often would have had to do this
+multiple times per patch release. This was compounded when we had to release
+multiple patches at once due to a security issue.
+
+We needed to automate all of this manual work. So we [started brainstorming].
+After much discussion we settled on the current solution of one file per entry,
+and then compiling the entries into the overall `CHANGELOG.md` file during the
+[release process].
+
+[boring solution]: https://about.gitlab.com/handbook/#boring-solutions
+[release managers]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/release-manager.md
+[started brainstorming]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17826
+[release process]: https://gitlab.com/gitlab-org/release-tools
+
+---
+
+[Return to Development documentation](README.md)
diff --git a/doc/development/post_deployment_migrations.md b/doc/development/post_deployment_migrations.md
new file mode 100644
index 00000000000..cfc91539bee
--- /dev/null
+++ b/doc/development/post_deployment_migrations.md
@@ -0,0 +1,75 @@
+# Post Deployment Migrations
+
+Post deployment migrations are regular Rails migrations that can optionally be
+executed after a deployment. By default these migrations are executed alongside
+the other migrations. To skip these migrations you will have to set the
+environment variable `SKIP_POST_DEPLOYMENT_MIGRATIONS` to a non-empty value
+when running `rake db:migrate`.
+
+For example, this would run all migrations including any post deployment
+migrations:
+
+```bash
+bundle exec rake db:migrate
+```
+
+This however will skip post deployment migrations:
+
+```bash
+SKIP_POST_DEPLOYMENT_MIGRATIONS=true bundle exec rake db:migrate
+```
+
+## Deployment Integration
+
+Say you're using Chef for deploying new versions of GitLab and you'd like to run
+post deployment migrations after deploying a new version. Let's assume you
+normally use the command `chef-client` to do so. To make use of this feature
+you'd have to run this command as follows:
+
+```bash
+SKIP_POST_DEPLOYMENT_MIGRATIONS=true sudo chef-client
+```
+
+Once all servers have been updated you can run `chef-client` again on a single
+server _without_ the environment variable.
+
+The process is similar for other deployment techniques: first you would deploy
+with the environment variable set, then you'll essentially re-deploy a single
+server but with the variable _unset_.
+
+## Creating Migrations
+
+To create a post deployment migration you can use the following Rails generator:
+
+```bash
+bundle exec rails g post_deployment_migration migration_name_here
+```
+
+This will generate the migration file in `db/post_migrate`. These migrations
+behave exactly like regular Rails migrations.
+
+## Use Cases
+
+Post deployment migrations can be used to perform migrations that mutate state
+that an existing version of GitLab depends on. For example, say you want to
+remove a column from a table. This requires downtime as a GitLab instance
+depends on this column being present while it's running. Normally you'd follow
+these steps in such a case:
+
+1. Stop the GitLab instance
+2. Run the migration removing the column
+3. Start the GitLab instance again
+
+Using post deployment migrations we can instead follow these steps:
+
+1. Deploy a new version of GitLab while ignoring post deployment migrations
+2. Re-run `rake db:migrate` but without the environment variable set
+
+Here we don't need any downtime as the migration takes place _after_ a new
+version (which doesn't depend on the column anymore) has been deployed.
+
+Some other examples where these migrations are useful:
+
+* Cleaning up data generated due to a bug in GitLab
+* Removing tables
+* Migrating jobs from one Sidekiq queue to another
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 8e91ac5e3ba..b0b26ccf57a 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -132,6 +132,42 @@ Adding new Spinach scenarios is acceptable _only if_ the new scenario requires
no more than one new `step` definition. If more than that is required, the
test should be re-implemented using RSpec instead.
+## Testing Rake Tasks
+
+To make testing Rake tasks a little easier, there is a helper that can be included
+in lieu of the standard Spec helper. Instead of `require 'spec_helper'`, use
+`require 'rake_helper'`. The helper includes `spec_helper` for you, and configures
+a few other things to make testing Rake tasks easier.
+
+At a minimum, requiring the Rake helper will redirect `stdout`, include the
+runtime task helpers, and include the `RakeHelpers` Spec support module.
+
+The `RakeHelpers` module exposes a `run_rake_task(<task>)` method to make
+executing tasks simple. See `spec/support/rake_helpers.rb` for all available
+methods.
+
+Example:
+
+```ruby
+require 'rake_helper'
+
+describe 'gitlab:shell rake tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/shell'
+
+ stub_warn_user_is_not_gitlab
+ end
+
+ describe 'install task' do
+ it 'invokes create_hooks task' do
+ expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
+
+ run_rake_task('gitlab:shell:install')
+ end
+ end
+end
+```
+
---
[Return to Development documentation](README.md)
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 795e1d23443..83090f46271 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -271,9 +271,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-13-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-14-stable gitlab
-**Note:** You can change `8-13-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-14-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index cf1557ddc44..2e31fd994de 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -135,7 +135,7 @@ password as they will be needed when configuring GitLab in the next section.
JIRA configuration in GitLab is done via a project's **Services**.
-#### GitLab 13.0 with JIRA v1000.x
+#### GitLab 8.13.0 with JIRA v1000.x
To enable JIRA integration in a project, navigate to the project's
and open the context menu clicking on the top right gear icon, then go to
@@ -160,7 +160,7 @@ with the linked JIRA project.
#### GitLab 6.x-7.7 with JIRA v6.x
-_**Note:** GitLab versions 13.0 and up contain various integration improvements.
+_**Note:** GitLab versions 8.13.0 and up contain various integration improvements.
We strongly recommend upgrading._
In `gitlab.yml` enable the JIRA issue tracker section by
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index f3b2a288776..4a242c321aa 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -268,13 +268,20 @@ message `Can't verify CSRF token authenticity`. This means that there is an erro
the SAML request, but this error never reaches GitLab due to the CSRF check.
To bypass this you can add `skip_before_action :verify_authenticity_token` to the
-`omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab,
-where it can then be seen in the usual logs, or as a flash message in the login
-screen.
-
-That file is located at `/opt/gitlab/embedded/service/gitlab-rails/app/controllers`
-for Omnibus installations and by default on `/home/git/gitlab/app/controllers` for
-installations from source.
+`omniauth_callbacks_controller.rb` file immediately after the `class` line and
+comment out the `protect_from_forgery` line using a `#` then restart Unicorn. This
+will allow the error to hit GitLab, where it can then be seen in the usual logs,
+or as a flash message on the login screen.
+
+That file is located in `/opt/gitlab/embedded/service/gitlab-rails/app/controllers`
+for Omnibus installations and by default in `/home/git/gitlab/app/controllers` for
+installations from source. Restart Unicorn using the `sudo gitlab-ctl restart unicorn`
+command on Omnibus installations and `sudo service gitlab restart` on installations
+from source.
+
+You may also find the [SSO Tracer](https://addons.mozilla.org/en-US/firefox/addon/sso-tracer)
+(Firefox) and [SAML Chrome Panel](https://chrome.google.com/webstore/detail/saml-chrome-panel/paijfdbeoenhembfhkhllainmocckace)
+(Chrome) browser extensions useful in your debugging.
### Invalid audience
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 315cb56a089..266aeb7d60e 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -1,188 +1,3 @@
-# Maintenance
+# Maintenance Rake Tasks
-## Gather information about GitLab and the system it runs on
-
-This command gathers information about your GitLab installation and the System it runs on. These may be useful when asking for help or reporting issues.
-
-```
-# omnibus-gitlab
-sudo gitlab-rake gitlab:env:info
-
-# installation from source
-bundle exec rake gitlab:env:info RAILS_ENV=production
-```
-
-Example output:
-
-```
-System information
-System: Debian 7.8
-Current User: git
-Using RVM: no
-Ruby Version: 2.1.5p273
-Gem Version: 2.4.3
-Bundler Version: 1.7.6
-Rake Version: 10.3.2
-Sidekiq Version: 2.17.8
-
-GitLab information
-Version: 7.7.1
-Revision: 41ab9e1
-Directory: /home/git/gitlab
-DB Adapter: postgresql
-URL: https://gitlab.example.com
-HTTP Clone URL: https://gitlab.example.com/some-project.git
-SSH Clone URL: git@gitlab.example.com:some-project.git
-Using LDAP: no
-Using Omniauth: no
-
-GitLab Shell
-Version: 2.4.1
-Repositories: /home/git/repositories/
-Hooks: /home/git/gitlab-shell/hooks/
-Git: /usr/bin/git
-```
-
-## Check GitLab configuration
-
-Runs the following rake tasks:
-
-- `gitlab:gitlab_shell:check`
-- `gitlab:sidekiq:check`
-- `gitlab:app:check`
-
-It will check that each component was setup according to the installation guide and suggest fixes for issues found.
-
-You may also have a look at our [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide).
-
-```
-# omnibus-gitlab
-sudo gitlab-rake gitlab:check
-
-# installation from source
-bundle exec rake gitlab:check RAILS_ENV=production
-```
-
-NOTE: Use SANITIZE=true for gitlab:check if you want to omit project names from the output.
-
-Example output:
-
-```
-Checking Environment ...
-
-Git configured for git user? ... yes
-Has python2? ... yes
-python2 is supported version? ... yes
-
-Checking Environment ... Finished
-
-Checking GitLab Shell ...
-
-GitLab Shell version? ... OK (1.2.0)
-Repo base directory exists? ... yes
-Repo base directory is a symlink? ... no
-Repo base owned by git:git? ... yes
-Repo base access is drwxrws---? ... yes
-post-receive hook up-to-date? ... yes
-post-receive hooks in repos are links: ... yes
-
-Checking GitLab Shell ... Finished
-
-Checking Sidekiq ...
-
-Running? ... yes
-
-Checking Sidekiq ... Finished
-
-Checking GitLab ...
-
-Database config exists? ... yes
-Database is SQLite ... no
-All migrations up? ... yes
-GitLab config exists? ... yes
-GitLab config outdated? ... no
-Log directory writable? ... yes
-Tmp directory writable? ... yes
-Init script exists? ... yes
-Init script up-to-date? ... yes
-Redis version >= 2.0.0? ... yes
-
-Checking GitLab ... Finished
-```
-
-## Rebuild authorized_keys file
-
-In some case it is necessary to rebuild the `authorized_keys` file.
-
-For Omnibus-packages:
-```
-sudo gitlab-rake gitlab:shell:setup
-```
-
-For installations from source:
-```
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
-```
-
-```
-This will rebuild an authorized_keys file.
-You will lose any data stored in authorized_keys file.
-Do you want to continue (yes/no)? yes
-```
-
-## Clear redis cache
-
-If for some reason the dashboard shows wrong information you might want to
-clear Redis' cache.
-
-For Omnibus-packages:
-```
-sudo gitlab-rake cache:clear
-```
-
-For installations from source:
-```
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
-```
-
-## Precompile the assets
-
-Sometimes during version upgrades you might end up with some wrong CSS or
-missing some icons. In that case, try to precompile the assets again.
-
-Note that this only applies to source installations and does NOT apply to
-omnibus packages.
-
-For installations from source:
-```
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
-```
-
-For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at
-the release of upstream GitLab. The omnibus version includes optimized versions
-of those assets. Unless you are modifying the JavaScript / CSS code on your
-production machine after installing the package, there should be no reason to redo
-rake assets:precompile on the production machine. If you suspect that assets
-have been corrupted, you should reinstall the omnibus package.
-
-## Tracking Deployments
-
-GitLab provides a Rake task that lets you track deployments in GitLab
-Performance Monitoring. This Rake task simply stores the current GitLab version
-in the GitLab Performance Monitoring database.
-
-For Omnibus-packages:
-
-```
-sudo gitlab-rake gitlab:track_deployment
-```
-
-For installations from source:
-
-```
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
-```
+This document was moved to [administration/raketasks/maintenance](../administration/raketasks/maintenance.md).
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
new file mode 100644
index 00000000000..787511fd6cf
--- /dev/null
+++ b/doc/update/8.13-to-8.14.md
@@ -0,0 +1,205 @@
+# From 8.13 to 8.14
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+We will continue supporting Ruby < 2.3 for the time being but we recommend you
+upgrade to Ruby 2.3 if you're running a source installation, as this is the same
+version that ships with our Omnibus package.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
+echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz
+cd ruby-2.3.1
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-14-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-14-stable-ee
+```
+
+### 5. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v4.0.0
+```
+
+### 6. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.8.5
+sudo -u git -H make
+```
+
+### 7. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+```
+
+### 8. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-13-stable:config/gitlab.yml.example origin/8-14-stable:config/gitlab.yml.example
+```
+
+#### Git configuration
+
+Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
+the GitLab server during `git gc`.
+
+```sh
+sudo -u git -H git config --global repack.writeBitmaps true
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-13-stable:lib/support/nginx/gitlab-ssl origin/8-14-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-13-stable:lib/support/nginx/gitlab origin/8-14-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-14-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+For Ubuntu 16.04.1 LTS:
+
+ sudo systemctl daemon-reload
+
+### 9. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 10. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.13)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.12 to 8.13](8.12-to-8.13.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/README.md b/doc/update/README.md
index 975d72164b4..837b31abb97 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -85,6 +85,8 @@ possible.
- [MySQL installation guide](../install/database_mysql.md) contains additional
information about configuring GitLab to work with a MySQL database.
- [Restoring from backup after a failed upgrade](restore_after_failure.md)
+- [Upgrading PostgreSQL Using Slony](upgrading_postgresql_using_slony.md), for
+ upgrading a PostgreSQL database with minimal downtime.
[omnidocker]: http://docs.gitlab.com/omnibus/docker/README.html
[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update
diff --git a/doc/update/upgrading_postgresql_using_slony.md b/doc/update/upgrading_postgresql_using_slony.md
new file mode 100644
index 00000000000..f009906256e
--- /dev/null
+++ b/doc/update/upgrading_postgresql_using_slony.md
@@ -0,0 +1,482 @@
+# Upgrading PostgreSQL Using Slony
+
+This guide describes the steps one can take to upgrade their PostgreSQL database
+to the latest version without the need for hours of downtime. This guide assumes
+you have two database servers: one database server running an older version of
+PostgreSQL (e.g. 9.2.18) and one server running a newer version (e.g. 9.6.0).
+
+For this process we'll use a PostgreSQL replication tool called
+["Slony"](http://www.slony.info/). Slony allows replication between different
+PostgreSQL versions and as such can be used to upgrade a cluster with a minimal
+amount of downtime.
+
+In various places we'll refer to the user `gitlab-psql`. This user should be the
+user used to run the various PostgreSQL OS processes. If you're using a
+different user (e.g. `postgres`) you should replace `gitlab-psql` with the name
+of said user. This guide also assumes your database is called
+`gitlabhq_production`. If you happen to use a different database name you should
+change this accordingly.
+
+## Database Dumps
+
+Slony only replicates data and not any schema changes. As a result we must
+ensure that all databases have the same database structure.
+
+To do so we'll generate a dump of our current database. This dump will only
+contain the structure, not any data. To generate this dump run the following
+command on your active database server:
+
+```bash
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_dump -h /var/opt/gitlab/postgresql -p 5432 -U gitlab-psql -s -f /tmp/structure.sql gitlabhq_production
+```
+
+If you're not using GitLab's Omnibus package you may have to adjust the paths to
+`pg_dump` and the PostgreSQL installation directory to match the paths of your
+configuration.
+
+Once the structure dump is generated we also need to generate a dump for the
+`schema_migrations` table. This table doesn't have any primary keys and as such
+can't be replicated easily by Slony. To generate this dump run the following
+command on your active database server:
+
+```bash
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_dump -h /var/opt/gitlab/postgresql/ -p 5432 -U gitlab-psql -a -t schema_migrations -f /tmp/migrations.sql gitlabhq_production
+```
+
+Next we'll need to move these files somewhere accessible by the new database
+server. The easiest way is to simply download these files to your local system:
+
+```bash
+scp your-user@production-database-host:/tmp/*.sql /tmp
+```
+
+This will copy all the SQL files located in `/tmp` to your local system's
+`/tmp` directory. Once copied you can safely remove the files from the database
+server.
+
+## Installing Slony
+
+Slony will be used to upgrade the database without requiring long downtimes.
+Slony can be downloaded from http://www.slony.info/. If you have installed
+PostgreSQL using your operating system's package manager you may also be able to
+install Slony using said package manager.
+
+When compiling Slony from source you *must* use the following commands to do so:
+
+```bash
+./configure --prefix=/path/to/installation/directory --with-perltools --with-pgconfigdir=/path/to/directory/containing/pg_config/bin
+make
+make install
+```
+
+Omnibus users can use the following commands:
+
+```bash
+./configure --prefix=/opt/gitlab/embedded --with-perltools --with-pgconfigdir=/opt/gitlab/embedded/bin
+make
+make install
+```
+
+This assumes you have installed GitLab into /opt/gitlab.
+
+To test if Slony is installed properly, run the following commands:
+
+```bash
+test -f /opt/gitlab/embedded/bin/slonik && echo 'Slony installed' || echo 'Slony not installed'
+test -f /opt/gitlab/embedded/bin/slonik_init_cluster && echo 'Slony Perl tools are available' || echo 'Slony Perl tools are not available'
+/opt/gitlab/embedded/bin/slonik -v
+```
+
+This assumes Slony was installed to `/opt/gitlab/embedded`. If Slony was
+installed properly the output of these commands will be (the mentioned "slonik"
+version may be different):
+
+```
+Slony installed
+Slony Perl tools are available
+slonik version 2.2.5
+```
+
+## Slony User
+
+Next we must set up a PostgreSQL user that Slony can use to replicate your
+database. To do so, log in to your production database using `psql` using a
+super user account. Once done run the following SQL queries:
+
+```sql
+CREATE ROLE slony WITH SUPERUSER LOGIN REPLICATION ENCRYPTED PASSWORD 'password string here';
+ALTER ROLE slony SET statement_timeout TO 0;
+```
+
+Make sure you replace "password string here" with the actual password for the
+user. A password is *required*. This user must be created on _both_ the old and
+new database server using the same password.
+
+Once the user has been created make sure you note down the password as we will
+need it later on.
+
+## Configuring Slony
+
+Now we can finally start configuring Slony. Slony uses a configuration file for
+most of the work so we'll need to set this one up. This configuration file
+specifies where to put log files, how Slony should connect to the databases,
+etc.
+
+First we'll need to create some required directories and set the correct
+permissions. To do so, run the following commands on both the old and new
+database server:
+
+```bash
+sudo mkdir -p /var/log/gitlab/slony /var/run/slony1 /var/opt/gitlab/postgresql/slony
+sudo chown gitlab-psql:root /var/log/gitlab/slony /var/run/slony1 /var/opt/gitlab/postgresql/slony
+```
+
+Here `gitlab-psql` is the user used to run the PostgreSQL database processes. If
+you're using a different user you should replace this with the name of said
+user.
+
+Now that the directories are in place we can create the configuration file. For
+this we can use the following template:
+
+```perl
+if ($ENV{"SLONYNODES"}) {
+ require $ENV{"SLONYNODES"};
+} else {
+ $CLUSTER_NAME = 'slony_replication';
+ $LOGDIR = '/var/log/gitlab/slony';
+ $MASTERNODE = 1;
+ $DEBUGLEVEL = 2;
+
+ add_node(host => 'OLD_HOST', dbname => 'gitlabhq_production', port =>5432,
+ user=>'slony', password=>'SLONY_PASSWORD', node=>1);
+
+ add_node(host => 'NEW_HOST', dbname => 'gitlabhq_production', port =>5432,
+ user=>'slony', password=>'SLONY_PASSWORD', node=>2, parent=>1 );
+}
+
+$SLONY_SETS = {
+ "set1" => {
+ "set_id" => 1,
+ "table_id" => 1,
+ "sequence_id" => 1,
+ "pkeyedtables" => [
+ TABLES
+ ],
+ },
+};
+
+if ($ENV{"SLONYSET"}) {
+ require $ENV{"SLONYSET"};
+}
+
+# Please do not add or change anything below this point.
+1;
+```
+
+In this configuration file you should replace a few placeholders before you can
+use it. The following placeholders should be replaced:
+
+* `OLD_HOST`: the address of the old database server.
+* `NEW_HOST`: the address of the new database server.
+* `SLONY_PASSWORD`: the password of the Slony user created earlier.
+* `TABLES`: the tables to replicate.
+
+The list of tables to replicate can be generated by running the following
+command on your old PostgreSQL database:
+
+```
+sudo gitlab-psql gitlabhq_production -c "select concat('\"', schemaname, '.', tablename, '\",') from pg_catalog.pg_tables where schemaname = 'public' and tableowner = 'gitlab' and tablename != 'schema_migrations' order by tablename asc;" -t
+```
+
+If you're not using Omnibus you should replace `gitlab-psql` with the
+appropriate path to the `psql` executable.
+
+The above command outputs a list of tables in a format that can be copy-pasted
+directly into the above configuration file. Make sure to _replace_ `TABLES` with
+this output, don't just append it below it. Once done you'll end up with
+something like this:
+
+```perl
+"pkeyedtables" => [
+ "public.abuse_reports",
+ "public.appearances",
+ "public.application_settings",
+ ... more rows here ...
+]
+```
+
+Once you have the configuration file generated you must install it on both the
+old and new database. To do so, place it in
+`/var/opt/gitlab/postgresql/slony/slon_tools.conf` (for which we created the
+directory earlier on).
+
+Now that the configuration file is in place we can _finally_ start replicating
+our database. First we must set up the schema in our new database. To do so make
+sure that the SQL files we generated earlier can be found in the `/tmp`
+directory of the new server. Once these files are in place start a `psql`
+session on this server:
+
+```
+sudo gitlab-psql gitlabhq_production
+```
+
+Now run the following commands:
+
+```
+\i /tmp/structure.sql
+\i /tmp/migrations.sql
+```
+
+To verify if the structure is in place close the session, start it again, then
+run `\d`. If all went well you should see output along the lines of the
+following:
+
+```
+ List of relations
+ Schema | Name | Type | Owner
+--------+---------------------------------------------+----------+-------------
+ public | abuse_reports | table | gitlab
+ public | abuse_reports_id_seq | sequence | gitlab
+ public | appearances | table | gitlab
+ public | appearances_id_seq | sequence | gitlab
+ public | application_settings | table | gitlab
+ public | application_settings_id_seq | sequence | gitlab
+ public | approvals | table | gitlab
+ ... more rows here ...
+```
+
+Now we can initialize the required tables and what not that Slony will use for
+its replication process. To do so, run the following on the old database:
+
+```
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/slonik_init_cluster --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf | /opt/gitlab/embedded/bin/slonik
+```
+
+If all went well this will produce something along the lines of:
+
+```
+<stdin>:10: Set up replication nodes
+<stdin>:13: Next: configure paths for each node/origin
+<stdin>:16: Replication nodes prepared
+<stdin>:17: Please start a slon replication daemon for each node
+```
+
+Next we need to start a replication node on every server. To do so, run the
+following on the old database:
+
+```
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/slon_start 1 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf
+```
+
+If all went well this will produce output such as:
+
+
+```
+Invoke slon for node 1 - /opt/gitlab/embedded/bin/slon -p /var/run/slony1/slony_replication_node1.pid -s 1000 -d2 slony_replication 'host=192.168.0.7 dbname=gitlabhq_production user=slony port=5432 password=hieng8ezohHuCeiqu0leeghai4aeyahp' > /var/log/gitlab/slony/node1/gitlabhq_production-2016-10-06.log 2>&1 &
+Slon successfully started for cluster slony_replication, node node1
+PID [26740]
+Start the watchdog process as well...
+```
+
+Next we need to run the following command on the _new_ database server:
+
+```
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/slon_start 2 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf
+```
+
+This will produce similar output if all went well.
+
+Next we need to tell the new database server what it should replicate. This can
+be done by running the following command on the _new_ database server:
+
+```
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/slonik_create_set 1 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf | /opt/gitlab/embedded/bin/slonik
+```
+
+This should produce output along the lines of the following:
+
+```
+<stdin>:11: Subscription set 1 (set1) created
+<stdin>:12: Adding tables to the subscription set
+<stdin>:16: Add primary keyed table public.abuse_reports
+<stdin>:20: Add primary keyed table public.appearances
+<stdin>:24: Add primary keyed table public.application_settings
+... more rows here ...
+<stdin>:327: Adding sequences to the subscription set
+<stdin>:328: All tables added
+```
+
+Finally we can start the replication process by running the following on the
+_new_ database server:
+
+```
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/slonik_subscribe_set 1 2 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf | /opt/gitlab/embedded/bin/slonik
+```
+
+This should produce the following output:
+
+```
+<stdin>:6: Subscribed nodes to set 1
+```
+
+At this point the new database server will start replicating the data of the old
+database server. This process can take anywhere from a few minutes to hours, if
+not days. Unfortunately Slony itself doesn't really provide a way of knowing
+when the two databases are in sync. To get an estimate of the progress you can
+use the following shell script:
+
+```
+#!/usr/bin/env bash
+
+set -e
+
+user='slony'
+pass='SLONY_PASSWORD'
+
+function main {
+ while :
+ do
+ local source
+ local target
+
+ source=$(PGUSER="${user}" PGPASSWORD="${pass}" /opt/gitlab/embedded/bin/psql -h OLD_HOST gitlabhq_production -c "select pg_size_pretty(pg_database_size('gitlabhq_production'));" -t -A)
+ target=$(PGUSER="${user}" PGPASSWORD="${pass}" /opt/gitlab/embedded/bin/psql -h NEW_HOST gitlabhq_production -c "select pg_size_pretty(pg_database_size('gitlabhq_production'));" -t -A)
+
+ echo "$(date): ${target} of ${source}" >> progress.log
+ echo "$(date): ${target} of ${source}"
+
+ sleep 60
+ done
+}
+
+main
+```
+
+This script will compare the sizes of the old and new database every minute and
+print the result to STDOUT as well as logging it to a file. Make sure to replace
+`SLONY_PASSWORD`, `OLD_HOST`, and `NEW_HOST` with the correct values.
+
+## Stopping Replication
+
+At some point the two databases are in sync. Once this is the case you'll need
+to plan for a few minutes of downtime. This small downtime window is used to
+stop the replication process, remove any Slony data from both databases, restart
+GitLab so it can use the new database, etc.
+
+First, let's stop all of GitLab. Omnibus users can do so by running the
+following on their GitLab server(s):
+
+```
+sudo gitlab-ctl stop unicorn
+sudo gitlab-ctl stop sidekiq
+sudo gitlab-ctl stop mailroom
+```
+
+If you have any other processes that use PostgreSQL you should also stop those.
+
+Once everything has been stopped you should update any configuration settings,
+DNS records, etc so they all point to the new database.
+
+Once the settings have been taken care of we need to stop the replication
+process. It's crucial that no new data is written to the databases at this point
+as this data will be lost.
+
+To stop replication, run the following on both database servers:
+
+```bash
+sudo -u gitlab-psql /opt/gitlab/embedded/bin/slon_kill --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf
+```
+
+This will stop all the Slony processes on the host the command was executed on.
+
+## Resetting Sequences
+
+The above setup does not replicate database sequences, as such these must be
+reset manually in the target database. You can use the following script for
+this:
+
+```bash
+#!/usr/bin/env bash
+set -e
+
+function main {
+ local fix_sequences
+ local fix_owners
+
+ fix_sequences='/tmp/fix_sequences.sql'
+ fix_owners='/tmp/fix_owners.sql'
+
+ # The SQL queries were taken from
+ # https://wiki.postgresql.org/wiki/Fixing_Sequences
+ sudo gitlab-psql gitlabhq_production -t -c "
+ SELECT 'ALTER SEQUENCE '|| quote_ident(MIN(schema_name)) ||'.'|| quote_ident(MIN(seq_name))
+ ||' OWNED BY '|| quote_ident(MIN(TABLE_NAME)) ||'.'|| quote_ident(MIN(column_name)) ||';'
+ FROM (
+ SELECT
+ n.nspname AS schema_name,
+ c.relname AS TABLE_NAME,
+ a.attname AS column_name,
+ SUBSTRING(d.adsrc FROM E'^nextval\\(''([^'']*)''(?:::text|::regclass)?\\)') AS seq_name
+ FROM pg_class c
+ JOIN pg_attribute a ON (c.oid=a.attrelid)
+ JOIN pg_attrdef d ON (a.attrelid=d.adrelid AND a.attnum=d.adnum)
+ JOIN pg_namespace n ON (c.relnamespace=n.oid)
+ WHERE has_schema_privilege(n.oid,'USAGE')
+ AND n.nspname NOT LIKE 'pg!_%' escape '!'
+ AND has_table_privilege(c.oid,'SELECT')
+ AND (NOT a.attisdropped)
+ AND d.adsrc ~ '^nextval'
+ ) seq
+ GROUP BY seq_name HAVING COUNT(*)=1;
+ " > "${fix_owners}"
+
+ sudo gitlab-psql gitlabhq_production -t -c "
+ SELECT 'SELECT SETVAL(' ||
+ quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
+ ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
+ quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
+ FROM pg_class AS S,
+ pg_depend AS D,
+ pg_class AS T,
+ pg_attribute AS C,
+ pg_tables AS PGT
+ WHERE S.relkind = 'S'
+ AND S.oid = D.objid
+ AND D.refobjid = T.oid
+ AND D.refobjid = C.attrelid
+ AND D.refobjsubid = C.attnum
+ AND T.relname = PGT.tablename
+ ORDER BY S.relname;
+ " > "${fix_sequences}"
+
+ sudo gitlab-psql gitlabhq_production -f "${fix_owners}"
+ sudo gitlab-psql gitlabhq_production -f "${fix_sequences}"
+
+ rm "${fix_owners}" "${fix_sequences}"
+}
+
+main
+```
+
+Upload this script to the _target_ server and execute it as follows:
+
+```bash
+bash path/to/the/script/above.sh
+```
+
+This will correct the ownership of sequences and reset the next value for the
+`id` column to the next available value.
+
+## Removing Slony
+
+Next we need to remove all Slony related data. To do so, run the following
+command on the _target_ server:
+
+```bash
+sudo gitlab-psql gitlabhq_production -c "DROP SCHEMA _slony_replication CASCADE;"
+```
+
+Once done you can safely remove any Slony related files (e.g. the log
+directory), and uninstall Slony if desired. At this point you can start your
+GitLab instance again and if all went well it should be using your new database
+server.
diff --git a/features/dashboard/active_tab.feature b/features/dashboard/active_tab.feature
index 08b87808f33..bd883a0ebfa 100644
--- a/features/dashboard/active_tab.feature
+++ b/features/dashboard/active_tab.feature
@@ -18,7 +18,7 @@ Feature: Dashboard Active Tab
Then the active main tab should be Merge Requests
And no other main tabs should be active
- Scenario: On Dashboard Help
- Given I visit dashboard help page
- Then the active main tab should be Help
+ Scenario: On Dashboard Groups
+ Given I visit dashboard groups page
+ Then the active main tab should be Groups
And no other main tabs should be active
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index b1d5e4a7acb..92061dac7f4 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -11,7 +11,6 @@ Feature: Dashboard
And I visit dashboard page
Scenario: I should see projects list
- Then I should see "New Project" link
Then I should see "Shop" project link
Then I should see "Shop" project CI status
diff --git a/features/steps/dashboard/help.rb b/features/steps/dashboard/help.rb
index 9c94dc70df0..3c5bf44c538 100644
--- a/features/steps/dashboard/help.rb
+++ b/features/steps/dashboard/help.rb
@@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
end
step 'I visit the "Rake Tasks" help page' do
- visit help_page_path("raketasks/maintenance")
+ visit help_page_path("administration/raketasks/maintenance")
end
step 'I should see "Rake Tasks" page markdown rendered' do
diff --git a/features/steps/shared/sidebar_active_tab.rb b/features/steps/shared/sidebar_active_tab.rb
index 5c47238777f..07fff16e867 100644
--- a/features/steps/shared/sidebar_active_tab.rb
+++ b/features/steps/shared/sidebar_active_tab.rb
@@ -1,12 +1,8 @@
module SharedSidebarActiveTab
include Spinach::DSL
- step 'the active main tab should be Help' do
- ensure_active_main_tab('Help')
- end
-
step 'no other main tabs should be active' do
- expect(page).to have_selector('.nav-sidebar > li.active', count: 1)
+ expect(page).to have_selector('.nav-sidebar li.active', count: 1)
end
def ensure_active_main_tab(content)
@@ -17,6 +13,10 @@ module SharedSidebarActiveTab
ensure_active_main_tab('Projects')
end
+ step 'the active main tab should be Groups' do
+ ensure_active_main_tab('Groups')
+ end
+
step 'the active main tab should be Projects' do
ensure_active_main_tab('Projects')
end
@@ -28,8 +28,4 @@ module SharedSidebarActiveTab
step 'the active main tab should be Merge Requests' do
ensure_active_main_tab('Merge Requests')
end
-
- step 'the active main tab should be Help' do
- ensure_active_main_tab('Help')
- end
end
diff --git a/generator_templates/rails/post_deployment_migration/migration.rb b/generator_templates/rails/post_deployment_migration/migration.rb
new file mode 100644
index 00000000000..1a7b8d5bf35
--- /dev/null
+++ b/generator_templates/rails/post_deployment_migration/migration.rb
@@ -0,0 +1,22 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class <%= migration_class_name %> < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index ab9d2d54f4b..d52496451a2 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -43,14 +43,13 @@ module API
end
class Hook < Grape::Entity
- expose :id, :url, :created_at
+ expose :id, :url, :created_at, :push_events, :tag_push_events
+ expose :enable_ssl_verification
end
class ProjectHook < Hook
- expose :project_id, :push_events
- expose :issues_events, :merge_requests_events, :tag_push_events
+ expose :project_id, :issues_events, :merge_requests_events
expose :note_events, :build_events, :pipeline_events, :wiki_page_events
- expose :enable_ssl_verification
end
class BasicProjectDetails < Grape::Entity
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 14f5be3b5f6..dd93a85dc54 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -47,7 +47,8 @@ module API
:build_events,
:pipeline_events,
:wiki_page_events,
- :enable_ssl_verification
+ :enable_ssl_verification,
+ :token
]
@hook = user_project.hooks.new(attrs)
@@ -82,7 +83,8 @@ module API
:build_events,
:pipeline_events,
:wiki_page_events,
- :enable_ssl_verification
+ :enable_ssl_verification,
+ :token
]
if @hook.update_attributes attrs
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 794e34874f4..32f731c5652 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -12,6 +12,7 @@ module API
end
get do
hooks = SystemHook.all
+
present hooks, with: Entities::Hook
end
@@ -19,10 +20,14 @@ module API
success Entities::Hook
end
params do
- requires :url, type: String, desc: 'The URL for the system hook'
+ requires :url, type: String, desc: "The URL to send the request to"
+ optional :token, type: String, desc: 'The token used to validate payloads'
+ optional :push_events, type: Boolean, desc: "Trigger hook on push events"
+ optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
+ optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
end
post do
- hook = SystemHook.new declared(params).to_h
+ hook = SystemHook.new declared(params, include_missing: false).to_h
if hook.save
present hook, with: Entities::Hook
diff --git a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
new file mode 100644
index 00000000000..7cb4bccb23c
--- /dev/null
+++ b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
@@ -0,0 +1,15 @@
+require 'rails/generators'
+
+module Rails
+ class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase
+ def create_migration_file
+ timestamp = Time.now.strftime('%Y%m%d%H%I%S')
+
+ template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb"
+ end
+
+ def migration_class_name
+ file_name.camelize
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb
index f755a404693..34169319b26 100644
--- a/lib/gitlab/import_export/attribute_cleaner.rb
+++ b/lib/gitlab/import_export/attribute_cleaner.rb
@@ -3,10 +3,25 @@ module Gitlab
class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
- def self.clean!(relation_hash:)
- relation_hash.reject! do |key, _value|
- key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
- end
+ def self.clean(*args)
+ new(*args).clean
+ end
+
+ def initialize(relation_hash:, relation_class:)
+ @relation_hash = relation_hash
+ @relation_class = relation_class
+ end
+
+ def clean
+ @relation_hash.reject do |key, _value|
+ prohibited_key?(key) || !@relation_class.attribute_method?(key)
+ end.except('id')
+ end
+
+ private
+
+ def prohibited_key?(key)
+ key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
end
end
end
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 113895ba22c..ffd17118c91 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -43,6 +43,14 @@ module Gitlab
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
+ remove_symlinks!
+ end
+
+ def remove_symlinks!
+ Dir["#{@shared.export_path}/**/*"].each do |path|
+ FileUtils.rm(path) if File.lstat(path).symlink?
+ end
+
true
end
end
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index 36c4cf6efa0..b790733f4a7 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -55,7 +55,12 @@ module Gitlab
end
def member_hash(member)
- member.except('id').merge(source_id: @project.id, importing: true)
+ parsed_hash(member).merge('source_id' => @project.id, 'importing' => true)
+ end
+
+ def parsed_hash(member)
+ Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: member.deep_stringify_keys,
+ relation_class: ProjectMember)
end
def find_project_user_query(member)
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 7cdba880a93..c551321c18d 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -9,8 +9,14 @@ module Gitlab
end
def restore
- json = IO.read(@path)
- @tree_hash = ActiveSupport::JSON.decode(json)
+ begin
+ json = IO.read(@path)
+ @tree_hash = ActiveSupport::JSON.decode(json)
+ rescue => e
+ Rails.logger.error("Import/Export error: #{e.message}")
+ raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
+ end
+
@project_members = @tree_hash.delete('project_members')
ActiveRecord::Base.no_touching do
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index dc630e76411..a0e80fccad9 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -14,7 +14,7 @@ module Gitlab
priorities: :label_priorities,
label: :project_label }.freeze
- USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze
+ USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id].freeze
PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze
@@ -30,7 +30,7 @@ module Gitlab
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project_id:)
@relation_name = OVERRIDES[relation_sym] || relation_sym
- @relation_hash = relation_hash.except('id', 'noteable_id').merge('project_id' => project_id)
+ @relation_hash = relation_hash.except('noteable_id').merge('project_id' => project_id)
@members_mapper = members_mapper
@user = user
@imported_object_retries = 0
@@ -172,11 +172,8 @@ module Gitlab
end
def parsed_relation_hash
- @parsed_relation_hash ||= begin
- Gitlab::ImportExport::AttributeCleaner.clean!(relation_hash: @relation_hash)
-
- @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
- end
+ @parsed_relation_hash ||= Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: @relation_hash,
+ relation_class: relation_class)
end
def set_st_diffs
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index fc08082fc86..bd3c3ee3b2f 100644
--- a/lib/gitlab/import_export/version_checker.rb
+++ b/lib/gitlab/import_export/version_checker.rb
@@ -24,12 +24,19 @@ module Gitlab
end
def verify_version!(version)
- if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
+ if different_version?(version)
raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
else
true
end
end
+
+ def different_version?(version)
+ Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
+ rescue => e
+ Rails.logger.error("Import/Export error: #{e.message}")
+ raise Gitlab::ImportExport::Error.new('Incorrect VERSION format')
+ end
end
end
end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 210899882b4..58761a129d4 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -63,11 +63,11 @@ namespace :gitlab do
# Launch installation process
system(*%W(bin/install) + repository_storage_paths_args)
-
- # (Re)create hooks
- system(*%W(bin/create-hooks) + repository_storage_paths_args)
end
+ # (Re)create hooks
+ Rake::Task['gitlab:shell:create_hooks'].invoke
+
# Required for debian packaging with PKGR: Setup .ssh/environment with
# the current PATH, so that the correct ruby version gets loaded
# Requires to set "PermitUserEnvironment yes" in sshd config (should not
@@ -102,6 +102,15 @@ namespace :gitlab do
end
end
end
+
+ desc 'Create or repair repository hooks symlink'
+ task create_hooks: :environment do
+ warn_user_is_not_gitlab
+
+ puts 'Creating/Repairing hooks symlinks for all repositories'
+ system(*%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
+ puts 'done'.color(:green)
+ end
end
def setup
diff --git a/package.json b/package.json
index d440307bd10..a303c9c1eac 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"devDependencies": {
"eslint": "^3.1.1",
"eslint-config-airbnb": "^12.0.0",
+ "eslint-plugin-filenames": "^1.1.0",
"eslint-plugin-import": "^2.0.1",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.1"
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
new file mode 100644
index 00000000000..8c8bc1b0f1c
--- /dev/null
+++ b/spec/bin/changelog_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+load File.expand_path('../../bin/changelog', __dir__)
+
+describe 'bin/changelog' do
+ describe ChangelogOptionParser do
+ it 'parses --ammend' do
+ options = described_class.parse(%w[foo bar --amend])
+
+ expect(options.amend).to eq true
+ end
+
+ it 'parses --force' do
+ options = described_class.parse(%w[foo --force bar])
+
+ expect(options.force).to eq true
+ end
+
+ it 'parses -f' do
+ options = described_class.parse(%w[foo -f bar])
+
+ expect(options.force).to eq true
+ end
+
+ it 'parses --merge-request' do
+ options = described_class.parse(%w[foo --merge-request 1234 bar])
+
+ expect(options.merge_request).to eq 1234
+ end
+
+ it 'parses -m' do
+ options = described_class.parse(%w[foo -m 4321 bar])
+
+ expect(options.merge_request).to eq 4321
+ end
+
+ it 'parses --dry-run' do
+ options = described_class.parse(%w[foo --dry-run bar])
+
+ expect(options.dry_run).to eq true
+ end
+
+ it 'parses -n' do
+ options = described_class.parse(%w[foo -n bar])
+
+ expect(options.dry_run).to eq true
+ end
+
+ it 'parses --git-username' do
+ allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
+ options = described_class.parse(%w[foo --git-username bar])
+
+ expect(options.author).to eq 'Jane Doe'
+ end
+
+ it 'parses -u' do
+ allow(described_class).to receive(:git_user_name).and_return('John Smith')
+ options = described_class.parse(%w[foo -u bar])
+
+ expect(options.author).to eq 'John Smith'
+ end
+
+ it 'parses -h' do
+ expect do
+ $stdout = StringIO.new
+
+ described_class.parse(%w[foo -h bar])
+ end.to raise_error(SystemExit)
+ end
+
+ it 'assigns title' do
+ options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend])
+
+ expect(options.title).to eq 'foo bar baz'
+ end
+ end
+end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 3234fabe288..a92075fec8f 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -53,7 +53,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'with lists' do
let(:milestone) { create(:milestone, project: project) }
- let(:planning) { create(:label, project: project, name: 'Planning') }
+ let(:planning) { create(:label, project: project, name: 'Planning', description: 'Test') }
let(:development) { create(:label, project: project, name: 'Development') }
let(:testing) { create(:label, project: project, name: 'Testing') }
let(:bug) { create(:label, project: project, name: 'Bug') }
@@ -91,6 +91,12 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_selector('.board', count: 4)
end
+ it 'shows description tooltip on list title' do
+ page.within('.board:nth-child(2)') do
+ expect(find('.board-title span.has-tooltip')[:title]).to eq('Test')
+ end
+ end
+
it 'shows issues in lists' do
wait_for_board_cards(2, 2)
wait_for_board_cards(3, 2)
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index 88e1549a22b..9dfa5d1de19 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -11,6 +11,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::None.title)
+ expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'No Milestone')
expect(page).to have_css('.issue', count: 1)
end
@@ -22,6 +23,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::Upcoming.title)
+ expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming')
expect(page).to have_css('.issue', count: 0)
end
@@ -33,6 +35,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::Upcoming.title)
+ expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming')
expect(page).to have_css('.issue', count: 1)
end
@@ -44,6 +47,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::Upcoming.title)
+ expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming')
expect(page).to have_css('.issue', count: 0)
end
end
@@ -55,6 +59,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(milestone.title)
+ expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: milestone.title)
expect(page).to have_css('.issue', count: 1)
end
@@ -70,6 +75,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(milestone.title)
+ expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: milestone.title)
expect(page).to have_css('.issue', count: 1)
end
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index b963d1305b5..c68e1ea4af9 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -59,4 +59,12 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_css('a.btn.active', text: 'Side-by-side')
end
end
+
+ it 'does not allow non-existing branches' do
+ visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
+
+ expect(page).to have_content('The form contains the following errors')
+ expect(page).to have_content('Source branch "non-exist-source" does not exist')
+ expect(page).to have_content('Target branch "non-exist-target" does not exist')
+ end
end
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index d1685f95503..63a23a14f20 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -216,7 +216,9 @@ describe "Builds" do
@build.run!
visit namespace_project_build_path(@project.namespace, @project, @build)
click_link 'Cancel'
- click_link 'Retry'
+ page.within('.build-header') do
+ click_link 'Retry build'
+ end
end
it 'shows the right status and buttons' do
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index d25cf7fb353..e796ee570b7 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -164,5 +164,23 @@ describe 'Edit Project Settings', feature: true do
expect(page).to have_content "Customize your workflow!"
end
+
+ it "hides project activity tabs" do
+ select "Disabled", from: "project_project_feature_attributes_repository_access_level"
+ select "Disabled", from: "project_project_feature_attributes_issues_access_level"
+ select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
+
+ click_button "Save changes"
+ wait_for_ajax
+
+ visit activity_namespace_project_path(project.namespace, project)
+
+ page.within(".event-filter") do
+ expect(page).to have_selector("a", count: 2)
+ expect(page).not_to have_content("Push events")
+ expect(page).not_to have_content("Merge events")
+ expect(page).not_to have_content("Comments")
+ end
+ end
end
end
diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml
index 2526e5e33a5..69445b61367 100644
--- a/spec/javascripts/fixtures/gl_field_errors.html.haml
+++ b/spec/javascripts/fixtures/gl_field_errors.html.haml
@@ -1,4 +1,4 @@
-%form.show-gl-field-errors{action: 'submit', method: 'post'}
+%form.gl-show-field-errors{action: 'submit', method: 'post'}
.form-group
%input.required-text{required: true, type: 'text'} Text
.form-group
@@ -10,6 +10,6 @@
.form-group
%input.hidden{ type:'hidden' }
.form-group
- %input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate
+ %input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate
.form-group
%input.submit{type: 'submit'} Submit
diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6
index 4bdd72800ea..0713e30e485 100644
--- a/spec/javascripts/gl_field_errors_spec.js.es6
+++ b/spec/javascripts/gl_field_errors_spec.js.es6
@@ -8,7 +8,7 @@
describe('GL Style Field Errors', function() {
beforeEach(function() {
fixture.load('gl_field_errors.html');
- const $form = this.$form = $('form.show-gl-field-errors');
+ const $form = this.$form = $('form.gl-show-field-errors');
this.fieldErrors = new global.GlFieldErrors($form);
});
@@ -21,7 +21,7 @@
});
it('should ignore elements with custom error handling', function() {
- const customErrorFlag = 'no-gl-field-errors';
+ const customErrorFlag = 'gl-field-error-ignore';
const customErrorElem = $(`.${customErrorFlag}`);
expect(customErrorElem.length).toBe(1);
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index a5aa387f4f7..62aa212f1f6 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -122,6 +122,14 @@ describe Gitlab::GitAccess, lib: true do
describe 'build authentication_abilities permissions' do
let(:authentication_abilities) { build_authentication_abilities }
+ describe 'owner' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ context 'pull code' do
+ it { expect(subject).to be_allowed }
+ end
+ end
+
describe 'reporter user' do
before { project.team << [user, :reporter] }
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index b8e7932eb4a..63bab0f0d0d 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -1,8 +1,10 @@
require 'spec_helper'
describe Gitlab::ImportExport::AttributeCleaner, lib: true do
+ let(:relation_class){ double('relation_class').as_null_object }
let(:unsafe_hash) do
{
+ 'id' => 101,
'service_id' => 99,
'moved_to_id' => 99,
'namespace_id' => 99,
@@ -27,8 +29,9 @@ describe Gitlab::ImportExport::AttributeCleaner, lib: true do
end
it 'removes unwanted attributes from the hash' do
- described_class.clean!(relation_hash: unsafe_hash)
+ # allow(relation_class).to receive(:attribute_method?).and_return(true)
+ parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
- expect(unsafe_hash).to eq(post_safe_hash)
+ expect(parsed_hash).to eq(post_safe_hash)
end
end
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
new file mode 100644
index 00000000000..a88ddd17aca
--- /dev/null
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::FileImporter, lib: true do
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+ let(:export_path) { "#{Dir::tmpdir}/file_importer_spec" }
+ let(:valid_file) { "#{shared.export_path}/valid.json" }
+ let(:symlink_file) { "#{shared.export_path}/invalid.json" }
+ let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
+
+ before do
+ stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
+
+ setup_files
+
+ described_class.import(archive_file: '', shared: shared)
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ end
+
+ it 'removes symlinks in root folder' do
+ expect(File.exist?(symlink_file)).to be false
+ end
+
+ it 'removes symlinks in subfolders' do
+ expect(File.exist?(subfolder_symlink_file)).to be false
+ end
+
+ it 'does not remove a valid file' do
+ expect(File.exist?(valid_file)).to be true
+ end
+
+ def setup_files
+ FileUtils.mkdir_p("#{shared.export_path}/subfolder/")
+ FileUtils.touch(valid_file)
+ FileUtils.ln_s(valid_file, symlink_file)
+ FileUtils.ln_s(valid_file, subfolder_symlink_file)
+ end
+end
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 069ea960321..3038ab53ad8 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
@@ -175,6 +176,19 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(MergeRequest.find_by_title('MR2').source_project_id).to eq(-1)
end
end
+
+ context 'project.json file access check' do
+ it 'does not read a symlink' do
+ Dir.mktmpdir do |tmpdir|
+ setup_symlink(tmpdir, 'project.json')
+ allow(shared).to receive(:export_path).and_call_original
+
+ restored_project_json
+
+ expect(shared.errors.first).not_to include('test')
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index c680e712b59..2405ac5abfe 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -1,8 +1,10 @@
require 'spec_helper'
+include ImportExport::CommonUtil
describe Gitlab::ImportExport::VersionChecker, services: true do
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
+
describe 'bundle a project Git repo' do
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
let(:version) { Gitlab::ImportExport.version }
before do
@@ -27,4 +29,16 @@ describe Gitlab::ImportExport::VersionChecker, services: true do
end
end
end
+
+ describe 'version file access check' do
+ it 'does not read a symlink' do
+ Dir.mktmpdir do |tmpdir|
+ setup_symlink(tmpdir, 'VERSION')
+
+ described_class.check!(shared: shared)
+
+ expect(shared.errors.first).not_to include('test')
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index a9f637147d1..ee0e38bd373 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe JiraService, models: true do
+ include Gitlab::Routing.url_helpers
+
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -66,6 +68,29 @@ describe JiraService, models: true do
).once
end
+ it "references the GitLab commit/merge request" do
+ @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+
+ expect(WebMock).to have_requested(:post, @comment_url).with(
+ body: /#{Gitlab.config.gitlab.url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/
+ ).once
+ end
+
+ it "references the GitLab commit/merge request (relative URL)" do
+ stub_config_setting(relative_url_root: '/gitlab')
+ stub_config_setting(url: Settings.send(:build_gitlab_url))
+
+ allow(JiraService).to receive(:default_url_options) do
+ { script_name: '/gitlab' }
+ end
+
+ @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
+
+ expect(WebMock).to have_requested(:post, @comment_url).with(
+ body: /#{Gitlab.config.gitlab.url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/
+ ).once
+ end
+
it "calls the api with jira_issue_transition_id" do
@jira_service.jira_issue_transition_id = 'this-is-a-custom-id'
@jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project))
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 658e3c13a73..96249a7d8c3 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -6,6 +6,7 @@ describe ProjectPolicy, models: true do
let(:dev) { create(:user) }
let(:master) { create(:user) }
let(:owner) { create(:user) }
+ let(:admin) { create(:admin) }
let(:project) { create(:empty_project, :public, namespace: owner.namespace) }
let(:guest_permissions) do
@@ -155,6 +156,19 @@ describe ProjectPolicy, models: true do
it do
is_expected.to include(*guest_permissions)
is_expected.to include(*reporter_permissions)
+ is_expected.to include(*team_member_reporter_permissions)
+ is_expected.to include(*developer_permissions)
+ is_expected.to include(*master_permissions)
+ is_expected.to include(*owner_permissions)
+ end
+ end
+
+ context 'admin' do
+ let(:current_user) { admin }
+
+ it do
+ is_expected.to include(*guest_permissions)
+ is_expected.to include(*reporter_permissions)
is_expected.not_to include(*team_member_reporter_permissions)
is_expected.to include(*developer_permissions)
is_expected.to include(*master_permissions)
diff --git a/spec/rake_helper.rb b/spec/rake_helper.rb
new file mode 100644
index 00000000000..9b5b4bf9fea
--- /dev/null
+++ b/spec/rake_helper.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+require 'rake'
+
+RSpec.configure do |config|
+ config.include RakeHelpers
+
+ # Redirect stdout so specs don't have so much noise
+ config.before(:all) do
+ $stdout = StringIO.new
+
+ Rake.application.rake_require 'tasks/gitlab/task_helpers'
+ Rake::Task.define_task :environment
+ end
+
+ # Reset stdout
+ config.after(:all) do
+ $stdout = STDOUT
+ end
+end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index cfcdcad74cd..5f39329a1b8 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -88,6 +88,7 @@ describe API::API, 'ProjectHooks', api: true do
expect do
post api("/projects/#{project.id}/hooks", user), url: "http://example.com", issues_events: true
end.to change {project.hooks.count}.by(1)
+
expect(response).to have_http_status(201)
expect(json_response['url']).to eq('http://example.com')
expect(json_response['issues_events']).to eq(true)
@@ -99,6 +100,24 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['pipeline_events']).to eq(false)
expect(json_response['wiki_page_events']).to eq(false)
expect(json_response['enable_ssl_verification']).to eq(true)
+ expect(json_response).not_to include('token')
+ end
+
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ expect do
+ post api("/projects/#{project.id}/hooks", user), url: "http://example.com", token: token
+ end.to change {project.hooks.count}.by(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response["url"]).to eq("http://example.com")
+ expect(json_response).not_to include("token")
+
+ hook = project.hooks.find(json_response["id"])
+
+ expect(hook.url).to eq("http://example.com")
+ expect(hook.token).to eq(token)
end
it "returns a 400 error if url not given" do
@@ -129,6 +148,19 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
end
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: "http://example.org", token: token
+
+ expect(response).to have_http_status(200)
+ expect(json_response["url"]).to eq("http://example.org")
+ expect(json_response).not_to include("token")
+
+ expect(hook.reload.url).to eq("http://example.org")
+ expect(hook.reload.token).to eq(token)
+ end
+
it "returns 404 error if hook id not found" do
put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
expect(response).to have_http_status(404)
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index f8a1aed5441..f685a3685e6 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -13,6 +13,7 @@ describe API::API, api: true do
context "when no user" do
it "returns authentication error" do
get api("/hooks")
+
expect(response).to have_http_status(401)
end
end
@@ -20,6 +21,7 @@ describe API::API, api: true do
context "when not an admin" do
it "returns forbidden error" do
get api("/hooks", user)
+
expect(response).to have_http_status(403)
end
end
@@ -27,9 +29,12 @@ describe API::API, api: true do
context "when authenticated as admin" do
it "returns an array of hooks" do
get api("/hooks", admin)
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['url']).to eq(hook.url)
+ expect(json_response.first['push_events']).to be true
+ expect(json_response.first['tag_push_events']).to be false
end
end
end
@@ -43,6 +48,7 @@ describe API::API, api: true do
it "responds with 400 if url not given" do
post api("/hooks", admin)
+
expect(response).to have_http_status(400)
end
@@ -51,6 +57,14 @@ describe API::API, api: true do
post api("/hooks", admin)
end.not_to change { SystemHook.count }
end
+
+ it 'sets default values for events' do
+ post api('/hooks', admin), url: 'http://mep.mep', enable_ssl_verification: true
+
+ expect(response).to have_http_status(201)
+ expect(json_response['enable_ssl_verification']).to be true
+ expect(json_response['tag_push_events']).to be false
+ end
end
describe "GET /hooks/:id" do
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index dbdf83a0dff..9bfc84c7425 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -284,7 +284,17 @@ describe 'Git LFS API and storage' do
let(:authorization) { authorize_ci_project }
shared_examples 'can download LFS only from own projects' do
- context 'for own project' do
+ context 'for owned project' do
+ let(:project) { create(:empty_project, namespace: user.namespace) }
+
+ let(:update_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it_behaves_like 'responds with a file'
+ end
+
+ context 'for member of project' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:update_permissions) do
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index c64df4979b0..bb26513103d 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -245,6 +245,12 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
it_behaves_like 'a pullable'
end
+
+ context 'when you are owner' do
+ let(:project) { create(:empty_project, namespace: current_user.namespace) }
+
+ it_behaves_like 'a pullable'
+ end
end
context 'for private' do
@@ -266,6 +272,12 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
it_behaves_like 'a pullable'
end
+
+ context 'when you are owner' do
+ let(:project) { create(:empty_project, namespace: current_user.namespace) }
+
+ it_behaves_like 'a pullable'
+ end
end
end
end
@@ -276,13 +288,21 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
end
context 'disallow for all' do
- let(:project) { create(:empty_project, :public) }
+ context 'when you are member' do
+ let(:project) { create(:empty_project, :public) }
- before do
- project.team << [current_user, :developer]
+ before do
+ project.team << [current_user, :developer]
+ end
+
+ it_behaves_like 'an inaccessible'
end
- it_behaves_like 'an inaccessible'
+ context 'when you are owner' do
+ let(:project) { create(:empty_project, :public, namespace: current_user.namespace) }
+
+ it_behaves_like 'an inaccessible'
+ end
end
end
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 3a3f07ddcb9..3f5df049ea2 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -25,6 +25,8 @@ describe MergeRequests::BuildService, services: true do
before do
allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare)
+ allow(project).to receive(:commit).and_return(commit_1)
+ allow(project).to receive(:commit).and_return(commit_2)
end
describe 'execute' do
@@ -193,5 +195,52 @@ describe MergeRequests::BuildService, services: true do
end
end
end
+
+ context 'source branch does not exist' do
+ before do
+ allow(project).to receive(:commit).with(source_branch).and_return(nil)
+ allow(project).to receive(:commit).with(target_branch).and_return(commit_1)
+ end
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+
+ it 'adds an error message to the merge request' do
+ expect(merge_request.errors).to contain_exactly('Source branch "feature-branch" does not exist')
+ end
+ end
+
+ context 'target branch does not exist' do
+ before do
+ allow(project).to receive(:commit).with(source_branch).and_return(commit_1)
+ allow(project).to receive(:commit).with(target_branch).and_return(nil)
+ end
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+
+ it 'adds an error message to the merge request' do
+ expect(merge_request.errors).to contain_exactly('Target branch "master" does not exist')
+ end
+ end
+
+ context 'both source and target branches do not exist' do
+ before do
+ allow(project).to receive(:commit).and_return(nil)
+ end
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+
+ it 'adds both error messages to the merge request' do
+ expect(merge_request.errors).to contain_exactly(
+ 'Source branch "feature-branch" does not exist',
+ 'Target branch "master" does not exist'
+ )
+ end
+ end
end
end
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
new file mode 100644
index 00000000000..2542a59bb00
--- /dev/null
+++ b/spec/support/import_export/common_util.rb
@@ -0,0 +1,10 @@
+module ImportExport
+ module CommonUtil
+ def setup_symlink(tmpdir, symlink_name)
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(tmpdir)
+
+ File.open("#{tmpdir}/test", 'w') { |file| file.write("test") }
+ FileUtils.ln_s("#{tmpdir}/test", "#{tmpdir}/#{symlink_name}")
+ end
+ end
+end
diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb
new file mode 100644
index 00000000000..52d80c69835
--- /dev/null
+++ b/spec/support/rake_helpers.rb
@@ -0,0 +1,10 @@
+module RakeHelpers
+ def run_rake_task(task_name)
+ Rake::Task[task_name].reenable
+ Rake.application.invoke_task task_name
+ end
+
+ def stub_warn_user_is_not_gitlab
+ allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab)
+ end
+end
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
new file mode 100644
index 00000000000..226d34fe2c9
--- /dev/null
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -0,0 +1,26 @@
+require 'rake_helper'
+
+describe 'gitlab:shell rake tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/shell'
+
+ stub_warn_user_is_not_gitlab
+ end
+
+ describe 'install task' do
+ it 'invokes create_hooks task' do
+ expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
+
+ run_rake_task('gitlab:shell:install')
+ end
+ end
+
+ describe 'create_hooks task' do
+ it 'calls gitlab-shell bin/create_hooks' do
+ expect_any_instance_of(Object).to receive(:system)
+ .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks", *repository_storage_paths_args)
+
+ run_rake_task('gitlab:shell:create_hooks')
+ end
+ end
+end
diff --git a/vendor/assets/javascripts/Sortable.js b/vendor/assets/javascripts/Sortable.js
index eca7c5012b2..f9e57bcb855 100644
--- a/vendor/assets/javascripts/Sortable.js
+++ b/vendor/assets/javascripts/Sortable.js
@@ -4,8 +4,7 @@
* @license MIT
*/
-
-(function (factory) {
+(function sortableModule(factory) {
"use strict";
if (typeof define === "function" && define.amd) {
@@ -15,15 +14,22 @@
module.exports = factory();
}
else if (typeof Package !== "undefined") {
+ //noinspection JSUnresolvedVariable
Sortable = factory(); // export for Meteor.js
}
else {
/* jshint sub:true */
window["Sortable"] = factory();
}
-})(function () {
+})(function sortableFactory() {
"use strict";
+ if (typeof window == "undefined" || !window.document) {
+ return function sortableError() {
+ throw new Error("Sortable.js requires a window with a document");
+ };
+ }
+
var dragEl,
parentEl,
ghostEl,
@@ -33,6 +39,7 @@
scrollEl,
scrollParentEl,
+ scrollCustomFn,
lastEl,
lastCSS,
@@ -42,6 +49,8 @@
newIndex,
activeGroup,
+ putSortable,
+
autoScroll = {},
tapEvt,
@@ -58,8 +67,15 @@
document = win.document,
parseInt = win.parseInt,
+ $ = win.jQuery || win.Zepto,
+ Polymer = win.Polymer,
+
supportDraggable = !!('draggable' in document.createElement('div')),
supportCssPointerEvents = (function (el) {
+ // false when IE11
+ if (!!navigator.userAgent.match(/Trident.*rv[ :]?11\./)) {
+ return false;
+ }
el = document.createElement('x');
el.style.cssText = 'pointer-events:auto';
return el.style.pointerEvents === 'auto';
@@ -88,13 +104,17 @@
winHeight = window.innerHeight,
vx,
- vy
+ vy,
+
+ scrollOffsetX,
+ scrollOffsetY
;
// Delect scrollEl
if (scrollParentEl !== rootEl) {
scrollEl = options.scroll;
scrollParentEl = rootEl;
+ scrollCustomFn = options.scrollFn;
if (scrollEl === true) {
scrollEl = rootEl;
@@ -136,11 +156,18 @@
if (el) {
autoScroll.pid = setInterval(function () {
+ scrollOffsetY = vy ? vy * speed : 0;
+ scrollOffsetX = vx ? vx * speed : 0;
+
+ if ('function' === typeof(scrollCustomFn)) {
+ return scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt);
+ }
+
if (el === win) {
- win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
+ win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY);
} else {
- vy && (el.scrollTop += vy * speed);
- vx && (el.scrollLeft += vx * speed);
+ el.scrollTop += scrollOffsetY;
+ el.scrollLeft += scrollOffsetX;
}
}, 24);
}
@@ -149,19 +176,39 @@
}, 30),
_prepareGroup = function (options) {
- var group = options.group;
+ function toFn(value, pull) {
+ if (value === void 0 || value === true) {
+ value = group.name;
+ }
- if (!group || typeof group != 'object') {
- group = options.group = {name: group};
+ if (typeof value === 'function') {
+ return value;
+ } else {
+ return function (to, from) {
+ var fromGroup = from.options.group.name;
+
+ return pull
+ ? value
+ : value && (value.join
+ ? value.indexOf(fromGroup) > -1
+ : (fromGroup == value)
+ );
+ };
+ }
}
- ['pull', 'put'].forEach(function (key) {
- if (!(key in group)) {
- group[key] = true;
- }
- });
+ var group = {};
+ var originalGroup = options.group;
+
+ if (!originalGroup || typeof originalGroup != 'object') {
+ originalGroup = {name: originalGroup};
+ }
- options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
+ group.name = originalGroup.name;
+ group.checkPull = toFn(originalGroup.pull, true);
+ group.checkPut = toFn(originalGroup.put);
+
+ options.group = group;
}
;
@@ -198,6 +245,7 @@
draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
ignore: 'a, img',
filter: null,
animation: 0,
@@ -211,7 +259,8 @@
forceFallback: false,
fallbackClass: 'sortable-fallback',
fallbackOnBody: false,
- fallbackTolerance: 0
+ fallbackTolerance: 0,
+ fallbackOffset: {x: 0, y: 0}
};
@@ -224,7 +273,7 @@
// Bind all private methods
for (var fn in this) {
- if (fn.charAt(0) === '_') {
+ if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
this[fn] = this[fn].bind(this);
}
}
@@ -258,7 +307,7 @@
type = evt.type,
touch = evt.touches && evt.touches[0],
target = (touch || evt).target,
- originalTarget = target,
+ originalTarget = evt.target.shadowRoot && evt.path[0] || target,
filter = options.filter,
startIndex;
@@ -271,13 +320,13 @@
return; // only left button or enabled
}
- target = _closest(target, options.draggable, el);
-
- if (!target) {
+ if (options.handle && !_closest(originalTarget, options.handle, el)) {
return;
}
- if (options.handle && !_closest(originalTarget, options.handle, el)) {
+ target = _closest(target, options.draggable, el);
+
+ if (!target) {
return;
}
@@ -332,16 +381,18 @@
this._lastX = (touch || evt).clientX;
this._lastY = (touch || evt).clientY;
+ dragEl.style['will-change'] = 'transform';
+
dragStartFn = function () {
// Delayed drag has been triggered
// we can re-enable the events: touchmove/mousemove
_this._disableDelayedDrag();
// Make the element draggable
- dragEl.draggable = true;
+ dragEl.draggable = _this.nativeDraggable;
// Chosen item
- _toggleClass(dragEl, _this.options.chosenClass, true);
+ _toggleClass(dragEl, options.chosenClass, true);
// Bind the events: dragstart/dragend
_this._triggerDragStart(touch);
@@ -408,7 +459,10 @@
try {
if (document.selection) {
- document.selection.empty();
+ // Timeout neccessary for IE9
+ setTimeout(function () {
+ document.selection.empty();
+ });
} else {
window.getSelection().removeAllRanges();
}
@@ -418,8 +472,11 @@
_dragStarted: function () {
if (rootEl && dragEl) {
+ var options = this.options;
+
// Apply effect
- _toggleClass(dragEl, this.options.ghostClass, true);
+ _toggleClass(dragEl, options.ghostClass, true);
+ _toggleClass(dragEl, options.dragClass, false);
Sortable.active = this;
@@ -443,12 +500,11 @@
var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
parent = target,
- groupName = ' ' + this.options.group.name + '',
i = touchDragOverListeners.length;
if (parent) {
do {
- if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
+ if (parent[expando]) {
while (i--) {
touchDragOverListeners[i]({
clientX: touchEvt.clientX,
@@ -478,9 +534,10 @@
if (tapEvt) {
var options = this.options,
fallbackTolerance = options.fallbackTolerance,
+ fallbackOffset = options.fallbackOffset,
touch = evt.touches ? evt.touches[0] : evt,
- dx = touch.clientX - tapEvt.clientX,
- dy = touch.clientY - tapEvt.clientY,
+ dx = (touch.clientX - tapEvt.clientX) + fallbackOffset.x,
+ dy = (touch.clientY - tapEvt.clientY) + fallbackOffset.y,
translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
// only set the status to dragging, when we are actually dragging
@@ -520,6 +577,7 @@
_toggleClass(ghostEl, options.ghostClass, false);
_toggleClass(ghostEl, options.fallbackClass, true);
+ _toggleClass(ghostEl, options.dragClass, true);
_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
@@ -545,13 +603,15 @@
this._offUpEvents();
- if (activeGroup.pull == 'clone') {
- cloneEl = dragEl.cloneNode(true);
+ if (activeGroup.checkPull(this, this, dragEl, evt) == 'clone') {
+ cloneEl = _clone(dragEl);
_css(cloneEl, 'display', 'none');
rootEl.insertBefore(cloneEl, dragEl);
_dispatchEvent(this, rootEl, 'clone', dragEl);
}
+ _toggleClass(dragEl, options.dragClass, true);
+
if (useFallback) {
if (useFallback === 'touch') {
// Bind touch events
@@ -581,10 +641,11 @@
var el = this.el,
target,
dragRect,
+ targetRect,
revert,
options = this.options,
group = options.group,
- groupPut = group.put,
+ activeSortable = Sortable.active,
isOwner = (activeGroup === group),
canSort = options.sort;
@@ -598,9 +659,9 @@
if (activeGroup && !options.disabled &&
(isOwner
? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
- : activeGroup.pull && groupPut && (
- (activeGroup.name === group.name) || // by Name
- (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
+ : (
+ putSortable === this ||
+ activeGroup.checkPull(this, activeSortable, dragEl, evt) && group.checkPut(this, activeSortable, dragEl, evt)
)
) &&
(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
@@ -614,6 +675,7 @@
target = _closest(evt.target, options.draggable, el);
dragRect = dragEl.getBoundingClientRect();
+ putSortable = this;
if (revert) {
_cloneHide(true);
@@ -633,7 +695,6 @@
if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
(el === evt.target) && (target = _ghostIsLast(el, evt))
) {
-
if (target) {
if (target.animated) {
return;
@@ -644,7 +705,7 @@
_cloneHide(isOwner);
- if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
+ if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt) !== false) {
if (!dragEl.contains(el)) {
el.appendChild(dragEl);
parentEl = el; // actualization
@@ -661,9 +722,9 @@
lastParentCSS = _css(target.parentNode);
}
+ targetRect = target.getBoundingClientRect();
- var targetRect = target.getBoundingClientRect(),
- width = targetRect.right - targetRect.left,
+ var width = targetRect.right - targetRect.left,
height = targetRect.bottom - targetRect.top,
floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
|| (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
@@ -671,7 +732,7 @@
isLong = (target.offsetHeight > dragEl.offsetHeight),
halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
nextSibling = target.nextElementSibling,
- moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
+ moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt),
after
;
@@ -784,6 +845,7 @@
}
_disableDraggable(dragEl);
+ dragEl.style['will-change'] = '';
// Remove class's
_toggleClass(dragEl, this.options.ghostClass, false);
@@ -793,15 +855,16 @@
newIndex = _index(dragEl, options.draggable);
if (newIndex >= 0) {
- // drag from one list and drop into another
- _dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
- _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
// Add event
_dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);
// Remove event
_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
+
+ // drag from one list and drop into another
+ _dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
+ _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
}
}
else {
@@ -821,7 +884,8 @@
}
if (Sortable.active) {
- if (newIndex === null || newIndex === -1) {
+ /* jshint eqnull:true */
+ if (newIndex == null || newIndex === -1) {
newIndex = oldIndex;
}
@@ -837,7 +901,7 @@
this._nulling();
},
- _nulling: function () {
+ _nulling: function() {
rootEl =
dragEl =
parentEl =
@@ -857,6 +921,7 @@
lastEl =
lastCSS =
+ putSortable =
activeGroup =
Sortable.active = null;
},
@@ -1011,14 +1076,21 @@
if ((selector === '>*' && el.parentNode === ctx) || _matches(el, selector)) {
return el;
}
- }
- while (el !== ctx && (el = el.parentNode));
+ /* jshint boss:true */
+ } while (el = _getParentOrHost(el));
}
return null;
}
+ function _getParentOrHost(el) {
+ var parent = el.host;
+
+ return (parent && parent.nodeType) ? parent : el.parentNode;
+ }
+
+
function _globalDragOver(/**Event*/evt) {
if (evt.dataTransfer) {
evt.dataTransfer.dropEffect = 'move';
@@ -1094,8 +1166,10 @@
function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
+ sortable = (sortable || rootEl[expando]);
+
var evt = document.createEvent('Event'),
- options = (sortable || rootEl[expando]).options,
+ options = sortable.options,
onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);
evt.initEvent(name, true, true);
@@ -1116,7 +1190,7 @@
}
- function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
+ function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt) {
var evt,
sortable = fromEl[expando],
onMoveFn = sortable.options.onMove,
@@ -1135,7 +1209,7 @@
fromEl.dispatchEvent(evt);
if (onMoveFn) {
- retVal = onMoveFn.call(sortable, evt);
+ retVal = onMoveFn.call(sortable, evt, originalEvt);
}
return retVal;
@@ -1155,9 +1229,14 @@
/** @returns {HTMLElement|false} */
function _ghostIsLast(el, evt) {
var lastEl = el.lastElementChild,
- rect = lastEl.getBoundingClientRect();
-
- return ((evt.clientY - (rect.top + rect.height) > 5) || (evt.clientX - (rect.right + rect.width) > 5)) && lastEl; // min delta
+ rect = lastEl.getBoundingClientRect();
+
+ // 5 — min delta
+ // abs — нельзя добавлять, а то глюки при наведении сверху
+ return (
+ (evt.clientY - (rect.top + rect.height) > 5) ||
+ (evt.clientX - (rect.right + rect.width) > 5)
+ ) && lastEl;
}
@@ -1251,6 +1330,15 @@
return dst;
}
+ function _clone(el) {
+ return $
+ ? $(el).clone(true)[0]
+ : (Polymer && Polymer.dom
+ ? Polymer.dom(el).cloneNode(true)
+ : el.cloneNode(true)
+ );
+ }
+
// Export utils
Sortable.utils = {
@@ -1265,6 +1353,7 @@
throttle: _throttle,
closest: _closest,
toggleClass: _toggleClass,
+ clone: _clone,
index: _index
};
diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore
index e5df7b9150e..935ceef0680 100644
--- a/vendor/gitignore/Android.gitignore
+++ b/vendor/gitignore/Android.gitignore
@@ -39,3 +39,6 @@ captures/
# Keystore files
*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
diff --git a/vendor/gitignore/C.gitignore b/vendor/gitignore/C.gitignore
index 7a065c709c7..8a365b3d829 100644
--- a/vendor/gitignore/C.gitignore
+++ b/vendor/gitignore/C.gitignore
@@ -7,6 +7,11 @@
*.obj
*.elf
+# Linker output
+*.ilk
+*.map
+*.exp
+
# Precompiled Headers
*.gch
*.pch
@@ -34,3 +39,13 @@
# Debug files
*.dSYM/
*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
diff --git a/vendor/gitignore/ExtJs.gitignore b/vendor/gitignore/ExtJs.gitignore
index 5ffc21546ec..c92aea0fe0c 100644
--- a/vendor/gitignore/ExtJs.gitignore
+++ b/vendor/gitignore/ExtJs.gitignore
@@ -1,4 +1,12 @@
.architect
+bootstrap.css
+bootstrap.js
bootstrap.json
+bootstrap.jsonp
build/
+classic.json
+classic.jsonp
ext/
+modern.json
+modern.jsonp
+resources/sass/.sass-cache/
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
index ea83a5eb620..0a254147875 100644
--- a/vendor/gitignore/Global/JetBrains.gitignore
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -4,9 +4,6 @@
# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
-.idea/dictionaries
-.idea/vcs.xml
-.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
diff --git a/vendor/gitignore/Global/macOS.gitignore b/vendor/gitignore/Global/macOS.gitignore
index 828a509a137..f0f3fbc06c8 100644
--- a/vendor/gitignore/Global/macOS.gitignore
+++ b/vendor/gitignore/Global/macOS.gitignore
@@ -1,26 +1,26 @@
-*.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE
index 0e259d42c99..670154e3538 100644
--- a/vendor/gitignore/LICENSE
+++ b/vendor/gitignore/LICENSE
@@ -1,121 +1,116 @@
-Creative Commons Legal Code
-
CC0 1.0 Universal
- CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
- LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
- ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
- INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
- REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
- PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
- THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
- HEREUNDER.
-
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
-exclusive Copyright and Related Rights (defined below) upon the creator
-and subsequent owner(s) (each and all, an "owner") of an original work of
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
-Certain owners wish to permanently relinquish those rights to a Work for
-the purpose of contributing to a commons of creative, cultural and
-scientific works ("Commons") that the public can reliably and without fear
-of later claims of infringement build upon, modify, incorporate in other
-works, reuse and redistribute as freely as possible in any form whatsoever
-and for any purposes, including without limitation commercial purposes.
-These owners may contribute to the Commons to promote the ideal of a free
-culture and the further production of creative, cultural and scientific
-works, or to gain reputation or greater distribution for their Work in
-part through the use and efforts of others.
-
-For these and/or other purposes and motivations, and without any
-expectation of additional consideration or compensation, the person
-associating CC0 with a Work (the "Affirmer"), to the extent that he or she
-is an owner of Copyright and Related Rights in the Work, voluntarily
-elects to apply CC0 to the Work and publicly distribute the Work under its
-terms, with knowledge of his or her Copyright and Related Rights in the
-Work and the meaning and intended legal effect of CC0 on those rights.
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific
+works ("Commons") that the public can reliably and without fear of later
+claims of infringement build upon, modify, incorporate in other works, reuse
+and redistribute as freely as possible in any form whatsoever and for any
+purposes, including without limitation commercial purposes. These owners may
+contribute to the Commons to promote the ideal of a free culture and the
+further production of creative, cultural and scientific works, or to gain
+reputation or greater distribution for their Work in part through the use and
+efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation
+of additional consideration or compensation, the person associating CC0 with a
+Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+and publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
-Related Rights"). Copyright and Related Rights include, but are not
-limited to, the following:
-
- i. the right to reproduce, adapt, distribute, perform, display,
- communicate, and translate a Work;
- ii. moral rights retained by the original author(s) and/or performer(s);
-iii. publicity and privacy rights pertaining to a person's image or
- likeness depicted in a Work;
- iv. rights protecting against unfair competition in regards to a Work,
- subject to the limitations in paragraph 4(a), below;
- v. rights protecting the extraction, dissemination, use and reuse of data
- in a Work;
- vi. database rights (such as those arising under Directive 96/9/EC of the
- European Parliament and of the Council of 11 March 1996 on the legal
- protection of databases, and under any national implementation
- thereof, including any amended or successor version of such
- directive); and
-vii. other similar, equivalent or corresponding rights throughout the
- world based on applicable law or treaty, and any national
- implementations thereof.
-
-2. Waiver. To the greatest extent permitted by, but not in contravention
-of, applicable law, Affirmer hereby overtly, fully, permanently,
-irrevocably and unconditionally waives, abandons, and surrenders all of
-Affirmer's Copyright and Related Rights and associated claims and causes
-of action, whether now known or unknown (including existing as well as
-future claims and causes of action), in the Work (i) in all territories
-worldwide, (ii) for the maximum duration provided by applicable law or
-treaty (including future time extensions), (iii) in any current or future
-medium and for any number of copies, and (iv) for any purpose whatsoever,
-including without limitation commercial, advertising or promotional
-purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
-member of the public at large and to the detriment of Affirmer's heirs and
-successors, fully intending that such Waiver shall not be subject to
-revocation, rescission, cancellation, termination, or any other legal or
-equitable action to disrupt the quiet enjoyment of the Work by the public
-as contemplated by Affirmer's express Statement of Purpose.
-
-3. Public License Fallback. Should any part of the Waiver for any reason
-be judged legally invalid or ineffective under applicable law, then the
-Waiver shall be preserved to the maximum extent permitted taking into
-account Affirmer's express Statement of Purpose. In addition, to the
-extent the Waiver is so judged Affirmer hereby grants to each affected
-person a royalty-free, non transferable, non sublicensable, non exclusive,
-irrevocable and unconditional license to exercise Affirmer's Copyright and
-Related Rights in the Work (i) in all territories worldwide, (ii) for the
-maximum duration provided by applicable law or treaty (including future
-time extensions), (iii) in any current or future medium and for any number
-of copies, and (iv) for any purpose whatsoever, including without
-limitation commercial, advertising or promotional purposes (the
-"License"). The License shall be deemed effective as of the date CC0 was
-applied by Affirmer to the Work. Should any part of the License for any
-reason be judged legally invalid or ineffective under applicable law, such
-partial invalidity or ineffectiveness shall not invalidate the remainder
-of the License, and in such case Affirmer hereby affirms that he or she
-will not (i) exercise any of his or her remaining Copyright and Related
-Rights in the Work or (ii) assert any associated claims and causes of
-action with respect to the Work, in either case contrary to Affirmer's
-express Statement of Purpose.
+Related Rights"). Copyright and Related Rights include, but are not limited
+to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+and Related Rights and associated claims and causes of action, whether now
+known or unknown (including existing as well as future claims and causes of
+action), in the Work (i) in all territories worldwide, (ii) for the maximum
+duration provided by applicable law or treaty (including future time
+extensions), (iii) in any current or future medium and for any number of
+copies, and (iv) for any purpose whatsoever, including without limitation
+commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+the Waiver for the benefit of each member of the public at large and to the
+detriment of Affirmer's heirs and successors, fully intending that such Waiver
+shall not be subject to revocation, rescission, cancellation, termination, or
+any other legal or equitable action to disrupt the quiet enjoyment of the Work
+by the public as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+judged legally invalid or ineffective under applicable law, then the Waiver
+shall be preserved to the maximum extent permitted taking into account
+Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+is so judged Affirmer hereby grants to each affected person a royalty-free,
+non transferable, non sublicensable, non exclusive, irrevocable and
+unconditional license to exercise Affirmer's Copyright and Related Rights in
+the Work (i) in all territories worldwide, (ii) for the maximum duration
+provided by applicable law or treaty (including future time extensions), (iii)
+in any current or future medium and for any number of copies, and (iv) for any
+purpose whatsoever, including without limitation commercial, advertising or
+promotional purposes (the "License"). The License shall be deemed effective as
+of the date CC0 was applied by Affirmer to the Work. Should any part of the
+License for any reason be judged legally invalid or ineffective under
+applicable law, such partial invalidity or ineffectiveness shall not
+invalidate the remainder of the License, and in such case Affirmer hereby
+affirms that he or she will not (i) exercise any of his or her remaining
+Copyright and Related Rights in the Work or (ii) assert any associated claims
+and causes of action with respect to the Work, in either case contrary to
+Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
- a. No trademark or patent rights held by Affirmer are waived, abandoned,
- surrendered, licensed or otherwise affected by this document.
- b. Affirmer offers the Work as-is and makes no representations or
- warranties of any kind concerning the Work, express, implied,
- statutory or otherwise, including without limitation warranties of
- title, merchantability, fitness for a particular purpose, non
- infringement, or the absence of latent or other defects, accuracy, or
- the present or absence of errors, whether or not discoverable, all to
- the greatest extent permissible under applicable law.
- c. Affirmer disclaims responsibility for clearing rights of other persons
- that may apply to the Work or any use thereof, including without
- limitation any person's Copyright and Related Rights in the Work.
- Further, Affirmer disclaims responsibility for obtaining any necessary
- consents, permissions or other rights required for any use of the
- Work.
- d. Affirmer understands and acknowledges that Creative Commons is not a
- party to this document and has no duty or obligation with respect to
- this CC0 or use of the Work.
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+<http://creativecommons.org/publicdomain/zero/1.0/>
diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore
index 1cd717b6921..e7c594fa3e2 100644
--- a/vendor/gitignore/Laravel.gitignore
+++ b/vendor/gitignore/Laravel.gitignore
@@ -7,6 +7,7 @@ app/storage/
# Laravel 5 & Lumen specific
bootstrap/cache/
+public/storage
.env.*.php
.env.php
.env
diff --git a/vendor/gitignore/Nanoc.gitignore b/vendor/gitignore/Nanoc.gitignore
index abc21828a3e..3f36ea2a878 100644
--- a/vendor/gitignore/Nanoc.gitignore
+++ b/vendor/gitignore/Nanoc.gitignore
@@ -1,6 +1,6 @@
-# For projects using nanoc (http://nanoc.ws/)
+# For projects using Nanoc (http://nanoc.ws/)
-# Default location for output, needs to match output_dir's value found in config.yaml
+# Default location for output (needs to match output_dir's value found in nanoc.yaml)
output/
# Temporary file directory
diff --git a/vendor/gitignore/OpenCart.gitignore b/vendor/gitignore/OpenCart.gitignore
index 28e45aa6aac..97be41faa38 100644
--- a/vendor/gitignore/OpenCart.gitignore
+++ b/vendor/gitignore/OpenCart.gitignore
@@ -11,3 +11,10 @@ system/cache/
system/logs/
system/storage/
+
+# vQmod log files
+vqmod/logs/*
+# vQmod cache files
+vqmod/vqcache/*
+vqmod/checked.cache
+vqmod/mods.cache
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 37fc9d40817..6a2bf47ade9 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -66,7 +66,7 @@ docs/_build/
# PyBuilder
target/
-# IPython Notebook
+# Jupyter Notebook
.ipynb_checkpoints
# pyenv
diff --git a/vendor/gitignore/Rust.gitignore b/vendor/gitignore/Rust.gitignore
index cb14a420640..50281a44270 100644
--- a/vendor/gitignore/Rust.gitignore
+++ b/vendor/gitignore/Rust.gitignore
@@ -5,3 +5,6 @@
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index f620fad23eb..1afbaf197f4 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -61,6 +61,15 @@ acs-*.bib
# fixme
*.lox
+# feynmf/feynmp
+*.mf
+*.mp
+*.t[1-9]
+*.t[1-9][0-9]
+*.tfm
+*.[1-9]
+*.[1-9][0-9]
+
#(r)(e)ledmac/(r)(e)ledpar
*.end
*.?end
diff --git a/vendor/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore
index be0e4913c3a..beec7b91f15 100644
--- a/vendor/gitignore/UnrealEngine.gitignore
+++ b/vendor/gitignore/UnrealEngine.gitignore
@@ -1,6 +1,9 @@
# Visual Studio 2015 user specific files
.vs/
+# Visual Studio 2015 database file
+*.VC.db
+
# Compiled Object files
*.slo
*.lo
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 1b86e7ec918..09e407344ca 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -1,11 +1,14 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
+*.vcxproj.filters
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
@@ -44,6 +47,7 @@ dlldata.c
project.lock.json
project.fragment.lock.json
artifacts/
+Properties/launchSettings.json
*_i.c
*_p.c
@@ -238,6 +242,9 @@ FakesAssemblies/
# Visual Studio 6 workspace options file
*.opt
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
diff --git a/vendor/gitlab-ci-yml/Clojure.gitlab-ci.yml b/vendor/gitlab-ci-yml/Clojure.gitlab-ci.yml
new file mode 100644
index 00000000000..f066285b1ad
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Clojure.gitlab-ci.yml
@@ -0,0 +1,22 @@
+# Based on openjdk:8, already includes lein
+image: clojure:lein-2.7.0
+# If you need to configure a database, add a `services` section here
+# See https://docs.gitlab.com/ce/ci/services/postgres.html
+# Make sure you configure the connection as well
+
+before_script:
+ # If you need to install any external applications, like a
+ # postgres client, you may want to uncomment the line below:
+ #
+ #- apt-get update -y
+ #
+ # Retrieve project dependencies
+ # Do this on before_script since it'll be shared between both test and
+ # any production sections a user adds
+ - lein deps
+
+test:
+ script:
+ # If you need to run any migrations or configure the database, this
+ # would be the point to do it.
+ - lein test
diff --git a/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml b/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml
new file mode 100644
index 00000000000..e8da49a935e
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Crystal.gitlab-ci.yml
@@ -0,0 +1,37 @@
+# This file is a template, and might need editing before it works on your project.
+# Official language image. Look for the different tagged releases at:
+# https://hub.docker.com/r/crystallang/crystal/
+image: "crystallang/crystal:latest"
+
+# Pick zero or more services to be used on all builds.
+# Only needed when using a docker container to run your tests in.
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+# services:
+# - mysql:latest
+# - redis:latest
+# - postgres:latest
+
+# variables:
+# POSTGRES_DB: database_name
+
+# Cache shards in between builds
+cache:
+ paths:
+ - libs
+
+# This is a basic example for a shard or script which doesn't use
+# services such as redis or postgres
+before_script:
+ - apt-get update -qq && apt-get install -y -qq libxml2-dev
+ - crystal -v # Print out Crystal version for debugging
+ - shards
+
+# If you are using built-in Crystal Spec.
+spec:
+ script:
+ - crystal spec
+
+# If you are using minitest.cr
+minitest:
+ script:
+ - crystal test/spec_test.cr # change to the file(s) you execute for tests