summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@selenight.nl>2016-03-21 23:22:21 +0100
committerDouwe Maan <douwe@selenight.nl>2016-03-21 23:22:21 +0100
commitae7b2ef62cdf61c990f914d776a6fdfc2bc49fa2 (patch)
tree6f0022bf04b1b566fa79b979cc9cc373cd0ebaa1
parent8d544645f0ef114586212835cf011a3e268c9ec1 (diff)
parent0305dd98b32b5a989f2b84e0810cf5ddc14abd7f (diff)
downloadgitlab-ce-ae7b2ef62cdf61c990f914d776a6fdfc2bc49fa2.tar.gz
Merge branch 'master' into issue_12658
# Conflicts: # app/models/issue.rb # app/views/projects/_home_panel.html.haml # app/views/shared/projects/_project.html.haml # db/schema.rb # spec/models/project_spec.rb
-rw-r--r--.rubocop.yml958
-rw-r--r--CHANGELOG5
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock30
-rw-r--r--app/assets/javascripts/application.js.coffee3
-rw-r--r--app/assets/javascripts/aside.js.coffee1
-rw-r--r--app/assets/javascripts/awards_handler.coffee2
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee6
-rw-r--r--app/assets/javascripts/issuable_form.js.coffee11
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee25
-rw-r--r--app/assets/javascripts/notes.js.coffee8
-rw-r--r--app/assets/javascripts/project.js.coffee1
-rw-r--r--app/assets/javascripts/stat_graph_contributors_util.js.coffee2
-rw-r--r--app/assets/stylesheets/framework/avatar.scss2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss2
-rw-r--r--app/assets/stylesheets/framework/layout.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss24
-rw-r--r--app/assets/stylesheets/notify.scss24
-rw-r--r--app/assets/stylesheets/pages/admin.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss6
-rw-r--r--app/assets/stylesheets/pages/login.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss12
-rw-r--r--app/assets/stylesheets/pages/profile.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss2
-rw-r--r--app/assets/stylesheets/pages/xterm.scss10
-rw-r--r--app/controllers/admin/groups_controller.rb6
-rw-r--r--app/controllers/admin/labels_controller.rb2
-rw-r--r--app/controllers/admin/projects_controller.rb6
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/autocomplete_controller.rb2
-rw-r--r--app/controllers/concerns/global_milestones.rb2
-rw-r--r--app/controllers/concerns/issuable_actions.rb23
-rw-r--r--app/controllers/concerns/issues_action.rb2
-rw-r--r--app/controllers/concerns/merge_requests_action.rb2
-rw-r--r--app/controllers/dashboard/groups_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb4
-rw-r--r--app/controllers/dashboard/snippets_controller.rb2
-rw-r--r--app/controllers/dashboard/todos_controller.rb2
-rw-r--r--app/controllers/explore/groups_controller.rb2
-rw-r--r--app/controllers/explore/projects_controller.rb6
-rw-r--r--app/controllers/explore/snippets_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/forks_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb10
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/milestones_controller.rb2
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/controllers/projects/tags_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/controllers/users_controller.rb2
-rw-r--r--app/helpers/blob_helper.rb6
-rw-r--r--app/helpers/button_helper.rb30
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb13
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/mailers/emails/issues.rb8
-rw-r--r--app/models/ability.rb4
-rw-r--r--app/models/concerns/internal_id.rb5
-rw-r--r--app/models/concerns/issuable.rb11
-rw-r--r--app/models/issue.rb21
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_wiki.rb12
-rw-r--r--app/models/repository.rb25
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/issues/close_service.rb6
-rw-r--r--app/services/issues/create_service.rb2
-rw-r--r--app/services/issues/move_service.rb94
-rw-r--r--app/services/merge_requests/post_merge_service.rb2
-rw-r--r--app/services/notification_service.rb10
-rw-r--r--app/services/system_note_service.rb22
-rw-r--r--app/views/admin/applications/_delete_form.html.haml2
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/doorkeeper/applications/new.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/error.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/show.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml2
-rw-r--r--app/views/layouts/nav/_explore.html.haml2
-rw-r--r--app/views/layouts/notify.html.haml30
-rw-r--r--app/views/notify/issue_moved_email.html.haml6
-rw-r--r--app/views/notify/issue_moved_email.text.erb4
-rw-r--r--app/views/profiles/keys/_key.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml4
-rw-r--r--app/views/projects/buttons/_download.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml4
-rw-r--r--app/views/projects/buttons/_notifications.html.haml2
-rw-r--r--app/views/projects/buttons/_star.html.haml4
-rw-r--r--app/views/projects/compare/_form.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml26
-rw-r--r--app/views/projects/diffs/_text_file.html.haml21
-rw-r--r--app/views/projects/forks/new.html.haml4
-rw-r--r--app/views/projects/issues/show.html.haml1
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml2
-rw-r--r--app/views/projects/notes/_edit_form.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml4
-rw-r--r--app/views/projects/tags/show.html.haml8
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--app/views/search/results/_milestone.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml6
-rw-r--r--app/views/shared/groups/_group.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml33
-rw-r--r--app/views/shared/milestones/_issuable.html.haml2
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/votes/_votes_block.html.haml2
-rw-r--r--app/workers/repository_fork_worker.rb5
-rw-r--r--config/application.rb1
-rw-r--r--config/initializers/premailer.rb7
-rw-r--r--config/routes.rb4
-rw-r--r--db/migrate/20160225090018_add_delete_at_to_issues.rb6
-rw-r--r--db/migrate/20160225101956_add_delete_at_to_merge_requests.rb6
-rw-r--r--db/migrate/20160317092222_add_moved_to_to_issue.rb5
-rw-r--r--db/schema.rb7
-rw-r--r--doc/api/issues.md18
-rw-r--r--doc/api/merge_requests.md19
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/update/8.5-to-8.6.md2
-rw-r--r--features/project/issues/issues.feature1
-rw-r--r--features/steps/project/create.rb4
-rw-r--r--features/steps/project/issues/issues.rb3
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/issues.rb7
-rw-r--r--lib/api/merge_requests.rb12
-rw-r--r--lib/banzai/filter/reference_filter.rb1
-rw-r--r--lib/gitlab/gfm/reference_rewriter.rb79
-rw-r--r--lib/gitlab/reference_extractor.rb18
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb35
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb23
-rw-r--r--spec/features/issues/move_spec.rb87
-rw-r--r--spec/helpers/labels_helper_spec.rb6
-rw-r--r--spec/helpers/projects_helper_spec.rb19
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb89
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb20
-rw-r--r--spec/mailers/notify_spec.rb27
-rw-r--r--spec/models/issue_spec.rb59
-rw-r--r--spec/models/merge_request_spec.rb5
-rw-r--r--spec/models/project_spec.rb41
-rw-r--r--spec/models/project_wiki_spec.rb12
-rw-r--r--spec/models/repository_spec.rb30
-rw-r--r--spec/requests/api/issues_spec.rb34
-rw-r--r--spec/requests/api/merge_requests_spec.rb37
-rw-r--r--spec/services/issues/move_service_spec.rb213
-rw-r--r--spec/services/system_note_service_spec.rb53
-rw-r--r--spec/workers/repository_fork_worker_spec.rb19
159 files changed, 2015 insertions, 761 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 89aa0591c31..71273ce6098 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,1041 +1,1061 @@
+AllCops:
+ TargetRubyVersion: 2.1
+ # Cop names are not displayed in offense messages by default. Change behavior
+ # by overriding DisplayCopNames, or by giving the -D/--display-cop-names
+ # option.
+ DisplayCopNames: true
+ # Style guide URLs are not displayed in offense messages by default. Change
+ # behavior by overriding DisplayStyleGuide, or by giving the
+ # -S/--display-style-guide option.
+ DisplayStyleGuide: false
+ # Exclude some GitLab files
+ Exclude:
+ - 'vendor/**/*'
+ - 'db/**/*'
+ - 'tmp/**/*'
+ - 'bin/**/*'
+ - 'lib/backup/**/*'
+ - 'lib/ci/backup/**/*'
+ - 'lib/tasks/**/*'
+ - 'lib/ci/migrate/**/*'
+ - 'lib/email_validator.rb'
+ - 'lib/gitlab/upgrader.rb'
+ - 'lib/gitlab/seeder.rb'
+
+
+##################### Style ##################################
+
+# Check indentation of private/protected visibility modifiers.
Style/AccessModifierIndentation:
- Description: Check indentation of private/protected visibility modifiers.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected'
Enabled: true
+# Check the naming of accessor methods for get_/set_.
Style/AccessorMethodName:
- Description: Check the naming of accessor methods for get_/set_.
Enabled: false
+# Use alias_method instead of alias.
Style/Alias:
- Description: 'Use alias_method instead of alias.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method'
+ EnforcedStyle: prefer_alias_method
Enabled: true
+# Align the elements of an array literal if they span more than one line.
Style/AlignArray:
- Description: >-
- Align the elements of an array literal if they span more than
- one line.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays'
Enabled: true
+# Align the elements of a hash literal if they span more than one line.
Style/AlignHash:
- Description: >-
- Align the elements of a hash literal if they span more than
- one line.
Enabled: true
+# Align the parameters of a method call if they span more than one line.
Style/AlignParameters:
- Description: >-
- Align the parameters of a method call if they span more
- than one line.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'
Enabled: false
+# Use &&/|| instead of and/or.
Style/AndOr:
- Description: 'Use &&/|| instead of and/or.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or'
Enabled: false
+# Use `Array#join` instead of `Array#*`.
Style/ArrayJoin:
- Description: 'Use Array#join instead of Array#*.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join'
Enabled: false
+# Use only ascii symbols in comments.
Style/AsciiComments:
- Description: 'Use only ascii symbols in comments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments'
Enabled: true
+# Use only ascii symbols in identifiers.
Style/AsciiIdentifiers:
- Description: 'Use only ascii symbols in identifiers.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers'
Enabled: true
+# Checks for uses of Module#attr.
Style/Attr:
- Description: 'Checks for uses of Module#attr.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr'
Enabled: false
+# Avoid the use of BEGIN blocks.
Style/BeginBlock:
- Description: 'Avoid the use of BEGIN blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks'
Enabled: true
+# Checks if usage of %() or %Q() matches configuration.
Style/BarePercentLiterals:
- Description: 'Checks if usage of %() or %Q() matches configuration.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand'
Enabled: false
+# Do not use block comments.
Style/BlockComments:
- Description: 'Do not use block comments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments'
Enabled: false
+# Put end statement of multiline block on its own line.
Style/BlockEndNewline:
- Description: 'Put end statement of multiline block on its own line.'
Enabled: true
+# Avoid using {...} for multi-line blocks (multiline chaining is # always
+# ugly). Prefer {...} over do...end for single-line blocks.
Style/BlockDelimiters:
- Description: >-
- Avoid using {...} for multi-line blocks (multiline chaining is
- always ugly).
- Prefer {...} over do...end for single-line blocks.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
Enabled: true
+# Enforce braces style around hash parameters.
Style/BracesAroundHashParameters:
- Description: 'Enforce braces style around hash parameters.'
Enabled: false
+# Avoid explicit use of the case equality operator(===).
Style/CaseEquality:
- Description: 'Avoid explicit use of the case equality operator(===).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality'
Enabled: false
+# Indentation of when in a case/when/[else/]end.
Style/CaseIndentation:
- Description: 'Indentation of when in a case/when/[else/]end.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case'
Enabled: true
+# Checks for uses of character literals.
Style/CharacterLiteral:
- Description: 'Checks for uses of character literals.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals'
Enabled: true
+# Use CamelCase for classes and modules.'
Style/ClassAndModuleCamelCase:
- Description: 'Use CamelCase for classes and modules.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes'
Enabled: true
+# Checks style of children classes and modules.
Style/ClassAndModuleChildren:
- Description: 'Checks style of children classes and modules.'
Enabled: false
+# Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.
Style/ClassCheck:
- Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.'
Enabled: false
+# Use self when defining module/class methods.
Style/ClassMethods:
- Description: 'Use self when defining module/class methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-singletons'
Enabled: false
+# Avoid the use of class variables.
Style/ClassVars:
- Description: 'Avoid the use of class variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars'
Enabled: true
+# Do not use :: for method call.
Style/ColonMethodCall:
- Description: 'Do not use :: for method call.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons'
Enabled: false
+# Checks formatting of special comments (TODO, FIXME, OPTIMIZE, HACK, REVIEW).
Style/CommentAnnotation:
- Description: >-
- Checks formatting of special comments
- (TODO, FIXME, OPTIMIZE, HACK, REVIEW).
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords'
Enabled: false
+# Indentation of comments.
Style/CommentIndentation:
- Description: 'Indentation of comments.'
Enabled: true
+# Use the return value of `if` and `case` statements for assignment to a
+# variable and variable comparison instead of assigning that variable
+# inside of each branch.
+Style/ConditionalAssignment:
+ Enabled: false
+
+# Constants should use SCREAMING_SNAKE_CASE.
Style/ConstantName:
- Description: 'Constants should use SCREAMING_SNAKE_CASE.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case'
Enabled: true
+# Use def with parentheses when there are arguments.
Style/DefWithParentheses:
- Description: 'Use def with parentheses when there are arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
Enabled: false
+# Checks for use of deprecated Hash methods.
Style/DeprecatedHashMethods:
- Description: 'Checks for use of deprecated Hash methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key'
Enabled: false
+# Document classes and non-namespace modules.
Style/Documentation:
- Description: 'Document classes and non-namespace modules.'
Enabled: false
+# Checks the position of the dot in multi-line method calls.
Style/DotPosition:
- Description: 'Checks the position of the dot in multi-line method calls.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains'
Enabled: false
+# Checks for uses of double negation (!!).
Style/DoubleNegation:
- Description: 'Checks for uses of double negation (!!).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang'
Enabled: false
+# Prefer `each_with_object` over `inject` or `reduce`.
Style/EachWithObject:
- Description: 'Prefer `each_with_object` over `inject` or `reduce`.'
Enabled: false
+# Align elses and elsifs correctly.
Style/ElseAlignment:
- Description: 'Align elses and elsifs correctly.'
Enabled: true
+# Avoid empty else-clauses.
Style/EmptyElse:
- Description: 'Avoid empty else-clauses.'
Enabled: false
+# Use empty lines between defs.
Style/EmptyLineBetweenDefs:
- Description: 'Use empty lines between defs.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods'
Enabled: false
+# Don't use several empty lines in a row.
Style/EmptyLines:
- Description: "Don't use several empty lines in a row."
Enabled: false
+# Keep blank lines around access modifiers.
Style/EmptyLinesAroundAccessModifier:
- Description: "Keep blank lines around access modifiers."
Enabled: false
+# Keeps track of empty lines around block bodies.
Style/EmptyLinesAroundBlockBody:
- Description: "Keeps track of empty lines around block bodies."
Enabled: false
+# Keeps track of empty lines around class bodies.
Style/EmptyLinesAroundClassBody:
- Description: "Keeps track of empty lines around class bodies."
Enabled: false
+# Keeps track of empty lines around module bodies.
Style/EmptyLinesAroundModuleBody:
- Description: "Keeps track of empty lines around module bodies."
Enabled: false
+# Keeps track of empty lines around method bodies.
Style/EmptyLinesAroundMethodBody:
- Description: "Keeps track of empty lines around method bodies."
Enabled: false
+# Prefer literals to Array.new/Hash.new/String.new.
Style/EmptyLiteral:
- Description: 'Prefer literals to Array.new/Hash.new/String.new.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash'
Enabled: false
+# Avoid the use of END blocks.
Style/EndBlock:
- Description: 'Avoid the use of END blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks'
Enabled: false
+# Use Unix-style line endings.
Style/EndOfLine:
- Description: 'Use Unix-style line endings.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf'
Enabled: false
+# Favor the use of Fixnum#even? && Fixnum#odd?
Style/EvenOdd:
- Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
Enabled: false
+# Do not use unnecessary spacing.
Style/ExtraSpacing:
- Description: 'Do not use unnecessary spacing.'
Enabled: false
+# Use snake_case for source file names.
Style/FileName:
- Description: 'Use snake_case for source file names.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
Enabled: false
+# Checks for flip flops.
Style/FlipFlop:
- Description: 'Checks for flip flops'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops'
Enabled: false
+# Checks use of for or each in multiline loops.
Style/For:
- Description: 'Checks use of for or each in multiline loops.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops'
Enabled: false
+# Enforce the use of Kernel#sprintf, Kernel#format or String#%.
Style/FormatString:
- Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf'
Enabled: false
+# Do not introduce global variables.
Style/GlobalVars:
- Description: 'Do not introduce global variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars'
Enabled: false
+# Check for conditionals that can be replaced with guard clauses.
Style/GuardClause:
- Description: 'Check for conditionals that can be replaced with guard clauses'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
Enabled: false
+# Prefer Ruby 1.9 hash syntax `{ a: 1, b: 2 }`
+# over 1.8 syntax `{ :a => 1, :b => 2 }`.
Style/HashSyntax:
- Description: >-
- Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax
- { :a => 1, :b => 2 }.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals'
Enabled: true
+# Finds if nodes inside else, which can be converted to elsif.
+Style/IfInsideElse:
+ Enabled: false
+
+# Favor modifier if/unless usage when you have a single-line body.
Style/IfUnlessModifier:
- Description: >-
- Favor modifier if/unless usage when you have a
- single-line body.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier'
Enabled: false
+# Do not use if x; .... Use the ternary operator instead.
Style/IfWithSemicolon:
- Description: 'Do not use if x; .... Use the ternary operator instead.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs'
Enabled: false
+# Checks that conditional statements do not have an identical line at the
+# end of each branch, which can validly be moved out of the conditional.
+Style/IdenticalConditionalBranches:
+ Enabled: false
+
+# Checks the indentation of the first line of the right-hand-side of a
+# multi-line assignment.
+Style/IndentAssignment:
+ Enabled: false
+
+# Keep indentation straight.
Style/IndentationConsistency:
- Description: 'Keep indentation straight.'
Enabled: true
+# Use 2 spaces for indentation.
Style/IndentationWidth:
- Description: 'Use 2 spaces for indentation.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
Enabled: true
+# Checks the indentation of the first element in an array literal.
Style/IndentArray:
- Description: >-
- Checks the indentation of the first element in an array
- literal.
Enabled: false
+# Checks the indentation of the first key in a hash literal.
Style/IndentHash:
- Description: 'Checks the indentation of the first key in a hash literal.'
Enabled: false
+# Use Kernel#loop for infinite loops.
Style/InfiniteLoop:
- Description: 'Use Kernel#loop for infinite loops.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop'
Enabled: false
+# Use the new lambda literal syntax for single-line blocks.
Style/Lambda:
- Description: 'Use the new lambda literal syntax for single-line blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line'
Enabled: false
+# Use lambda.call(...) instead of lambda.(...).
Style/LambdaCall:
- Description: 'Use lambda.call(...) instead of lambda.(...).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call'
Enabled: false
+# Comments should start with a space.
Style/LeadingCommentSpace:
- Description: 'Comments should start with a space.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space'
Enabled: false
+# Use \ instead of + or << to concatenate two string literals at line end.
Style/LineEndConcatenation:
- Description: >-
- Use \ instead of + or << to concatenate two string literals at
- line end.
Enabled: false
+# Do not use parentheses for method calls with no arguments.
Style/MethodCallParentheses:
- Description: 'Do not use parentheses for method calls with no arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens'
Enabled: false
+# Checks if the method definitions have or don't have parentheses.
Style/MethodDefParentheses:
- Description: >-
- Checks if the method definitions have or don't have
- parentheses.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
Enabled: false
+# Use the configured style when naming methods.
Style/MethodName:
- Description: 'Use the configured style when naming methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
Enabled: false
+# Checks for usage of `extend self` in modules.
Style/ModuleFunction:
- Description: 'Checks for usage of `extend self` in modules.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function'
Enabled: false
+# Avoid multi-line chains of blocks.
Style/MultilineBlockChain:
- Description: 'Avoid multi-line chains of blocks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
Enabled: false
+# Ensures newlines after multiline block do statements.
Style/MultilineBlockLayout:
- Description: 'Ensures newlines after multiline block do statements.'
Enabled: true
+# Do not use then for multi-line if/unless.
Style/MultilineIfThen:
- Description: 'Do not use then for multi-line if/unless.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then'
Enabled: false
+# Checks indentation of method calls with the dot operator that span more than
+# one line.
+Style/MultilineMethodCallIndentation:
+ Enabled: false
+
+# Checks indentation of binary operations that span more than one line.
Style/MultilineOperationIndentation:
- Description: >-
- Checks indentation of binary operations that span more than
- one line.
Enabled: false
+# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
Style/MultilineTernaryOperator:
- Description: >-
- Avoid multi-line ?: (the ternary operator);
- use if/unless instead.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary'
Enabled: false
+# Do not assign mutable objects to constants.
+Style/MutableConstant:
+ Enabled: false
+
+# Favor unless over if for negative conditions (or control flow or).
Style/NegatedIf:
- Description: >-
- Favor unless over if for negative conditions
- (or control flow or).
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives'
Enabled: false
+# Favor until over while for negative conditions.
Style/NegatedWhile:
- Description: 'Favor until over while for negative conditions.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives'
Enabled: false
+# Avoid using nested modifiers.
+Style/NestedModifier:
+ Enabled: false
+
+# Parenthesize method calls which are nested inside the argument list of
+# another parenthesized method call.
+Style/NestedParenthesizedCalls:
+ Enabled: false
+
+# Use one expression per branch in a ternary operator.
Style/NestedTernaryOperator:
- Description: 'Use one expression per branch in a ternary operator.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary'
Enabled: true
+# Use `next` to skip iteration instead of a condition at the end.
Style/Next:
- Description: 'Use `next` to skip iteration instead of a condition at the end.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
Enabled: false
+# Prefer x.nil? to x == nil.
Style/NilComparison:
- Description: 'Prefer x.nil? to x == nil.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
Enabled: true
+# Checks for redundant nil checks.
Style/NonNilCheck:
- Description: 'Checks for redundant nil checks.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks'
Enabled: true
+# Use ! instead of not.
Style/Not:
- Description: 'Use ! instead of not.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not'
Enabled: true
+# Add underscores to large numeric literals to improve their readability.
Style/NumericLiterals:
- Description: >-
- Add underscores to large numeric literals to improve their
- readability.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics'
Enabled: false
+# Favor the ternary operator(?:) over if/then/else/end constructs.
Style/OneLineConditional:
- Description: >-
- Favor the ternary operator(?:) over
- if/then/else/end constructs.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator'
Enabled: true
+# When defining binary operators, name the argument other.
Style/OpMethod:
- Description: 'When defining binary operators, name the argument other.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
Enabled: false
+# Check for simple usages of parallel assignment. It will only warn when
+# the number of variables matches on both sides of the assignment.
Style/ParallelAssignment:
- Description: >-
- Check for simple usages of parallel assignment.
- It will only warn when the number of variables
- matches on both sides of the assignment.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment'
Enabled: false
+# Don't use parentheses around the condition of an if/unless/while.
Style/ParenthesesAroundCondition:
- Description: >-
- Don't use parentheses around the condition of an
- if/unless/while.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if'
Enabled: true
+# Use `%`-literal delimiters consistently.
Style/PercentLiteralDelimiters:
- Description: 'Use `%`-literal delimiters consistently'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces'
Enabled: false
+# Checks if uses of %Q/%q match the configured preference.
Style/PercentQLiterals:
- Description: 'Checks if uses of %Q/%q match the configured preference.'
Enabled: false
+# Avoid Perl-style regex back references.
Style/PerlBackrefs:
- Description: 'Avoid Perl-style regex back references.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'
Enabled: false
+# Check the names of predicate methods.
Style/PredicateName:
- Description: 'Check the names of predicate methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark'
Enabled: false
+# Use proc instead of Proc.new.
Style/Proc:
- Description: 'Use proc instead of Proc.new.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc'
Enabled: false
+# Checks the arguments passed to raise/fail.
Style/RaiseArgs:
- Description: 'Checks the arguments passed to raise/fail.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages'
Enabled: false
+# Don't use begin blocks when they are not needed.
Style/RedundantBegin:
- Description: "Don't use begin blocks when they are not needed."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit'
Enabled: false
+# Checks for an obsolete RuntimeException argument in raise/fail.
Style/RedundantException:
- Description: "Checks for an obsolete RuntimeException argument in raise/fail."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror'
Enabled: false
+# Checks usages of Object#freeze on immutable objects.
+Style/RedundantFreeze:
+ Enabled: false
+
+# TODO: Enable RedundantParentheses Cop.
+# Checks for parentheses that seem not to serve any purpose.
+Style/RedundantParentheses:
+ Enabled: false
+
+# Don't use return where it's not required.
Style/RedundantReturn:
- Description: "Don't use return where it's not required."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return'
Enabled: true
+# Don't use self where it's not needed.
Style/RedundantSelf:
- Description: "Don't use self where it's not needed."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required'
Enabled: false
+# Use %r for regular expressions matching more than `MaxSlashes` '/'
+# characters. Use %r only for regular expressions matching more
+# than `MaxSlashes` '/' character.
Style/RegexpLiteral:
- Description: >-
- Use %r for regular expressions matching more than
- `MaxSlashes` '/' characters.
- Use %r only for regular expressions matching more than
- `MaxSlashes` '/' character.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r'
Enabled: false
+# Avoid using rescue in its modifier form.
Style/RescueModifier:
- Description: 'Avoid using rescue in its modifier form.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers'
Enabled: false
+# Checks for places where self-assignment shorthand should have been used.
Style/SelfAssignment:
- Description: >-
- Checks for places where self-assignment shorthand should have
- been used.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment'
Enabled: false
+# Don't use semicolons to terminate expressions.
Style/Semicolon:
- Description: "Don't use semicolons to terminate expressions."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon'
Enabled: false
+# Checks for proper usage of fail and raise.
Style/SignalException:
- Description: 'Checks for proper usage of fail and raise.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method'
Enabled: false
+# Enforces the names of some block params.
Style/SingleLineBlockParams:
- Description: 'Enforces the names of some block params.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks'
Enabled: false
+# Avoid single-line methods.
Style/SingleLineMethods:
- Description: 'Avoid single-line methods.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods'
- Enabled: false
-
-Style/SingleSpaceBeforeFirstArg:
- Description: >-
- Checks that exactly one space is used between a method name
- and the first argument for method calls without parentheses.
Enabled: false
+# Use spaces after colons.
Style/SpaceAfterColon:
- Description: 'Use spaces after colons.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
Enabled: false
+# Use spaces after commas.
Style/SpaceAfterComma:
- Description: 'Use spaces after commas.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
- Enabled: false
-
-Style/SpaceAfterControlKeyword:
- Description: 'Use spaces after if/elsif/unless/while/until/case/when.'
Enabled: false
+# Do not put a space between a method name and the opening parenthesis in a
+# method definition.
Style/SpaceAfterMethodName:
- Description: >-
- Do not put a space between a method name and the opening
- parenthesis in a method definition.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
Enabled: false
+# Tracks redundant space after the ! operator.
Style/SpaceAfterNot:
- Description: Tracks redundant space after the ! operator.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang'
Enabled: false
+# Use spaces after semicolons.
Style/SpaceAfterSemicolon:
- Description: 'Use spaces after semicolons.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
Enabled: false
-Style/SpaceBeforeBlockBraces:
- Description: >-
- Checks that the left block brace has or doesn't have space
- before it.
+# Checks that the equals signs in parameter default assignments have or don't
+# have surrounding space depending on configuration.
+Style/SpaceAroundEqualsInParameterDefault:
Enabled: false
-Style/SpaceBeforeComma:
- Description: 'No spaces before commas.'
+# TODO: Enable SpaceAroundKeyword Cop.
+# Use a space around keywords if appropriate.
+Style/SpaceAroundKeyword:
Enabled: false
-Style/SpaceBeforeComment:
- Description: >-
- Checks for missing space between code and a comment on the
- same line.
+# Use a single space around operators.
+Style/SpaceAroundOperators:
Enabled: false
-Style/SpaceBeforeSemicolon:
- Description: 'No spaces before semicolons.'
+# Checks that the left block brace has or doesn't have space before it.
+Style/SpaceBeforeBlockBraces:
Enabled: false
-Style/SpaceInsideBlockBraces:
- Description: >-
- Checks that block braces have or don't have surrounding space.
- For blocks taking parameters, checks that the left brace has
- or doesn't have trailing space.
+# No spaces before commas.
+Style/SpaceBeforeComma:
Enabled: false
-Style/SpaceAroundEqualsInParameterDefault:
- Description: >-
- Checks that the equals signs in parameter default assignments
- have or don't have surrounding space depending on
- configuration.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals'
+# Checks for missing space between code and a comment on the same line.
+Style/SpaceBeforeComment:
Enabled: false
-Style/SpaceAroundOperators:
- Description: 'Use spaces around operators.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+# Checks that exactly one space is used between a method name and the first
+# argument for method calls without parentheses.
+Style/SpaceBeforeFirstArg:
+ Enabled: false
+
+# No spaces before semicolons.
+Style/SpaceBeforeSemicolon:
Enabled: false
-Style/SpaceBeforeModifierKeyword:
- Description: 'Put a space before the modifier keyword.'
+# Checks that block braces have or don't have surrounding space.
+# For blocks taking parameters, checks that the left brace has or doesn't
+# have trailing space.
+Style/SpaceInsideBlockBraces:
Enabled: false
+# No spaces after [ or before ].
Style/SpaceInsideBrackets:
- Description: 'No spaces after [ or before ].'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
Enabled: false
+# Use spaces inside hash literal braces - or don't.
Style/SpaceInsideHashLiteralBraces:
- Description: "Use spaces inside hash literal braces - or don't."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
Enabled: true
+# No spaces after ( or before ).
Style/SpaceInsideParens:
- Description: 'No spaces after ( or before ).'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
Enabled: false
+# No spaces inside range literals.
Style/SpaceInsideRangeLiteral:
- Description: 'No spaces inside range literals.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals'
Enabled: false
+# Checks for padding/surrounding spaces inside string interpolation.
+Style/SpaceInsideStringInterpolation:
+ Enabled: false
+
+# Avoid Perl-style global variables.
Style/SpecialGlobalVars:
- Description: 'Avoid Perl-style global variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms'
Enabled: false
+# Check for the usage of parentheses around stabby lambda arguments.
+Style/StabbyLambdaParentheses:
+ Enabled: false
+
+# Checks if uses of quotes match the configured preference.
Style/StringLiterals:
- Description: 'Checks if uses of quotes match the configured preference.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
Enabled: false
+# Checks if uses of quotes inside expressions in interpolated strings match the
+# configured preference.
Style/StringLiteralsInInterpolation:
- Description: >-
- Checks if uses of quotes inside expressions in interpolated
- strings match the configured preference.
Enabled: false
+# Checks if configured preferred methods are used over non-preferred.
+Style/StringMethods:
+ Enabled: false
+
+# Use %i or %I for arrays of symbols.
+Style/SymbolArray:
+ Enabled: false
+
+# Use symbols as procs instead of blocks when possible.
Style/SymbolProc:
- Description: 'Use symbols as procs instead of blocks when possible.'
Enabled: false
+# No hard tabs.
Style/Tab:
- Description: 'No hard tabs.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
Enabled: true
+# Checks trailing blank lines and final newline.
Style/TrailingBlankLines:
- Description: 'Checks trailing blank lines and final newline.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof'
Enabled: true
-Style/TrailingComma:
- Description: 'Checks for trailing comma in parameter lists and literals.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
+# Checks for trailing comma in array and hash literals.
+Style/TrailingCommaInLiteral:
Enabled: false
+# Checks for trailing comma in argument lists.
+Style/TrailingCommaInArguments:
+ Enabled: false
+
+# Avoid trailing whitespace.
Style/TrailingWhitespace:
- Description: 'Avoid trailing whitespace.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace'
Enabled: false
+# Checks for the usage of unneeded trailing underscores at the end of
+# parallel variable assignment.
Style/TrailingUnderscoreVariable:
- Description: >-
- Checks for the usage of unneeded trailing underscores at the
- end of parallel variable assignment.
- AllowNamedUnderscoreVariables: true
Enabled: false
+# Prefer attr_* methods to trivial readers/writers.
Style/TrivialAccessors:
- Description: 'Prefer attr_* methods to trivial readers/writers.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'
Enabled: false
+# Do not use unless with else. Rewrite these with the positive case first.
Style/UnlessElse:
- Description: >-
- Do not use unless with else. Rewrite these with the positive
- case first.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless'
Enabled: false
+# Checks for %W when interpolation is not needed.
Style/UnneededCapitalW:
- Description: 'Checks for %W when interpolation is not needed.'
Enabled: false
+# TODO: Enable UnneededInterpolation Cop.
+# Checks for strings that are just an interpolated expression.
+Style/UnneededInterpolation:
+ Enabled: false
+
+# Checks for %q/%Q when single quotes or double quotes would do.
Style/UnneededPercentQ:
- Description: 'Checks for %q/%Q when single quotes or double quotes would do.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q'
Enabled: false
+# Don't interpolate global, instance and class variables directly in strings.
Style/VariableInterpolation:
- Description: >-
- Don't interpolate global, instance and class variables
- directly in strings.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate'
Enabled: false
+# Use the configured style when naming variables.
Style/VariableName:
- Description: 'Use the configured style when naming variables.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
Enabled: false
+# Use when x then ... for one-line cases.
Style/WhenThen:
- Description: 'Use when x then ... for one-line cases.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases'
Enabled: false
+# Checks for redundant do after while or until.
Style/WhileUntilDo:
- Description: 'Checks for redundant do after while or until.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do'
Enabled: false
+# Favor modifier while/until usage when you have a single-line body.
Style/WhileUntilModifier:
- Description: >-
- Favor modifier while/until usage when you have a
- single-line body.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier'
Enabled: false
+# Use %w or %W for arrays of words.
Style/WordArray:
- Description: 'Use %w or %W for arrays of words.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w'
Enabled: false
+# TODO: Enable ZeroLengthPredicate Cop.
+# Use #empty? when testing for objects of length 0.
+Style/ZeroLengthPredicate:
+ Enabled: false
+
+
#################### Metrics ################################
+# A calculated magnitude based on number of assignments,
+# branches, and conditions.
Metrics/AbcSize:
- Description: >-
- A calculated magnitude based on number of assignments,
- branches, and conditions.
Enabled: true
Max: 70
-Metrics/CyclomaticComplexity:
- Description: >-
- A complexity metric that is strongly correlated to the number
- of test cases needed to validate a method.
- Enabled: true
- Max: 17
-
-Metrics/PerceivedComplexity:
- Description: >-
- A complexity metric geared towards measuring complexity for a
- human reader.
- Enabled: true
- Max: 17
-
-Metrics/ParameterLists:
- Description: 'Avoid parameter lists longer than three or four parameters.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
- Enabled: true
- Max: 8
-
+# Avoid excessive block nesting.
Metrics/BlockNesting:
- Description: 'Avoid excessive block nesting'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
Enabled: true
Max: 4
+# Avoid classes longer than 100 lines of code.
Metrics/ClassLength:
- Description: 'Avoid classes longer than 100 lines of code.'
Enabled: false
+# A complexity metric that is strongly correlated to the number
+# of test cases needed to validate a method.
+Metrics/CyclomaticComplexity:
+ Enabled: true
+ Max: 17
+
+# Limit lines to 80 characters.
Metrics/LineLength:
- Description: 'Limit lines to 80 characters.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
Enabled: false
+# Avoid methods longer than 10 lines of code.
Metrics/MethodLength:
- Description: 'Avoid methods longer than 10 lines of code.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false
+# Avoid modules longer than 100 lines of code.
Metrics/ModuleLength:
- Description: 'Avoid modules longer than 100 lines of code.'
Enabled: false
+# Avoid parameter lists longer than three or four parameters.
+Metrics/ParameterLists:
+ Enabled: true
+ Max: 8
+
+# A complexity metric geared towards measuring complexity for a human reader.
+Metrics/PerceivedComplexity:
+ Enabled: true
+ Max: 17
+
+
#################### Lint ################################
-### Warnings
+# Checks for ambiguous operators in the first argument of a method invocation
+# without parentheses.
Lint/AmbiguousOperator:
- Description: >-
- Checks for ambiguous operators in the first argument of a
- method invocation without parentheses.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args'
Enabled: false
+# Checks for ambiguous regexp literals in the first argument of a method
+# invocation without parentheses.
Lint/AmbiguousRegexpLiteral:
- Description: >-
- Checks for ambiguous regexp literals in the first argument of
- a method invocation without parenthesis.
Enabled: false
+# Don't use assignment in conditions.
Lint/AssignmentInCondition:
- Description: "Don't use assignment in conditions."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition'
Enabled: false
+# Align block ends correctly.
Lint/BlockAlignment:
- Description: 'Align block ends correctly.'
Enabled: false
+# Default values in optional keyword arguments and optional ordinal arguments
+# should not refer back to the name of the argument.
+Lint/CircularArgumentReference:
+ Enabled: false
+
+# Checks for condition placed in a confusing position relative to the keyword.
Lint/ConditionPosition:
- Description: >-
- Checks for condition placed in a confusing position relative to
- the keyword.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'
Enabled: false
+# Check for debugger calls.
Lint/Debugger:
- Description: 'Check for debugger calls.'
Enabled: false
+# Align ends corresponding to defs correctly.
Lint/DefEndAlignment:
- Description: 'Align ends corresponding to defs correctly.'
Enabled: false
+# Check for deprecated class method calls.
Lint/DeprecatedClassMethods:
- Description: 'Check for deprecated class method calls.'
Enabled: false
+# Check for duplicate method definitions.
+Lint/DuplicateMethods:
+ Enabled: false
+
+# Check for duplicate keys in hash literals.
+Lint/DuplicatedKey:
+ Enabled: false
+
+# Check for immutable argument given to each_with_object.
+Lint/EachWithObjectArgument:
+ Enabled: false
+
+# Check for odd code arrangement in an else block.
Lint/ElseLayout:
- Description: 'Check for odd code arrangement in an else block.'
Enabled: false
+# Checks for empty ensure block.
Lint/EmptyEnsure:
- Description: 'Checks for empty ensure block.'
Enabled: false
+# Checks for empty string interpolation.
Lint/EmptyInterpolation:
- Description: 'Checks for empty string interpolation.'
Enabled: false
+# Align ends correctly.
Lint/EndAlignment:
- Description: 'Align ends correctly.'
Enabled: false
+# END blocks should not be placed inside method definitions.
Lint/EndInMethod:
- Description: 'END blocks should not be placed inside method definitions.'
Enabled: false
+# Do not use return in an ensure block.
Lint/EnsureReturn:
- Description: 'Do not use return in an ensure block.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure'
Enabled: false
+# The use of eval represents a serious security risk.
Lint/Eval:
- Description: 'The use of eval represents a serious security risk.'
Enabled: false
+# Catches floating-point literals too large or small for Ruby to represent.
+Lint/FloatOutOfRange:
+ Enabled: false
+
+# The number of parameters to format/sprint must match the fields.
+Lint/FormatParameterMismatch:
+ Enabled: false
+
+# Don't suppress exception.
Lint/HandleExceptions:
- Description: "Don't suppress exception."
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'
Enabled: false
+# TODO: Enable ImplicitStringConcatenation Cop.
+# Checks for adjacent string literals on the same line, which could better be
+# represented as a single string literal.
+Lint/ImplicitStringConcatenation:
+ Enabled: false
+
+# TODO: Enable IneffectiveAccessModifier Cop.
+# Checks for attempts to use `private` or `protected` to set the visibility
+# of a class method, which does not work.
+Lint/IneffectiveAccessModifier:
+ Enabled: false
+
+# Checks for invalid character literals with a non-escaped whitespace
+# character.
Lint/InvalidCharacterLiteral:
- Description: >-
- Checks for invalid character literals with a non-escaped
- whitespace character.
Enabled: false
+# Checks of literals used in conditions.
Lint/LiteralInCondition:
- Description: 'Checks of literals used in conditions.'
Enabled: false
+# Checks for literals used in interpolation.
Lint/LiteralInInterpolation:
- Description: 'Checks for literals used in interpolation.'
Enabled: false
+# Use Kernel#loop with break rather than begin/end/until or begin/end/while
+# for post-loop tests.
Lint/Loop:
- Description: >-
- Use Kernel#loop with break rather than begin/end/until or
- begin/end/while for post-loop tests.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break'
Enabled: false
+# Do not use nested method definitions.
+Lint/NestedMethodDefinition:
+ Enabled: false
+
+# Do not omit the accumulator when calling `next` in a `reduce`/`inject` block.
+Lint/NextWithoutAccumulator:
+ Enabled: false
+
+# Checks for method calls with a space before the opening parenthesis.
Lint/ParenthesesAsGroupedExpression:
- Description: >-
- Checks for method calls with a space before the opening
- parenthesis.
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
Enabled: true
+# Checks for `rand(1)` calls. Such calls always return `0` and most likely
+# a mistake.
+Lint/RandOne:
+ Enabled: false
+
+# Use parentheses in the method call to avoid confusion about precedence.
Lint/RequireParentheses:
- Description: >-
- Use parentheses in the method call to avoid confusion
- about precedence.
Enabled: false
+# Avoid rescuing the Exception class.
Lint/RescueException:
- Description: 'Avoid rescuing the Exception class.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues'
Enabled: true
+# Do not use the same name as outer local variable for block arguments
+# or block local variables.
Lint/ShadowingOuterLocalVariable:
- Description: >-
- Do not use the same name as outer local variable
- for block arguments or block local variables.
- Enabled: false
-
-Lint/SpaceBeforeFirstArg:
- Description: >-
- Put a space between a method name and the first argument
- in a method call without parentheses.
Enabled: false
+# 'Checks for Object#to_s usage in string interpolation.
Lint/StringConversionInInterpolation:
- Description: 'Checks for Object#to_s usage in string interpolation.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s'
Enabled: false
+# Do not use prefix `_` for a variable that is used.
Lint/UnderscorePrefixedVariableName:
- Description: 'Do not use prefix `_` for a variable that is used.'
Enabled: true
+# Checks for rubocop:disable comments that can be removed.
+# Note: this cop is not disabled when disabling all cops.
+# It must be explicitly disabled.
+Lint/UnneededDisable:
+ Enabled: false
+
+# Checks for unused block arguments.
Lint/UnusedBlockArgument:
- Description: 'Checks for unused block arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
Enabled: false
+# Checks for unused method arguments.
Lint/UnusedMethodArgument:
- Description: 'Checks for unused method arguments.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
Enabled: false
+# Unreachable code.
Lint/UnreachableCode:
- Description: 'Unreachable code.'
Enabled: false
+# Checks for useless access modifiers.
Lint/UselessAccessModifier:
- Description: 'Checks for useless access modifiers.'
Enabled: false
+# Checks for useless assignment to a local variable.
Lint/UselessAssignment:
- Description: 'Checks for useless assignment to a local variable.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
Enabled: true
+# Checks for comparison of something with itself.
Lint/UselessComparison:
- Description: 'Checks for comparison of something with itself.'
Enabled: false
+# Checks for useless `else` in `begin..end` without `rescue`.
Lint/UselessElseWithoutRescue:
- Description: 'Checks for useless `else` in `begin..end` without `rescue`.'
Enabled: false
+# Checks for useless setter call to a local variable.
Lint/UselessSetterCall:
- Description: 'Checks for useless setter call to a local variable.'
Enabled: false
+# Possible use of operator/literal/variable in void context.
Lint/Void:
- Description: 'Possible use of operator/literal/variable in void context.'
Enabled: false
+
+##################### Performance ############################
+
+# TODO: Enable Casecmp Cop.
+# Use `casecmp` rather than `downcase ==`.
+Performance/Casecmp:
+ Enabled: false
+
+# TODO: Enable DoubleStartEndWith Cop.
+# Use `str.{start,end}_with?(x, ..., y, ...)` instead of
+# `str.{start,end}_with?(x, ...) || str.{start,end}_with?(y, ...)`.
+Performance/DoubleStartEndWith:
+ Enabled: false
+
+# TODO: Enable EndWith Cop.
+# Use `end_with?` instead of a regex match anchored to the end of a string.
+Performance/EndWith:
+ Enabled: false
+
+# TODO: Enable LstripRstrip Cop.
+# Use `strip` instead of `lstrip.rstrip`.
+Performance/LstripRstrip:
+ Enabled: false
+
+# TODO: Enable RangeInclude Cop.
+# Use `Range#cover?` instead of `Range#include?`.
+Performance/RangeInclude:
+ Enabled: false
+
+# TODO: Enable RedundantBlockCall Cop.
+# Use `yield` instead of `block.call`.
+Performance/RedundantBlockCall:
+ Enabled: false
+
+# TODO: Enable RedundantMatch Cop.
+# Use `=~` instead of `String#match` or `Regexp#match` in a context where the
+# returned `MatchData` is not needed.
+Performance/RedundantMatch:
+ Enabled: false
+
+# TODO: Enable RedundantMerge Cop.
+# Use `Hash#[]=`, rather than `Hash#merge!` with a single key-value pair.
+Performance/RedundantMerge:
+ # Max number of key-value pairs to consider an offense
+ MaxKeyValuePairs: 2
+ Enabled: false
+
+# TODO: Enable RedundantSortBy Cop.
+# Use `sort` instead of `sort_by { |x| x }`.
+Performance/RedundantSortBy:
+ Enabled: false
+
+# TODO: Enable StartWith Cop.
+# Use `start_with?` instead of a regex match anchored to the beginning of a
+# string.
+Performance/StartWith:
+ Enabled: false
+# Use `tr` instead of `gsub` when you are replacing the same number of
+# characters. Use `delete` instead of `gsub` when you are deleting
+# characters.
+Performance/StringReplacement:
+ Enabled: false
+
+# TODO: Enable TimesMap Cop.
+# Checks for `.times.map` calls.
+Performance/TimesMap:
+ Enabled: false
+
+
##################### Rails ##################################
+# Enables Rails cops.
+Rails:
+ Enabled: true
+
+# Enforces consistent use of action filter methods.
Rails/ActionFilter:
- Description: 'Enforces consistent use of action filter methods.'
Enabled: true
+ EnforcedStyle: action
+# Checks the correct usage of date aware methods, such as `Date.today`,
+# `Date.current`, etc.
Rails/Date:
- Description: >-
- Checks the correct usage of date aware methods,
- such as Date.today, Date.current etc.
Enabled: false
-Rails/DefaultScope:
- Description: 'Checks if the argument passed to default_scope is a block.'
+# Prefer delegate method for delegations.
+Rails/Delegate:
Enabled: false
-Rails/Delegate:
- Description: 'Prefer delegate method for delegations.'
+# Prefer `find_by` over `where.first`.
+Rails/FindBy:
+ Enabled: false
+
+# Prefer `all.find_each` over `all.find`.
+Rails/FindEach:
Enabled: false
+# Prefer has_many :through to has_and_belongs_to_many.
Rails/HasAndBelongsToMany:
- Description: 'Prefer has_many :through to has_and_belongs_to_many.'
Enabled: true
+# Checks for calls to puts, print, etc.
Rails/Output:
- Description: 'Checks for calls to puts, print, etc.'
Enabled: true
+# Checks for incorrect grammar when using methods like `3.day.ago`.
+Rails/PluralizationGrammar:
+ Enabled: false
+
+# Checks for `read_attribute(:attr)` and `write_attribute(:attr, val)`.
Rails/ReadWriteAttribute:
- Description: >-
- Checks for read_attribute(:attr) and
- write_attribute(:attr, val).
Enabled: false
+# Checks the arguments of ActiveRecord scopes.
Rails/ScopeArgs:
- Description: 'Checks the arguments of ActiveRecord scopes.'
Enabled: false
+# Checks the correct usage of time zone aware methods.
+# http://danilenko.org/2012/7/6/rails_timezones
Rails/TimeZone:
- Description: 'Checks the correct usage of time zone aware methods.'
- StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time'
- Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
Enabled: false
+# Use validates :attribute, hash of validations.
Rails/Validation:
- Description: 'Use validates :attribute, hash of validations.'
Enabled: false
-
-
-# Exclude some of GitLab files
-#
-#
-AllCops:
- RunRailsCops: true
- Exclude:
- - 'vendor/**/*'
- - 'db/**/*'
- - 'tmp/**/*'
- - 'bin/**/*'
- - 'lib/backup/**/*'
- - 'lib/ci/backup/**/*'
- - 'lib/tasks/**/*'
- - 'lib/ci/migrate/**/*'
- - 'lib/email_validator.rb'
- - 'lib/gitlab/upgrader.rb'
- - 'lib/gitlab/seeder.rb'
diff --git a/CHANGELOG b/CHANGELOG
index d3171da81c3..20a21abfb69 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,13 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased)
+ - Add ability to move issue to another project
+ - Prevent tokens in the import URL to be showed by the UI
- Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu)
+ - Make HTTP(s) label consistent on clone bar (Stan Hu)
- Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu)
+ - Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu)
- Support Golang subpackage fetching (Stan Hu)
- Bump Capybara gem to 2.6.2 (Stan Hu)
- New branch button appears on issues where applicable
@@ -57,6 +61,7 @@ v 8.6.0 (unreleased)
- User deletion is now done in the background so the request can not time out
- Canceled builds are now ignored in compound build status if marked as `allowed to fail`
- Trigger a todo for mentions on commits page
+ - Let project owners and admins soft delete issues and merge requests
v 8.5.8
- Bump Git version requirement to 2.7.4
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ef5e4454454..39e898a4f95 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.6.5
+0.7.1
diff --git a/Gemfile b/Gemfile
index e500bfb7885..cebd76e7370 100644
--- a/Gemfile
+++ b/Gemfile
@@ -222,6 +222,8 @@ gem 'net-ssh', '~> 3.0.1'
# Sentry integration
gem 'sentry-raven', '~> 0.15'
+gem 'premailer-rails', '~> 1.9.0'
+
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
@@ -285,7 +287,7 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
- gem 'rubocop', '~> 0.35.0', require: false
+ gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index e2dba76ce5e..16c09ab6e6d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -61,9 +61,7 @@ GEM
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
asciidoctor (1.5.3)
- ast (2.1.0)
- astrolabe (1.3.1)
- parser (~> 2.2)
+ ast (2.2.0)
attr_encrypted (1.3.4)
encryptor (>= 1.3.0)
attr_required (1.0.0)
@@ -150,6 +148,8 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
+ css_parser (1.3.7)
+ addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
daemons (1.2.3)
@@ -423,6 +423,7 @@ GEM
haml (~> 4.0.0)
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
+ htmlentities (4.3.4)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.5.3)
@@ -554,8 +555,8 @@ GEM
orm_adapter (0.5.0)
paranoia (2.1.4)
activerecord (~> 4.0)
- parser (2.2.3.0)
- ast (>= 1.1, < 3.0)
+ parser (2.3.0.6)
+ ast (~> 2.2)
pg (0.18.4)
poltergeist (1.9.0)
capybara (~> 2.1)
@@ -564,6 +565,12 @@ GEM
websocket-driver (>= 0.2.0)
posix-spawn (0.3.11)
powerpack (0.1.1)
+ premailer (1.8.6)
+ css_parser (>= 1.3.6)
+ htmlentities (>= 4.0.0)
+ premailer-rails (1.9.0)
+ actionmailer (>= 3, < 5)
+ premailer (~> 1.7, >= 1.7.9)
pry (0.10.3)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@@ -615,7 +622,7 @@ GEM
activesupport (= 4.2.5.2)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rainbow (2.0.0)
+ rainbow (2.1.0)
raindrops (0.15.0)
rake (10.5.0)
raphael-rails (2.1.2)
@@ -687,13 +694,12 @@ GEM
rspec-retry (0.4.5)
rspec-core
rspec-support (3.3.0)
- rubocop (0.35.1)
- astrolabe (~> 1.3)
- parser (>= 2.2.3.0, < 3.0)
+ rubocop (0.38.0)
+ parser (>= 2.3.0.6, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
- tins (<= 1.6.0)
+ unicode-display_width (~> 1.0, >= 1.0.1)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
@@ -843,6 +849,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
+ unicode-display_width (1.0.2)
unicorn (4.9.0)
kgio (~> 2.6)
rack
@@ -992,6 +999,7 @@ DEPENDENCIES
paranoia (~> 2.0)
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
+ premailer-rails (~> 1.9.0)
pry-rails
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.1)
@@ -1013,7 +1021,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0)
rspec-retry
- rubocop (~> 0.35.0)
+ rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index d415bbd3476..01451830653 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -7,6 +7,7 @@
#= require jquery
#= require jquery-ui/autocomplete
#= require jquery-ui/datepicker
+#= require jquery-ui/draggable
#= require jquery-ui/effect-highlight
#= require jquery-ui/sortable
#= require jquery_ujs
@@ -138,7 +139,7 @@ $ ->
# Initialize tooltips
$('body').tooltip(
- selector: '.has_tooltip, [data-toggle="tooltip"]'
+ selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
$el.data('placement') || 'bottom'
diff --git a/app/assets/javascripts/aside.js.coffee b/app/assets/javascripts/aside.js.coffee
index 85473101944..66ab5054326 100644
--- a/app/assets/javascripts/aside.js.coffee
+++ b/app/assets/javascripts/aside.js.coffee
@@ -5,7 +5,6 @@ class @Aside
e.preventDefault()
btn = $(e.currentTarget)
icon = btn.find('i')
- console.log('1')
if icon.hasClass('fa-angle-left')
btn.parent().find('section').hide()
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 03a44874161..47b080406d4 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -122,7 +122,7 @@ class @AwardsHandler
nodes = []
nodes.push(
- "<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>",
+ "<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>",
"</button>"
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index c81e8bf760a..960585245d7 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -167,7 +167,11 @@ class GitLabDropdown
hidden: =>
if @options.filterable
- @dropdown.find(".dropdown-input-field").blur().val("")
+ @dropdown
+ .find(".dropdown-input-field")
+ .blur()
+ .val("")
+ .trigger("keyup")
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee
index 6c1699c178c..7a788f761b7 100644
--- a/app/assets/javascripts/issuable_form.js.coffee
+++ b/app/assets/javascripts/issuable_form.js.coffee
@@ -1,5 +1,7 @@
class @IssuableForm
+ issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?'
wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i
+
constructor: (@form) ->
GitLab.GfmAutoComplete.setup()
new UsersSelect()
@@ -7,12 +9,13 @@ class @IssuableForm
@titleField = @form.find("input[name*='[title]']")
@descriptionField = @form.find("textarea[name*='[description]']")
+ @issueMoveField = @form.find("#move_to_project_id")
return unless @titleField.length && @descriptionField.length
@initAutosave()
- @form.on "submit", @resetAutosave
+ @form.on "submit", @handleSubmit
@form.on "click", ".btn-cancel", @resetAutosave
@initWip()
@@ -30,6 +33,12 @@ class @IssuableForm
"description"
]
+ handleSubmit: =>
+ if (parseInt(@issueMoveField?.val()) ? 0) > 0
+ return false unless confirm(@issueMoveConfirmMsg)
+
+ @resetAutosave()
+
resetAutosave: =>
@titleField.data("autosave").reset()
@descriptionField.data("autosave").reset()
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 8322b4c46ad..839e6ec2c08 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -3,6 +3,8 @@
# Handles persisting and restoring the current tab selection and lazily-loading
# content on the MergeRequests#show page.
#
+#= require jquery.cookie
+#
# ### Example Markup
#
# <ul class="nav-links merge-request-tabs">
@@ -68,11 +70,15 @@ class @MergeRequestTabs
if action == 'commits'
@loadCommits($target.attr('href'))
+ @expandView()
else if action == 'diffs'
@loadDiff($target.attr('href'))
@shrinkView()
else if action == 'builds'
@loadBuilds($target.attr('href'))
+ @expandView()
+ else
+ @expandView()
@setCurrentAction(action)
@@ -189,11 +195,24 @@ class @MergeRequestTabs
$('.container-fluid').removeClass('container-limited')
shrinkView: ->
- $gutterIcon = $('.js-sidebar-toggle i')
+ $gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set
setTimeout( ->
- # Only when sidebar is collapsed
+ # Only when sidebar is expanded
if $gutterIcon.is('.fa-angle-double-right')
- $gutterIcon.closest('a').trigger('click',[true])
+ $gutterIcon.closest('a').trigger('click', [true])
+ , 0)
+
+ # Expand the issuable sidebar unless the user explicitly collapsed it
+ expandView: ->
+ return if $.cookie('collapsed_gutter') == 'true'
+
+ $gutterIcon = $('.js-sidebar-toggle i:visible')
+
+ # Wait until listeners are set
+ setTimeout( ->
+ # Only when sidebar is collapsed
+ if $gutterIcon.is('.fa-angle-double-left')
+ $gutterIcon.closest('a').trigger('click', [true])
, 0)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 82532216589..ff06c57f2b5 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -361,14 +361,12 @@ class @Notes
showEditForm: (e) ->
e.preventDefault()
note = $(this).closest(".note")
- note.find(".note-body > .note-text").hide()
- note.find(".note-header").hide()
+ note.addClass "is-editting"
form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)')
if isNewForm
form.addClass('gfm-form')
form.addClass('current-note-edit-form')
- form.show()
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
@@ -402,11 +400,9 @@ class @Notes
cancelEdit: (e) ->
e.preventDefault()
note = $(this).closest(".note")
- note.find(".note-body > .note-text").show()
- note.find(".note-header").show()
+ note.removeClass "is-editting"
note.find(".current-note-edit-form")
.removeClass("current-note-edit-form")
- .hide()
###
Called in response to deleting a note of any kind.
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 76bc4ff42a2..87d313ed67c 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -11,7 +11,6 @@ class @Project
$(@).toggleClass('active')
url = $("#project_clone").val()
- console.log("url",url)
# Update the input field
$('#project_clone').val(url)
diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
index f5584bcfe4b..31617c88b4a 100644
--- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
@@ -95,4 +95,4 @@ window.ContributorsStatGraphUtil =
if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
true
else
- false \ No newline at end of file
+ false
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index b7ffa3e6ffb..5aa425dab6c 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -16,7 +16,7 @@
}
&.group-avatar, &.project-avatar, &.avatar-tile {
- @include border-radius(0px);
+ @include border-radius(0);
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 2fac6abbac9..62b2af0dbf7 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -107,7 +107,7 @@
margin: 0;
font-size: 23px;
font-weight: normal;
- margin: 16px 0 5px 0;
+ margin: 16px 0 5px;
color: #4c4e54;
font-size: 23px;
line-height: 1.1;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index a48b6c17fa0..d92cf6e6c44 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -75,7 +75,7 @@
width: 240px;
margin-top: 2px;
margin-bottom: 0;
- padding: 10px 10px;
+ padding: 10px;
font-size: 14px;
font-weight: normal;
background-color: $dropdown-bg;
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 2a4cf4fc335..c83cf881596 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -117,4 +117,4 @@ body {
&.ui_violet {
@include gitlab-theme(#98c, $theme-violet, #436, #325);
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index e901c78d02f..8bb047db2dd 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -16,7 +16,7 @@ body {
}
.container .content {
- margin: 0 0;
+ margin: 0;
}
.navless-container {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 377bfa174bd..250d6309291 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -1,7 +1,7 @@
/**
* Generic mixins
*/
- @mixin box-shadow($shadow) {
+@mixin box-shadow($shadow) {
-webkit-box-shadow: $shadow;
-moz-box-shadow: $shadow;
-ms-box-shadow: $shadow;
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b3371229d5a..fa7944cdabe 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -41,7 +41,7 @@
}
.select2-drop {
- @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
+ @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
@include border-radius ($border-radius-default);
border: none;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 949295a1d0c..b1886fbe67b 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -39,8 +39,8 @@
h1 {
font-size: 1.3em;
font-weight: 600;
- margin: 24px 0 12px 0;
- padding: 0 0 10px 0;
+ margin: 24px 0 12px;
+ padding: 0 0 10px;
border-bottom: 1px solid #e7e9ed;
color: #313236;
}
@@ -48,27 +48,27 @@
h2 {
font-size: 1.2em;
font-weight: 600;
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
color: #313236;
}
h3 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 1.1em;
}
h4 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 0.98em;
}
h5 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 0.95em;
}
h6 {
- margin: 24px 0 12px 0;
+ margin: 24px 0 12px;
font-size: 0.90em;
}
@@ -76,7 +76,7 @@
color: #7f8fa4;
font-size: inherit;
padding: 8px 21px;
- margin: 12px 0 12px;
+ margin: 12px 0;
border-left: 3px solid #e7e9ed;
}
@@ -88,13 +88,13 @@
p {
color: #5c5d5e;
- margin: 6px 0 0 0;
+ margin: 6px 0 0;
}
table {
@extend .table;
@extend .table-bordered;
- margin: 12px 0 12px 0;
+ margin: 12px 0;
color: #5c5d5e;
th {
background: #f8fafc;
@@ -102,7 +102,7 @@
}
pre {
- margin: 12px 0 12px 0;
+ margin: 12px 0;
font-size: 13px;
line-height: 1.6em;
overflow-x: auto;
@@ -191,7 +191,7 @@ body {
line-height: 1.3;
font-size: 1.25em;
font-weight: 600;
- margin: 12px 7px 12px 7px;
+ margin: 12px 7px;
}
h1, h2, h3, h4, h5, h6 {
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
new file mode 100644
index 00000000000..f1d42f80f56
--- /dev/null
+++ b/app/assets/stylesheets/notify.scss
@@ -0,0 +1,24 @@
+img {
+ max-width: 100%;
+ height: auto;
+}
+p.details {
+ font-style:italic;
+ color:#777
+}
+.footer p {
+ font-size:small;
+ color:#777
+}
+pre.commit-message {
+ white-space: pre-wrap;
+}
+.file-stats a {
+ text-decoration: none;
+}
+.file-stats .new-file {
+ color: #090;
+}
+.file-stats .deleted-file {
+ color: #B00;
+}
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index a61161810a3..e05f14e7496 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -34,9 +34,9 @@
background: #fff
}
- .visibility-levels {
- .controls {
- margin-bottom: 9px;
+ .visibility-levels {
+ .controls {
+ margin-bottom: 9px;
}
i {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index d5862a11aca..f1368d74b3b 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -132,7 +132,7 @@
}
.image-info {
font-size: 12px;
- margin: 5px 0 0 0;
+ margin: 5px 0 0;
color: grey;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 2760af8a48a..5300bb52a1b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -183,7 +183,7 @@
.block {
width: $sidebar_collapsed_width - 1px;
margin-left: -19px;
- padding: 15px 0 0 0;
+ padding: 15px 0 0;
border-bottom: none;
overflow: hidden;
}
@@ -273,12 +273,12 @@
}
.participants-list {
- margin: -5px -5px;
+ margin: -5px;
}
.participants-author {
display: inline-block;
- padding: 5px 5px;
+ padding: 5px;
.author_link {
display: block;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index bc41f7d306f..777bcbca5c3 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -45,7 +45,7 @@
.login-heading h3 {
font-weight: 300;
line-height: 1.5;
- margin: 0 0 10px 0;
+ margin: 0 0 10px;
}
.login-footer {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 61783ec46aa..daf2651425f 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -26,7 +26,7 @@
display: none;
}
-.new_note, .edit_note {
+.new_note, .note-edit-form {
.note-form-actions {
margin-top: $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index d408853cc80..4bd2016bdcf 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -100,6 +100,18 @@ ul.notes {
display: block;
position: relative;
+ &.is-editting {
+ .note-header,
+ .note-text,
+ .edited-text {
+ display: none;
+ }
+
+ .note-edit-form {
+ display: block;
+ }
+ }
+
.note-body {
overflow: auto;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 260179074cf..e96dfc8e8d8 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -54,7 +54,7 @@
}
.account-well {
- padding: 10px 10px;
+ padding: 10px;
background-color: $help-well-bg;
border: 1px solid $help-well-border;
border-radius: $border-radius-base;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 250de079ff5..c68bd673a67 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -37,7 +37,7 @@
.dropdown-menu {
left: auto;
width: auto;
- right: 0px;
+ right: 0;
max-width: 240px;
}
}
@@ -311,7 +311,7 @@ pre.light-well {
}
.git-empty {
- margin: 0 7px 0 7px;
+ margin: 0 7px;
h5 {
color: #5c5d5e;
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index b9be47e7700..85a0304196c 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -16,7 +16,7 @@
#contributors {
.contributors-list {
- margin: 0 0 10px 0;
+ margin: 0 0 10px;
list-style: none;
padding: 0;
}
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index 8886c1dff56..3f28e402929 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -21,19 +21,19 @@
$l-white: #fff;
.term-bold {
- font-weight: bold;
+ font-weight: bold;
}
.term-italic {
- font-style: italic;
+ font-style: italic;
}
.term-conceal {
- visibility: hidden;
+ visibility: hidden;
}
.term-underline {
- text-decoration: underline;
+ text-decoration: underline;
}
.term-cross {
- text-decoration: line-through;
+ text-decoration: line-through;
}
.term-fg-black {
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 8e2a981be7c..a6db4690df0 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -5,12 +5,12 @@ class Admin::GroupsController < Admin::ApplicationController
@groups = Group.all
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
- @groups = @groups.page(params[:page]).per(PER_PAGE)
+ @groups = @groups.page(params[:page])
end
def show
- @members = @group.members.order("access_level DESC").page(params[:members_page]).per(PER_PAGE)
- @projects = @group.projects.page(params[:projects_page]).per(PER_PAGE)
+ @members = @group.members.order("access_level DESC").page(params[:members_page])
+ @projects = @group.projects.page(params[:projects_page])
end
def new
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index d79ce2b10fe..d496f08a598 100644
--- a/app/controllers/admin/labels_controller.rb
+++ b/app/controllers/admin/labels_controller.rb
@@ -2,7 +2,7 @@ class Admin::LabelsController < Admin::ApplicationController
before_action :set_label, only: [:show, :edit, :update, :destroy]
def index
- @labels = Label.templates.page(params[:page]).per(PER_PAGE)
+ @labels = Label.templates.page(params[:page])
end
def show
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 8b212225b2d..4089091d569 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -11,15 +11,15 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE)
+ @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
end
def show
if @group
- @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(PER_PAGE)
+ @group_members = @group.members.order("access_level DESC").page(params[:group_members_page])
end
- @project_members = @project.project_members.page(params[:project_members_page]).per(PER_PAGE)
+ @project_members = @project.project_members.page(params[:project_members_page])
end
def transfer
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 3a0eb96a460..c81cb85dc1b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,8 +6,6 @@ class ApplicationController < ActionController::Base
include GitlabRoutingHelper
include PageLayoutHelper
- PER_PAGE = 20
-
before_action :authenticate_user_from_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 77c8dafc012..81ba58ce49c 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -7,7 +7,7 @@ class AutocompleteController < ApplicationController
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.reorder(:name)
- @users = @users.page(params[:page]).per(PER_PAGE)
+ @users = @users.page(params[:page])
if params[:search].blank?
# Include current user if available to filter by "Me"
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
index 3e4c0e63601..54ea1e454fc 100644
--- a/app/controllers/concerns/global_milestones.rb
+++ b/app/controllers/concerns/global_milestones.rb
@@ -6,7 +6,7 @@ module GlobalMilestones
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
- @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
+ @milestones = Kaminari.paginate_array(@milestones).page(params[:page])
end
def milestone
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
new file mode 100644
index 00000000000..f40b62446e5
--- /dev/null
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -0,0 +1,23 @@
+module IssuableActions
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :authorize_destroy_issuable!, only: :destroy
+ end
+
+ def destroy
+ issuable.destroy
+
+ name = issuable.class.name.titleize.downcase
+ flash[:notice] = "The #{name} was successfully deleted."
+ redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
+ end
+
+ private
+
+ def authorize_destroy_issuable!
+ unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
+ return access_denied!
+ end
+ end
+end
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index ef8e74a4641..4feabc32b1c 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -3,7 +3,7 @@ module IssuesAction
def issues
@issues = get_issues_collection.non_archived
- @issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE)
+ @issues = @issues.page(params[:page])
@issues = @issues.preload(:author, :project)
@label = @issuable_finder.labels.first
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index 9c49596bd0b..06a6b065e7e 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -3,7 +3,7 @@ module MergeRequestsAction
def merge_requests
@merge_requests = get_merge_requests_collection.non_archived
- @merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE)
+ @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:author, :target_project)
@label = @issuable_finder.labels.first
diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb
index 3bc94ff2187..71ba6153021 100644
--- a/app/controllers/dashboard/groups_controller.rb
+++ b/app/controllers/dashboard/groups_controller.rb
@@ -1,5 +1,5 @@
class Dashboard::GroupsController < Dashboard::ApplicationController
def index
- @group_members = current_user.group_members.page(params[:page]).per(PER_PAGE)
+ @group_members = current_user.group_members.page(params[:page])
end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 0e8b63872ca..71acc244a91 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
@last_push = current_user.recent_push
@@ -32,7 +32,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
@last_push = current_user.recent_push
@groups = []
diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb
index b3594d82530..bcfdbe14be9 100644
--- a/app/controllers/dashboard/snippets_controller.rb
+++ b/app/controllers/dashboard/snippets_controller.rb
@@ -6,6 +6,6 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
user: current_user,
scope: params[:scope]
)
- @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ @snippets = @snippets.page(params[:page])
end
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index be488483b09..5abf97342c3 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -2,7 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy, :destroy_all]
def index
- @todos = @todos.page(params[:page]).per(PER_PAGE)
+ @todos = @todos.page(params[:page])
end
def destroy
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index 9575a87ee41..a962f9a0937 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -3,6 +3,6 @@ class Explore::GroupsController < Explore::ApplicationController
@groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort])
- @groups = @groups.page(params[:page]).per(PER_PAGE)
+ @groups = @groups.page(params[:page])
end
end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 8271ca87436..88a0c18180b 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -8,7 +8,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
+ @projects = @projects.includes(:namespace).page(params[:page])
respond_to do |format|
format.html
@@ -23,7 +23,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
@projects = TrendingProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects)
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
respond_to do |format|
format.html
@@ -39,7 +39,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = ProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects)
@projects = @projects.reorder('star_count DESC')
- @projects = @projects.page(params[:page]).per(PER_PAGE)
+ @projects = @projects.page(params[:page])
respond_to do |format|
format.html
diff --git a/app/controllers/explore/snippets_controller.rb b/app/controllers/explore/snippets_controller.rb
index b70ac51d06e..28760c3f84b 100644
--- a/app/controllers/explore/snippets_controller.rb
+++ b/app/controllers/explore/snippets_controller.rb
@@ -1,6 +1,6 @@
class Explore::SnippetsController < Explore::ApplicationController
def index
@snippets = SnippetsFinder.new.execute(current_user, filter: :all)
- @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ @snippets = @snippets.page(params[:page])
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 87efb0a8970..c1adc999567 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -42,7 +42,7 @@ class GroupsController < Groups::ApplicationController
@projects = @projects.includes(:namespace)
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
+ @projects = @projects.page(params[:page]) if params[:filter_projects].blank?
@shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 32fca6b838e..9042d8e5f0d 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -34,8 +34,7 @@ class ProfilesController < Profiles::ApplicationController
def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC").
- page(params[:page]).
- per(PER_PAGE)
+ page(params[:page])
end
def update_username
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 43ea717cbd2..c0a53734921 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController
def index
@sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort)
- @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE)
+ @branches = Kaminari.paginate_array(@branches).page(params[:page])
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index a1b8632df98..ade01c706a7 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -15,7 +15,7 @@ class Projects::ForksController < Projects::ApplicationController
@sort = params[:sort] || 'id_desc'
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
- @forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE)
+ @forks = @forks.order_by(@sort).page(params[:page])
respond_to do |format|
format.html
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 6603f28a082..877b39c9b1b 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,5 +1,6 @@
class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction
+ include IssuableActions
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show]
@@ -33,7 +34,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
- @issues = @issues.page(params[:page]).per(PER_PAGE)
+ @issues = @issues.page(params[:page])
@label = @project.labels.find_by(title: params[:label_name])
respond_to do |format|
@@ -90,6 +91,12 @@ class Projects::IssuesController < Projects::ApplicationController
def update
@issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue)
+ if params[:move_to_project_id].to_i > 0
+ new_project = Project.find(params[:move_to_project_id])
+ move_service = Issues::MoveService.new(project, current_user)
+ @issue = move_service.execute(@issue, new_project)
+ end
+
respond_to do |format|
format.js
format.html do
@@ -127,6 +134,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
alias_method :subscribable_resource, :issue
+ alias_method :issuable, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 5f471d405f5..ff771ea6d9c 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -11,7 +11,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to :js, :html
def index
- @labels = @project.labels.page(params[:page]).per(PER_PAGE)
+ @labels = @project.labels.page(params[:page])
respond_to do |format|
format.html
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 7248ede1699..b830d777752 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,6 +1,7 @@
class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
include DiffHelper
+ include IssuableActions
before_action :module_enabled
before_action :merge_request, only: [
@@ -34,7 +35,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
- @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
+ @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
@label = @project.labels.find_by(title: params[:label_name])
@@ -255,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end
alias_method :subscribable_resource, :merge_request
+ alias_method :issuable, :merge_request
def closes_issues
@closes_issues ||= @merge_request.closes_issues
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 0998b191c07..b2e974eff17 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -22,7 +22,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to do |format|
format.html do
- @milestones = @milestones.page(params[:page]).per(PER_PAGE)
+ @milestones = @milestones.page(params[:page])
end
format.json do
render json: @milestones
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 92b0caa2efb..b578b419a46 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -21,7 +21,7 @@ class Projects::SnippetsController < Projects::ApplicationController
filter: :by_project,
project: @project
})
- @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ @snippets = @snippets.page(params[:page])
end
def new
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index e580487a2c6..46b242aa5ff 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -7,7 +7,7 @@ class Projects::TagsController < Projects::ApplicationController
def index
sorted = VersionSorter.rsort(@repository.tag_names)
- @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE)
+ @tags = Kaminari.paginate_array(sorted).page(params[:page])
@releases = project.releases.where(tag: @tags)
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 88fccfed509..02ceb8f4334 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -7,7 +7,7 @@ class Projects::WikisController < Projects::ApplicationController
before_action :load_project_wiki
def pages
- @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE)
+ @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
end
def show
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index c72df73af46..2daceed039b 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -25,7 +25,7 @@ class SnippetsController < ApplicationController
filter: :by_user,
user: @user,
scope: params[:scope] }).
- page(params[:page]).per(PER_PAGE)
+ page(params[:page])
render 'index'
else
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 481d00d6aae..8e7956da48f 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -100,7 +100,7 @@ class UsersController < ApplicationController
def load_projects
@projects =
PersonalProjectsFinder.new(@user).execute(current_user)
- .page(params[:page]).per(PER_PAGE)
+ .page(params[:page])
end
def load_contributed_projects
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 0f77b3b299a..820d69c230b 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -27,7 +27,7 @@ module BlobHelper
link_opts)
if !on_top_of_branch?(project, ref)
- button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+ button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn'
elsif can?(current_user, :fork_project, project)
@@ -50,9 +50,9 @@ module BlobHelper
return unless blob
if !on_top_of_branch?(project, ref)
- button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
+ button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer?
- button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
+ button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index d6c05843743..a9047ede8c5 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -23,36 +23,34 @@ module ButtonHelper
end
def http_clone_button(project)
- klass = 'btn js-protocol-switch'
- klass << ' active' if default_clone_protocol == 'http'
- klass << ' has_tooltip' if current_user.try(:require_password?)
+ klass = 'http-selector'
+ klass << ' has-tooltip' if current_user.try(:require_password?)
protocol = gitlab_config.protocol.upcase
- content_tag :button, protocol,
+ content_tag :a, protocol,
class: klass,
+ href: @project.http_url_to_repo,
data: {
- clone: project.http_url_to_repo,
+ html: true,
+ placement: 'right',
container: 'body',
- html: 'true',
title: "Set a password on your account<br>to pull or push via #{protocol}"
- },
- type: :button
+ }
end
def ssh_clone_button(project)
- klass = 'btn js-protocol-switch'
- klass << ' active' if default_clone_protocol == 'ssh'
- klass << ' has_tooltip' if current_user.try(:require_ssh_key?)
+ klass = 'ssh-selector'
+ klass << ' has-tooltip' if current_user.try(:require_ssh_key?)
- content_tag :button, 'SSH',
+ content_tag :a, 'SSH',
class: klass,
+ href: project.ssh_url_to_repo,
data: {
- clone: project.ssh_url_to_repo,
+ html: true,
+ placement: 'right',
container: 'body',
- html: 'true',
title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
- },
- type: :button
+ }
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index f994c9e6170..bde0799f3de 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -182,7 +182,7 @@ module CommitsHelper
end
options = {
- class: "commit-#{options[:source]}-link has_tooltip",
+ class: "commit-#{options[:source]}-link has-tooltip",
data: { 'original-title'.to_sym => sanitize(source_email) }
}
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index e00d3204027..24b90fef4fe 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -57,6 +57,19 @@ module IssuesHelper
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
+ def project_options(issuable, current_user, ability: :read_project)
+ projects = current_user.authorized_projects
+ projects = projects.select do |project|
+ current_user.can?(ability, project)
+ end
+
+ no_project = OpenStruct.new(id: 0, name_with_namespace: 'No project')
+ projects.unshift(no_project)
+ projects.delete(issuable.project)
+
+ options_from_collection_for_select(projects, :id, :name_with_namespace)
+ end
+
def status_box_class(item)
if item.respond_to?(:expired?) && item.expired?
'status-box-expired'
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index ed37176aa6b..e0a8552dfa7 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -56,7 +56,7 @@ module LabelsHelper
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
- span = %(<span class="label color-label #{"has_tooltip" if tooltip}" ) +
+ span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
%(style="background-color: #{label_color}; color: #{text_color}" ) +
%(title="#{escape_once(label.description)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 5473419ef24..4e4c6e301d5 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -52,7 +52,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
- link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
+ link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
end
end
@@ -209,7 +209,7 @@ module ProjectsHelper
def default_clone_protocol
if !current_user || current_user.require_ssh_key?
- "http"
+ gitlab_config.protocol
else
"ssh"
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 5f9adb32e00..6f54c42146c 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -36,6 +36,14 @@ module Emails
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
end
+ def issue_moved_email(recipient, issue, new_issue, updated_by_user)
+ setup_issue_mail(issue.id, recipient.id)
+
+ @new_issue = new_issue
+ @new_project = new_issue.project
+ mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id))
+ end
+
private
def setup_issue_mail(issue_id, recipient_id)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index de9253fcdd8..42b978e04d5 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -234,7 +234,9 @@ class Ability
:rename_project,
:remove_project,
:archive_project,
- :remove_fork_project
+ :remove_fork_project,
+ :destroy_merge_request,
+ :destroy_issue
]
end
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
index 821ed54fb98..51288094ef1 100644
--- a/app/models/concerns/internal_id.rb
+++ b/app/models/concerns/internal_id.rb
@@ -7,7 +7,10 @@ module InternalId
end
def set_iid
- max_iid = project.send(self.class.name.tableize).maximum(:iid)
+ records = project.send(self.class.name.tableize)
+ records = records.with_deleted if self.paranoid?
+ max_iid = records.maximum(:iid)
+
self.iid = max_iid.to_i + 1
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 86ab84615ba..476e1ce7af0 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -58,6 +58,8 @@ module Issuable
attr_mentionable :description, cache: true
participant :author, :assignee, :notes_with_associations
strip_attributes :title
+
+ acts_as_paranoid
end
module ClassMethods
@@ -209,4 +211,13 @@ module Issuable
Taskable.get_updated_tasks(old_content: previous_changes['description'].first,
new_content: description)
end
+
+ ##
+ # Method that checks if issuable can be moved to another project.
+ #
+ # Should be overridden if issuable can be moved.
+ #
+ def can_move?(*)
+ false
+ end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 60250322b04..f32db59ac9f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -16,6 +16,7 @@
# state :string(255)
# iid :integer
# updated_by_id :integer
+# moved_to_id :integer
#
require 'carrierwave/orm/activerecord'
@@ -31,6 +32,8 @@ class Issue < ActiveRecord::Base
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
+ belongs_to :moved_to, class_name: 'Issue'
+
validates :project, presence: true
scope :cared, ->(user) { where(assignee_id: user) }
@@ -102,9 +105,9 @@ class Issue < ActiveRecord::Base
end
def related_branches
- return [] if self.project.empty_repo?
-
- self.project.repository.branch_names.select { |branch| branch.end_with?("-#{iid}") }
+ project.repository.branch_names.select do |branch|
+ branch.end_with?("-#{iid}")
+ end
end
# Reset issue events cache
@@ -134,6 +137,18 @@ class Issue < ActiveRecord::Base
end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
end
+ def moved?
+ !moved_to.nil?
+ end
+
+ def can_move?(user, to_project = nil)
+ if to_project
+ return false unless user.can?(:admin_issue, to_project)
+ end
+
+ !moved? && user.can?(:admin_issue, self.project)
+ end
+
def to_branch_name
"#{title.parameterize}-#{iid}"
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 4d23cc8de40..9c8246e8ac0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -431,6 +431,7 @@ class Project < ActiveRecord::Base
def safe_import_url
result = URI.parse(self.import_url)
result.password = '*****' unless result.password.nil?
+ result.user = '*****' unless result.user.nil? || result.user == "git" #tokens or other data may be saved as user
result.to_s
rescue
self.import_url
@@ -887,6 +888,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
unless forked?
if gitlab_shell.add_repository(path_with_namespace)
+ repository.after_create
true
else
errors.add(:base, 'Failed to create repository via gitlab-shell')
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 59b1b86d1fb..7c1a61bb0bf 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -123,23 +123,27 @@ class ProjectWiki
end
def repository
- Repository.new(path_with_namespace, @project)
+ @repository ||= Repository.new(path_with_namespace, @project)
end
def default_branch
wiki.class.default_ref
end
- private
-
def create_repo!
if init_repo(path_with_namespace)
- Gollum::Wiki.new(path_to_repo)
+ wiki = Gollum::Wiki.new(path_to_repo)
else
raise CouldNotCreateWikiError
end
+
+ repository.after_create
+
+ wiki
end
+ private
+
def init_repo(path_with_namespace)
gitlab_shell.add_repository(path_with_namespace)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 25d24493f6e..13154eb4205 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -42,12 +42,15 @@ class Repository
end
def exists?
- return false unless raw_repository
+ return @exists unless @exists.nil?
- raw_repository.rugged
- true
- rescue Gitlab::Git::Repository::NoRepository
- false
+ @exists = cache.fetch(:exists?) do
+ begin
+ raw_repository && raw_repository.rugged ? true : false
+ rescue Gitlab::Git::Repository::NoRepository
+ false
+ end
+ end
end
def empty?
@@ -320,12 +323,23 @@ class Repository
@avatar = nil
end
+ def expire_exists_cache
+ cache.expire(:exists?)
+ @exists = nil
+ end
+
+ # Runs code after a repository has been created.
+ def after_create
+ expire_exists_cache
+ end
+
# Runs code just before a repository is deleted.
def before_delete
expire_cache if exists?
expire_root_ref_cache
expire_emptiness_caches
+ expire_exists_cache
end
# Runs code just before the HEAD of a repository is changed.
@@ -351,6 +365,7 @@ class Repository
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
+ expire_exists_cache
end
# Runs code after a new commit has been pushed.
diff --git a/app/models/user.rb b/app/models/user.rb
index c011af03591..9c315cfe966 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -435,7 +435,7 @@ class User < ActiveRecord::Base
Group.where("namespaces.id IN (#{union.to_sql})")
end
- # Returns the groups a user is authorized to access.
+ # Returns projects user is authorized to access.
def authorized_projects
Project.where("projects.id IN (#{projects_union.to_sql})")
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 14e2a2c0699..c007d648dd6 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -120,7 +120,7 @@ class GitPushService < BaseService
closed_issues = commit.closes_issues(current_user)
closed_issues.each do |issue|
if can?(current_user, :update_issue, issue)
- Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
+ Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit: commit)
end
end
end
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 78254b49af3..859c934ea3b 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -1,6 +1,6 @@
module Issues
class CloseService < Issues::BaseService
- def execute(issue, commit = nil)
+ def execute(issue, commit: nil, notifications: true, system_note: true)
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(commit, issue)
todo_service.close_issue(issue, current_user)
@@ -9,8 +9,8 @@ module Issues
if project.default_issues_tracker? && issue.close
event_service.close_issue(issue, current_user)
- create_note(issue, commit)
- notification_service.close_issue(issue, current_user)
+ create_note(issue, commit) if system_note
+ notification_service.close_issue(issue, current_user) if notifications
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 10787e8873c..e63e1af8766 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -4,7 +4,7 @@ module Issues
filter_params
label_params = params[:label_ids]
issue = project.issues.new(params.except(:label_ids))
- issue.author = current_user
+ issue.author = params[:author] || current_user
if issue.save
issue.update_attributes(label_ids: label_params)
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
new file mode 100644
index 00000000000..3cfbafe1576
--- /dev/null
+++ b/app/services/issues/move_service.rb
@@ -0,0 +1,94 @@
+module Issues
+ class MoveService < Issues::BaseService
+ class MoveError < StandardError; end
+
+ def execute(issue, new_project)
+ @old_issue = issue
+ @old_project = @project
+ @new_project = new_project
+
+ unless issue.can_move?(current_user, new_project)
+ raise MoveError, 'Cannot move issue due to insufficient permissions!'
+ end
+
+ if @project == new_project
+ raise MoveError, 'Cannot move issue to project it originates from!'
+ end
+
+ # Using transaction because of a high resources footprint
+ # on rewriting notes (unfolding references)
+ #
+ ActiveRecord::Base.transaction do
+ # New issue tasks
+ #
+ @new_issue = create_new_issue
+
+ rewrite_notes
+ add_note_moved_from
+
+ # Old issue tasks
+ #
+ add_note_moved_to
+ close_issue
+ mark_as_moved
+ end
+
+ notify_participants
+
+ @new_issue
+ end
+
+ private
+
+ def create_new_issue
+ new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
+ project: @new_project, author: @old_issue.author,
+ description: unfold_references(@old_issue.description) }
+
+ new_params = @old_issue.serializable_hash.merge(new_params)
+ CreateService.new(@new_project, @current_user, new_params).execute
+ end
+
+ def rewrite_notes
+ @old_issue.notes.find_each do |note|
+ new_note = note.dup
+ new_params = { project: @new_project, noteable: @new_issue,
+ note: unfold_references(new_note.note),
+ created_at: note.created_at }
+
+ new_note.update(new_params)
+ end
+ end
+
+ def close_issue
+ close_service = CloseService.new(@old_project, @current_user)
+ close_service.execute(@old_issue, notifications: false, system_note: false)
+ end
+
+ def add_note_moved_from
+ SystemNoteService.noteable_moved(@new_issue, @new_project,
+ @old_issue, @current_user,
+ direction: :from)
+ end
+
+ def add_note_moved_to
+ SystemNoteService.noteable_moved(@old_issue, @old_project,
+ @new_issue, @current_user,
+ direction: :to)
+ end
+
+ def unfold_references(content)
+ rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
+ @current_user)
+ rewriter.rewrite(@new_project)
+ end
+
+ def notify_participants
+ notification_service.issue_moved(@old_issue, @new_issue, @current_user)
+ end
+
+ def mark_as_moved
+ @old_issue.update(moved_to: @new_issue)
+ end
+ end
+end
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index ebb67c7db65..064910f81f7 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -22,7 +22,7 @@ module MergeRequests
closed_issues = merge_request.closes_issues(current_user)
closed_issues.each do |issue|
if can?(current_user, :update_issue, issue)
- Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+ Issues::CloseService.new(project, current_user, {}).execute(issue, commit: merge_request)
end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 19a6779dea9..3bdf00a8291 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -236,6 +236,16 @@ class NotificationService
end
end
+ def issue_moved(issue, new_issue, current_user)
+ recipients = build_recipients(issue, issue.project, current_user)
+
+ recipients.map do |recipient|
+ email = mailer.issue_moved_email(recipient, issue, new_issue, current_user)
+ email.deliver_later
+ email
+ end
+ end
+
protected
# Get project users with WATCH notification level
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index c644cd0b951..e022a046c48 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -411,4 +411,26 @@ class SystemNoteService
body = "Marked the task **#{new_task.source}** as #{status_label}"
create_note(noteable: noteable, project: project, author: author, note: body)
end
+
+ # Called when noteable has been moved to another project
+ #
+ # direction - symbol, :to or :from
+ # noteable - Noteable object
+ # noteable_ref - Referenced noteable
+ # author - User performing the move
+ #
+ # Example Note text:
+ #
+ # "Moved to some_namespace/project_new#11"
+ #
+ # Returns the created Note object
+ def self.noteable_moved(noteable, project, noteable_ref, author, direction:)
+ unless [:to, :from].include?(direction)
+ raise ArgumentError, "Invalid direction `#{direction}`"
+ end
+
+ cross_reference = noteable_ref.to_reference(project)
+ body = "Moved #{direction} #{cross_reference}"
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
end
diff --git a/app/views/admin/applications/_delete_form.html.haml b/app/views/admin/applications/_delete_form.html.haml
index 3147cbd659f..042971e1eed 100644
--- a/app/views/admin/applications/_delete_form.html.haml
+++ b/app/views/admin/applications/_delete_form.html.haml
@@ -1,4 +1,4 @@
- submit_btn_css ||= 'btn btn-link btn-remove btn-sm'
= form_tag admin_application_path(application) do
%input{:name => "_method", :type => "hidden", :value => "delete"}/
- = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css \ No newline at end of file
+ = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index 4974bb7f7fb..8e81671b7e7 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -6,4 +6,4 @@
%label{for: "remember_me"}
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = button_tag "Sign in", class: "btn-save btn" \ No newline at end of file
+ = button_tag "Sign in", class: "btn-save btn"
diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml
index fd32a468b45..d3692d1f759 100644
--- a/app/views/doorkeeper/applications/new.html.haml
+++ b/app/views/doorkeeper/applications/new.html.haml
@@ -4,4 +4,4 @@
%hr
-= render 'form', application: @application \ No newline at end of file
+= render 'form', application: @application
diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml
index 7561ec85ed9..a4c607cea60 100644
--- a/app/views/doorkeeper/authorizations/error.html.haml
+++ b/app/views/doorkeeper/authorizations/error.html.haml
@@ -1,3 +1,3 @@
%h3.page-title An error has occurred
%main{:role => "main"}
- %pre= @pre_auth.error_response.body[:error_description] \ No newline at end of file
+ %pre= @pre_auth.error_response.body[:error_description]
diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml
index 9a402007194..01f9e46f142 100644
--- a/app/views/doorkeeper/authorizations/show.html.haml
+++ b/app/views/doorkeeper/authorizations/show.html.haml
@@ -1,3 +1,3 @@
%h3.page-title Authorization code:
%main{:role => "main"}
- %code#authorization_code= params[:code] \ No newline at end of file
+ %code#authorization_code= params[:code]
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index db0cf393922..4a0069f18f8 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,7 +1,7 @@
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
= link_to dashboard_projects_path, title: 'Projects' do
- = icon('home fw')
+ = icon('bookmark fw')
%span
Projects
= nav_link(controller: :todos) do
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 48039ca2918..f08c5edf99c 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,7 +1,7 @@
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do
- = icon('home fw')
+ = icon('bookmark fw')
%span
Projects
= nav_link(controller: :groups) do
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 37b4d562966..2997f59d946 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -1,33 +1,9 @@
%html{lang: "en"}
%head
%meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
- %title
- GitLab
- :css
- img {
- max-width: 100%;
- height: auto;
- }
- p.details {
- font-style:italic;
- color:#777
- }
- .footer p {
- font-size:small;
- color:#777
- }
- pre.commit-message {
- white-space: pre-wrap;
- }
- .file-stats a {
- text-decoration: none;
- }
- .file-stats .new-file {
- color: #090;
- }
- .file-stats .deleted-file {
- color: #B00;
- }
+ %title
+ GitLab
+ = stylesheet_link_tag 'notify'
%body
%div.content
= yield
diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml
new file mode 100644
index 00000000000..40f7d61fe19
--- /dev/null
+++ b/app/views/notify/issue_moved_email.html.haml
@@ -0,0 +1,6 @@
+%p
+ Issue was moved to another project.
+%p
+ New issue:
+ = link_to namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) do
+ = @new_issue.title
diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb
new file mode 100644
index 00000000000..b3bd43c2055
--- /dev/null
+++ b/app/views/notify/issue_moved_email.text.erb
@@ -0,0 +1,4 @@
+Issue was moved to another project.
+
+New issue location:
+<%= namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) %>
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 25e9e8ff008..4dbaa662b66 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -8,7 +8,7 @@
= key.fingerprint
.pull-right
%span.key-created-at
- created #{time_ago_with_tooltip(key.created_at)} ago
+ created #{time_ago_with_tooltip(key.created_at)}
= link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
%span.sr-only Remove
= icon('trash')
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 76a823d3828..57e507e68c8 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -11,7 +11,7 @@
- if branch.name == @repository.root_ref
%span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name
- %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
+ %span.label.label-info.has-tooltip(title="Merged into #{@repository.root_ref}")
merged
- if @project.protected_branch? branch.name
@@ -30,7 +30,7 @@
Compare
- if can_remove_branch?(@project, branch.name)
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if branch.name != @repository.root_ref
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 6a60cfeff76..58f43ecb5d5 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,4 +1,4 @@
- unless @project.empty_repo?
- if can? current_user, :download_code, @project
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do
= icon('download')
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 133531887a2..88cbb7c03c5 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -1,7 +1,7 @@
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
- = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
= icon('code-fork fw')
Fork
%div.count-with-arrow
@@ -9,7 +9,7 @@
%span.count
= @project.forks_count
- 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 has-tooltip' do
= icon('code-fork fw')
Fork
%div.count-with-arrow
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index 3e83ec3912f..a3786c35a1f 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -14,7 +14,7 @@
= notification_list_item(level, @membership)
- when GroupMember
- .btn.disabled.notifications-btn.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
+ .btn.disabled.notifications-btn.has-tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
= icon('bell')
= notification_label(@membership)
= icon('angle-down')
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 21ba426aaa1..02dbb2985a4 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: "Star project" do
+ = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has-tooltip', method: :post, remote: true, title: "Star project" do
- if current_user.starred?(@project)
= icon('star fw')
%span.starred Unstar
@@ -12,7 +12,7 @@
= @project.star_count
- else
- = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
+ = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw')
Star
%div.count-with-arrow
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index 4ab81f3635c..dd590a4b8ec 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,7 +1,7 @@
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
.clearfix
- if params[:to] && params[:from]
- = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
+ = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
.form-group
.input-group.inline-input-group
%span.input-group-addon from
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 3898bb202c5..698ed02ea0e 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -28,7 +28,7 @@
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
- = link_to '#', class: 'js-toggle-diff-comments btn active has_tooltip', title: "Toggle comments for this file" do
+ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do
= icon('comments')
\
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
new file mode 100644
index 00000000000..9464c8dc996
--- /dev/null
+++ b/app/views/projects/diffs/_line.html.haml
@@ -0,0 +1,26 @@
+- type = line.type
+%tr.line_holder{id: line_code, class: type}
+ - case type
+ - when 'match'
+ = render "projects/diffs/match_line", {line: line.text,
+ line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
+ - when 'nonewline'
+ %td.old_line.diff-line-num
+ %td.new_line.diff-line-num
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{class: type}
+ - link_text = raw(type == "new" ? "&nbsp;" : line.old_pos)
+ - if defined?(plain) && plain
+ = link_text
+ - else
+ = link_to link_text, "##{line_code}", id: line_code
+ - if @comments_allowed && can?(current_user, :create_note, @project)
+ = link_to_new_diff_note(line_code)
+ %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
+ - link_text = raw(type == "old" ? "&nbsp;" : line.new_pos)
+ - if defined?(plain) && plain
+ = link_text
+ - else
+ = link_to link_text, "##{line_code}", id: line_code
+ %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 9a8208202e4..e7169d7b599 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -8,26 +8,9 @@
- last_line = 0
- raw_diff_lines = diff_file.diff_lines.to_a
- diff_file.highlighted_diff_lines.each_with_index do |line, index|
- - type = line.type
- - last_line = line.new_pos
- line_code = generate_line_code(diff_file.file_path, line)
- - line_old = line.old_pos
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- = render "projects/diffs/match_line", {line: line.text,
- line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
- - elsif type == 'nonewline'
- %td.old_line.diff-line-num
- %td.new_line.diff-line-num
- %td.line_content.match= line.text
- - else
- %td.old_line.diff-line-num{class: type}
- = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
- - if @comments_allowed && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(line_code)
- %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
- = link_to raw(type == "old" ? "&nbsp;" : line.new_pos), "##{line_code}", id: line_code
- %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
+ - last_line = line.new_pos
+ = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
- if @reply_allowed
- comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index edabc2d3b44..73a7fc0e1ac 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -12,7 +12,7 @@
.col-md-2.col-sm-3
- if fork = namespace.find_fork_of(@project)
.fork-thumbnail
- = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
+ = link_to project_path(fork), title: "Visit project fork", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
@@ -22,7 +22,7 @@
- else
.fork-thumbnail
- = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has-tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 52df3de8a27..6fa059cbe68 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -45,7 +45,6 @@
- if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
-
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o')
Edit
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 13d0cbdde1d..391193eed6c 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -17,7 +17,7 @@
- if merge_request.open? && merge_request.broken?
%li
- = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
+ = link_to merge_request_path(merge_request), class: "has-tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
= icon('exclamation-triangle')
- if merge_request.assignee
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index eeb605e2dc5..ab4b1f14be5 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -29,7 +29,7 @@
- if @merge_request.open?
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
- %i.fa.fa-pencil-square-o
+ = icon('pencil-square-o')
Edit
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 13e624764d9..2999befffc6 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -5,6 +5,6 @@
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
= render 'projects/notes/hints'
- .note-form-actions
+ .note-form-actions.clearfix
= f.submit 'Save Comment', class: 'btn btn-nr btn-save btn-grouped js-comment-button'
= link_to 'Cancel', '#', class: 'btn btn-nr btn-cancel note-edit-cancel'
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 399782273d3..dbc35c16feb 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -15,11 +15,11 @@
= render 'projects/tags/download', ref: tag.name, project: @project
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has_tooltip', title: "Edit release notes" do
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has-tooltip', title: "Edit release notes" do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if commit
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 8c7f93f93b6..1dc9b799a95 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -5,17 +5,17 @@
.gray-content-block
.pull-right
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has_tooltip', title: 'Edit release notes' do
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do
= icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse files' do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do
= icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse commits' do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.pull-right
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+ = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.title
%span.item-title= @tag.name
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 3eb626e6dca..ba69569b1e7 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -15,7 +15,7 @@
- if current_user
%li
- if !on_top_of_branch?
- %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
+ %span.btn.btn-sm.add-to-tree.disabled.has-tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
= icon('plus')
- else
%span.dropdown
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index e0b18733d74..b31595d8d1c 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -6,4 +6,4 @@
- if milestone.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(milestone.description)) \ No newline at end of file
+ = search_md_sanitize(markdown(milestone.description))
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index faf7e49ed29..974751d9970 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -8,11 +8,9 @@
= icon('angle-down')
%ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
%li
- %a#ssh-selector{href: @project.ssh_url_to_repo}
- SSH
+ = ssh_clone_button(project)
%li
- %a#http-selector{href: @project.http_url_to_repo}
- HTTPS
+ = http_clone_button(project)
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 03103d46bb9..db416b9d91a 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -14,7 +14,7 @@
.stats
%span
- = icon('home')
+ = icon('bookmark')
= number_with_delimiter(group.projects.count)
%span
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 80418e69d9c..551f0cc0b51 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -85,13 +85,26 @@
- if can? current_user, :admin_label, issuable.project
= link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
+- if issuable.can_move?(current_user)
+ %hr
+ .form-group
+ = label_tag :move_to_project_id, 'Move', class: 'control-label'
+ .col-sm-10
+ - projects = project_options(issuable, current_user, ability: :admin_issue)
+ = select_tag(:move_to_project_id, projects, include_blank: true,
+ class: 'select2', data: { placeholder: 'Select project' })
+ &nbsp;
+ %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
+ title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
+ = icon('question-circle')
+
- if issuable.is_a?(MergeRequest)
%hr
- - if @merge_request.new_record?
- .form-group
- = f.label :source_branch, class: 'control-label'
- .col-sm-10
- = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
+ - if @merge_request.new_record?
+ .form-group
+ = f.label :source_branch, class: 'control-label'
+ .col-sm-10
+ = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group
= f.label :target_branch, class: 'control-label'
.col-sm-10
@@ -114,7 +127,11 @@
for this project.
- if issuable.new_record?
- - cancel_project = issuable.source_project
+ = link_to 'Cancel', namespace_project_issues_path(@project.namespace, @project), class: 'btn btn-cancel'
- else
- - cancel_project = issuable.project
- = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel'
+ .pull-right
+ - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
+ = link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), method: :delete, class: 'btn btn-grouped' do
+ = icon('trash-o')
+ Delete
+ = link_to 'Cancel', namespace_project_issue_path(@project.namespace, @project, issuable), class: 'btn btn-grouped btn-cancel'
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index 85888096722..e1127b2311c 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -23,5 +23,5 @@
- if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
- class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
+ class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index aa5acee9c14..3c445f67236 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,5 +1,5 @@
.detail-page-header
- .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
+ .snippet-box.has-tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
= visibility_level_icon(@snippet.visibility_level, fw: false)
= visibility_level_label(@snippet.visibility_level)
%span.identifier
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 20d2d5f317b..02647229776 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,6 +1,6 @@
.awards.votes-block
- awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
- %button.btn.award-control.js-emoji-btn.has_tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}}
+ %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}}
= emoji_icon(emoji)
%span.award-control-text.js-counter
= notes.count
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 21d311579e3..f9e32337983 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -20,14 +20,15 @@ class RepositoryForkWorker
return
end
+ project.repository.after_import
+
unless project.valid_repo?
- logger.error("Project #{id} had an invalid repository after fork")
+ logger.error("Project #{project_id} had an invalid repository after fork")
project.update(import_error: "The forked repository is invalid.")
project.import_fail
return
end
- project.repository.after_import
project.import_finish
end
end
diff --git a/config/application.rb b/config/application.rb
index 2b103c4592d..5a0ac70aa2a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -49,6 +49,7 @@ module Gitlab
config.assets.paths << Gemojione.index.images_path
config.assets.precompile << "*.png"
config.assets.precompile << "print.css"
+ config.assets.precompile << "notify.css"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/config/initializers/premailer.rb b/config/initializers/premailer.rb
new file mode 100644
index 00000000000..a44316bc3a4
--- /dev/null
+++ b/config/initializers/premailer.rb
@@ -0,0 +1,7 @@
+# See https://github.com/fphilipe/premailer-rails#configuration
+Premailer::Rails.config.merge!(
+ generate_text_part: false,
+ preserve_styles: true,
+ remove_comments: true,
+ remove_ids: true
+)
diff --git a/config/routes.rb b/config/routes.rb
index 561987322b2..90d858d7fc1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -613,7 +613,7 @@ Rails.application.routes.draw do
end
end
- resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
+ resources :merge_requests, constraints: { id: /\d+/ } do
member do
get :commits
get :diffs
@@ -684,7 +684,7 @@ Rails.application.routes.draw do
end
end
- resources :issues, constraints: { id: /\d+/ }, except: [:destroy] do
+ resources :issues, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
end
diff --git a/db/migrate/20160225090018_add_delete_at_to_issues.rb b/db/migrate/20160225090018_add_delete_at_to_issues.rb
new file mode 100644
index 00000000000..3ddbef92978
--- /dev/null
+++ b/db/migrate/20160225090018_add_delete_at_to_issues.rb
@@ -0,0 +1,6 @@
+class AddDeleteAtToIssues < ActiveRecord::Migration
+ def change
+ add_column :issues, :deleted_at, :datetime
+ add_index :issues, :deleted_at
+ end
+end
diff --git a/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb b/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb
new file mode 100644
index 00000000000..9d09105f17d
--- /dev/null
+++ b/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb
@@ -0,0 +1,6 @@
+class AddDeleteAtToMergeRequests < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :deleted_at, :datetime
+ add_index :merge_requests, :deleted_at
+ end
+end
diff --git a/db/migrate/20160317092222_add_moved_to_to_issue.rb b/db/migrate/20160317092222_add_moved_to_to_issue.rb
new file mode 100644
index 00000000000..461e7fb3a9b
--- /dev/null
+++ b/db/migrate/20160317092222_add_moved_to_to_issue.rb
@@ -0,0 +1,5 @@
+class AddMovedToToIssue < ActiveRecord::Migration
+ def change
+ add_reference :issues, :moved_to, references: :issues
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8537e5729a7..bb3f0497539 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -416,7 +416,9 @@ ActiveRecord::Schema.define(version: 20160320204112) do
t.string "state"
t.integer "iid"
t.integer "updated_by_id"
- t.boolean "confidential", default: false
+ t.integer "moved_to_id"
+ t.boolean "confidential", default: false
+ t.datetime "deleted_at"
end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
@@ -424,6 +426,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do
add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree
add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
+ add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
@@ -546,12 +549,14 @@ ActiveRecord::Schema.define(version: 20160320204112) do
t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id"
t.string "merge_commit_sha"
+ t.datetime "deleted_at"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
+ add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree
add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 9e704648b25..18d64c41986 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -326,17 +326,25 @@ Example response:
}
```
-## Delete existing issue (**Deprecated**)
+## Delete an issue
-This call is deprecated and returns a `405 Method Not Allowed` error if called.
-An issue gets now closed and is done by calling
-`PUT /projects/:id/issues/:issue_id` with the parameter `state_event` set to
-`close`. See [edit issue](#edit-issue) for more details.
+Only for admins and project owners. Soft deletes the issue in question.
+If the operation is successful, a status code `200` is returned. In case you cannot
+destroy this issue, or it is not present, code `404` is given.
```
DELETE /projects/:id/issues/:issue_id
```
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85
+```
+
## Comments on issues
Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 5c527d55481..b20a6300b7a 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -380,6 +380,25 @@ Parameters:
If the operation is successful, 200 and the updated merge request is returned.
If an error occurs, an error number and a message explaining the reason is returned.
+## Delete a merge request
+
+Only for admins and project owners. Soft deletes the merge request in question.
+If the operation is successful, a status code `200` is returned. In case you cannot
+destroy this merge request, or it is not present, code `404` is given.
+
+```
+DELETE /projects/:id/merge_requests/:merge_request_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_id` | integer | yes | The ID of a project's merge request |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_request/85
+```
+
## Accept MR
Merge changes submitted with MR using this API.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index c567846f624..bffbc776500 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -348,7 +348,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout 0.6.5
+ sudo -u git -H git checkout v0.7.1
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md
index 7d63915af5e..712e9fdf93a 100644
--- a/doc/update/8.5-to-8.6.md
+++ b/doc/update/8.5-to-8.6.md
@@ -58,7 +58,7 @@ GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
-sudo -u git -H git checkout 0.6.5
+sudo -u git -H git checkout v0.7.1
sudo -u git -H make
```
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index ff21c7d1b83..de7e2b37725 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -160,6 +160,7 @@ Feature: Project Issues
Scenario: Issues on empty project
Given empty project "Empty Project"
+ And I have an ssh key
When I visit empty project page
And I see empty project details with ssh clone info
When I visit empty project's issues page
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 8a0e8fc2b6c..422b151eaa2 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -27,7 +27,7 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
step 'I click on HTTP' do
find('#clone-dropdown').click
- find('#http-selector').click
+ find('.http-selector').click
end
step 'Remote url should update to http link' do
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
step 'If I click on SSH' do
find('#clone-dropdown').click
- find('#ssh-selector').click
+ find('.ssh-selector').click
end
step 'Remote url should update to ssh link' do
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 8c31fa890b2..aff5ca676be 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -5,6 +5,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
include SharedNote
include SharedPaths
include SharedMarkdown
+ include SharedUser
step 'I should see "Release 0.4" in issues' do
expect(page).to have_content "Release 0.4"
@@ -240,7 +241,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'empty project "Empty Project"' do
- create :empty_project, name: 'Empty Project', namespace: @user.namespace
+ create :project_empty_repo, name: 'Empty Project', namespace: @user.namespace
end
When 'I visit empty project page' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a72044e8058..4921ae99e78 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -118,9 +118,7 @@ module API
end
def authorize!(action, subject)
- unless abilities.allowed?(current_user, action, subject)
- forbidden!
- end
+ forbidden! unless abilities.allowed?(current_user, action, subject)
end
def authorize_push_project
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index fda6f841438..e5ae88eb96f 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -191,7 +191,7 @@ module API
end
end
- # Delete a project issue (deprecated)
+ # Delete a project issue
#
# Parameters:
# id (required) - The ID of a project
@@ -199,7 +199,10 @@ module API
# Example Request:
# DELETE /projects/:id/issues/:issue_id
delete ":id/issues/:issue_id" do
- not_allowed!
+ issue = user_project.issues.find_by(id: params[:issue_id])
+
+ authorize!(:destroy_issue, issue)
+ issue.destroy
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index c5e5d57ed4d..93052fba06b 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -100,6 +100,18 @@ module API
end
end
+ # Delete a MR
+ #
+ # Parameters:
+ # id (required) - The ID of the project
+ # merge_request_id (required) - The MR id
+ delete ":id/merge_requests/:merge_request_id" do
+ merge_request = user_project.merge_requests.find_by(id: params[:merge_request_id])
+
+ authorize!(:destroy_merge_request, merge_request)
+ merge_request.destroy
+ end
+
# Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0
# Use "merge_requests/:merge_request_id/..." instead.
#
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 3637b1bac94..132f0a4bd93 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -47,6 +47,7 @@ module Banzai
# Returns a String
def data_attribute(attributes = {})
attributes[:reference_filter] = self.class.name.demodulize
+ attributes.delete(:original) if context[:no_original_data]
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ")
end
diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb
new file mode 100644
index 00000000000..a1c6ee7bd69
--- /dev/null
+++ b/lib/gitlab/gfm/reference_rewriter.rb
@@ -0,0 +1,79 @@
+module Gitlab
+ module Gfm
+ ##
+ # Class that unfolds local references in text.
+ #
+ # The initializer takes text in Markdown and project this text is valid
+ # in context of.
+ #
+ # `unfold` method tries to find all local references and unfold each of
+ # those local references to cross reference format, assuming that the
+ # argument passed to this method is a project that references will be
+ # viewed from (see `Referable#to_reference method).
+ #
+ # Examples:
+ #
+ # 'Hello, this issue is related to #123 and
+ # other issues labeled with ~"label"', will be converted to:
+ #
+ # 'Hello, this issue is related to gitlab-org/gitlab-ce#123 and
+ # other issue labeled with gitlab-org/gitlab-ce~"label"'.
+ #
+ # It does respect markdown lexical rules, so text in code block will not be
+ # replaced, see another example:
+ #
+ # 'Merge request for issue #1234, see also link:
+ # http://gitlab.com/some/link/#1234, and code `puts #1234`' =>
+ #
+ # 'Merge request for issue gitlab-org/gitlab-ce#1234, se also link:
+ # http://gitlab.com/some/link/#1234, and code `puts #1234`'
+ #
+ class ReferenceRewriter
+ def initialize(text, source_project, current_user)
+ @text = text
+ @source_project = source_project
+ @current_user = current_user
+ @original_html = markdown(text)
+ end
+
+ def rewrite(target_project)
+ pattern = Gitlab::ReferenceExtractor.references_pattern
+
+ @text.gsub(pattern) do |reference|
+ unfold_reference(reference, Regexp.last_match, target_project)
+ end
+ end
+
+ private
+
+ def unfold_reference(reference, match, target_project)
+ before = @text[0...match.begin(0)]
+ after = @text[match.end(0)..-1]
+
+ referable = find_referable(reference)
+ return reference unless referable
+
+ cross_reference = referable.to_reference(target_project)
+ return reference if reference == cross_reference
+
+ new_text = before + cross_reference + after
+ substitution_valid?(new_text) ? cross_reference : reference
+ end
+
+ def find_referable(reference)
+ extractor = Gitlab::ReferenceExtractor.new(@source_project,
+ @current_user)
+ extractor.analyze(reference)
+ extractor.all.first
+ end
+
+ def substitution_valid?(substituted)
+ @original_html == markdown(substituted)
+ end
+
+ def markdown(text)
+ Banzai.render(text, project: @source_project, no_original_data: true)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 4d830aa45e1..13c4d64c99b 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,6 +1,7 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
+ REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range)
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil, author = nil)
@@ -17,7 +18,7 @@ module Gitlab
super(text, context.merge(project: project))
end
- %i(user label milestone merge_request snippet commit commit_range).each do |type|
+ REFERABLES.each do |type|
define_method("#{type}s") do
@references[type] ||= references(type, reference_context)
end
@@ -31,6 +32,21 @@ module Gitlab
end
end
+ def all
+ REFERABLES.each { |referable| send(referable.to_s.pluralize) }
+ @references.values.flatten
+ end
+
+ def self.references_pattern
+ return @pattern if @pattern
+
+ patterns = REFERABLES.map do |ref|
+ ref.to_s.classify.constantize.try(:reference_pattern)
+ end
+
+ @pattern = Regexp.union(patterns.compact)
+ end
+
private
def reference_context
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 2cd81231144..d6e4cd71ce6 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1,11 +1,11 @@
require('spec_helper')
describe Projects::IssuesController do
- describe "GET #index" do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:issue) { create(:issue, project: project) }
+ let(:project) { create(:project_empty_repo) }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ describe "GET #index" do
before do
sign_in(user)
project.team << [user, :developer]
@@ -41,7 +41,7 @@ describe Projects::IssuesController do
end
describe 'Confidential Issues' do
- let(:project) { create(:empty_project, :public) }
+ let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
let(:author) { create(:user) }
let(:non_member) { create(:user) }
@@ -186,4 +186,29 @@ describe Projects::IssuesController do
end
end
end
+
+ describe "DELETE #destroy" do
+ context "when the user is a developer" do
+ before { sign_in(user) }
+ it "rejects a developer to destroy an issue" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "when the user is owner" do
+ let(:owner) { create(:user) }
+ let(:namespace) { create(:namespace, owner: owner) }
+ let(:project) { create(:project, namespace: namespace) }
+
+ before { sign_in(owner) }
+
+ it "deletes the issue" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
+
+ expect(response.status).to eq(302)
+ expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index e82fe26c7a6..c5b034dc064 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -157,6 +157,29 @@ describe Projects::MergeRequestsController do
end
end
+ describe "DELETE #destroy" do
+ it "denies access to users unless they're admin or project owner" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+
+ expect(response.status).to eq(404)
+ end
+
+ context "when the user is owner" do
+ let(:owner) { create(:user) }
+ let(:namespace) { create(:namespace, owner: owner) }
+ let(:project) { create(:project, namespace: namespace) }
+
+ before { sign_in owner }
+
+ it "deletes the merge request" do
+ delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
+
+ expect(response.status).to eq(302)
+ expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
+ end
+ end
+ end
+
describe 'GET diffs' do
def go(format: 'html')
get :diffs,
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
new file mode 100644
index 00000000000..6fda0c31866
--- /dev/null
+++ b/spec/features/issues/move_spec.rb
@@ -0,0 +1,87 @@
+require 'rails_helper'
+
+feature 'issue move to another project' do
+ let(:user) { create(:user) }
+ let(:old_project) { create(:project) }
+ let(:text) { 'Some issue description' }
+
+ let(:issue) do
+ create(:issue, description: text, project: old_project, author: user)
+ end
+
+ background { login_as(user) }
+
+ context 'user does not have permission to move issue' do
+ background do
+ old_project.team << [user, :guest]
+
+ edit_issue(issue)
+ end
+
+ scenario 'moving issue to another project not allowed' do
+ expect(page).to have_no_select('move_to_project_id')
+ end
+ end
+
+ context 'user has permission to move issue' do
+ let!(:mr) { create(:merge_request, source_project: old_project) }
+ let(:new_project) { create(:project) }
+ let(:text) { 'Text with !1' }
+ let(:cross_reference) { old_project.to_reference }
+
+ background do
+ old_project.team << [user, :reporter]
+ new_project.team << [user, :reporter]
+
+ edit_issue(issue)
+ end
+
+ scenario 'moving issue to another project' do
+ select(new_project.name_with_namespace, from: 'move_to_project_id')
+ click_button('Save changes')
+
+ expect(current_url).to include project_path(new_project)
+
+ page.within('.issue') do
+ expect(page).to have_content("Text with #{cross_reference}!1")
+ expect(page).to have_content("Moved from #{cross_reference}#1")
+ expect(page).to have_content(issue.title)
+ end
+ end
+
+ context 'projects user does not have permission to move issue to exist' do
+ let!(:private_project) { create(:project, :private) }
+ let(:another_project) { create(:project) }
+ background { another_project.team << [user, :guest] }
+
+ scenario 'browsing projects in projects select' do
+ options = [ '', 'No project', new_project.name_with_namespace ]
+ expect(page).to have_select('move_to_project_id', options: options)
+ end
+ end
+
+ context 'issue has been already moved' do
+ let(:new_issue) { create(:issue, project: new_project) }
+ let(:issue) do
+ create(:issue, project: old_project, author: user, moved_to: new_issue)
+ end
+
+ scenario 'user wants to move issue that has already been moved' do
+ expect(page).to have_no_select('move_to_project_id')
+ end
+ end
+ end
+
+ def edit_issue(issue)
+ visit issue_path(issue)
+ page.within('.issuable-header') { click_link 'Edit' }
+ end
+
+ def issue_path(issue)
+ namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ end
+
+ def project_path(project)
+ namespace_project_path(new_project.namespace, new_project)
+ end
+end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index eca8bc8ab2d..39042ff7e91 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -11,7 +11,7 @@ describe LabelsHelper do
end
it 'uses the instance variable' do
- expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}"><span class="[\w\s\-]*has_tooltip".*</span></a>}
+ expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}"><span class="[\w\s\-]*has-tooltip".*</span></a>}
end
end
@@ -41,8 +41,8 @@ describe LabelsHelper do
context 'with a tooltip argument' do
context 'set to false' do
- it 'does not include the has_tooltip class' do
- expect(link_to_label(label, tooltip: false)).not_to match %r{has_tooltip}
+ it 'does not include the has-tooltip class' do
+ expect(link_to_label(label, tooltip: false)).not_to match %r{has-tooltip}
end
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index dc324f1e60e..c258cfebd73 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -86,4 +86,23 @@ describe ProjectsHelper do
end
end
end
+
+ describe 'default_clone_protocol' do
+ describe 'using HTTP' do
+ it 'returns HTTP' do
+ expect(helper).to receive(:current_user).and_return(nil)
+
+ expect(helper.send(:default_clone_protocol)).to eq('http')
+ end
+ end
+
+ describe 'using HTTPS' do
+ it 'returns HTTPS' do
+ allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https')
+ expect(helper).to receive(:current_user).and_return(nil)
+
+ expect(helper.send(:default_clone_protocol)).to eq('https')
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 4c1d4a2d24c..94468abcbb3 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -56,7 +56,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
describe 'label span element' do
it 'includes default classes' do
doc = reference_filter("Label #{reference}")
- expect(doc.css('a span').first.attr('class')).to eq 'label color-label has_tooltip'
+ expect(doc.css('a span').first.attr('class')).to eq 'label color-label has-tooltip'
end
it 'includes a style attribute' do
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
new file mode 100644
index 00000000000..0a7ca3ec848
--- /dev/null
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Gitlab::Gfm::ReferenceRewriter do
+ let(:text) { 'some text' }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before { old_project.team << [user, :guest] }
+
+ describe '#rewrite' do
+ subject do
+ described_class.new(text, old_project, user).rewrite(new_project)
+ end
+
+ context 'multiple issues and merge requests referenced' do
+ let!(:issue_first) { create(:issue, project: old_project) }
+ let!(:issue_second) { create(:issue, project: old_project) }
+ let!(:merge_request) { create(:merge_request, source_project: old_project) }
+
+ context 'plain text description' do
+ let(:text) { 'Description that references #1, #2 and !1' }
+
+ it { is_expected.to include issue_first.to_reference(new_project) }
+ it { is_expected.to include issue_second.to_reference(new_project) }
+ it { is_expected.to include merge_request.to_reference(new_project) }
+ end
+
+ context 'description with ignored elements' do
+ let(:text) do
+ "Hi. This references #1, but not `#2`\n" +
+ '<pre>and not !1</pre>'
+ end
+
+ it { is_expected.to include issue_first.to_reference(new_project) }
+ it { is_expected.to_not include issue_second.to_reference(new_project) }
+ it { is_expected.to_not include merge_request.to_reference(new_project) }
+ end
+
+ context 'description ambigous elements' do
+ context 'url' do
+ let(:url) { 'http://gitlab.com/#1' }
+ let(:text) { "This references #1, but not #{url}" }
+
+ it { is_expected.to include url }
+ end
+
+ context 'code' do
+ let(:text) { "#1, but not `[#1]`" }
+ it { is_expected.to eq "#{issue_first.to_reference(new_project)}, but not `[#1]`" }
+ end
+
+ context 'code reverse' do
+ let(:text) { "not `#1`, but #1" }
+ it { is_expected.to eq "not `#1`, but #{issue_first.to_reference(new_project)}" }
+ end
+
+ context 'code in random order' do
+ let(:text) { "#1, `#1`, #1, `#1`" }
+ let(:ref) { issue_first.to_reference(new_project) }
+
+ it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" }
+ end
+
+ context 'description with labels' do
+ let!(:label) { create(:label, id: 123, name: 'test', project: old_project) }
+ let(:project_ref) { old_project.to_reference }
+
+ context 'label referenced by id' do
+ let(:text) { '#1 and ~123' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} }
+ end
+
+ context 'label referenced by text' do
+ let(:text) { '#1 and ~"test"' }
+ it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} }
+ end
+ end
+ end
+
+ context 'reference contains milestone' do
+ let(:milestone) { create(:milestone) }
+ let(:text) { "milestone ref: #{milestone.to_reference}" }
+
+ it { is_expected.to eq text }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 65af37e24f1..7c617723e6d 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -124,4 +124,24 @@ describe Gitlab::ReferenceExtractor, lib: true do
expect(extracted).to match_array([issue])
end
end
+
+ describe '#all' do
+ let(:issue) { create(:issue, project: project) }
+ let(:label) { create(:label, project: project) }
+ let(:text) { "Ref. #{issue.to_reference} and #{label.to_reference}" }
+
+ before do
+ project.team << [project.creator, :developer]
+ subject.analyze(text)
+ end
+
+ it 'returns all referables' do
+ expect(subject.all).to match_array([issue, label])
+ end
+ end
+
+ describe '.references_pattern' do
+ subject { described_class.references_pattern }
+ it { is_expected.to be_kind_of Regexp }
+ end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index f910424d85b..9b47acfe0cd 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -158,6 +158,33 @@ describe Notify do
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
+
+ describe 'moved to another project' do
+ let(:new_issue) { create(:issue) }
+ subject { Notify.issue_moved_email(recipient, issue, new_issue, current_user) }
+
+ it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
+
+ it 'contains description about action taken' do
+ is_expected.to have_body_text 'Issue was moved to another project'
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i
+ end
+
+ it 'contains link to new issue' do
+ new_issue_url = namespace_project_issue_path(new_issue.project.namespace,
+ new_issue.project, new_issue)
+ is_expected.to have_body_text new_issue_url
+ end
+
+ it 'contains a link to the original issue' do
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ end
+ end
end
context 'for merge requests' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 540a62eb1f8..3c34b1d397f 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -37,6 +37,11 @@ describe Issue, models: true do
subject { create(:issue) }
+ describe "act_as_paranoid" do
+ it { is_expected.to have_db_column(:deleted_at) }
+ it { is_expected.to have_db_index(:deleted_at) }
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "##{subject.iid}"
@@ -130,12 +135,62 @@ describe Issue, models: true do
end
end
+ describe '#can_move?' do
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue) }
+ subject { issue.can_move?(user) }
+
+ context 'user is not a member of project issue belongs to' do
+ it { is_expected.to eq false}
+ end
+
+ context 'user is reporter in project issue belongs to' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ before { project.team << [user, :reporter] }
+
+ it { is_expected.to eq true }
+
+ context 'checking destination project also' do
+ subject { issue.can_move?(user, to_project) }
+ let(:to_project) { create(:project) }
+
+ context 'destination project allowed' do
+ before { to_project.team << [user, :reporter] }
+ it { is_expected.to eq true }
+ end
+
+ context 'destination project not allowed' do
+ before { to_project.team << [user, :guest] }
+ it { is_expected.to eq false }
+ end
+ end
+ end
+ end
+
+ describe '#moved?' do
+ let(:issue) { create(:issue) }
+ subject { issue.moved? }
+
+ context 'issue not moved' do
+ it { is_expected.to eq false }
+ end
+
+ context 'issue already moved' do
+ let(:moved_to_issue) { create(:issue) }
+ let(:issue) { create(:issue, moved_to: moved_to_issue) }
+
+ it { is_expected.to eq true }
+ end
+ end
+
describe '#related_branches' do
it "selects the right branches" do
allow(subject.project.repository).to receive(:branch_names).
- and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name])
+ and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name])
- expect(subject.related_branches).to eq [subject.to_branch_name]
+ expect(subject.related_branches).to eq([subject.to_branch_name])
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f2f07e4ee17..bd0a4ebe337 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -49,6 +49,11 @@ describe MergeRequest, models: true do
it { is_expected.to include_module(Taskable) }
end
+ describe "act_as_paranoid" do
+ it { is_expected.to have_db_column(:deleted_at) }
+ it { is_expected.to have_db_index(:deleted_at) }
+ end
+
describe 'validation' do
it { is_expected.to validate_presence_of(:target_branch) }
it { is_expected.to validate_presence_of(:source_branch) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index dba7ffc8565..757324184bd 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -732,4 +732,45 @@ describe Project, models: true do
it { expect(internal_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
end
end
+
+ describe '#create_repository' do
+ let(:project) { create(:project) }
+ let(:shell) { Gitlab::Shell.new }
+
+ before do
+ allow(project).to receive(:gitlab_shell).and_return(shell)
+ end
+
+ context 'using a regular repository' do
+ it 'creates the repository' do
+ expect(shell).to receive(:add_repository).
+ with(project.path_with_namespace).
+ and_return(true)
+
+ expect(project.repository).to receive(:after_create)
+
+ expect(project.create_repository).to eq(true)
+ end
+
+ it 'adds an error if the repository could not be created' do
+ expect(shell).to receive(:add_repository).
+ with(project.path_with_namespace).
+ and_return(false)
+
+ expect(project.repository).not_to receive(:after_create)
+
+ expect(project.create_repository).to eq(false)
+ expect(project.errors).not_to be_empty
+ end
+ end
+
+ context 'using a forked repository' do
+ it 'does nothing' do
+ expect(project).to receive(:forked?).and_return(true)
+ expect(shell).not_to receive(:add_repository)
+
+ project.create_repository
+ end
+ end
+ end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index a2085df5bcd..532e3f013fd 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -244,6 +244,18 @@ describe ProjectWiki, models: true do
end
end
+ describe '#create_repo!' do
+ it 'creates a repository' do
+ expect(subject).to receive(:init_repo).
+ with(subject.path_with_namespace).
+ and_return(true)
+
+ expect(subject.repository).to receive(:after_create)
+
+ expect(subject.create_repo!).to be_an_instance_of(Gollum::Wiki)
+ end
+ end
+
private
def create_temp_repo(path)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index a57229a4fdf..7eac70ae948 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -537,6 +537,12 @@ describe Repository, models: true do
repository.before_delete
end
+
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.before_delete
+ end
end
describe 'when a repository exists' do
@@ -593,6 +599,12 @@ describe Repository, models: true do
repository.after_import
end
+
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.after_import
+ end
end
describe '#after_push_commit' do
@@ -619,6 +631,14 @@ describe Repository, models: true do
end
end
+ describe '#after_create' do
+ it 'flushes the exists cache' do
+ expect(repository).to receive(:expire_exists_cache)
+
+ repository.after_create
+ end
+ end
+
describe "#main_language" do
it 'shows the main language of the project' do
expect(repository.main_language).to eq("Ruby")
@@ -781,6 +801,16 @@ describe Repository, models: true do
end
end
+ describe '#expire_exists_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:exists?)
+
+ repository.expire_exists_cache
+ end
+ end
+
describe '#build_cache' do
let(:cache) { repository.send(:cache) }
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index bb2ab058003..ce55cb7b0ae 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -2,12 +2,12 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
- let(:user) { create(:user) }
- let(:non_member) { create(:user) }
- let(:author) { create(:author) }
- let(:assignee) { create(:assignee) }
- let(:admin) { create(:admin) }
- let!(:project) { create(:project, :public, namespace: user.namespace ) }
+ let(:user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:assignee) }
+ let(:admin) { create(:user, :admin) }
+ let!(:project) { create(:project, :public, namespace: user.namespace ) }
let!(:closed_issue) do
create :closed_issue,
author: user,
@@ -469,9 +469,25 @@ describe API::API, api: true do
end
describe "DELETE /projects/:id/issues/:issue_id" do
- it "should delete a project issue" do
- delete api("/projects/#{project.id}/issues/#{issue.id}", user)
- expect(response.status).to eq(405)
+ it "rejects a non member from deleting an issue" do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", non_member)
+ expect(response.status).to be(403)
+ end
+
+ it "rejects a developer from deleting an issue" do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", author)
+ expect(response.status).to be(403)
+ end
+
+ context "when the user is project owner" do
+ let(:owner) { create(:user) }
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ it "deletes the issue if an admin requests it" do
+ delete api("/projects/#{project.id}/issues/#{issue.id}", owner)
+ expect(response.status).to eq(200)
+ expect(json_response['state']).to eq 'opened'
+ end
end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 4fd1df25568..c9175a4d6eb 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -2,15 +2,17 @@ require "spec_helper"
describe API::API, api: true do
include ApiHelpers
- let(:base_time) { Time.now }
- let(:user) { create(:user) }
- let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
+ let(:base_time) { Time.now }
+ let(:user) { create(:user) }
+ let(:admin) { create(:user, :admin) }
+ let(:non_member) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
- let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
- let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
- let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
+ let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
before do
project.team << [user, :reporters]
@@ -315,6 +317,29 @@ describe API::API, api: true do
end
end
+ describe "DELETE /projects/:id/merge_requests/:merge_request_id" do
+ context "when the user is developer" do
+ let(:developer) { create(:user) }
+
+ before do
+ project.team << [developer, :developer]
+ end
+
+ it "denies the deletion of the merge request" do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer)
+ expect(response.status).to be(403)
+ end
+ end
+
+ context "when the user is project owner" do
+ it "destroys the merge request owners can destroy" do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
new file mode 100644
index 00000000000..14cc20e529a
--- /dev/null
+++ b/spec/services/issues/move_service_spec.rb
@@ -0,0 +1,213 @@
+require 'spec_helper'
+
+describe Issues::MoveService, services: true do
+ let(:user) { create(:user) }
+ let(:author) { create(:user) }
+ let(:title) { 'Some issue' }
+ let(:description) { 'Some issue description' }
+ let(:old_project) { create(:project) }
+ let(:new_project) { create(:project) }
+
+ let(:old_issue) do
+ create(:issue, title: title, description: description,
+ project: old_project, author: author)
+ end
+
+ let(:move_service) do
+ described_class.new(old_project, user)
+ end
+
+ shared_context 'user can move issue' do
+ before do
+ old_project.team << [user, :reporter]
+ new_project.team << [user, :reporter]
+ end
+ end
+
+ describe '#execute' do
+ shared_context 'issue move executed' do
+ let!(:new_issue) { move_service.execute(old_issue, new_project) }
+ end
+
+ context 'issue movable' do
+ include_context 'user can move issue'
+
+ context 'generic issue' do
+ include_context 'issue move executed'
+
+ it 'creates a new issue in a new project' do
+ expect(new_issue.project).to eq new_project
+ end
+
+ it 'rewrites issue title' do
+ expect(new_issue.title).to eq title
+ end
+
+ it 'rewrites issue description' do
+ expect(new_issue.description).to eq description
+ end
+
+ it 'adds system note to old issue at the end' do
+ expect(old_issue.notes.last.note).to match /^Moved to/
+ end
+
+ it 'adds system note to new issue at the end' do
+ expect(new_issue.notes.last.note).to match /^Moved from/
+ end
+
+ it 'closes old issue' do
+ expect(old_issue.closed?).to be true
+ end
+
+ it 'persists new issue' do
+ expect(new_issue.persisted?).to be true
+ end
+
+ it 'persists all changes' do
+ expect(old_issue.changed?).to be false
+ expect(new_issue.changed?).to be false
+ end
+
+ it 'preserves author' do
+ expect(new_issue.author).to eq author
+ end
+
+ it 'removes data that is invalid in new context' do
+ expect(new_issue.milestone).to be_nil
+ expect(new_issue.labels).to be_empty
+ end
+
+ it 'creates a new internal id for issue' do
+ expect(new_issue.iid).to be 1
+ end
+
+ it 'marks issue as moved' do
+ expect(old_issue.moved?).to eq true
+ expect(old_issue.moved_to).to eq new_issue
+ end
+ end
+
+ context 'issue with notes' do
+ context 'notes without references' do
+ let(:notes_params) do
+ [{ system: false, note: 'Some comment 1' },
+ { system: true, note: 'Some system note' },
+ { system: false, note: 'Some comment 2' }]
+ end
+
+ let(:notes_contents) { notes_params.map { |n| n[:note] } }
+
+ before do
+ note_params = { noteable: old_issue, project: old_project, author: author }
+ notes_params.each do |note|
+ create(:note, note_params.merge(note))
+ end
+ end
+
+ include_context 'issue move executed'
+
+ let(:all_notes) { new_issue.notes.order('id ASC') }
+ let(:system_notes) { all_notes.system }
+ let(:user_notes) { all_notes.user }
+
+ it 'rewrites existing notes in valid order' do
+ expect(all_notes.pluck(:note).first(3)).to eq notes_contents
+ end
+
+ it 'adds a system note about move after rewritten notes' do
+ expect(system_notes.last.note).to match /^Moved from/
+ end
+
+ it 'preserves orignal author of comment' do
+ expect(user_notes.pluck(:author_id)).to all(eq(author.id))
+ end
+
+ it 'preserves time when note has been created at' do
+ expect(old_issue.notes.first.created_at)
+ .to eq new_issue.notes.first.created_at
+ end
+ end
+
+ context 'notes with references' do
+ before do
+ create(:merge_request, source_project: old_project)
+ create(:note, noteable: old_issue, project: old_project, author: author,
+ note: 'Note with reference to merge request !1')
+ end
+
+ include_context 'issue move executed'
+ let(:new_note) { new_issue.notes.first }
+
+ it 'rewrites references using a cross reference to old project' do
+ expect(new_note.note)
+ .to eq "Note with reference to merge request #{old_project.to_reference}!1"
+ end
+ end
+ end
+
+ describe 'rewritting references' do
+ include_context 'issue move executed'
+
+ context 'issue reference' do
+ let(:another_issue) { create(:issue, project: old_project) }
+ let(:description) { "Some description #{another_issue.to_reference}" }
+
+ it 'rewrites referenced issues creating cross project reference' do
+ expect(new_issue.description)
+ .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}"
+ end
+ end
+ end
+
+ context 'moving to same project' do
+ let(:new_project) { old_project }
+
+ it 'raises error' do
+ expect { move_service.execute(old_issue, new_project) }
+ .to raise_error(StandardError, /Cannot move issue/)
+ end
+ end
+ end
+
+ describe 'move permissions' do
+ let(:move) { move_service.execute(old_issue, new_project) }
+
+ context 'user is reporter in both projects' do
+ include_context 'user can move issue'
+ it { expect { move }.to_not raise_error }
+ end
+
+ context 'user is reporter only in new project' do
+ before { new_project.team << [user, :reporter] }
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter only in old project' do
+ before { old_project.team << [user, :reporter] }
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'user is reporter in one project and guest in another' do
+ before do
+ new_project.team << [user, :guest]
+ old_project.team << [user, :reporter]
+ end
+
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+
+ context 'issue has already been moved' do
+ include_context 'user can move issue'
+
+ let(:moved_to_issue) { create(:issue) }
+
+ let(:old_issue) do
+ create(:issue, project: old_project, author: author,
+ moved_to: moved_to_issue)
+ end
+
+ it { expect { move }.to raise_error(StandardError, /permissions/) }
+ end
+ end
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 8e6292014d4..240eae10052 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -453,6 +453,59 @@ describe SystemNoteService, services: true do
end
end
+ describe '.noteable_moved' do
+ let(:new_project) { create(:project) }
+ let(:new_noteable) { create(:issue, project: new_project) }
+
+ subject do
+ described_class.noteable_moved(noteable, project, new_noteable, author, direction: direction)
+ end
+
+ shared_examples 'cross project mentionable' do
+ include GitlabMarkdownHelper
+
+ it 'should contain cross reference to new noteable' do
+ expect(subject.note).to include cross_project_reference(new_project, new_noteable)
+ end
+
+ it 'should mention referenced noteable' do
+ expect(subject.note).to include new_noteable.to_reference
+ end
+
+ it 'should mention referenced project' do
+ expect(subject.note).to include new_project.to_reference
+ end
+ end
+
+ context 'moved to' do
+ let(:direction) { :to }
+
+ it_behaves_like 'cross project mentionable'
+
+ it 'should notify about noteable being moved to' do
+ expect(subject.note).to match /Moved to/
+ end
+ end
+
+ context 'moved from' do
+ let(:direction) { :from }
+
+ it_behaves_like 'cross project mentionable'
+
+ it 'should notify about noteable being moved from' do
+ expect(subject.note).to match /Moved from/
+ end
+ end
+
+ context 'invalid direction' do
+ let(:direction) { :invalid }
+
+ it 'should raise error' do
+ expect { subject }.to raise_error StandardError, /Invalid direction/
+ end
+ end
+ end
+
include JiraServiceHelper
describe 'JIRA integration' do
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 172537474ee..4ef05eb29d2 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -3,12 +3,17 @@ require 'spec_helper'
describe RepositoryForkWorker do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:shell) { Gitlab::Shell.new }
subject { RepositoryForkWorker.new }
+ before do
+ allow(subject).to receive(:gitlab_shell).and_return(shell)
+ end
+
describe "#perform" do
it "creates a new repository from a fork" do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with(
+ expect(shell).to receive(:fork_repository).with(
project.path_with_namespace,
fork_project.namespace.path
).and_return(true)
@@ -19,20 +24,26 @@ describe RepositoryForkWorker do
fork_project.namespace.path)
end
- it 'flushes the empty caches' do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).
+ it 'flushes various caches' do
+ expect(shell).to receive(:fork_repository).
with(project.path_with_namespace, fork_project.namespace.path).
and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
and_call_original
+ expect_any_instance_of(Repository).to receive(:expire_exists_cache).
+ and_call_original
+
subject.perform(project.id, project.path_with_namespace,
fork_project.namespace.path)
end
it "handles bad fork" do
- expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
+ expect(shell).to receive(:fork_repository).and_return(false)
+
+ expect(subject.logger).to receive(:error)
+
subject.perform(
project.id,
project.path_with_namespace,