diff options
author | Zeger-Jan van de Weg <zegerjan@gitlab.com> | 2016-03-21 19:32:37 +0100 |
---|---|---|
committer | Zeger-Jan van de Weg <zegerjan@gitlab.com> | 2016-03-21 19:32:37 +0100 |
commit | 6e5461c6ee8b408e35324b32c0b5ba99328ec763 (patch) | |
tree | f233cbfad4cd0773e7d0b30f057fed0076fee91d | |
parent | d28a587e82836d28524339586e1b6c1546a4bff5 (diff) | |
parent | 2bcbc7c6db934d56448c4c261861e62982b9b573 (diff) | |
download | gitlab-ce-6e5461c6ee8b408e35324b32c0b5ba99328ec763.tar.gz |
Merge branch 'master' into 2489-soft-delete-issues
100 files changed, 1708 insertions, 647 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 78418fd1b94..23d69e01ae5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,12 @@ 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 + - 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 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 @@ -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 63ed9441c62..284964e7ec8 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..7f983325049 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 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/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/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/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/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/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/projects.scss b/app/assets/stylesheets/pages/projects.scss index 82c5069638d..4caf11929f8 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; } } diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 668396a0f20..b2b817e64f9 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 ae1de06b983..4a6edafdeec 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -12,15 +12,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 1f55b18e0b1..de7d323907a 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/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 a9bf4321f73..2580f15c432 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 = Group.order_id_desc @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 06c5c8be9a5..5f9844104b8 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -44,7 +44,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 = @group.shared_projects 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 7c5a597cb6d..877b39c9b1b 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -34,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| @@ -91,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 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 e16acab696c..b830d777752 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -35,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]) 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 e10c633690f..169bef670aa 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/button_helper.rb b/app/helpers/button_helper.rb index d6c05843743..ee2cb6ab0ea 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 = '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 = '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/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/projects_helper.rb b/app/helpers/projects_helper.rb index 5473419ef24..322a7021fdd 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -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/concerns/issuable.rb b/app/models/concerns/issuable.rb index 31ab5682ad2..476e1ce7af0 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -211,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 5347d4fa1be..ddb51ad5775 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 :of_group, @@ -105,9 +108,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 @@ -137,6 +140,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 412c6c6732d..fd36dca0cad 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -876,6 +876,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/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/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 6086ad3661e..2e1a37aa06d 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -20,4 +20,4 @@ - next unless blob = render 'projects/diffs/file', i: index, project: project, - diff_file: diff_file, diff_commit: diff_commit, blob: blob + diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diff_refs diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index dc34032b1b8..3898bb202c5 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -53,6 +53,6 @@ = render "projects/diffs/text_file", diff_file: diff_file, index: i - elsif blob.image? - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file) - = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i + = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs - else .nothing-here-block No preview for this file type diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml index 752e92e2e6b..8367112a9cb 100644 --- a/app/views/projects/diffs/_image.html.haml +++ b/app/views/projects/diffs/_image.html.haml @@ -1,6 +1,7 @@ - diff = diff_file.diff - file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path)) -- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path)) +- old_commit_id = diff_refs.first.id +- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path)) - if diff.renamed_file || diff.new_file || diff.deleted_file .image %span.wrap @@ -12,7 +13,7 @@ %div.two-up.view %span.wrap .frame.deleted - %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))} + %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))} %img{src: old_file_raw_path} %p.image-info.hide %span.meta-filesize= "#{number_to_human_size old_file.size}" 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" ? " " : 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" ? " " : 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" ? " " : 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" ? " " : 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/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/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/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 3457c54bab9..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' }) + + %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 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/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 a4a17770cd6..692f8e0616a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160316204731) do +ActiveRecord::Schema.define(version: 20160317092222) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -416,6 +416,7 @@ ActiveRecord::Schema.define(version: 20160316204731) do t.string "state" t.integer "iid" t.integer "updated_by_id" + t.integer "moved_to_id" t.boolean "confidential", default: false t.datetime "deleted_at" end 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/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 53d27bb636d..d6e4cd71ce6 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1,7 +1,7 @@ require('spec_helper') describe Projects::IssuesController do - let(:project) { create(:project) } + let(:project) { create(:project_empty_repo) } let(:user) { create(:user) } let(:issue) { create(:issue, project: project) } @@ -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) } 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/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 53207767581..86cbd29830c 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -94,4 +94,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/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 5107e9a5030..3c34b1d397f 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -135,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/project_spec.rb b/spec/models/project_spec.rb index b8b9a455b83..624022c1dda 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -720,4 +720,45 @@ describe Project, models: true do expect(described_class.search_by_title('KITTENS')).to eq([project]) 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/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, |