diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-07-18 10:40:22 +0200 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-07-18 10:40:22 +0200 |
commit | ffea9c46000ade225852ee32e90cb8ad0f4f8316 (patch) | |
tree | c798d483a2ccab175d2341fc6e4828a5b5859d66 | |
parent | 22c2814b09960255d9ece0c620858500719858cb (diff) | |
parent | f1083ba1f6e4ff0c01d5e08b2a6c0b1f9b5a9b9a (diff) | |
download | gitlab-ce-ffea9c46000ade225852ee32e90cb8ad0f4f8316.tar.gz |
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into store-variables-and-when-in-builds-tablestore-variables-and-when-in-builds-table
# Conflicts:
# db/schema.rb
111 files changed, 1979 insertions, 1673 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 3aac8401848..db0bcfadcf4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,8 @@ require: - rubocop-rspec - ./rubocop/rubocop +inherit_from: .rubocop_todo.yml + AllCops: TargetRubyVersion: 2.1 # Cop names are not displayed in offense messages by default. Change behavior @@ -52,14 +54,6 @@ Style/AlignArray: Style/AlignHash: Enabled: true -# Align the parameters of a method call if they span more than one line. -Style/AlignParameters: - Enabled: false - -# Use &&/|| instead of and/or. -Style/AndOr: - Enabled: false - # Use `Array#join` instead of `Array#*`. Style/ArrayJoin: Enabled: true @@ -80,10 +74,6 @@ Style/Attr: Style/BeginBlock: Enabled: true -# Checks if usage of %() or %Q() matches configuration. -Style/BarePercentLiterals: - Enabled: false - # Do not use block comments. Style/BlockComments: Enabled: true @@ -97,14 +87,6 @@ Style/BlockEndNewline: Style/BlockDelimiters: Enabled: true -# Enforce braces style around hash parameters. -Style/BracesAroundHashParameters: - Enabled: false - -# Avoid explicit use of the case equality operator(===). -Style/CaseEquality: - Enabled: false - # Indentation of when in a case/when/[else/]end. Style/CaseIndentation: Enabled: true @@ -133,24 +115,10 @@ Style/ClassMethods: Style/ClassVars: Enabled: true -# Do not use :: for method call. -Style/ColonMethodCall: - Enabled: false - -# Checks formatting of special comments (TODO, FIXME, OPTIMIZE, HACK, REVIEW). -Style/CommentAnnotation: - Enabled: false - # Indentation of comments. Style/CommentIndentation: 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: Enabled: true @@ -159,34 +127,14 @@ Style/ConstantName: Style/DefWithParentheses: Enabled: true -# Checks for use of deprecated Hash methods. -Style/DeprecatedHashMethods: - Enabled: false - # Document classes and non-namespace modules. Style/Documentation: Enabled: false -# Checks the position of the dot in multi-line method calls. -Style/DotPosition: - Enabled: false - -# Checks for uses of double negation (!!). -Style/DoubleNegation: - Enabled: false - -# Prefer `each_with_object` over `inject` or `reduce`. -Style/EachWithObject: - Enabled: false - # Align elses and elsifs correctly. Style/ElseAlignment: Enabled: true -# Avoid empty else-clauses. -Style/EmptyElse: - Enabled: false - # Use empty lines between defs. Style/EmptyLineBetweenDefs: Enabled: false @@ -215,10 +163,6 @@ Style/EmptyLinesAroundModuleBody: Style/EmptyLinesAroundMethodBody: Enabled: false -# Prefer literals to Array.new/Hash.new/String.new. -Style/EmptyLiteral: - Enabled: false - # Avoid the use of END blocks. Style/EndBlock: Enabled: true @@ -231,10 +175,6 @@ Style/EndOfLine: Style/EvenOdd: Enabled: true -# Do not use unnecessary spacing. -Style/ExtraSpacing: - Enabled: false - # Use snake_case for source file names. Style/FileName: Enabled: true @@ -252,31 +192,15 @@ Style/FlipFlop: Style/For: Enabled: true -# Enforce the use of Kernel#sprintf, Kernel#format or String#%. -Style/FormatString: - Enabled: false - # Do not introduce global variables. Style/GlobalVars: Enabled: true -# Check for conditionals that can be replaced with guard clauses. -Style/GuardClause: - Enabled: false - # Prefer Ruby 1.9 hash syntax `{ a: 1, b: 2 }` # over 1.8 syntax `{ :a => 1, :b => 2 }`. Style/HashSyntax: 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: - Enabled: false - # Do not use if x; .... Use the ternary operator instead. Style/IfWithSemicolon: Enabled: true @@ -299,22 +223,10 @@ Style/IndentationConsistency: Style/IndentationWidth: Enabled: true -# Checks the indentation of the first element in an array literal. -Style/IndentArray: - Enabled: false - -# Checks the indentation of the first key in a hash literal. -Style/IndentHash: - Enabled: false - # Use Kernel#loop for infinite loops. Style/InfiniteLoop: Enabled: true -# Use the new lambda literal syntax for single-line blocks. -Style/Lambda: - Enabled: false - # Use lambda.call(...) instead of lambda.(...). Style/LambdaCall: Enabled: true @@ -323,14 +235,6 @@ Style/LambdaCall: Style/LeadingCommentSpace: Enabled: true -# Use \ instead of + or << to concatenate two string literals at line end. -Style/LineEndConcatenation: - Enabled: false - -# Do not use parentheses for method calls with no arguments. -Style/MethodCallParentheses: - Enabled: false - # Checks if the method definitions have or don't have parentheses. Style/MethodDefParentheses: Enabled: true @@ -387,39 +291,18 @@ Style/MultilineMethodDefinitionBraceLayout: Style/MultilineOperationIndentation: Enabled: false -# Avoid multi-line `? :` (the ternary operator), use if/unless instead. -Style/MultilineTernaryOperator: - 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: Enabled: true -# Favor until over while for negative conditions. -Style/NegatedWhile: - Enabled: false - # Avoid using nested modifiers. Style/NestedModifier: Enabled: true -# 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: Enabled: true -# Use `next` to skip iteration instead of a condition at the end. -Style/Next: - Enabled: false - # Prefer x.nil? to x == nil. Style/NilComparison: Enabled: true @@ -444,51 +327,10 @@ Style/OneLineConditional: Style/OpMethod: Enabled: true -# 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: - Enabled: false - # Don't use parentheses around the condition of an if/unless/while. Style/ParenthesesAroundCondition: Enabled: true -# Use `%`-literal delimiters consistently. -Style/PercentLiteralDelimiters: - Enabled: false - -# Checks if uses of %Q/%q match the configured preference. -Style/PercentQLiterals: - Enabled: false - -# Avoid Perl-style regex back references. -Style/PerlBackrefs: - Enabled: false - -# Check the names of predicate methods. -Style/PredicateName: - Enabled: false - -# Use proc instead of Proc.new. -Style/Proc: - Enabled: false - -# Checks the arguments passed to raise/fail. -Style/RaiseArgs: - Enabled: false - -# Don't use begin blocks when they are not needed. -Style/RedundantBegin: - Enabled: false - -# Checks for an obsolete RuntimeException argument in raise/fail. -Style/RedundantException: - Enabled: false - -# Checks usages of Object#freeze on immutable objects. -Style/RedundantFreeze: - Enabled: false - # Checks for parentheses that seem not to serve any purpose. Style/RedundantParentheses: Enabled: true @@ -497,24 +339,6 @@ Style/RedundantParentheses: Style/RedundantReturn: Enabled: true -# Don't use self where it's not needed. -Style/RedundantSelf: - 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: - Enabled: false - -# Avoid using rescue in its modifier form. -Style/RescueModifier: - Enabled: false - -# Checks for places where self-assignment shorthand should have been used. -Style/SelfAssignment: - Enabled: false - # Don't use semicolons to terminate expressions. Style/Semicolon: Enabled: true @@ -524,14 +348,6 @@ Style/SignalException: EnforcedStyle: only_raise Enabled: true -# Enforces the names of some block params. -Style/SingleLineBlockParams: - Enabled: false - -# Avoid single-line methods. -Style/SingleLineMethods: - Enabled: false - # Use spaces after colons. Style/SpaceAfterColon: Enabled: true @@ -553,11 +369,6 @@ Style/SpaceAfterNot: Style/SpaceAfterSemicolon: Enabled: true -# Checks that the equals signs in parameter default assignments have or don't -# have surrounding space depending on configuration. -Style/SpaceAroundEqualsInParameterDefault: - Enabled: false - # Use a space around keywords if appropriate. Style/SpaceAroundKeyword: Enabled: true @@ -566,10 +377,6 @@ Style/SpaceAroundKeyword: Style/SpaceAroundOperators: Enabled: true -# Checks that the left block brace has or doesn't have space before it. -Style/SpaceBeforeBlockBraces: - Enabled: false - # No spaces before commas. Style/SpaceBeforeComma: Enabled: true @@ -578,33 +385,14 @@ Style/SpaceBeforeComma: Style/SpaceBeforeComment: Enabled: true -# 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: true -# 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: - Enabled: false - # Use spaces inside hash literal braces - or don't. Style/SpaceInsideHashLiteralBraces: Enabled: true -# No spaces after ( or before ). -Style/SpaceInsideParens: - Enabled: false - # No spaces inside range literals. Style/SpaceInsideRangeLiteral: Enabled: true @@ -614,10 +402,6 @@ Style/SpaceInsideStringInterpolation: EnforcedStyle: no_space Enabled: true -# Avoid Perl-style global variables. -Style/SpecialGlobalVars: - Enabled: false - # Check for the usage of parentheses around stabby lambda arguments. Style/StabbyLambdaParentheses: EnforcedStyle: require_parentheses @@ -627,25 +411,12 @@ Style/StabbyLambdaParentheses: Style/StringLiterals: Enabled: false -# Checks if uses of quotes inside expressions in interpolated strings match the -# configured preference. -Style/StringLiteralsInInterpolation: - Enabled: false - # Checks if configured preferred methods are used over non-preferred. Style/StringMethods: PreferredMethods: intern: to_sym Enabled: true -# Use %i or %I for arrays of symbols. -Style/SymbolArray: - Enabled: false - -# Use symbols as procs instead of blocks when possible. -Style/SymbolProc: - Enabled: false - # No hard tabs. Style/Tab: Enabled: true @@ -654,40 +425,10 @@ Style/Tab: Style/TrailingBlankLines: Enabled: true -# 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: - Enabled: false - -# Checks for the usage of unneeded trailing underscores at the end of -# parallel variable assignment. -Style/TrailingUnderscoreVariable: - Enabled: false - -# Prefer attr_* methods to trivial readers/writers. -Style/TrivialAccessors: - Enabled: false - -# Do not use unless with else. Rewrite these with the positive case first. -Style/UnlessElse: - Enabled: false - # Checks for %W when interpolation is not needed. Style/UnneededCapitalW: Enabled: true -# 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: Enabled: false @@ -717,12 +458,6 @@ Style/WhileUntilModifier: Style/WordArray: 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, @@ -776,15 +511,6 @@ Metrics/PerceivedComplexity: Lint/AmbiguousOperator: Enabled: true -# Checks for ambiguous regexp literals in the first argument of a method -# invocation without parentheses. -Lint/AmbiguousRegexpLiteral: - Enabled: false - -# Don't use assignment in conditions. -Lint/AssignmentInCondition: - Enabled: false - # Align block ends correctly. Lint/BlockAlignment: Enabled: true @@ -810,14 +536,6 @@ Lint/DefEndAlignment: Lint/DeprecatedClassMethods: Enabled: true -# 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: true @@ -830,10 +548,6 @@ Lint/ElseLayout: Lint/EmptyEnsure: Enabled: true -# Checks for empty string interpolation. -Lint/EmptyInterpolation: - Enabled: false - # Align ends correctly. Lint/EndAlignment: Enabled: true @@ -858,21 +572,11 @@ Lint/FloatOutOfRange: Lint/FormatParameterMismatch: Enabled: true -# Don't suppress exception. -Lint/HandleExceptions: - Enabled: false - # Checks for adjacent string literals on the same line, which could better be # represented as a single string literal. Lint/ImplicitStringConcatenation: Enabled: true -# 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: @@ -886,11 +590,6 @@ Lint/LiteralInCondition: Lint/LiteralInInterpolation: Enabled: true -# Use Kernel#loop with break rather than begin/end/until or begin/end/while -# for post-loop tests. -Lint/Loop: - Enabled: false - # Do not use nested method definitions. Lint/NestedMethodDefinition: Enabled: true @@ -916,13 +615,8 @@ Lint/RequireParentheses: Lint/RescueException: Enabled: true -# Do not use the same name as outer local variable for block arguments -# or block local variables. -Lint/ShadowingOuterLocalVariable: - Enabled: false - -# 'Checks for Object#to_s usage in string interpolation. -Lint/StringConversionInInterpolation: +# Checks for the order which exceptions are rescued to avoid rescueing a less specific exception before a more specific exception. +Lint/ShadowedException: Enabled: false # Do not use prefix `_` for a variable that is used. @@ -935,22 +629,10 @@ Lint/UnderscorePrefixedVariableName: Lint/UnneededDisable: Enabled: false -# Checks for unused block arguments. -Lint/UnusedBlockArgument: - Enabled: false - -# Checks for unused method arguments. -Lint/UnusedMethodArgument: - Enabled: false - # Unreachable code. Lint/UnreachableCode: Enabled: true -# Checks for useless access modifiers. -Lint/UselessAccessModifier: - Enabled: false - # Checks for useless assignment to a local variable. Lint/UselessAssignment: Enabled: true @@ -983,11 +665,6 @@ Performance/Casecmp: Performance/DoubleStartEndWith: Enabled: true -# TODO: Enable EndWith Cop. -# Use `end_with?` instead of a regex match anchored to the end of a string. -Performance/EndWith: - Enabled: false - # Use `strip` instead of `lstrip.rstrip`. Performance/LstripRstrip: Enabled: true @@ -996,24 +673,6 @@ Performance/LstripRstrip: Performance/RangeInclude: Enabled: true -# 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 - # Use `sort` instead of `sort_by { |x| x }`. Performance/RedundantSortBy: Enabled: true @@ -1082,18 +741,6 @@ Rails/ReadWriteAttribute: Rails/ScopeArgs: Enabled: true -# Checks the correct usage of time zone aware methods. -# http://danilenko.org/2012/7/6/rails_timezones -Rails/TimeZone: - Enabled: false - -# Use validates :attribute, hash of validations. -Rails/Validation: - Enabled: false - -Rails/UniqBeforePluck: - Enabled: false - ##################### RSpec ################################## # Check that instances are not being stubbed globally. diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000000..9310e711889 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,462 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --exclude-limit 0` +# on 2016-07-13 12:36:08 -0600 using RuboCop version 0.41.2. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 154 +Lint/AmbiguousRegexpLiteral: + Enabled: false + +# Offense count: 43 +# Configuration parameters: AllowSafeAssignment. +Lint/AssignmentInCondition: + Enabled: false + +# Offense count: 14 +Lint/HandleExceptions: + Enabled: false + +# Offense count: 21 +Lint/IneffectiveAccessModifier: + Enabled: false + +# Offense count: 2 +Lint/Loop: + Enabled: false + +# Offense count: 15 +Lint/ShadowingOuterLocalVariable: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Lint/StringConversionInInterpolation: + Enabled: false + +# Offense count: 44 +# Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Enabled: false + +# Offense count: 129 +# Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +Lint/UnusedMethodArgument: + Enabled: false + +# Offense count: 11 +Lint/UselessAccessModifier: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +Performance/PushSplat: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Performance/RedundantBlockCall: + Enabled: false + +# Offense count: 4 +# Cop supports --auto-correct. +Performance/RedundantMatch: + Enabled: false + +# Offense count: 24 +# Cop supports --auto-correct. +# Configuration parameters: MaxKeyValuePairs. +Performance/RedundantMerge: + Enabled: false + +# Offense count: 60 +Rails/OutputSafety: + Enabled: false + +# Offense count: 128 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: strict, flexible +Rails/TimeZone: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/Validation: + Enabled: false + +# Offense count: 217 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: with_first_parameter, with_fixed_indentation +Style/AlignParameters: + Enabled: false + +# Offense count: 32 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: always, conditionals +Style/AndOr: + Enabled: false + +# Offense count: 47 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: percent_q, bare_percent +Style/BarePercentLiterals: + Enabled: false + +# Offense count: 258 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: braces, no_braces, context_dependent +Style/BracesAroundHashParameters: + Enabled: false + +# Offense count: 5 +Style/CaseEquality: + Enabled: false + +# Offense count: 19 +# Cop supports --auto-correct. +Style/ColonMethodCall: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW +Style/CommentAnnotation: + Enabled: false + +# Offense count: 34 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Enabled: false + +# Offense count: 789 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: leading, trailing +Style/DotPosition: + Enabled: false + +# Offense count: 13 +Style/DoubleNegation: + Enabled: false + +# Offense count: 3 +Style/EachWithObject: + Enabled: false + +# Offense count: 30 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: empty, nil, both +Style/EmptyElse: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Style/EmptyLiteral: + Enabled: false + +# Offense count: 123 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. +Style/ExtraSpacing: + Enabled: false + +# Offense count: 7 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: format, sprintf, percent +Style/FormatString: + Enabled: false + +# Offense count: 48 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Enabled: false + +# Offense count: 11 +Style/IfInsideElse: + Enabled: false + +# Offense count: 177 +# Cop supports --auto-correct. +# Configuration parameters: MaxLineLength. +Style/IfUnlessModifier: + Enabled: false + +# Offense count: 52 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Style/IndentArray: + Enabled: false + +# Offense count: 89 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Style/IndentHash: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: line_count_dependent, lambda, literal +Style/Lambda: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/LineEndConcatenation: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Style/MethodCallParentheses: + Enabled: false + +# Offense count: 3 +Style/MultilineTernaryOperator: + Enabled: false + +# Offense count: 62 +# Cop supports --auto-correct. +Style/MutableConstant: + Enabled: false + +# Offense count: 10 +# Cop supports --auto-correct. +Style/NestedParenthesizedCalls: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. +# SupportedStyles: skip_modifier_ifs, always +Style/Next: + Enabled: false + +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles. +# SupportedOctalStyles: zero_with_o, zero_only +Style/NumericLiteralPrefix: + Enabled: false + +# Offense count: 29 +# Cop supports --auto-correct. +Style/ParallelAssignment: + Enabled: false + +# Offense count: 208 +# Cop supports --auto-correct. +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Enabled: false + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: lower_case_q, upper_case_q +Style/PercentQLiterals: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Style/PerlBackrefs: + Enabled: false + +# Offense count: 32 +# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. +# NamePrefix: is_, has_, have_ +# NamePrefixBlacklist: is_, has_, have_ +# NameWhitelist: is_a? +Style/PredicateName: + Enabled: false + +# Offense count: 28 +# Cop supports --auto-correct. +Style/PreferredHashMethods: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/Proc: + Enabled: false + +# Offense count: 20 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: compact, exploded +Style/RaiseArgs: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Style/RedundantBegin: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantException: + Enabled: false + +# Offense count: 23 +# Cop supports --auto-correct. +Style/RedundantFreeze: + Enabled: false + +# Offense count: 377 +# Cop supports --auto-correct. +Style/RedundantSelf: + Enabled: false + +# Offense count: 94 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed +Style/RegexpLiteral: + Enabled: false + +# Offense count: 17 +# Cop supports --auto-correct. +Style/RescueModifier: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SelfAssignment: + Enabled: false + +# Offense count: 2 +# Configuration parameters: Methods. +# Methods: {"reduce"=>["a", "e"]}, {"inject"=>["a", "e"]} +Style/SingleLineBlockParams: + Enabled: false + +# Offense count: 50 +# Cop supports --auto-correct. +# Configuration parameters: AllowIfMethodIsEmpty. +Style/SingleLineMethods: + Enabled: false + +# Offense count: 14 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceAroundEqualsInParameterDefault: + Enabled: false + +# Offense count: 119 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceBeforeBlockBraces: + Enabled: false + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Style/SpaceBeforeFirstArg: + Enabled: false + +# Offense count: 130 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +Style/SpaceInsideBlockBraces: + Enabled: false + +# Offense count: 98 +# Cop supports --auto-correct. +Style/SpaceInsideBrackets: + Enabled: false + +# Offense count: 60 +# Cop supports --auto-correct. +Style/SpaceInsideParens: + Enabled: false + +# Offense count: 5 +# Cop supports --auto-correct. +Style/SpaceInsidePercentLiteralDelimiters: + Enabled: false + +# Offense count: 36 +# Cop supports --auto-correct. +# Configuration parameters: SupportedStyles. +# SupportedStyles: use_perl_names, use_english_names +Style/SpecialGlobalVars: + EnforcedStyle: use_perl_names + +# Offense count: 30 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiteralsInInterpolation: + Enabled: false + +# Offense count: 24 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: respond_to, define_method +Style/SymbolProc: + Enabled: false + +# Offense count: 23 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. +# SupportedStyles: comma, consistent_comma, no_comma +Style/TrailingCommaInArguments: + Enabled: false + +# Offense count: 113 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. +# SupportedStyles: comma, consistent_comma, no_comma +Style/TrailingCommaInLiteral: + Enabled: false + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: AllowNamedUnderscoreVariables. +Style/TrailingUnderscoreVariable: + Enabled: false + +# Offense count: 90 +# Cop supports --auto-correct. +Style/TrailingWhitespace: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist. +# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym +Style/TrivialAccessors: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Style/UnlessElse: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Style/UnneededInterpolation: + Enabled: false + +# Offense count: 8 +# Cop supports --auto-correct. +Style/ZeroLengthPredicate: + Enabled: false diff --git a/CHANGELOG b/CHANGELOG index 32f33131de1..cb17be985a8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.10.0 (unreleased) - Expose {should,force}_remove_source_branch (Ben Boeckel) + - Disable PostgreSQL statement timeout during migrations - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho) - Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Replace Haml with Hamlit to make view rendering faster. !3666 @@ -23,12 +24,15 @@ v 8.10.0 (unreleased) - Add Sidekiq queue duration to transaction metrics. - Add a new column `artifacts_size` to table `ci_builds` !4964 - Let Workhorse serve format-patch diffs + - Display tooltip for mentioned users and groups !5261 (winniehell) - Added day name to contribution calendar tooltips - Make images fit to the size of the viewport !4810 - Fix check for New Branch button on Issue page !4630 (winniehell) - Fix MR-auto-close text added to description. !4836 + - Support U2F devices in Firefox. !5177 - Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Add Spring EmojiOne updates. + - Fix fetching LFS objects for private CI projects - Add syntax for multiline blockquote using `>>>` fence !3954 - Fix viewing notification settings when a project is pending deletion - Updated compare dropdown menus to use GL dropdown @@ -53,12 +57,16 @@ v 8.10.0 (unreleased) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - Only show New Snippet button to users that can create snippets. - PipelinesFinder uses git cache data + - Track a user who created a pipeline - Actually render old and new sections of parallel diff next to each other - Throttle the update of `project.pushes_since_gc` to 1 minute. - Allow expanding and collapsing files in diff view (!4990) - Collapse large diffs by default (!4990) + - Fix mentioned users list on diff notes + - Fix creation of deployment on build that is retried, redeployed or rollback - Check for conflicts with existing Project's wiki path when creating a new project. - Show last push widget in upstream after push to fork + - Fix stage status shown for pipelines - Cache todos pending/done dashboard query counts. - Don't instantiate a git tree on Projects show default view - Bump Rinku to 2.0.0 @@ -79,7 +87,9 @@ v 8.10.0 (unreleased) - Add basic system information like memory and disk usage to the admin panel - Don't garbage collect commits that have related DB records like comments - More descriptive message for git hooks and file locks + - Aliases of award emoji should be stored as original name. !5060 (dixpac) - Handle custom Git hook result in GitLab UI + - Allow to access Container Registry for Public and Internal projects - Allow '?', or '&' for label names - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests - Add date when user joined the team on the member page @@ -95,6 +105,12 @@ v 8.10.0 (unreleased) - Redesign Builds and Pipelines pages - Change status color and icon for running builds - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.` + - Project export filename now includes the project and namespace path + - Fix last update timestamp on issues not preserved on gitlab.com and project imports + - Fix issues importing projects from EE to CE + - Fix creating group with space in group path + - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) + - Limit the number of retries on error to 3 for exporting projects v 8.9.6 - Fix importing of events under notes for GitLab projects. !5154 @@ -61,7 +61,7 @@ gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap' # Git Wiki # Required manually in config/initializers/gollum.rb to control load order -gem 'gollum-lib', '~> 4.1.0', require: false +gem 'gollum-lib', '~> 4.2', require: false gem 'gollum-rugged_adapter', '~> 0.4.2', require: false # Language detection @@ -105,7 +105,7 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' -gem 'github-markup', '~> 1.3.1' +gem 'github-markup', '~> 1.4' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' @@ -113,7 +113,7 @@ gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' -gem 'rouge', '~> 1.11' +gem 'rouge', '~> 2.0' # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM @@ -299,7 +299,7 @@ group :development, :test do gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-teaspoon', '~> 0.0.2' - gem 'rubocop', '~> 0.40.0', require: false + gem 'rubocop', '~> 0.41.2', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false gem 'simplecov', '~> 0.11.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 67c0645c3d9..0987fd5665a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,7 +58,7 @@ GEM faraday_middleware-multi_json (~> 0.0) oauth2 (~> 1.0) asciidoctor (1.5.3) - ast (2.2.0) + ast (2.3.0) attr_encrypted (3.0.1) encryptor (~> 3.0.0) attr_required (1.0.0) @@ -264,7 +264,7 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) - github-markup (1.3.3) + github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -287,13 +287,13 @@ GEM rubyntlm (~> 0.3) globalid (0.3.6) activesupport (>= 4.1.0) - gollum-grit_adapter (1.0.0) + gollum-grit_adapter (1.0.1) gitlab-grit (~> 2.7, >= 2.7.1) - gollum-lib (4.1.0) - github-markup (~> 1.3.3) + gollum-lib (4.2.1) + github-markup (~> 1.4.0) gollum-grit_adapter (~> 1.0) nokogiri (~> 1.6.4) - rouge (~> 1.9) + rouge (~> 2.0) sanitize (~> 2.1.0) stringex (~> 2.5.1) gollum-rugged_adapter (0.4.2) @@ -473,7 +473,7 @@ GEM orm_adapter (0.5.0) paranoia (2.1.4) activerecord (~> 4.0) - parser (2.3.1.0) + parser (2.3.1.2) ast (~> 2.2) pg (0.18.4) pkg-config (1.1.7) @@ -578,7 +578,7 @@ GEM railties (>= 4.2.0, < 5.1) rinku (2.0.0) rotp (2.1.2) - rouge (1.11.0) + rouge (2.0.3) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) @@ -606,8 +606,8 @@ GEM rspec-retry (0.4.5) rspec-core rspec-support (3.5.0) - rubocop (0.40.0) - parser (>= 2.3.1.0, < 3.0) + rubocop (0.41.2) + parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) @@ -758,7 +758,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.2) - unicode-display_width (1.0.5) + unicode-display_width (1.1.0) unicorn (4.9.0) kgio (~> 2.6) rack @@ -859,12 +859,12 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) gemojione (~> 2.6) github-linguist (~> 4.7.0) - github-markup (~> 1.3.1) + github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) gitlab_git (~> 10.2) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) - gollum-lib (~> 4.1.0) + gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) gon (~> 6.0.1) grape (~> 0.13.0) @@ -933,11 +933,11 @@ DEPENDENCIES request_store (~> 1.3.0) rerun (~> 0.11.0) responders (~> 2.0) - rouge (~> 1.11) + rouge (~> 2.0) rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) rspec-retry (~> 0.4.5) - rubocop (~> 0.40.0) + rubocop (~> 0.41.2) rubocop-rspec (~> 1.5.0) ruby-fogbugz (~> 0.2.1) sanitize (~> 2.0) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index c98763d6271..eceff6d91d5 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -53,7 +53,6 @@ #= require_directory ./u2f #= require_directory . #= require fuzzaldrin-plus -#= require u2f window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index b5da15e9e49..afaa6407b05 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -39,8 +39,6 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new GLForm($('.issue-form')) new IssuableForm($('.issue-form')) - new LabelsSelect() - new MilestoneSelect() when 'projects:merge_requests:new', 'projects:merge_requests:edit' new Diff() shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 1a802b81452..7688609b301 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -184,22 +184,20 @@ class @LabelsSelect .value() if $dropdown.hasClass 'js-extra-options' - extraData = [] - if showAny - extraData.push( - isAny: true - title: 'Any Label' - ) - if showNo - extraData.push( + data.unshift( id: 0 title: 'No Label' ) - if extraData.length - extraData.push 'divider' - data = extraData.concat(data) + if showAny + data.unshift( + isAny: true + title: 'Any Label' + ) + + if data.length > 2 + data.splice 2, 0, 'divider' callback data @@ -289,12 +287,6 @@ class @LabelsSelect defaultLabel fieldName: $dropdown.data('field-name') id: (label) -> - if $dropdown.hasClass('js-issuable-form-dropdown') - if label.id is 0 - return - else - return label.id - if $dropdown.hasClass("js-filter-submit") and not label.isAny? label.title else @@ -308,9 +300,6 @@ class @LabelsSelect $selectbox.hide() # display:block overrides the hide-collapse rule $value.removeAttr('style') - - return if $dropdown.hasClass('js-issuable-form-dropdown') - if $dropdown.hasClass 'js-multiselect' if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) selectedLabels = $dropdown @@ -332,7 +321,7 @@ class @LabelsSelect clicked: (label) -> _this.enableBulkLabelDropdown() - if $dropdown.hasClass('js-filter-bulk-update') or $dropdown.hasClass('js-issuable-form-dropdown') + if $dropdown.hasClass('js-filter-bulk-update') return page = $('body').data 'page' diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 3a036569317..8ab03ed93ee 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -62,7 +62,7 @@ class @MilestoneSelect title: 'Upcoming' ) - if extraOptions.length > 0 + if extraOptions.length > 2 extraOptions.push 'divider' callback(extraOptions.concat(data)) diff --git a/app/assets/javascripts/u2f/authenticate.js.coffee b/app/assets/javascripts/u2f/authenticate.js.coffee index 6deb902c8de..918c0a560fd 100644 --- a/app/assets/javascripts/u2f/authenticate.js.coffee +++ b/app/assets/javascripts/u2f/authenticate.js.coffee @@ -6,8 +6,20 @@ class @U2FAuthenticate constructor: (@container, u2fParams) -> @appId = u2fParams.app_id - @challenges = u2fParams.challenges - @signRequests = u2fParams.sign_requests + @challenge = u2fParams.challenge + + # The U2F Javascript API v1.1 requires a single challenge, with + # _no challenges per-request_. The U2F Javascript API v1.0 requires a + # challenge per-request, which is done by copying the single challenge + # into every request. + # + # In either case, we don't need the per-request challenges that the server + # has generated, so we can remove them. + # + # Note: The server library fixes this behaviour in (unreleased) version 1.0.0. + # This can be removed once we upgrade. + # https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4 + @signRequests = u2fParams.sign_requests.map (request) -> _(request).omit('challenge') start: () => if U2FUtil.isU2FSupported() @@ -16,7 +28,7 @@ class @U2FAuthenticate @renderNotSupported() authenticate: () => - u2f.sign(@appId, @challenges, @signRequests, (response) => + u2f.sign(@appId, @challenge, @signRequests, (response) => if response.errorCode error = new U2FError(response.errorCode) @renderError(error); diff --git a/app/assets/javascripts/u2f/util.js.coffee b/app/assets/javascripts/u2f/util.js.coffee new file mode 100644 index 00000000000..5ef324f609d --- /dev/null +++ b/app/assets/javascripts/u2f/util.js.coffee @@ -0,0 +1,3 @@ +class @U2FUtil + @isU2FSupported: -> + window.u2f diff --git a/app/assets/javascripts/u2f/util.js.coffee.erb b/app/assets/javascripts/u2f/util.js.coffee.erb deleted file mode 100644 index d59341c38b9..00000000000 --- a/app/assets/javascripts/u2f/util.js.coffee.erb +++ /dev/null @@ -1,15 +0,0 @@ -# Helper class for U2F (universal 2nd factor) device registration and authentication. - -class @U2FUtil - @isU2FSupported: -> - if @testMode - true - else - gon.u2f.browser_supports_u2f - - @enableTestMode: -> - @testMode = true - -<% if Rails.env.test? %> -U2FUtil.enableTestMode(); -<% end %> diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index e061f042ca9..344be811e0d 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -155,13 +155,11 @@ class @UsersSelect # display:block overrides the hide-collapse rule $value.css('display', '') - clicked: (user, $el, e) -> + clicked: (user) -> page = $('body').data 'page' isIssueIndex = page is 'projects:issues:index' isMRIndex = page is page is 'projects:merge_requests:index' - if $dropdown.hasClass('js-filter-bulk-update') or $dropdown.hasClass('js-issuable-form-dropdown') - e.preventDefault() - selectedId = user.id + if $dropdown.hasClass('js-filter-bulk-update') return if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) @@ -174,8 +172,7 @@ class @UsersSelect .closest('.selectbox') .find("input[name='#{$dropdown.data('field-name')}']").val() assignTo(selected) - id: (user) -> - user.id + renderRow: (user) -> username = if user.username then "@#{user.username}" else "" avatar = if user.avatar_url then user.avatar_url else false diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 3fd0e12568d..d4e900f80ef 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -56,7 +56,7 @@ position: absolute; top: 50%; right: 6px; - margin-top: -6px; + margin-top: -4px; color: $dropdown-toggle-icon-color; font-size: 10px; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index dba9a7ab3ee..15c6c9f231a 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -324,10 +324,6 @@ .issuable-form-select-holder { display: inline-block; width: 250px; - - .dropdown-menu-toggle { - width: 100%; - } } .table-holder { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 08da4e290dc..a0334207c68 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -129,6 +129,8 @@ } .cancel-retry-btns { + vertical-align: middle; + .btn:not(:first-child) { margin-left: 8px; } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9cc31620d9f..a1004d9bcea 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -344,10 +344,6 @@ class ApplicationController < ActionController::Base session[:skip_tfa] && session[:skip_tfa] > Time.current end - def browser_supports_u2f? - browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile? - end - def redirect_to_home_page_url? # If user is not signed-in and tries to access root_path - redirect him to landing page # Don't redirect to the default URL to prevent endless redirections diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index 998b8adc411..ba07cea569c 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -57,7 +57,7 @@ module AuthenticatesWithTwoFactor # Authenticate using the response from a U2F (universal 2nd factor) device def authenticate_with_two_factor_via_u2f(user) - if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenges]) + if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge]) # Remove any lingering user data from login session.delete(:otp_user_id) session.delete(:challenges) @@ -77,11 +77,9 @@ module AuthenticatesWithTwoFactor if key_handles.present? sign_requests = u2f.authentication_requests(key_handles) - challenges = sign_requests.map(&:challenge) - session[:challenges] = challenges - gon.push(u2f: { challenges: challenges, app_id: u2f_app_id, - sign_requests: sign_requests, - browser_supports_u2f: browser_supports_u2f? }) + session[:challenge] ||= u2f.challenge + gon.push(u2f: { challenge: session[:challenge], app_id: u2f_app_id, + sign_requests: sign_requests }) end end end diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 6a358fdcc05..e37e9e136db 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -100,7 +100,6 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController gon.push(u2f: { challenges: session[:challenges], app_id: u2f_app_id, register_requests: registration_requests, - sign_requests: sign_requests, - browser_supports_u2f: browser_supports_u2f? }) + sign_requests: sign_requests }) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index f7ada5cfee4..b6e80762e3c 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -119,10 +119,6 @@ class Projects::IssuesController < Projects::ApplicationController render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) end end - - rescue ActiveRecord::StaleObjectError - @conflict = true - render :edit end def referenced_merge_requests @@ -220,7 +216,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue_params params.require(:issue).permit( :title, :assignee_id, :position, :description, :confidential, - :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [] + :milestone_id, :due_date, :state_event, :task_num, label_ids: [] ) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2deb7959700..df659bb8c3b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -196,9 +196,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController else render "edit" end - rescue ActiveRecord::StaleObjectError - @conflict = true - render :edit end def remove_wip @@ -427,7 +424,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :state_event, :description, :task_num, :force_remove_source_branch, - :lock_version, label_ids: [] + label_ids: [] ) end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 428a42266d0..abe115d8c68 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,10 +1,7 @@ module BlobHelper - def highlighter(blob_name, blob_content, repository: nil, nowrap: false) - Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository) - end - - def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false) - Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository) + def highlight(blob_name, blob_content, repository: nil, plain: false) + highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository) + raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>) end def no_highlight_files diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a3a8c7d5ff9..47d174361db 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -9,7 +9,7 @@ module IssuablesHelper def multi_label_name(current_labels, default_label) # current_labels may be a string from before - if current_labels.is_a?(Array) && current_labels.any? + if current_labels.is_a?(Array) if current_labels.count > 1 "#{current_labels[0]} +#{current_labels.count - 1} more" else diff --git a/app/helpers/u2f_helper.rb b/app/helpers/u2f_helper.rb new file mode 100644 index 00000000000..143b4ca6b51 --- /dev/null +++ b/app/helpers/u2f_helper.rb @@ -0,0 +1,5 @@ +module U2fHelper + def inject_u2f_api? + browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile? + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index eeb0ceba081..6fd18f2ee24 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -204,7 +204,8 @@ class Ability :download_code, :fork_project, :read_commit_status, - :read_pipeline + :read_pipeline, + :read_container_image ] end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 8c18f7ceb12..ffac3a22efc 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -56,6 +56,7 @@ module Ci new_build.yaml_variables = build.yaml_variables new_build.when = build.when new_build.user = user + new_build.environment = build.environment new_build.save MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) new_build diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index fa4071e2482..b468434866b 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -6,6 +6,8 @@ module Ci self.table_name = 'ci_commits' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id + belongs_to :user + has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id @@ -215,6 +217,8 @@ module Ci end def keep_around_commits + return unless project + project.repository.keep_around(self.sha) project.repository.keep_around(self.before_sha) end diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index 06beff177b1..800a16ab246 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -65,8 +65,7 @@ module Awardable def create_award_emoji(name, current_user) return unless emoji_awardable? - - award_emoji.create(name: name, user: current_user) + award_emoji.create(name: normalize_name(name), user: current_user) end def remove_award_emoji(name, current_user) @@ -80,4 +79,10 @@ module Awardable create_award_emoji(emoji_name, current_user) end end + + private + + def normalize_name(name) + Gitlab::AwardEmoji.normalize_emoji_name(name) + end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index fb49bd7dd64..acb6f5a2998 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -87,12 +87,6 @@ module Issuable User.find(assignee_id_was).update_cache_counts if assignee_id_was assignee.update_cache_counts if assignee end - - # We want to use optimistic lock for cases when only title or description are involved - # http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html - def locking_enabled? - title_changed? || description_changed? - end end module ClassMethods diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 8cac47246db..ec9e0f1b1d0 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -14,14 +14,14 @@ module Mentionable attr = attr.to_s mentionable_attrs << [attr, options] end + end + included do # Accessor for attributes marked mentionable. - def mentionable_attrs - @mentionable_attrs ||= [] + cattr_accessor :mentionable_attrs, instance_accessor: false do + [] end - end - included do if self < Participable participant -> (user, ext) { all_references(user, extractor: ext) } end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 9822844357d..70740c76e43 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -41,9 +41,12 @@ module Participable def participant(attr) participant_attrs << attr end + end - def participant_attrs - @participant_attrs ||= [] + included do + # Accessor for participant attributes. + cattr_accessor :participant_attrs, instance_accessor: false do + [] end end diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 790dfd4d480..04a651d50ab 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -38,7 +38,7 @@ class LegacyDiffNote < Note end def diff_line - @diff_line ||= diff_file.line_for_line_code(self.line_code) + @diff_line ||= diff_file.line_for_line_code(self.line_code) if diff_file end def for_line?(line) @@ -55,7 +55,7 @@ class LegacyDiffNote < Note def active? return @active if defined?(@active) return true if for_commit? - return true unless self.diff + return true unless diff_line return false unless noteable noteable_diff = find_noteable_diff diff --git a/app/models/note.rb b/app/models/note.rb index 8dca2ef09a8..0ce10c77de9 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -229,8 +229,7 @@ class Note < ActiveRecord::Base end def award_emoji_name - original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] - Gitlab::AwardEmoji.normalize_emoji_name(original_name) + note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] end private diff --git a/app/models/project.rb b/app/models/project.rb index e7b9835692d..1cdf22f6a51 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -162,7 +162,7 @@ class Project < ActiveRecord::Base validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id - validates :import_url, addressable_url: true, if: :import_url + validates :import_url, addressable_url: true, if: :external_import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, @@ -482,7 +482,7 @@ class Project < ActiveRecord::Base end def create_or_update_import_data(data: nil, credentials: nil) - return unless valid_import_url? + return unless import_url.present? && valid_import_url? project_import_data = import_data || build_import_data if data @@ -1038,8 +1038,8 @@ class Project < ActiveRecord::Base pipelines.order(id: :desc).find_by(sha: sha, ref: ref) end - def ensure_pipeline(sha, ref) - pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref) + def ensure_pipeline(sha, ref, current_user = nil) + pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user) end def enable_ci diff --git a/app/models/user.rb b/app/models/user.rb index 7a72c202150..3d0a033785c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -85,6 +85,7 @@ class User < ActiveRecord::Base has_one :abuse_report, dependent: :destroy has_many :spam_logs, dependent: :destroy has_many :builds, dependent: :nullify, class_name: 'Ci::Build' + has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' has_many :todos, dependent: :destroy has_many :notification_settings, dependent: :destroy has_many :award_emoji, dependent: :destroy diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index b1ee6874190..be91bf0db85 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -2,6 +2,7 @@ module Ci class CreatePipelineService < BaseService def execute pipeline = project.pipelines.new(params) + pipeline.user = current_user unless ref_names.include?(params[:ref]) pipeline.errors.add(:base, 'Reference not found') diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index f947e8f452e..0b66b854dea 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -14,7 +14,13 @@ class CreateCommitBuildsService return false end - @pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag) + @pipeline = Ci::Pipeline.new( + project: project, + sha: sha, + ref: ref, + before_sha: before_sha, + tag: tag, + user: user) ## # Skip creating pipeline if no gitlab-ci.yml is found diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 02fca5c0ea3..18971bd0be3 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -8,7 +8,6 @@ module Notes if note.award_emoji? noteable = note.noteable todo_service.new_award_emoji(noteable, current_user) - return noteable.create_award_emoji(note.award_emoji_name, current_user) end diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 6afc048576d..998789d64d2 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -10,7 +10,7 @@ module Projects def save_all if [version_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) - Gitlab::ImportExport::Saver.save(shared: @shared) + Gitlab::ImportExport::Saver.save(project: project, shared: @shared) notify_success else cleanup_and_notify diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 6bb0a72d30e..6b48d68cccb 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -194,7 +194,7 @@ class TodoService end def create_assignment_todo(issuable, author) - if issuable.assignee && issuable.assignee != author + if issuable.assignee attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED) create_todos(issuable.assignee, attributes) end @@ -239,7 +239,6 @@ class TodoService def filter_mentioned_users(project, target, author) mentioned_users = target.mentioned_users(author) mentioned_users = reject_users_without_access(mentioned_users, project, target) - mentioned_users.delete(author) mentioned_users.uniq end diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index a373f61bd3c..4debd3d608f 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -1,3 +1,7 @@ +- if inject_u2f_api? + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('u2f.js') + %div .login-box .login-heading diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 245b9c3b4d4..f7580f00159 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -44,7 +44,7 @@ name: "#{j(@project.name)}" }; - - if @group and @group.path + - if @group && @group.persisted? && @group.path :javascript gl.groupOptions = gl.groupOptions || {}; gl.groupOptions["#{j(@group.path)}"] = { diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index 35c4b862bb7..ea7e3d199fd 100644 --- a/app/views/notify/note_merge_request_email.html.haml +++ b/app/views/notify/note_merge_request_email.html.haml @@ -1,4 +1,4 @@ -- if @note.diff_note? +- if @note.diff_note? && @note.diff_file %p.details New comment on diff for = link_to @note.diff_file.file_path, @target_url diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 8780da1dec4..366f1fed35b 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -2,6 +2,10 @@ - header_title "Two-Factor Authentication", profile_two_factor_auth_path = render 'profiles/head' +- if inject_u2f_api? + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('u2f.js') + .row.prepend-top-default .col-lg-3 %h4.prepend-top-0 diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index b53a8633937..0557d384e33 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -32,7 +32,7 @@ Cant find HEAD commit for this branch - - stages_status = pipeline.statuses.stages_status + - stages_status = pipeline.statuses.latest.stages_status - stages.each do |stage| %td - status = stages_status[stage] @@ -58,16 +58,7 @@ .controls.hidden-xs.pull-right - artifacts = pipeline.builds.latest.select { |b| b.artifacts? } - if artifacts.present? - .btn-group.inline - .btn-group - %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} - = icon("play") - %b.caret - %ul.dropdown-menu.dropdown-menu-align-right - %li - = link_to '#' do - = icon("play") - %span Deploy to production + .inline .btn-group %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'} = icon("download") @@ -80,7 +71,7 @@ %span Download '#{build.name}' artifacts - if can?(current_user, :update_pipeline, @project) - .cancel-retry-btns + .cancel-retry-btns.inline - if pipeline.retryable? = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do = icon("repeat") diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index d5199bd86dd..094d6636c66 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -21,10 +21,10 @@ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter - = render "shared/issuable/milestone_dropdown", selected: params[:milestone_title], name: :milestone_title, show_any: true, show_upcoming: true + = render "shared/issuable/milestone_dropdown" .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: params[:label_name], data_options: { field_name: "label_name[]" } + = render "shared/issuable/label_dropdown" .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 07b39c233b1..c30bdb0ae91 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -1,12 +1,5 @@ = form_errors(issuable) -- if @conflict - .alert.alert-danger - Someone edited the #{issuable.class.model_name.human.downcase} the same time you did. - Please check out - = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank" - and make sure your changes will not unintentionally remove theirs - .form-group = f.label :title, class: 'control-label' .col-sm-10 @@ -59,24 +52,38 @@ = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - - project = @target_project || @project - - if issuable.assignee_id - = hidden_field_tag("#{issuable.class.model_name.param_key}[assignee_id]", issuable.assignee_id) - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (project.id if project), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) + = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", + placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, + selected: issuable.assignee_id, project: @target_project || @project, + first_user: true, current_user: true, include_blank: true) + %div + = link_to 'Assign to me', '#', class: 'assign-to-me-link prepend-top-5 inline' .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } - .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone_id, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false + - if milestone_options(issuable).present? + .issuable-form-select-holder + = f.select(:milestone_id, milestone_options(issuable), + { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) + - else + .prepend-top-10 + %span.light No open milestones available. + - if can? current_user, :admin_milestone, issuable.project + %div + = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline" .form-group - has_labels = issuable.project.labels.any? - - selected_labels = issuable.label_ids.any? ? issuable.label_ids : nil - - label_dropdown_toggle = issuable.labels.map { |label| label.title } = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } - .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: selected_labels, selected_toggle: label_dropdown_toggle, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: "false" } + - if has_labels + .issuable-form-select-holder + = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, + { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" } + - else + %span.light No labels yet. + - if can? current_user, :admin_label, issuable.project + %div + = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline" - if has_due_date .col-lg-6 .form-group @@ -142,5 +149,3 @@ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' - -= f.hidden_field :lock_version diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index bcbe133ce62..d34d28f6736 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -4,21 +4,19 @@ - show_footer = local_assigns.fetch(:show_footer, true) - data_options = local_assigns.fetch(:data_options, {}) - classes = local_assigns.fetch(:classes, []) -- selected = local_assigns.fetch(:selected, nil) -- selected_toggle = local_assigns.fetch(:selected_toggle, nil) -- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", selected: selected, project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"} +- dropdown_data = {toggle: 'dropdown', field_name: 'label_name[]', show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options - classes << 'js-filter-submit' if filter_submit -- if selected.present? - - if selected.respond_to?('any?') - - selected.each do |label| - = hidden_field_tag data_options[:field_name], label, id: nil +- if params[:label_name].present? + - if params[:label_name].respond_to?('any?') + - params[:label_name].each do |label| + = hidden_field_tag "label_name[]", label, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text - = h(multi_label_name(selected_toggle || selected, "Label")) + = h(multi_label_name(params[:label_name], "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 9188ef72d52..2fcf40ece99 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,7 +1,7 @@ -- if selected.present? - = hidden_field_tag(name, selected) -= dropdown_tag(milestone_dropdown_label(selected), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected, project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do +- if params[:milestone_title].present? + = hidden_field_tag(:milestone_title, params[:milestone_title]) += dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", + placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if @project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, @project diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb index 39f6037e077..615311e63f5 100644 --- a/app/workers/project_export_worker.rb +++ b/app/workers/project_export_worker.rb @@ -1,7 +1,7 @@ class ProjectExportWorker include Sidekiq::Worker - sidekiq_options queue: :gitlab_shell, retry: true + sidekiq_options queue: :gitlab_shell, retry: 3 def perform(current_user_id, project_id) current_user = User.find(current_user_id) diff --git a/config/application.rb b/config/application.rb index 21e7cc7b6e8..5f7b6a3c049 100644 --- a/config/application.rb +++ b/config/application.rb @@ -87,6 +87,7 @@ module Gitlab config.assets.precompile << "profile/application.js" config.assets.precompile << "lib/utils/*.js" config.assets.precompile << "lib/*.js" + config.assets.precompile << "u2f.js" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' diff --git a/db/migrate/20160707104333_add_lock_to_issuables.rb b/db/migrate/20160707104333_add_lock_to_issuables.rb deleted file mode 100644 index cb516672800..00000000000 --- a/db/migrate/20160707104333_add_lock_to_issuables.rb +++ /dev/null @@ -1,17 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddLockToIssuables < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - disable_ddl_transaction! - - def up - add_column_with_default :issues, :lock_version, :integer, default: 0 - add_column_with_default :merge_requests, :lock_version, :integer, default: 0 - end - - def down - remove_column :issues, :lock_version - remove_column :merge_requests, :lock_version - end -end diff --git a/db/migrate/20160715132507_add_user_id_to_pipeline.rb b/db/migrate/20160715132507_add_user_id_to_pipeline.rb new file mode 100644 index 00000000000..af0461c4daf --- /dev/null +++ b/db/migrate/20160715132507_add_user_id_to_pipeline.rb @@ -0,0 +1,7 @@ +class AddUserIdToPipeline < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + add_column :ci_commits, :user_id, :integer + end +end diff --git a/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb new file mode 100644 index 00000000000..e09caa0e6d7 --- /dev/null +++ b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb @@ -0,0 +1,7 @@ +class AddIndexForPipelineUserId < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + add_concurrent_index :ci_commits, :user_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 4a0df65aafb..d250d65fe21 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -70,11 +70,11 @@ ActiveRecord::Schema.define(version: 20160716115710) do t.string "recaptcha_site_key" t.string "recaptcha_private_key" t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" t.integer "metrics_sample_interval", default: 15 t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" t.boolean "repository_checks_enabled", default: false @@ -84,10 +84,10 @@ ActiveRecord::Schema.define(version: 20160716115710) do t.string "health_check_access_token" t.boolean "send_user_confirmation_email", default: false t.integer "container_registry_token_expire_delay", default: 5 + t.boolean "user_default_external", default: false, null: false t.text "after_sign_up_text" t.string "repository_storage", default: "default" t.string "enabled_git_access_protocol" - t.boolean "user_default_external", default: false, null: false end create_table "audit_events", force: :cascade do |t| @@ -165,8 +165,8 @@ ActiveRecord::Schema.define(version: 20160716115710) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.datetime "artifacts_expire_at" t.string "environment" + t.datetime "artifacts_expire_at" t.integer "artifacts_size" t.string "when" t.text "yaml_variables" @@ -201,6 +201,7 @@ ActiveRecord::Schema.define(version: 20160716115710) do t.datetime "started_at" t.datetime "finished_at" t.integer "duration" + t.integer "user_id" end add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree @@ -212,6 +213,7 @@ ActiveRecord::Schema.define(version: 20160716115710) do add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree + add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree create_table "ci_events", force: :cascade do |t| t.integer "project_id" @@ -483,11 +485,10 @@ ActiveRecord::Schema.define(version: 20160716115710) 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" t.date "due_date" - t.integer "lock_version", default: 0, null: false + t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -627,7 +628,6 @@ ActiveRecord::Schema.define(version: 20160716115710) do t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" - t.integer "lock_version", default: 0, null: false end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -777,10 +777,10 @@ ActiveRecord::Schema.define(version: 20160716115710) do t.integer "user_id", null: false t.string "token", null: false t.string "name", null: false - t.boolean "revoked", default: false - t.datetime "expires_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "revoked", default: false + t.datetime "expires_at" end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree @@ -900,9 +900,9 @@ ActiveRecord::Schema.define(version: 20160716115710) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 16a1461a7e4..50fa263f693 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -985,11 +985,11 @@ directive defined in `.postgres_services` and `.mysql_services` respectively: - ruby test:postgres: - << *job_definition + <<: *job_definition services: *postgres_definition test:mysql: - << *job_definition + <<: *job_definition services: *mysql_definition ``` diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 975bb82c37d..fac35ec964d 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -44,7 +44,7 @@ it organized and easy to find. - When introducing a new document, be careful for the headings to be grammatically and syntactically correct. It is advised to mention one or all of the following GitLab members for a review: `@axil`, `@rspeicher`, - `@dblessing`, `@ashleys`, `@nearlythere`. This is to ensure that no document + `@dblessing`, `@ashleys`. This is to ensure that no document with wrong heading is going live without an audit, thus preventing dead links and redirection issues when corrected - Leave exactly one newline after a heading diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 80670063ea0..358e622b736 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -37,7 +37,6 @@ Feature: Project Issues And I submit new issue "500 error on profile" Then I should see issue "500 error on profile" - @javascript Scenario: I submit new unassigned issue with labels Given project "Shop" has labels: "bug", "feature", "enhancement" And I click link "New Issue" diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 8176ec5ab45..21768c15c17 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -89,7 +89,7 @@ Feature: Project Merge Requests Then The list should be sorted by "Oldest updated" @javascript - Scenario: Visiting Merge Requests from a different Project after sorting + Scenario: Visiting Merge Requests from a differente Project after sorting Given I visit project "Shop" merge requests page And I sort the list by "Oldest updated" And I visit dashboard merge requests page diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 8f71dfdd899..6b56a77b832 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -135,17 +135,19 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I click "Assign to" dropdown"' do - click_button 'Assignee' + first('.ajax-users-select').click end step 'I should see the target project ID in the input selector' do - expect(find('.js-assignee-search')["data-project-id"]).to eq "#{@project.id}" + expect(page).to have_selector("input[data-project-id=\"#{@project.id}\"]") end step 'I should see the users from the target project ID' do - expect(page).to have_content 'Unassigned' - expect(page).to have_content current_user.name - expect(page).to have_content @project.users.first.name + expect(page).to have_selector('.user-result', visible: true, count: 3) + users = page.all('.user-name') + expect(users[0].text).to eq 'Unassigned' + expect(users[1].text).to eq current_user.name + expect(users[2].text).to eq @project.users.first.name end # Verify a link is generated against the correct project diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index b785e15f70e..35f166c7c08 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -82,8 +82,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps step 'I submit new issue "500 error on profile" with label \'bug\'' do fill_in "issue_title", with: "500 error on profile" - click_button "Label" - click_link "bug" + select 'bug', from: "Labels" click_button "Submit issue" end diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index c4fa1838b5a..2efe7e3adf3 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -56,9 +56,9 @@ module API not_found!('Award Emoji') unless can_read_awardable? - award = awardable.award_emoji.new(name: params[:name], user: current_user) + award = awardable.create_award_emoji(params[:name], current_user) - if award.save + if award.persisted? present award, with: Entities::AwardEmoji else not_found!("Award Emoji #{award.errors.messages}") diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 323a7086890..acb4812b5cf 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -64,7 +64,7 @@ module API ref = branches.first end - pipeline = @project.ensure_pipeline(commit.sha, ref) + pipeline = @project.ensure_pipeline(commit.sha, ref, current_user) name = params[:name] || params[:context] status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index d5dfba5e0cc..959b700de78 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -63,7 +63,12 @@ module API if access_status.status # Return the repository full path so that gitlab-shell has it when # handling ssh commands - response[:repository_path] = project.repository.path_to_repo + response[:repository_path] = + if wiki? + project.wiki.repository.path_to_repo + else + project.repository.path_to_repo + end end response diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 536b478979f..91f0159f9a1 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -19,14 +19,22 @@ module Banzai language = node.attr('class') code = node.text + css_classes = "code highlight" + + lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText + formatter = Rouge::Formatters::HTML.new + begin - highlighted = block_code(code, language) + code = formatter.format(lexer.lex(code)) + + css_classes << " js-syntax-highlight #{lexer.tag}" rescue # Gracefully handle syntax highlighter bugs/errors to ensure # users can still access an issue/comment/etc. - highlighted = "<pre>#{code}</pre>" end + highlighted = %(<pre class="#{css_classes}"><code>#{code}</code></pre>) + # Extracted to a method to measure it replace_parent_pre_element(node, highlighted) end @@ -40,8 +48,7 @@ module Banzai # Override Rouge::Plugins::Redcarpet#rouge_formatter def rouge_formatter(lexer) - Rouge::Formatters::HTMLGitlab.new( - cssclass: "code highlight js-syntax-highlight #{lexer.tag}") + Rouge::Formatters::HTML.new end end end diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index 5b0a6d8541b..e1ca7f4d24b 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -112,7 +112,7 @@ module Banzai data = data_attribute(project: project.id, author: author.try(:id)) text = link_text || User.reference_prefix + 'all' - link_tag(url, data, text) + link_tag(url, data, text, 'All Project and Group Members') end def link_to_namespace(namespace, link_text: nil) @@ -128,7 +128,7 @@ module Banzai data = data_attribute(group: namespace.id) text = link_text || Group.reference_prefix + group - link_tag(url, data, text) + link_tag(url, data, text, namespace.name) end def link_to_user(user, namespace, link_text: nil) @@ -136,11 +136,11 @@ module Banzai data = data_attribute(user: namespace.owner_id) text = link_text || User.reference_prefix + user - link_tag(url, data, text) + link_tag(url, data, text, namespace.owner_name) end - def link_tag(url, data, text) - %(<a href="#{url}" #{data} class="#{link_class}">#{escape_once(text)}</a>) + def link_tag(url, data, text, title) + %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{escape_once(text)}</a>) end end end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 478f145bfed..ab94abeda77 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -63,7 +63,7 @@ module Grack def ci_request?(login, password) matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login) - if project && matched_login.present? && git_cmd == 'git-upload-pack' + if project && matched_login.present? underscored_service = matched_login['s'].underscore if underscored_service == 'gitlab_ci' diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index dec20d8659b..927f9dad20b 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -20,11 +20,19 @@ module Gitlab if Database.postgresql? options = options.merge({ algorithm: :concurrently }) + disable_statement_timeout end add_index(table_name, column_name, options) end + # Long-running migrations may take more than the timeout allowed by + # the database. Disable the session's statement timeout to ensure + # migrations don't get killed prematurely. (PostgreSQL only) + def disable_statement_timeout + ActiveRecord::Base.connection.execute('SET statement_timeout TO 0') if Database.postgresql? + end + # Updates the value of a column in batches. # # This method updates the table in batches of 5% of the total row count. @@ -133,6 +141,8 @@ module Gitlab 'in the body of your migration class' end + disable_statement_timeout + transaction do add_column(table, column, type, default: nil) diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index e6d31ea04c0..46d40f75be6 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -15,32 +15,35 @@ module Gitlab end def execute - project_identifier = CGI.escape(project.import_source) - - # Issues && Comments - issues = client.issues(project_identifier) - - issues.each do |issue| - body = @formatter.author_line(issue["author"]["name"]) - body += issue["description"] - - comments = client.issue_comments(project_identifier, issue["id"]) - - if comments.any? - body += @formatter.comments_header + ActiveRecord::Base.no_touching do + project_identifier = CGI.escape(project.import_source) + + # Issues && Comments + issues = client.issues(project_identifier) + + issues.each do |issue| + body = @formatter.author_line(issue["author"]["name"]) + body += issue["description"] + + comments = client.issue_comments(project_identifier, issue["id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"]) + end + + project.issues.create!( + iid: issue["iid"], + description: body, + title: issue["title"], + state: issue["state"], + updated_at: issue["updated_at"], + author_id: gl_user_id(project, issue["author"]["id"]) + ) end - - comments.each do |comment| - body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"]) - end - - project.issues.create!( - iid: issue["iid"], - description: body, - title: issue["title"], - state: issue["state"], - author_id: gl_user_id(project, issue["author"]["id"]) - ) end true diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 41296415e35..9360afedfcb 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -1,7 +1,7 @@ module Gitlab class Highlight - def self.highlight(blob_name, blob_content, repository: nil, nowrap: true, plain: false) - new(blob_name, blob_content, nowrap: nowrap, repository: repository). + def self.highlight(blob_name, blob_content, repository: nil, plain: false) + new(blob_name, blob_content, repository: repository). highlight(blob_content, continue: false, plain: plain) end @@ -13,30 +13,34 @@ module Gitlab highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe) end - attr_reader :lexer - def initialize(blob_name, blob_content, repository: nil, nowrap: true) + def initialize(blob_name, blob_content, repository: nil) + @formatter = Rouge::Formatters::HTMLGitlab.new + @repository = repository @blob_name = blob_name @blob_content = blob_content - @repository = repository - @formatter = rouge_formatter(nowrap: nowrap) - - @lexer = custom_language || begin - Rouge::Lexer.guess(filename: blob_name, source: blob_content).new - rescue Rouge::Lexer::AmbiguousGuess => e - e.alternatives.sort_by(&:tag).first - end end def highlight(text, continue: true, plain: false) if plain - @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe + hl_lexer = Rouge::Lexers::PlainText + continue = false else - @formatter.format(@lexer.lex(text, continue: continue)).html_safe + hl_lexer = self.lexer end + + @formatter.format(hl_lexer.lex(text, continue: continue)).html_safe rescue @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe end + def lexer + @lexer ||= custom_language || begin + Rouge::Lexer.guess(filename: @blob_name, source: @blob_content).new + rescue Rouge::Guesser::Ambiguous => e + e.alternatives.sort_by(&:tag).first + end + end + private def custom_language @@ -46,16 +50,5 @@ module Gitlab Rouge::Lexer.find_fancy(language_name) end - - def rouge_formatter(options = {}) - options = options.reverse_merge( - nowrap: true, - cssclass: 'code highlight', - lineanchors: true, - lineanchorsid: 'LC' - ) - - Rouge::Formatters::HTMLGitlab.new(options) - end end end diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 588647e5adb..bab2ea73c4f 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -3,6 +3,7 @@ module Gitlab extend self VERSION = '0.1.1' + FILENAME_LIMIT = 50 def export_path(relative_path:) File.join(storage_path, relative_path) @@ -28,6 +29,12 @@ module Gitlab 'VERSION' end + def export_filename(project:) + basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.namespace.path}_#{project.path}" + + "#{basename[0..FILENAME_LIMIT]}_export.tar.gz" + end + def version VERSION end diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 025ecc12f9f..051110c23cf 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -12,7 +12,10 @@ module Gitlab json = IO.read(@path) @tree_hash = ActiveSupport::JSON.decode(json) @project_members = @tree_hash.delete('project_members') - create_relations + + ActiveRecord::Base.no_touching do + create_relations + end rescue => e @shared.error(e) false diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 9824df3f274..6ba25a31641 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -87,7 +87,7 @@ module Gitlab project_id = @relation_hash.delete('project_id') # project_id may not be part of the export, but we always need to populate it if required. - @relation_hash['project_id'] = project_id if relation_class.column_names.include?('project_id') + @relation_hash['project_id'] = project_id @relation_hash['gl_project_id'] = project_id if @relation_hash['gl_project_id'] @relation_hash['target_project_id'] = project_id if @relation_hash['target_project_id'] @relation_hash['source_project_id'] = -1 if @relation_hash['source_project_id'] @@ -111,7 +111,7 @@ module Gitlab end def imported_object - imported_object = relation_class.new(@relation_hash) + imported_object = relation_class.new(parsed_relation_hash) yield(imported_object) if block_given? imported_object.importing = true if imported_object.respond_to?(:importing) imported_object @@ -125,6 +125,10 @@ module Gitlab def admin_user? @user.is_admin? end + + def parsed_relation_hash + @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + end end end end diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb index 6a60b65071f..6130c124dd1 100644 --- a/lib/gitlab/import_export/saver.rb +++ b/lib/gitlab/import_export/saver.rb @@ -7,7 +7,8 @@ module Gitlab new(*args).save end - def initialize(shared:) + def initialize(project:, shared:) + @project = project @shared = shared end @@ -36,7 +37,7 @@ module Gitlab end def archive_file - @archive_file ||= File.join(@shared.export_path, '..', "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_project_export.tar.gz") + @archive_file ||= File.join(@shared.export_path, '..', Gitlab::ImportExport.export_filename(project: @project)) end end end diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index 811363405a8..a1ee1aa81ff 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -47,6 +47,8 @@ module Gitlab end def render_storage_upload_store_response(oid, size, tmp_file_name) + return render_forbidden unless tmp_file_name + render_response_to_push do render_lfs_upload_ok(oid, size, tmp_file_name) end diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb index 69bd5e62305..f2a76a56b8f 100644 --- a/lib/gitlab/lfs/router.rb +++ b/lib/gitlab/lfs/router.rb @@ -74,8 +74,6 @@ module Gitlab lfs.render_storage_upload_authorize_response(oid, size) else tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP']) - return nil unless tmp_file_name - lfs.render_storage_upload_store_response(oid, size, tmp_file_name) end end diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index 3358ed6773e..f818dc78d34 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -1,171 +1,27 @@ -require 'cgi' - module Rouge module Formatters - class HTMLGitlab < Rouge::Formatter + class HTMLGitlab < Rouge::Formatters::HTML tag 'html_gitlab' # Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance. # - # [+nowrap+] If set to True, don't wrap the output at all, not - # even inside a <tt><pre></tt> tag (default: false). - # [+cssclass+] CSS class for the wrapping <tt><div></tt> tag - # (default: 'highlight'). - # [+linenos+] If set to 'table', output line numbers as a table - # with two cells, one containing the line numbers, - # the other the whole code. This is copy paste friendly, - # but may cause alignment problems with some browsers - # or fonts. If set to 'inline', the line numbers will - # be integrated in the <tt><pre></tt> tag that contains - # the code (default: nil). # [+linenostart+] The line number for the first line (default: 1). - # [+lineanchors+] If set to true the formatter will wrap each output - # line in an anchor tag with a name of L-linenumber. - # This allows easy linking to certain lines - # (default: false). - # [+lineanchorsid+] If lineanchors is true the name of the anchors can - # be changed with lineanchorsid to e.g. foo-linenumber - # (default: 'L'). - # [+anchorlinenos+] If set to true, will wrap line numbers in <tt><a></tt> - # tags. Used in combination with linenos and lineanchors - # (default: false). - # [+inline_theme+] Inline CSS styles for the <pre> tag (default: false). - def initialize( - nowrap: false, - cssclass: 'highlight', - linenos: nil, - linenostart: 1, - lineanchors: false, - lineanchorsid: 'L', - anchorlinenos: false, - inline_theme: nil - ) - @nowrap = nowrap - @cssclass = cssclass - @linenos = linenos + def initialize(linenostart: 1) @linenostart = linenostart - @lineanchors = lineanchors - @lineanchorsid = lineanchorsid - @anchorlinenos = anchorlinenos - @inline_theme = Theme.find(inline_theme).new if inline_theme.is_a?(String) - end - - def render(tokens) - case @linenos - when 'table' - render_tableized(tokens) - when 'inline' - render_untableized(tokens) - else - render_untableized(tokens) - end - end - - alias_method :format, :render - - private - - def render_untableized(tokens) - data = process_tokens(tokens) - - html = '' - html << "<pre class=\"#{@cssclass}\"><code>" unless @nowrap - html << wrap_lines(data[:code]) - html << "</code></pre>\n" unless @nowrap - html + @line_number = linenostart end - def render_tableized(tokens) - data = process_tokens(tokens) - - html = '' - html << "<div class=\"#{@cssclass}\">" unless @nowrap - html << '<table><tbody>' - html << "<td class=\"linenos\"><pre>" - html << wrap_linenos(data[:numbers]) - html << '</pre></td>' - html << "<td class=\"lines\"><pre><code>" - html << wrap_lines(data[:code]) - html << '</code></pre></td>' - html << '</tbody></table>' - html << '</div>' unless @nowrap - html - end - - def process_tokens(tokens) - rendered = [] - current_line = '' - - tokens.each do |tok, val| - # In the case of multi-line values (e.g. comments), we need to apply - # styling to each line since span elements are inline. - val.lines.each do |line| - stripped = line.chomp - current_line << span(tok, stripped) - - if line.end_with?("\n") - rendered << current_line - current_line = '' - end - end - end - - # Add leftover text - rendered << current_line if current_line.present? - - num_lines = rendered.size - numbers = (@linenostart..num_lines + @linenostart - 1).to_a - - { numbers: numbers, code: rendered } - end - - def wrap_linenos(numbers) - if @anchorlinenos - numbers.map! do |number| - "<a href=\"##{@lineanchorsid}#{number}\">#{number}</a>" - end - end - numbers.join("\n") - end - - def wrap_lines(lines) - if @lineanchors - lines = lines.each_with_index.map do |line, index| - number = index + @linenostart - - if @linenos == 'inline' - "<a name=\"L#{number}\"></a>" \ - "<span class=\"linenos\">#{number}</span>" \ - "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \ - '</span>' - else - "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \ - '</span>' - end - end - elsif @linenos == 'inline' - lines = lines.each_with_index.map do |line, index| - number = index + @linenostart - "<span class=\"linenos\">#{number}</span>#{line}" - end - end - - lines.join("\n") - end + def stream(tokens, &b) + is_first = true + token_lines(tokens) do |line| + yield "\n" unless is_first + is_first = false - def span(tok, val) - # http://stackoverflow.com/a/1600584/2587286 - val = CGI.escapeHTML(val) + yield %(<span id="LC#{@line_number}" class="line">) + line.each { |token, value| yield span(token, value) } + yield %(</span>) - if tok.shortname.empty? - val - else - if @inline_theme - rules = @inline_theme.style_for(tok).rendered_rules - "<span style=\"#{rules.to_a.join(';')}\"#{val}</span>" - else - "<span class=\"#{tok.shortname}\">#{val}</span>" - end + @line_number += 1 end end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 891df65216d..2d8b59472e8 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -1,14 +1,26 @@ require 'spec_helper' feature 'Group', feature: true do + before do + login_as(:admin) + end + + describe 'creating a group with space in group path' do + it 'renders new group form with validation errors' do + visit new_group_path + fill_in 'Group path', with: 'space group' + + click_button 'Create group' + + expect(current_path).to eq(groups_path) + expect(page).to have_content("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.'.") + end + end + describe 'description' do let(:group) { create(:group) } let(:path) { group_path(group) } - before do - login_as(:admin) - end - it 'parses Markdown' do group.update_attribute(:description, 'This is **my** group') visit path diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 055210399a7..7773c486b4e 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -55,7 +55,7 @@ feature 'issue move to another project' do first('.select2-choice').click end - fill_in('s2id_autogen1_search', with: new_project_search.name) + fill_in('s2id_autogen2_search', with: new_project_search.name) page.within '.select2-drop' do expect(page).to have_content(new_project_search.name) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 3fec75a07df..d51c9abea19 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -50,8 +50,9 @@ describe 'Issues', feature: true do expect(page).to have_content "Assignee #{@user.name}" - first('.js-user-search').click - click_link 'Unassigned' + first('#s2id_issue_assignee_id').click + sleep 2 # wait for ajax stuff to complete + first('.user-result').click click_button 'Save changes' @@ -120,17 +121,6 @@ describe 'Issues', feature: true do expect(page).to have_content date.to_s(:medium) end end - - it 'warns about version conflict' do - issue.update(title: "New title") - - fill_in 'issue_title', with: 'bug 345' - fill_in 'issue_description', with: 'bug description' - - click_button 'Save changes' - - expect(page).to have_content 'Someone edited the issue the same time you did' - end end end diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index 8ad884492d1..9e007ab7635 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -17,16 +17,5 @@ feature 'Edit Merge Request', feature: true do it 'form should have class js-quick-submit' do expect(page).to have_selector('.js-quick-submit') end - - it 'warns about version conflict' do - merge_request.update(title: "New title") - - fill_in 'merge_request_title', with: 'bug 345' - fill_in 'merge_request_description', with: 'bug description' - - click_button 'Save changes' - - expect(page).to have_content 'Someone edited the merge request the same time you did' - end end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 13d980a326f..b6acc509342 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -426,4 +426,23 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end + + describe "GET /:project_path/container_registry" do + before do + stub_container_registry_tags('latest') + stub_container_registry_config(enabled: true) + end + + subject { namespace_project_container_registry_index_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index ac9690cc127..ccb5c06dab0 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -362,4 +362,23 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end + + describe "GET /:project_path/container_registry" do + before do + stub_container_registry_tags('latest') + stub_container_registry_config(enabled: true) + end + + subject { namespace_project_container_registry_index_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 737897de52b..985663e7c98 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -426,4 +426,23 @@ describe "Public Project Access", feature: true do it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } end + + describe "GET /:project_path/container_registry" do + before do + stub_container_registry_tags('latest') + stub_container_registry_config(enabled: true) + end + + subject { namespace_project_container_registry_index_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :external } + it { is_expected.to be_allowed_for :visitor } + end end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index 14613754f74..9335f5bf120 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do + before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) } + def register_u2f_device(u2f_device = nil) u2f_device ||= FakeU2fDevice.new(page) u2f_device.respond_to_u2f_registration @@ -208,21 +210,52 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: expect(page.body).to match('Authentication via U2F device failed') end end - end - describe "when two-factor authentication is disabled" do - let(:user) { create(:user) } + describe "when more than one device has been registered by the same user" do + it "allows logging in with either device" do + # Register first device + user = login_as(:user) + user.update_attribute(:otp_required_for_login, true) + visit profile_two_factor_auth_path + expect(page).to have_content("Your U2F device needs to be set up.") + first_device = register_u2f_device + + # Register second device + visit profile_two_factor_auth_path + expect(page).to have_content("Your U2F device needs to be set up.") + second_device = register_u2f_device + logout + + # Authenticate as both devices + [first_device, second_device].each do |device| + login_as(user) + device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + click_on "Authenticate via U2F Device" - before do - login_as(user) - user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - click_on 'Manage Two-Factor Authentication' - register_u2f_device + expect(page.body).to match('Signed in successfully') + + logout + end + end end - it "deletes u2f registrations" do - expect { click_on "Disable" }.to change { U2fRegistration.count }.from(1).to(0) + describe "when two-factor authentication is disabled" do + let(:user) { create(:user) } + + before do + user = login_as(:user) + user.update_attribute(:otp_required_for_login, true) + visit profile_account_path + click_on 'Manage Two-Factor Authentication' + expect(page).to have_content("Your U2F device needs to be set up.") + register_u2f_device + end + + it "deletes u2f registrations" do + expect { click_on "Disable" }.to change { U2fRegistration.count }.by(-1) + end end end end diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml index 333eda1191a..37066c8e930 100644 --- a/spec/fixtures/parallel_diff_result.yml +++ b/spec/fixtures/parallel_diff_result.yml @@ -121,7 +121,7 @@ :type: old :number: 9 :text: | - -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span> + -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9 :position: !ruby/object:Gitlab::Diff::Position attributes: @@ -136,7 +136,7 @@ :type: new :number: 9 :text: | - +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span> + +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9 :position: !ruby/object:Gitlab::Diff::Position attributes: @@ -241,7 +241,7 @@ :type: old :number: 13 :text: | - -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}</span></span> + -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13 :position: !ruby/object:Gitlab::Diff::Position attributes: @@ -315,7 +315,7 @@ :type: new :number: 15 :text: | - +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span></span> + +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 :position: !ruby/object:Gitlab::Diff::Position attributes: @@ -623,7 +623,7 @@ :type: :number: 20 :text: |2 - <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> + <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 :position: !ruby/object:Gitlab::Diff::Position attributes: @@ -638,7 +638,7 @@ :type: :number: 26 :text: |2 - <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> + <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 :position: !ruby/object:Gitlab::Diff::Position attributes: diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 6d1c02db297..bd0108f9938 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -16,19 +16,19 @@ describe BlobHelper do describe '#highlight' do it 'should return plaintext for unknown lexer context' do - result = helper.highlight(blob_name, no_context_content, nowrap: true) - expect(result).to eq('<span id="LC1" class="line">:type "assem"))</span>') + result = helper.highlight(blob_name, no_context_content) + expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line">:type "assem"))</span></code></pre>]) end it 'should highlight single block' do - expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> -<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span>] + expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> +<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>] - expect(helper.highlight(blob_name, blob_content, nowrap: true)).to eq(expected) + expect(helper.highlight(blob_name, blob_content)).to eq(expected) end it 'should highlight multi-line comments' do - result = helper.highlight(blob_name, multiline_content, nowrap: true) + result = helper.highlight(blob_name, multiline_content) html = Nokogiri::HTML(result) lines = html.search('.s') expect(lines.count).to eq(3) @@ -41,33 +41,19 @@ describe BlobHelper do let(:blob_name) { 'test.diff' } let(:blob_content) { "+aaa\n+bbb\n- ccc\n ddd\n"} let(:expected) do - %q(<span id="LC1" class="line"><span class="gi">+aaa</span></span> + %q(<pre class="code highlight"><code><span id="LC1" class="line"><span class="gi">+aaa</span></span> <span id="LC2" class="line"><span class="gi">+bbb</span></span> <span id="LC3" class="line"><span class="gd">- ccc</span></span> -<span id="LC4" class="line"> ddd</span>) +<span id="LC4" class="line"> ddd</span></code></pre>) end it 'should highlight each line properly' do - result = helper.highlight(blob_name, blob_content, nowrap: true) + result = helper.highlight(blob_name, blob_content) expect(result).to eq(expected) end end end - describe "#highlighter" do - it 'should highlight continued blocks' do - # Both lines have LC1 as ID since formatter doesn't support continue at the moment - expected = [ - '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>', - '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span>' - ] - - highlighter = helper.highlighter(blob_name, blob_content, nowrap: true) - result = split_content.map{ |content| highlighter.highlight(content) } - expect(result).to eq(expected) - end - end - describe "#sanitize_svg" do let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') } let(:data) { open(input_svg_path).read } diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index c0d2be98e85..6b5e3d93d48 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -57,7 +57,7 @@ describe EventsHelper do expected = '<pre class="code highlight js-syntax-highlight ruby">' \ "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \ " <span class=\"s1\">\'hello world\'</span>\n" \ - "<span class=\"k\">end</span>" \ + "<span class=\"k\">end</span>\n" \ '</code></pre>' expect(helper.event_note(input)).to eq(expected) end diff --git a/spec/javascripts/u2f/authenticate_spec.coffee b/spec/javascripts/u2f/authenticate_spec.coffee index e8a2892d678..8ffeda11704 100644 --- a/spec/javascripts/u2f/authenticate_spec.coffee +++ b/spec/javascripts/u2f/authenticate_spec.coffee @@ -5,13 +5,12 @@ #= require ./mock_u2f_device describe 'U2FAuthenticate', -> - U2FUtil.enableTestMode() fixture.load('u2f/authenticate') beforeEach -> @u2fDevice = new MockU2FDevice @container = $("#js-authenticate-u2f") - @component = new U2FAuthenticate(@container, {}, "token") + @component = new U2FAuthenticate(@container, {sign_requests: []}, "token") @component.start() it 'allows authenticating via a U2F device', -> diff --git a/spec/javascripts/u2f/register_spec.js.coffee b/spec/javascripts/u2f/register_spec.js.coffee index 0858abeca1a..87dc769792b 100644 --- a/spec/javascripts/u2f/register_spec.js.coffee +++ b/spec/javascripts/u2f/register_spec.js.coffee @@ -5,7 +5,6 @@ #= require ./mock_u2f_device describe 'U2FRegister', -> - U2FUtil.enableTestMode() fixture.load('u2f/register') beforeEach -> diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 407617f3307..b1370bca833 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -3,15 +3,35 @@ require 'spec_helper' describe Banzai::Filter::SyntaxHighlightFilter, lib: true do include FilterSpecHelper - it 'highlights valid code blocks' do - result = filter('<pre><code>def fun end</code>') - expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n") + context "when no language is specified" do + it "highlights as plaintext" do + result = filter('<pre><code>def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>def fun end</code></pre>') + end end - it 'passes through invalid code blocks' do - allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError) + context "when a valid language is specified" do + it "highlights as that language" do + result = filter('<pre><code class="ruby">def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>') + end + end + + context "when an invalid language is specified" do + it "highlights as plaintext" do + result = filter('<pre><code class="gnuplot">This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>This is a test</code></pre>') + end + end + + context "when Rouge formatting fails" do + before do + allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError) + end - result = filter('<pre><code>This is a test</code></pre>') - expect(result.to_html).to eq('<pre>This is a test</pre>') + it "highlights as plaintext" do + result = filter('<pre><code class="ruby">This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight"><code>This is a test</code></pre>') + end end end diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb index 760d66a1488..7543c29bcc4 100644 --- a/spec/lib/gitlab/bitbucket_import/client_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -54,12 +54,12 @@ describe Gitlab::BitbucketImport::Client, lib: true do context 'project import' do it 'calls .from_project with no errors' do project = create(:empty_project) + project.import_url = "ssh://git@bitbucket.org/test/test.git" project.create_or_update_import_data(credentials: { user: "git", password: nil, bb_session: { bitbucket_access_token: "test", bitbucket_access_token_secret: "test" } }) - project.import_url = "ssh://git@bitbucket.org/test/test.git" expect { described_class.from_project(project) }.not_to raise_error end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 9096ad101b0..4ec3f19e03f 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -13,6 +13,10 @@ describe Gitlab::Database::MigrationHelpers, lib: true do context 'outside a transaction' do before do expect(model).to receive(:transaction_open?).and_return(false) + + unless Gitlab::Database.postgresql? + allow_any_instance_of(Gitlab::Database::MigrationHelpers).to receive(:disable_statement_timeout) + end end context 'using PostgreSQL' do diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index fb5d50a5c68..88e4115c453 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -28,13 +28,13 @@ describe Gitlab::Diff::Highlight, lib: true do end it 'highlights and marks removed lines' do - code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} expect(subject[4].text).to eq(code) end it 'highlights and marks added lines' do - code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} expect(subject[5].text).to eq(code) end diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb new file mode 100644 index 00000000000..d6409a29550 --- /dev/null +++ b/spec/lib/gitlab/import_export/import_export_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::ImportExport, services: true do + describe 'export filename' do + let(:project) { create(:project, :public, path: 'project-path') } + + it 'contains the project path' do + expect(described_class.export_filename(project: project)).to include(project.path) + end + + it 'contains the namespace path' do + expect(described_class.export_filename(project: project)).to include(project.namespace.path) + end + + it 'does not go over a certain length' do + project.path = 'a' * 100 + + expect(described_class.export_filename(project: project).length).to be < 70 + end + end +end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 7286b0c39c0..4113d829c3c 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -26,6 +26,7 @@ "deleted_at": null, "due_date": null, "moved_to_id": null, + "test_ee_field": "test", "notes": [ { "id": 351, diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 05ffec8ea0a..877be300262 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -30,6 +30,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty end + it 'preserves updated_at on issues' do + restored_project_json + + issue = Issue.where(description: 'Aliquam enim illo et possimus.').first + + expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC') + end + context 'event at forth level of the tree' do let(:event) { Event.where(title: 'test levels').first } diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb deleted file mode 100644 index 659facd6c19..00000000000 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ /dev/null @@ -1,730 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Lfs::Router, lib: true do - let(:project) { create(:project) } - let(:public_project) { create(:project, :public) } - let(:forked_project) { fork_project(public_project, user) } - - let(:user) { create(:user) } - let(:user_two) { create(:user) } - let!(:lfs_object) { create(:lfs_object, :with_file) } - - let(:request) { Rack::Request.new(env) } - let(:env) do - { - 'rack.input' => '', - 'REQUEST_METHOD' => 'GET', - } - end - - let(:lfs_router_auth) { new_lfs_router(project, user: user) } - let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) } - let(:lfs_router_noauth) { new_lfs_router(project) } - let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) } - let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) } - let(:lfs_router_public_noauth) { new_lfs_router(public_project) } - let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) } - let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) } - let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) } - - let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } - let(:sample_size) { 499013 } - let(:respond_with_deprecated) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} - let(:respond_with_disabled) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} - - describe 'when lfs is disabled' do - before do - allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) - env['REQUEST_METHOD'] = 'POST' - body = { - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ], - 'operation' => 'upload' - }.to_json - env['rack.input'] = StringIO.new(body) - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" - end - - it 'responds with 501' do - expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) - end - end - - describe 'when fetching lfs object using deprecated API' do - before do - enable_lfs - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" - end - - it 'responds with 501' do - expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) - end - end - - describe 'when fetching lfs object' do - before do - enable_lfs - env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" - end - - describe 'and request comes from gitlab-workhorse' do - context 'without user being authorized' do - it "responds with status 401" do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - - context 'with required headers' do - before do - project.lfs_objects << lfs_object - env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" - end - - context 'when user does not have project access' do - it "responds with status 403" do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'when user has project access' do - before do - project.team << [user, :master] - end - - it "responds with status 200" do - expect(lfs_router_auth.try_call.first).to eq(200) - end - - it "responds with the file location" do - expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") - expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) - end - end - - context 'when CI is authorized' do - it "responds with status 200" do - expect(lfs_router_ci_auth.try_call.first).to eq(200) - end - - it "responds with the file location" do - expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") - expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) - end - end - end - - context 'without required headers' do - it "responds with status 403" do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end - end - - describe 'when handling lfs request using deprecated API' do - before do - enable_lfs - env['REQUEST_METHOD'] = 'POST' - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects" - end - - it 'responds with 501' do - expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) - end - end - - describe 'when handling lfs batch request' do - before do - enable_lfs - env['REQUEST_METHOD'] = 'POST' - env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" - end - - describe 'download' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - shared_examples 'an authorized requests' do - context 'when downloading an lfs object that is assigned to our project' do - before do - project.lfs_objects << lfs_object - end - - it 'responds with status 200 and href to download' do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => auth } - } - } - }]) - end - end - - context 'when downloading an lfs object that is assigned to other project' do - before do - public_project.lfs_objects << lfs_object - end - - it 'responds with status 200 and error message' do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end - end - - context 'when downloading a lfs object that does not exist' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - it "responds with status 200 and error message" do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end - end - - context 'when downloading one new and one existing lfs object' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ] - }.to_json - env['rack.input'] = StringIO.new(body) - project.lfs_objects << lfs_object - end - - it "responds with status 200 with upload hypermedia link for the new object" do - response = router.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }, - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => auth } - } - } - }]) - end - end - end - - context 'when user is authenticated' do - let(:auth) { authorize(user) } - - before do - env["HTTP_AUTHORIZATION"] = auth - project.team << [user, role] - end - - it_behaves_like 'an authorized requests' do - let(:role) { :reporter } - let(:router) { lfs_router_auth } - end - - context 'when user does is not member of the project' do - let(:role) { :guest } - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'when user does not have download access' do - let(:role) { :guest } - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end - - context 'when CI is authorized' do - let(:auth) { 'gitlab-ci-token:password' } - - before do - env["HTTP_AUTHORIZATION"] = auth - end - - it_behaves_like 'an authorized requests' do - let(:router) { lfs_router_ci_auth } - end - end - - context 'when user is not authenticated' do - describe 'is accessing public project' do - before do - public_project.lfs_objects << lfs_object - end - - it 'responds with status 200 and href to download' do - response = lfs_router_public_noauth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) - - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => {} - } - } - }]) - end - end - - describe 'is accessing non-public project' do - before do - project.lfs_objects << lfs_object - end - - it 'responds with authorization required' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - end - end - - describe 'upload' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - describe 'when request is authenticated' do - describe 'when user has project push access' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :developer] - end - - context 'when pushing an lfs object that already exists' do - before do - public_project.lfs_objects << lfs_object - end - - it "responds with status 200 and links the object to the project" do - response_body = lfs_router_auth.try_call.last - response = ActiveSupport::JSON.decode(response_body.first) - - expect(response['objects']).to be_kind_of(Array) - expect(response['objects'].first['oid']).to eq(sample_oid) - expect(response['objects'].first['size']).to eq(sample_size) - expect(lfs_object.projects.pluck(:id)).not_to include(project.id) - expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") - expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) - end - end - - context 'when pushing a lfs object that does not exist' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - it "responds with status 200 and upload hypermedia link" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - - response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body['objects']).to be_kind_of(Array) - expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(response_body['objects'].first['size']).to eq(1575078) - expect(lfs_object.projects.pluck(:id)).not_to include(project.id) - expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) - end - end - - context 'when pushing one new and one existing lfs object' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ] - }.to_json - env['rack.input'] = StringIO.new(body) - project.lfs_objects << lfs_object - end - - it "responds with status 200 with upload hypermedia link for the new object" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - - response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body['objects']).to be_kind_of(Array) - - expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(response_body['objects'].first['size']).to eq(1575078) - expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth) - - expect(response_body['objects'].last['oid']).to eq(sample_oid) - expect(response_body['objects'].last['size']).to eq(sample_size) - expect(response_body['objects'].last).not_to have_key('actions') - end - end - end - - context 'when user does not have push access' do - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'when CI is authorized' do - it 'responds with 401' do - expect(lfs_router_ci_auth.try_call.first).to eq(401) - end - end - end - - context 'when user is not authenticated' do - context 'when user has push access' do - before do - project.team << [user, :master] - end - - it "responds with status 401" do - expect(lfs_router_public_noauth.try_call.first).to eq(401) - end - end - - context 'when user does not have push access' do - it "responds with status 401" do - expect(lfs_router_public_noauth.try_call.first).to eq(401) - end - end - end - - context 'when CI is authorized' do - let(:auth) { 'gitlab-ci-token:password' } - - before do - env["HTTP_AUTHORIZATION"] = auth - end - - it "responds with status 403" do - expect(lfs_router_public_ci_auth.try_call.first).to eq(401) - end - end - end - - describe 'unsupported' do - before do - body = { 'operation' => 'other', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end - - it 'responds with status 404' do - expect(lfs_router_public_noauth.try_call.first).to eq(404) - end - end - end - - describe 'when pushing a lfs object' do - before do - enable_lfs - env['REQUEST_METHOD'] = 'PUT' - end - - shared_examples 'unauthorized' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(router.project) - end - - it 'responds with status 401' do - expect(router.try_call.first).to eq(401) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(router.project) - end - - it 'responds with status 401' do - expect(router.try_call.first).to eq(401) - end - end - - context 'and request is sent with a malformed headers' do - before do - env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" - env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" - end - - it 'does not recognize it as a valid lfs command' do - expect(router.try_call).to eq(nil) - end - end - end - - shared_examples 'forbidden' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(router.project) - end - - it 'responds with 403' do - expect(router.try_call.first).to eq(403) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(router.project) - end - - it 'responds with 403' do - expect(router.try_call.first).to eq(403) - end - end - end - - describe 'to one project' do - describe 'when user is authenticated' do - describe 'when user has push access to the project' do - before do - project.team << [user, :developer] - end - - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end - - it 'responds with status 200, location of lfs store and object details' do - json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) - - expect(lfs_router_auth.try_call.first).to eq(200) - expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") - expect(json_response['LfsOid']).to eq(sample_oid) - expect(json_response['LfsSize']).to eq(sample_size) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with status 200 and lfs object is linked to the project' do - expect(lfs_router_auth.try_call.first).to eq(200) - expect(lfs_object.projects.pluck(:id)).to include(project.id) - end - end - end - - describe 'and user does not have push access' do - let(:router) { lfs_router_auth } - - it_behaves_like 'forbidden' - end - end - - context 'when CI is authenticated' do - let(:router) { lfs_router_ci_auth } - - it_behaves_like 'unauthorized' - end - - context 'for unauthenticated' do - let(:router) { new_lfs_router(project) } - - it_behaves_like 'unauthorized' - end - end - - describe 'to a forked project' do - let(:forked_project) { fork_project(public_project, user) } - - describe 'when user is authenticated' do - describe 'when user has push access to the project' do - before do - forked_project.team << [user_two, :developer] - end - - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end - - it 'responds with status 200, location of lfs store and object details' do - json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) - - expect(lfs_router_forked_auth.try_call.first).to eq(200) - expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") - expect(json_response['LfsOid']).to eq(sample_oid) - expect(json_response['LfsSize']).to eq(sample_size) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end - - it 'responds with status 200 and lfs object is linked to the source project' do - expect(lfs_router_forked_auth.try_call.first).to eq(200) - expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - end - end - end - - describe 'and user does not have push access' do - let(:router) { lfs_router_forked_auth } - - it_behaves_like 'forbidden' - end - end - - context 'when CI is authenticated' do - let(:router) { lfs_router_forked_ci_auth } - - it_behaves_like 'unauthorized' - end - - context 'for unauthenticated' do - let(:router) { lfs_router_forked_noauth } - - it_behaves_like 'unauthorized' - end - - describe 'and second project not related to fork or a source project' do - let(:second_project) { create(:project) } - let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) } - - before do - public_project.lfs_objects << lfs_object - headers_for_upload_finalize(second_project) - end - - context 'when pushing the same lfs object to the second project' do - before do - second_project.team << [user, :master] - end - - it 'responds with 200 and links the lfs object to the project' do - expect(lfs_router_second_project.try_call.first).to eq(200) - expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id) - end - end - end - end - end - - def enable_lfs - allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - end - - def authorize(user) - ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) - end - - def new_lfs_router(project, user: nil, ci: false) - Gitlab::Lfs::Router.new(project, user, ci, request) - end - - def header_for_upload_authorize(project) - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" - end - - def headers_for_upload_finalize(project) - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" - env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4" - end - - def fork_project(project, user, object = nil) - allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) - Projects::ForkService.new(project, user, {}).execute - end -end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 4e5481f9154..10db79bd15f 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -5,9 +5,12 @@ describe Ci::Pipeline, models: true do let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:statuses) } it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:builds) } + it { is_expected.to validate_presence_of :sha } it { is_expected.to validate_presence_of :status } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 05f22c7a9eb..ff6371ad685 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -177,10 +177,10 @@ describe CommitStatus, models: true do describe '#stages' do before do - FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'success' - FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'failed' - FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'deploy', stage_idx: 2, status: 'running' - FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'test', stage_idx: 1, status: 'success' + create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success' + create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed' + create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running' + create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success' end context 'stages list' do @@ -192,7 +192,7 @@ describe CommitStatus, models: true do end context 'stages with statuses' do - subject { CommitStatus.where(pipeline: pipeline).stages_status } + subject { CommitStatus.where(pipeline: pipeline).latest.stages_status } it 'return list of stages with statuses' do is_expected.to eq({ @@ -201,6 +201,20 @@ describe CommitStatus, models: true do 'deploy' => 'running' }) end + + context 'when build is retried' do + before do + create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success' + end + + it 'ignores a previous state' do + is_expected.to eq({ + 'build' => 'success', + 'test' => 'success', + 'deploy' => 'running' + }) + end + end end end diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb index d64d89edbd3..d23fc06c3ad 100644 --- a/spec/models/legacy_diff_note_spec.rb +++ b/spec/models/legacy_diff_note_spec.rb @@ -16,10 +16,10 @@ describe LegacyDiffNote, models: true do end describe '#active?' do - it 'is always true when the note has no associated diff' do + it 'is always true when the note has no associated diff line' do note = build(:legacy_diff_note_on_merge_request) - expect(note).to receive(:diff).and_return(nil) + expect(note).to receive(:diff_line).and_return(nil) expect(note).to be_active end @@ -27,7 +27,7 @@ describe LegacyDiffNote, models: true do it 'is never true when the note has no noteable associated' do note = build(:legacy_diff_note_on_merge_request) - expect(note).to receive(:diff).and_return(double) + expect(note).to receive(:diff_line).and_return(double) expect(note).to receive(:noteable).and_return(nil) expect(note).not_to be_active @@ -47,7 +47,7 @@ describe LegacyDiffNote, models: true do merge = build_stubbed(:merge_request, :simple) note = build(:legacy_diff_note_on_merge_request, noteable: merge) - allow(note).to receive(:diff).and_return(double) + allow(note).to receive(:diff_line).and_return(double) expect(note).to receive(:find_noteable_diff).and_return(nil) expect(note).not_to be_active diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e842c58dd82..9dc34276f18 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -142,10 +142,10 @@ describe Project, models: true do expect(project2).to be_valid end - it 'does not allow to introduce an empty URI' do + it 'allows an empty URI' do project2 = build(:project, import_url: '') - expect(project2).not_to be_valid + expect(project2).to be_valid end it 'does not produce import data on an empty URI' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ff39f187759..fc74488ac0e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -31,6 +31,8 @@ describe User, models: true do it { is_expected.to have_many(:spam_logs).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) } + it { is_expected.to have_many(:builds).dependent(:nullify) } + it { is_expected.to have_many(:pipelines).dependent(:nullify) } describe '#group_members' do it 'does not include group memberships for which user is a requester' do diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 72a6d45f47d..2b74dd4bbb0 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -135,6 +135,22 @@ describe API::API, api: true do expect(response).to have_http_status(401) end + + it "normalizes +1 as thumbsup award" do + post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1' + + expect(issue.award_emoji.last.name).to eq("thumbsup") + end + + context 'when the emoji already has been awarded' do + it 'returns a 404 status code' do + post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' + post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' + + expect(response).to have_http_status(404) + expect(json_response["message"]).to match("has already been taken") + end + end end end @@ -147,6 +163,22 @@ describe API::API, api: true do expect(response).to have_http_status(201) expect(json_response['user']['username']).to eq(user.username) end + + it "normalizes +1 as thumbsup award" do + post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1' + + expect(note.award_emoji.last.name).to eq("thumbsup") + end + + context 'when the emoji already has been awarded' do + it 'returns a 404 status code' do + post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' + post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' + + expect(response).to have_http_status(404) + expect(json_response["message"]).to match("has already been taken") + end + end end describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index e567d36afa8..f6f85d6e95e 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -56,13 +56,21 @@ describe API::API, api: true do context "git push with project.wiki" do it 'responds with success' do - project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki') - project_wiki.team << [user, :developer] + push(key, project.wiki) - push(key, project_wiki) + expect(response).to have_http_status(200) + expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) + end + end + + context "git pull with project.wiki" do + it 'responds with success' do + pull(key, project.wiki) expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb new file mode 100644 index 00000000000..93d2bc160cc --- /dev/null +++ b/spec/requests/lfs_http_spec.rb @@ -0,0 +1,768 @@ +require 'spec_helper' + +describe Gitlab::Lfs::Router do + let(:user) { create(:user) } + let!(:lfs_object) { create(:lfs_object, :with_file) } + + let(:headers) do + { + 'Authorization' => authorization, + 'X-Sendfile-Type' => sendfile + }.compact + end + let(:authorization) { } + let(:sendfile) { } + + let(:sample_oid) { lfs_object.oid } + let(:sample_size) { lfs_object.size } + + describe 'when lfs is disabled' do + let(:project) { create(:empty_project) } + let(:body) do + { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ], + 'operation' => 'upload' + } + end + + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers + end + + it 'responds with 501' do + expect(response).to have_http_status(501) + expect(json_response).to include('message' => 'Git LFS is not enabled on this GitLab server, contact your admin.') + end + end + + describe 'deprecated API' do + let(:project) { create(:empty_project) } + + before do + enable_lfs + end + + shared_examples 'a deprecated' do + it 'responds with 501' do + expect(response).to have_http_status(501) + end + + it 'returns deprecated message' do + expect(json_response).to include('message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.') + end + end + + context 'when fetching lfs object using deprecated API' do + let(:authorization) { authorize_user } + + before do + get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers + end + + it_behaves_like 'a deprecated' + end + + context 'when handling lfs request using deprecated API' do + before do + post_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers + end + + it_behaves_like 'a deprecated' + end + end + + describe 'when fetching lfs object' do + let(:project) { create(:empty_project) } + let(:update_permissions) { } + + before do + enable_lfs + update_permissions + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + end + + context 'and request comes from gitlab-workhorse' do + context 'without user being authorized' do + it 'responds with status 401' do + expect(response).to have_http_status(401) + end + end + + context 'with required headers' do + shared_examples 'responds with a file' do + let(:sendfile) { 'X-Sendfile' } + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'responds with the file location' do + expect(response.headers['Content-Type']).to eq('application/octet-stream') + expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path) + end + end + + context 'with user is authorized' do + let(:authorization) { authorize_user } + + context 'and does not have project access' do + let(:update_permissions) do + project.lfs_objects << lfs_object + end + + it 'responds with status 403' do + expect(response).to have_http_status(403) + end + end + + context 'and does have project access' do + let(:update_permissions) do + project.team << [user, :master] + project.lfs_objects << lfs_object + end + + it_behaves_like 'responds with a file' + end + end + + context 'when CI is authorized' do + let(:authorization) { authorize_ci_project } + + let(:update_permissions) do + project.lfs_objects << lfs_object + end + + it_behaves_like 'responds with a file' + end + end + + context 'without required headers' do + let(:authorization) { authorize_user } + + it 'responds with status 403' do + expect(response).to have_http_status(403) + end + end + end + end + + describe 'when handling lfs batch request' do + let(:update_lfs_permissions) { } + let(:update_user_permissions) { } + + before do + enable_lfs + update_lfs_permissions + update_user_permissions + post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers + end + + describe 'download' do + let(:project) { create(:empty_project) } + let(:body) do + { 'operation' => 'download', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + } + end + + shared_examples 'an authorized requests' do + context 'when downloading an lfs object that is assigned to our project' do + let(:update_lfs_permissions) do + project.lfs_objects << lfs_object + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'with href to download' do + expect(json_response).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => authorization } + } + } + }]) + end + end + + context 'when downloading an lfs object that is assigned to other project' do + let(:other_project) { create(:empty_project) } + let(:update_lfs_permissions) do + other_project.lfs_objects << lfs_object + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'with href to download' do + expect(json_response).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) + end + end + + context 'when downloading a lfs object that does not exist' do + let(:body) do + { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + } + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'with an 404 for specific object' do + expect(json_response).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) + end + end + + context 'when downloading one new and one existing lfs object' do + let(:body) do + { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + } + end + + let(:update_lfs_permissions) do + project.lfs_objects << lfs_object + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'responds with upload hypermedia link for the new object' do + expect(json_response).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }, + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => authorization } + } + } + }]) + end + end + end + + context 'when user is authenticated' do + let(:authorization) { authorize_user } + + let(:update_user_permissions) do + project.team << [user, role] + end + + it_behaves_like 'an authorized requests' do + let(:role) { :reporter } + end + + context 'when user does is not member of the project' do + let(:role) { :guest } + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + + context 'when user does not have download access' do + let(:role) { :guest } + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + end + + context 'when CI is authorized' do + let(:authorization) { authorize_ci_project } + + it_behaves_like 'an authorized requests' + end + + context 'when user is not authenticated' do + describe 'is accessing public project' do + let(:project) { create(:project, :public) } + + let(:update_lfs_permissions) do + project.lfs_objects << lfs_object + end + + it 'responds with status 200 and href to download' do + expect(response).to have_http_status(200) + end + + it 'responds with status 200 and href to download' do + expect(json_response).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => {} + } + } + }]) + end + end + + describe 'is accessing non-public project' do + let(:update_lfs_permissions) do + project.lfs_objects << lfs_object + end + + it 'responds with authorization required' do + expect(response).to have_http_status(401) + end + end + end + end + + describe 'upload' do + let(:project) { create(:project, :public) } + let(:body) do + { 'operation' => 'upload', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + } + end + + describe 'when request is authenticated' do + describe 'when user has project push access' do + let(:authorization) { authorize_user } + + let(:update_user_permissions) do + project.team << [user, :developer] + end + + context 'when pushing an lfs object that already exists' do + let(:other_project) { create(:empty_project) } + let(:update_lfs_permissions) do + other_project.lfs_objects << lfs_object + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'responds with links the object to the project' do + expect(json_response['objects']).to be_kind_of(Array) + expect(json_response['objects'].first['oid']).to eq(sample_oid) + expect(json_response['objects'].first['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).not_to include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(other_project.id) + expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") + expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization) + end + end + + context 'when pushing a lfs object that does not exist' do + let(:body) do + { 'operation' => 'upload', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + } + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'responds with upload hypermedia link' do + expect(json_response['objects']).to be_kind_of(Array) + expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(json_response['objects'].first['size']).to eq(1575078) + expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization) + end + end + + context 'when pushing one new and one existing lfs object' do + let(:body) do + { 'operation' => 'upload', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + } + end + + let(:update_lfs_permissions) do + project.lfs_objects << lfs_object + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'responds with upload hypermedia link for the new object' do + expect(json_response['objects']).to be_kind_of(Array) + + expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(json_response['objects'].first['size']).to eq(1575078) + expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(json_response['objects'].first['actions']['upload']['header']).to eq("Authorization" => authorization) + + expect(json_response['objects'].last['oid']).to eq(sample_oid) + expect(json_response['objects'].last['size']).to eq(sample_size) + expect(json_response['objects'].last).not_to have_key('actions') + end + end + end + + context 'when user does not have push access' do + let(:authorization) { authorize_user } + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + + context 'when CI is authorized' do + let(:authorization) { authorize_ci_project } + + it 'responds with 401' do + expect(response).to have_http_status(401) + end + end + end + + context 'when user is not authenticated' do + context 'when user has push access' do + let(:update_user_permissions) do + project.team << [user, :master] + end + + it 'responds with status 401' do + expect(response).to have_http_status(401) + end + end + + context 'when user does not have push access' do + it 'responds with status 401' do + expect(response).to have_http_status(401) + end + end + end + + context 'when CI is authorized' do + let(:authorization) { authorize_ci_project } + + it 'responds with status 403' do + expect(response).to have_http_status(401) + end + end + end + + describe 'unsupported' do + let(:project) { create(:empty_project) } + let(:body) do + { 'operation' => 'other', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + } + end + + it 'responds with status 404' do + expect(response).to have_http_status(404) + end + end + end + + describe 'when pushing a lfs object' do + before do + enable_lfs + end + + shared_examples 'unauthorized' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end + + it 'responds with status 401' do + expect(response).to have_http_status(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end + + it 'responds with status 401' do + expect(response).to have_http_status(401) + end + end + + context 'and request is sent with a malformed headers' do + before do + put_finalize('cat /etc/passwd') + end + + it 'does not recognize it as a valid lfs command' do + expect(response).to have_http_status(403) + end + end + end + + shared_examples 'forbidden' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + end + + describe 'to one project' do + let(:project) { create(:empty_project) } + + describe 'when user is authenticated' do + let(:authorization) { authorize_user } + + describe 'when user has push access to the project' do + before do + project.team << [user, :developer] + end + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'responds with status 200, location of lfs store and object details' do + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'lfs object is linked to the project' do + expect(lfs_object.projects.pluck(:id)).to include(project.id) + end + end + end + + describe 'and user does not have push access' do + it_behaves_like 'forbidden' + end + end + + context 'when CI is authenticated' do + let(:authorization) { authorize_ci_project } + + it_behaves_like 'unauthorized' + end + + context 'for unauthenticated' do + it_behaves_like 'unauthorized' + end + end + + describe 'to a forked project' do + let(:upstream_project) { create(:project, :public) } + let(:project_owner) { create(:user) } + let(:project) { fork_project(upstream_project, project_owner) } + + describe 'when user is authenticated' do + let(:authorization) { authorize_user } + + describe 'when user has push access to the project' do + before do + project.team << [user, :developer] + end + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'with location of lfs store and object details' do + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'lfs object is linked to the source project' do + expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id) + end + end + end + + describe 'and user does not have push access' do + it_behaves_like 'forbidden' + end + end + + context 'when CI is authenticated' do + let(:authorization) { authorize_ci_project } + + it_behaves_like 'unauthorized' + end + + context 'for unauthenticated' do + it_behaves_like 'unauthorized' + end + + describe 'and second project not related to fork or a source project' do + let(:second_project) { create(:empty_project) } + let(:authorization) { authorize_user } + + before do + second_project.team << [user, :master] + upstream_project.lfs_objects << lfs_object + end + + context 'when pushing the same lfs object to the second project' do + before do + put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil, + headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact + end + + it 'responds with status 200' do + expect(response).to have_http_status(200) + end + + it 'links the lfs object to the project' do + expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id) + end + end + end + end + + def put_authorize + put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers + end + + def put_finalize(lfs_tmp = lfs_tmp_file) + put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil, + headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp).compact + end + + def lfs_tmp_file + "#{sample_oid}012345678" + end + end + + def enable_lfs + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + def authorize_ci_project + ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token) + end + + def authorize_user + ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + def fork_project(project, user, object = nil) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + Projects::ForkService.new(project, user, {}).execute + end + + def post_json(url, body = nil, headers = nil) + post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json')) + end + + def json_response + @json_response ||= JSON.parse(response.body) + end +end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 67777ad48bc..7cc71f706ce 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -87,51 +87,105 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end context 'user authorization' do - let(:project) { create(:project) } let(:current_user) { create(:user) } - context 'allow to use scope-less authentication' do - it_behaves_like 'a valid token' - end + context 'for private project' do + let(:project) { create(:empty_project) } - context 'allow developer to push images' do - before { project.team << [current_user, :developer] } + context 'allow to use scope-less authentication' do + it_behaves_like 'a valid token' + end - let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push" } + context 'allow developer to push images' do + before { project.team << [current_user, :developer] } + + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:push" } + end + + it_behaves_like 'a pushable' end - it_behaves_like 'a pushable' - end + context 'allow reporter to pull images' do + before { project.team << [current_user, :reporter] } + + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:pull" } + end - context 'allow reporter to pull images' do - before { project.team << [current_user, :reporter] } + it_behaves_like 'a pullable' + end - let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull" } + context 'return a least of privileges' do + before { project.team << [current_user, :reporter] } + + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:push,pull" } + end + + it_behaves_like 'a pullable' end - it_behaves_like 'a pullable' + context 'disallow guest to pull or push images' do + before { project.team << [current_user, :guest] } + + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:pull,push" } + end + + it_behaves_like 'an inaccessible' + end end - context 'return a least of privileges' do - before { project.team << [current_user, :reporter] } + context 'for public project' do + let(:project) { create(:empty_project, :public) } - let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:push,pull" } + context 'allow anyone to pull images' do + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:pull" } + end + + it_behaves_like 'a pullable' end - it_behaves_like 'a pullable' + context 'disallow anyone to push images' do + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:push" } + end + + it_behaves_like 'an inaccessible' + end end - context 'disallow guest to pull or push images' do - before { project.team << [current_user, :guest] } + context 'for internal project' do + let(:project) { create(:empty_project, :internal) } - let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull,push" } + context 'for internal user' do + context 'allow anyone to pull images' do + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:pull" } + end + + it_behaves_like 'a pullable' + end + + context 'disallow anyone to push images' do + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:push" } + end + + it_behaves_like 'an inaccessible' + end end - it_behaves_like 'an inaccessible' + context 'for external user' do + let(:current_user) { create(:user, external: true) } + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:pull,push" } + end + + it_behaves_like 'an inaccessible' + end end end diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb index 4d09bc5fb12..d4c5e584421 100644 --- a/spec/services/create_commit_builds_service_spec.rb +++ b/spec/services/create_commit_builds_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe CreateCommitBuildsService, services: true do let(:service) { CreateCommitBuildsService.new } let(:project) { FactoryGirl.create(:empty_project) } - let(:user) { nil } + let(:user) { create(:user) } before do stub_ci_pipeline_to_return_yaml_file @@ -24,6 +24,7 @@ describe CreateCommitBuildsService, services: true do it { expect(pipeline).to be_valid } it { expect(pipeline).to be_persisted } it { expect(pipeline).to eq(project.pipelines.last) } + it { expect(pipeline).to have_attributes(user: user) } it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) } end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 654e441f3cd..8da2a2b3c1b 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -89,6 +89,12 @@ describe CreateDeploymentService, services: true do expect_any_instance_of(described_class).to receive(:execute) subject end + + it 'is set as deployable' do + subject + + expect(Deployment.last.deployable).to eq(deployable) + end end context 'without environment specified' do @@ -105,6 +111,8 @@ describe CreateDeploymentService, services: true do context 'when build succeeds' do it_behaves_like 'does create environment and deployment' do + let(:deployable) { build } + subject { build.success } end end @@ -114,6 +122,14 @@ describe CreateDeploymentService, services: true do subject { build.drop } end end + + context 'when build is retried' do + it_behaves_like 'does create environment and deployment' do + let(:deployable) { Ci::Build.retry(build) } + + subject { deployable.success } + end + end end end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index b4522536724..34d8ea9090e 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -35,8 +35,11 @@ describe TodoService, services: true do should_not_create_any_todo { service.new_issue(unassigned_issue, author) } end - it 'does not create a todo if assignee is the current user' do - should_not_create_any_todo { service.new_issue(unassigned_issue, john_doe) } + it 'creates a todo if assignee is the current user' do + unassigned_issue.update_attribute(:assignee, john_doe) + service.new_issue(unassigned_issue, john_doe) + + should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED) end it 'creates a todo for each valid mentioned user' do @@ -44,7 +47,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: guest, target: issue, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) + should_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) end @@ -57,7 +60,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) - should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end context 'when a private group is mentioned' do @@ -87,7 +90,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: guest, target: issue, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) + should_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) end @@ -105,7 +108,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) - should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end context 'issues with a task list' do @@ -156,10 +159,11 @@ describe TodoService, services: true do should_not_create_any_todo { service.reassigned_issue(issue, author) } end - it 'does not create a todo if new assignee is the current user' do + it 'creates a todo if new assignee is the current user' do unassigned_issue.update_attribute(:assignee, john_doe) + service.reassigned_issue(unassigned_issue, john_doe) - should_not_create_any_todo { service.reassigned_issue(unassigned_issue, john_doe) } + should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED) end end @@ -250,7 +254,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) - should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) + should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) end @@ -262,7 +266,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) - should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) + should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) end it 'creates a todo for each valid mentioned user when leaving a note on commit' do @@ -270,7 +274,7 @@ describe TodoService, services: true do should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) - should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) end @@ -312,7 +316,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) end @@ -325,7 +329,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) end @@ -382,10 +386,11 @@ describe TodoService, services: true do should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) } end - it 'does not create a todo if new assignee is the current user' do + it 'creates a todo if new assignee is the current user' do mr_assigned.update_attribute(:assignee, john_doe) + service.reassigned_merge_request(mr_assigned, john_doe) - should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, john_doe) } + should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED) end end @@ -435,6 +440,24 @@ describe TodoService, services: true do should_create_todo(user: author, target: mr_unassigned, action: Todo::MARKED) end end + + describe '#new_note' do + let(:mention) { john_doe.to_reference } + let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") } + let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") } + + it 'creates a todo for mentioned user on new diff note' do + service.new_note(diff_note_on_merge_request, author) + + should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request) + end + + it 'creates a todo for mentioned user on legacy diff note' do + service.new_note(legacy_diff_note_on_merge_request, author) + + should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) + end + end end it 'updates cached counts when a todo is created' do diff --git a/spec/support/fake_u2f_device.rb b/spec/support/fake_u2f_device.rb index 553fe9f1fbc..f550e9a0160 100644 --- a/spec/support/fake_u2f_device.rb +++ b/spec/support/fake_u2f_device.rb @@ -18,8 +18,8 @@ class FakeU2fDevice def respond_to_u2f_authentication app_id = @page.evaluate_script('gon.u2f.app_id') - challenges = @page.evaluate_script('gon.u2f.challenges') - json_response = u2f_device(app_id).sign_response(challenges[0]) + challenge = @page.evaluate_script('gon.u2f.challenge') + json_response = u2f_device(app_id).sign_response(challenge) @page.execute_script(" u2f.sign = function(appId, challenges, signRequests, callback) { |