summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2016-11-03 20:43:24 +0800
committerLin Jen-Shin <godfat@godfat.org>2016-11-03 20:43:24 +0800
commitb0af0ab62fa7b0b64443e510ed388cef83db996d (patch)
tree4c8f383a9e79c8ad747962545a171f6a1c59c51f
parent9176a19e3d858a6d64a2254260febe000474af6d (diff)
parentca1096e77f1f44089cd8e37e2fe7fa392571542f (diff)
downloadgitlab-ce-b0af0ab62fa7b0b64443e510ed388cef83db996d.tar.gz
Merge remote-tracking branch 'upstream/master' into pipeline-notifications
* upstream/master: (26 commits) 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 Make ESLint ignore instrumented files for coverage analysis (!7236) Check that JavaScript file names match convention (!7238) Removed z-index for filters on issue boards GitLab 8.13 not 13 Replace MR Description Format links Fix gdb backtrace command Update gitlab.yml.example remove extra spaces from app/workers/post_receive.rb Add Rake task to create/repair GitLab Shell hooks symlinks Added guide for upgrading Postgres using Slony Ensure hook tokens are write-only in the API Add support for token attr in project hooks API Add a CHANGELOG entry Fix edit button wiki Updated Sortable JS plugin Allow owners to fetch source code in CI builds fixes milestone dropdown not select issue ...
-rw-r--r--.eslintignore1
-rw-r--r--.eslintrc6
-rw-r--r--CHANGELOG.md8
-rw-r--r--CONTRIBUTING.md17
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock8
-rw-r--r--app/assets/stylesheets/pages/boards.scss9
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/policies/project_policy.rb12
-rw-r--r--app/views/projects/edit.html.haml1
-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/issuable/_milestone_dropdown.html.haml4
-rw-r--r--app/workers/post_receive.rb6
-rwxr-xr-xbin/changelog170
-rw-r--r--config/gitlab.yml.example4
-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/development/README.md1
-rw-r--r--doc/development/changelog.md182
-rw-r--r--doc/development/testing.md36
-rw-r--r--doc/integration/jira.md4
-rw-r--r--doc/raketasks/maintenance.md189
-rw-r--r--doc/update/README.md2
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md482
-rw-r--r--features/steps/dashboard/help.rb2
-rw-r--r--lib/api/project_hooks.rb6
-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/issues/filter_by_milestone_spec.rb6
-rw-r--r--spec/lib/gitlab/git_access_spec.rb8
-rw-r--r--spec/models/project_services/jira_service_spec.rb22
-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/lfs_http_spec.rb12
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb28
-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
43 files changed, 1568 insertions, 295 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 bd62b6839a8..95a2b0de3b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,21 +29,25 @@ Please view this file on the master branch, on stable branches it's out of date.
- Better handle when no users were selected for adding to group or project. (Linus Thiel)
- Only show register tab if signup enabled.
+- 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
@@ -51,10 +55,13 @@ Please view this file on the master branch, on stable branches it's out of date.
- 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)
- Update mail_room and enable sentinel support to Reply By Email (!7101)
- Add task completion status in Issues and Merge Requests tabs: "X of Y tasks completed" (!6527, @gmesalazar)
@@ -65,6 +72,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- 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
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/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/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/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index ff6a899b92a..c40ad06969e 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -181,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/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/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..2cd337af778
--- /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]"
+
+ # 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/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/development/README.md b/doc/development/README.md
index 3f2151bbe8e..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
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
new file mode 100644
index 00000000000..390eb549d0b
--- /dev/null
+++ b/doc/development/changelog.md
@@ -0,0 +1,182 @@
+# 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.
+
+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/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/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/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/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/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/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/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/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/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/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index a9f637147d1..a3e9adae4e2 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+include Gitlab::Routing.url_helpers
describe JiraService, models: true do
describe "Associations" do
@@ -66,6 +67,27 @@ 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))
+
+ Project.default_url_options[:script_name] = "/gitlab"
+
+ @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/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/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
};