diff --git a/.gitattributes b/.gitattributes index 7e800609e6..17cbaa5eef 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -CHANGELOG merge=union \ No newline at end of file +CHANGELOG merge=union +*.js.es6 gitlab-language=javascript diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 83a906932d..2d33bad588 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,5 @@ image: "ruby:2.1" -services: - - mysql:latest - - redis:alpine - cache: key: "ruby21" paths: @@ -18,6 +14,7 @@ variables: SIMPLECOV: "true" USE_DB: "true" USE_BUNDLE_INSTALL: "true" + GIT_DEPTH: "20" before_script: - source ./scripts/prepare_build.sh @@ -33,7 +30,6 @@ stages: - post-test # Prepare and merge knapsack tests - .knapsack-state: &knapsack-state services: [] variables: @@ -67,8 +63,14 @@ update-knapsack: # Execute all testing suites +.use-db: &use-db + services: + - mysql:latest + - redis:alpine + .rspec-knapsack: &rspec-knapsack stage: test + <<: *use-db script: - bundle exec rake assets:precompile 2>/dev/null - JOB_NAME=( $CI_BUILD_NAME ) @@ -84,6 +86,7 @@ update-knapsack: .spinach-knapsack: &spinach-knapsack stage: test + <<: *use-db script: - bundle exec rake assets:precompile 2>/dev/null - JOB_NAME=( $CI_BUILD_NAME ) @@ -129,75 +132,95 @@ spinach 7 10: *spinach-knapsack spinach 8 10: *spinach-knapsack spinach 9 10: *spinach-knapsack -# Execute all testing suites against Ruby 2.2 - -.ruby-22: &ruby-22 - image: "ruby:2.2" +# Execute all testing suites against Ruby 2.3 +.ruby-23: &ruby-23 + image: "ruby:2.3" + <<: *use-db only: - master cache: - key: "ruby22" + key: "ruby-23" paths: - - vendor + - vendor/apt + - vendor/ruby -.rspec-knapsack-ruby22: &rspec-knapsack-ruby22 +.rspec-knapsack-ruby23: &rspec-knapsack-ruby23 <<: *rspec-knapsack - <<: *ruby-22 + <<: *ruby-23 -.spinach-knapsack-ruby22: &spinach-knapsack-ruby22 +.spinach-knapsack-ruby23: &spinach-knapsack-ruby23 <<: *spinach-knapsack - <<: *ruby-22 - -rspec 0 20 ruby22: *rspec-knapsack-ruby22 -rspec 1 20 ruby22: *rspec-knapsack-ruby22 -rspec 2 20 ruby22: *rspec-knapsack-ruby22 -rspec 3 20 ruby22: *rspec-knapsack-ruby22 -rspec 4 20 ruby22: *rspec-knapsack-ruby22 -rspec 5 20 ruby22: *rspec-knapsack-ruby22 -rspec 6 20 ruby22: *rspec-knapsack-ruby22 -rspec 7 20 ruby22: *rspec-knapsack-ruby22 -rspec 8 20 ruby22: *rspec-knapsack-ruby22 -rspec 9 20 ruby22: *rspec-knapsack-ruby22 -rspec 10 20 ruby22: *rspec-knapsack-ruby22 -rspec 11 20 ruby22: *rspec-knapsack-ruby22 -rspec 12 20 ruby22: *rspec-knapsack-ruby22 -rspec 13 20 ruby22: *rspec-knapsack-ruby22 -rspec 14 20 ruby22: *rspec-knapsack-ruby22 -rspec 15 20 ruby22: *rspec-knapsack-ruby22 -rspec 16 20 ruby22: *rspec-knapsack-ruby22 -rspec 17 20 ruby22: *rspec-knapsack-ruby22 -rspec 18 20 ruby22: *rspec-knapsack-ruby22 -rspec 19 20 ruby22: *rspec-knapsack-ruby22 + <<: *ruby-23 -spinach 0 10 ruby22: *spinach-knapsack-ruby22 -spinach 1 10 ruby22: *spinach-knapsack-ruby22 -spinach 2 10 ruby22: *spinach-knapsack-ruby22 -spinach 3 10 ruby22: *spinach-knapsack-ruby22 -spinach 4 10 ruby22: *spinach-knapsack-ruby22 -spinach 5 10 ruby22: *spinach-knapsack-ruby22 -spinach 6 10 ruby22: *spinach-knapsack-ruby22 -spinach 7 10 ruby22: *spinach-knapsack-ruby22 -spinach 8 10 ruby22: *spinach-knapsack-ruby22 -spinach 9 10 ruby22: *spinach-knapsack-ruby22 +rspec 0 20 ruby23: *rspec-knapsack-ruby23 +rspec 1 20 ruby23: *rspec-knapsack-ruby23 +rspec 2 20 ruby23: *rspec-knapsack-ruby23 +rspec 3 20 ruby23: *rspec-knapsack-ruby23 +rspec 4 20 ruby23: *rspec-knapsack-ruby23 +rspec 5 20 ruby23: *rspec-knapsack-ruby23 +rspec 6 20 ruby23: *rspec-knapsack-ruby23 +rspec 7 20 ruby23: *rspec-knapsack-ruby23 +rspec 8 20 ruby23: *rspec-knapsack-ruby23 +rspec 9 20 ruby23: *rspec-knapsack-ruby23 +rspec 10 20 ruby23: *rspec-knapsack-ruby23 +rspec 11 20 ruby23: *rspec-knapsack-ruby23 +rspec 12 20 ruby23: *rspec-knapsack-ruby23 +rspec 13 20 ruby23: *rspec-knapsack-ruby23 +rspec 14 20 ruby23: *rspec-knapsack-ruby23 +rspec 15 20 ruby23: *rspec-knapsack-ruby23 +rspec 16 20 ruby23: *rspec-knapsack-ruby23 +rspec 17 20 ruby23: *rspec-knapsack-ruby23 +rspec 18 20 ruby23: *rspec-knapsack-ruby23 +rspec 19 20 ruby23: *rspec-knapsack-ruby23 + +spinach 0 10 ruby23: *spinach-knapsack-ruby23 +spinach 1 10 ruby23: *spinach-knapsack-ruby23 +spinach 2 10 ruby23: *spinach-knapsack-ruby23 +spinach 3 10 ruby23: *spinach-knapsack-ruby23 +spinach 4 10 ruby23: *spinach-knapsack-ruby23 +spinach 5 10 ruby23: *spinach-knapsack-ruby23 +spinach 6 10 ruby23: *spinach-knapsack-ruby23 +spinach 7 10 ruby23: *spinach-knapsack-ruby23 +spinach 8 10 ruby23: *spinach-knapsack-ruby23 +spinach 9 10 ruby23: *spinach-knapsack-ruby23 # Other generic tests +.static-analyses-variables: &static-analyses-variables + variables: + SIMPLECOV: "false" + USE_DB: "false" + USE_BUNDLE_INSTALL: "true" + .exec: &exec + <<: *static-analyses-variables stage: test script: - bundle exec $CI_BUILD_NAME -teaspoon: *exec rubocop: *exec rake scss_lint: *exec rake brakeman: *exec rake flog: *exec rake flay: *exec -rake db:migrate:reset: *exec license_finder: *exec +rake downtime_check: *exec + +rake db:migrate:reset: + stage: test + <<: *use-db + script: + - rake db:migrate:reset + +teaspoon: + stage: test + <<: *use-db + script: + - teaspoon bundler:audit: stage: test + <<: *static-analyses-variables only: - master script: diff --git a/.hound.yml b/.hound.yml deleted file mode 100644 index 3bde29fb2b..0000000000 --- a/.hound.yml +++ /dev/null @@ -1,4 +0,0 @@ -# Prefer single quotes -StringLiterals: - EnforcedStyle: single_quotes - Enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index dbdabbb9d4..db0bcfadcf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,8 @@ -require: rubocop-rspec +require: + - rubocop-rspec + - ./rubocop/rubocop + +inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 2.1 @@ -50,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 @@ -78,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 @@ -95,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 @@ -131,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 @@ -157,41 +127,21 @@ 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 # Don't use several empty lines in a row. Style/EmptyLines: - Enabled: false + Enabled: true # Keep blank lines around access modifiers. Style/EmptyLinesAroundAccessModifier: @@ -213,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 @@ -229,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 @@ -250,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 @@ -282,7 +208,7 @@ Style/IfWithSemicolon: # Checks that conditional statements do not have an identical line at the # end of each branch, which can validly be moved out of the conditional. Style/IdenticalConditionalBranches: - Enabled: false + Enabled: true # Checks the indentation of the first line of the right-hand-side of a # multi-line assignment. @@ -297,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 @@ -321,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 @@ -385,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 @@ -442,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 @@ -495,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 @@ -522,21 +348,13 @@ 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: false + Enabled: true # Use spaces after commas. Style/SpaceAfterComma: - Enabled: false + Enabled: true # Do not put a space between a method name and the opening parenthesis in a # method definition. @@ -551,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 @@ -564,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 @@ -576,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 @@ -612,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 @@ -625,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 @@ -652,39 +425,9 @@ 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: false - -# TODO: Enable UnneededInterpolation Cop. -# Checks for strings that are just an interpolated expression. -Style/UnneededInterpolation: - Enabled: false + Enabled: true # Checks for %q/%Q when single quotes or double quotes would do. Style/UnneededPercentQ: @@ -715,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, @@ -774,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 @@ -808,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 @@ -828,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 @@ -856,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: @@ -884,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 @@ -914,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. @@ -933,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 @@ -981,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 @@ -994,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 @@ -1080,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 0000000000..9310e71188 --- /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/.teatro.yml b/.teatro.yml deleted file mode 100644 index 3005436198..0000000000 --- a/.teatro.yml +++ /dev/null @@ -1,8 +0,0 @@ -stage: - before: - - cp config/gitlab.teatro.yml config/gitlab.yml - - mkdir /apps/gitlab-satellites - - mkdir /apps/repositories - - database: - - RAILS_ENV=development force=yes bundle exec rake db:create gitlab:setup \ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG index c09e147833..bae0f96c52 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,325 @@ Please view this file on the master branch, on stable branches it's out of date. -v 8.9.0 (unreleased) +v 8.10.5 + - Add a data migration to fix some missing timestamps in the members table. !5670 + - Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706 + - Cache project count for 5 minutes to reduce DB load. !5746 & !5754 + +v 8.10.4 + - Don't close referenced upstream issues from a forked project. + - Fixes issue with dropdowns `enter` key not working correctly. !5544 + - Fix Import/Export project import not working in HA mode. !5618 + - Fix Import/Export error checking versions. !5638 + +v 8.10.3 + - Fix Import/Export issue importing milestones and labels not associated properly. !5426 + - Fix timing problems running imports on production. !5523 + - Add a log message when a project is scheduled for destruction for debugging. !5540 + - Fix hooks missing on imported GitLab projects. !5549 + - Properly abort a merge when merge conflicts occur. !5569 + - Fix importer for GitHub Pull Requests when a branch was removed. !5573 + - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584 + - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 + +v 8.10.2 + - User can now search branches by name. !5144 + - Page is now properly rendered after committing the first file and creating the first branch. !5399 + - Add branch or tag icon to ref in builds page. !5434 + - Fix backup restore. !5459 + - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 + - Fix issue with autocomplete search not working with enter key. !5466 + - Add iid to MR API response. !5468 + - Disable MySQL foreign key checks before dropping all tables. !5472 + - Ensure relative paths for video are rewritten as we do for images. !5474 + - Ensure current user can retry a build before showing the 'Retry' button. !5476 + - Add ENV variable to skip repository storages validations. !5478 + - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486 + - Don't show comment button in gutter of diffs on MR discussion tab. !5493 + - Rescue Rugged::OSError (lock exists) when creating references. !5497 + - Fix expand all diffs button in compare view. !5500 + - Show release notes in tags list. !5503 + - Fix a bug where forking a project from a repository storage to another would fail. !5509 + - Fix missing schema update for `20160722221922`. !5512 + - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516 + +v 8.10.1 + - Refactor repository storages documentation. !5428 + - Gracefully handle case when keep-around references are corrupted or exist already. !5430 + - Add detailed info on storage path mountpoints. !5437 + - Fix Error 500 when creating Wiki pages with hyphens or spaces. !5444 + - Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page. !5446 + - Ignore invalid trusted proxies in X-Forwarded-For header. !5454 + - Add links to the real markdown.md file for all GFM examples. !5458 + +v 8.10.0 + - Fix profile activity heatmap to show correct day name (eanplatter) + - Speed up ExternalWikiHelper#get_project_wiki_path + - Expose {should,force}_remove_source_branch (Ben Boeckel) + - Add the functionality to be able to rename a file. !5049 + - Disable PostgreSQL statement timeout during migrations + - Fix projects dropdown loading performance with a simplified api cal. !5113 + - Fix commit builds API, return all builds for all pipelines for given commit. !4849 + - Replace Haml with Hamlit to make view rendering faster. !3666 + - Refresh the branch cache after `git gc` runs + - Allow to disable request access button on projects/groups + - Refactor repository paths handling to allow multiple git mount points + - Optimize system note visibility checking by memoizing the visible reference count. !5070 + - Add Application Setting to configure default Repository Path for new projects + - Delete award emoji when deleting a user + - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell) + - Add an API for downloading latest successful build from a particular branch or tag. !5347 + - Avoid data-integrity issue when cleaning up repository archive cache. + - Add link to profile to commit avatar. !5163 (winniehell) + - Wrap code blocks on Activies and Todos page. !4783 (winniehell) + - Align flash messages with left side of page content. !4959 (winniehell) + - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell) + - Use default cursor for table header of project files. !5165 (winniehell) + - Store when and yaml variables in builds table + - Display last commit of deleted branch in push events. !4699 (winniehell) + - Escape file extension when parsing search results. !5141 (winniehell) + - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004 + - Add image border in Markdown preview. !5162 (winniehell) + - Apply the trusted_proxies config to the rack request object for use with rack_attack + - Added the ability to block sign ups using a domain blacklist. !5259 + - Upgrade to Rails 4.2.7. !5236 + - Extend exposed environment variables for CI builds + - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead + - Add API "deploy_keys" for admins to get all deploy keys + - Allow to pull code with deploy key from public projects + - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts) + - 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) + - Allow build email service to be tested + - Added day name to contribution calendar tooltips + - Refactor user authorization check for a single project to avoid querying all user projects + - Make images fit to the size of the viewport. !4810 + - Fix check for New Branch button on Issue page. !4630 (winniehell) + - Fix GFM autocomplete not working on wiki pages + - Fixed enter key not triggering click on first row when searching in a dropdown + - 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. + - Added Rake task for tracking deployments. !5320 + - Fix fetching LFS objects for private CI projects + - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237 + - 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 + - Redirects back to issue after clicking login link + - Eager load award emoji on notes + - Allow to define manual actions/builds on Pipelines and Environments + - Fix pagination when sorting by columns with lots of ties (like priority) + - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 + - Updated project header design + - Issuable collapsed assignee tooltip is now the users name + - Exclude email check from the standard health check + - Updated layout for Projects, Groups, Users on Admin area. !4424 + - Fix changing issue state columns in milestone view + - Update health_check gem to version 2.1.0 + - Add notification settings dropdown for groups + - Render inline diffs for multiple changed lines following eachother + - Wildcards for protected branches. !4665 + - Allow importing from Github using Personal Access Tokens. (Eric K Idema) + - API: Expose `due_date` for issues (Robert Schilling) + - API: Todos. !3188 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling) + - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling) + - Add "Enabled Git access protocols" to Application Settings + - Diffs will create button/diff form on demand no on server side + - Reduce size of HTML used by diff comment forms + - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard) + - 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 + - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes) + - Fix creation of deployment on build that is retried, redeployed or rollback + - Don't parse Rinku returned value to DocFragment when it didn't change the original html string. + - 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 + - Remove unused front-end variable -> default_issues_tracker + - ObjectRenderer retrieve renderer content using Rails.cache.read_multi + - Better caching of git calls on ProjectsController#show. + - Avoid to retrieve MR closes_issues as much as possible. + - Hide project name in project activities. !5068 (winniehell) + - Add API endpoint for a group issues. !4520 (mahcsig) + - Add Bugzilla integration. !4930 (iamtjg) + - Fix new snippet style bug (elliotec) + - Instrument Rinku usage + - Be explicit to define merge request discussion variables + - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab + - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. + - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) + - Set import_url validation to be more strict + - Memoize MR merged/closed events retrieval + - Don't render discussion notes when requesting diff tab through AJAX + - 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 + - Allow to setup event by channel on slack service + - 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 + - Support redirected blobs for Container Registry integration + - 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 + - Fix 404 redirect after validation fails importing a GitLab project + - Added setting to set new users by default as external. !4545 (Dravere) + - Add min value for project limit field on user's form. !3622 (jastkand) + - Reset project pushes_since_gc when we enqueue the git gc call + - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt) + - Collapsed diffs lines/size don't acumulate to overflow diffs. + - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) + - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay) + - Fix GitHub client requests when rate limit is disabled + - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention) + - Redesign Builds and Pipelines pages + - Change status color and icon for running builds + - Fix commenting issue in side by side diff view for unchanged lines + - 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 + - Improve cron_jobs loading error messages. !5318 / !5360 + - Prevent toggling sidebar when clipboard icon clicked + - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) + - Limit the number of retries on error to 3 for exporting projects + - Allow empty repositories on project import/export + - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska) + - Allow bulk (un)subscription from issues in issue index + - Fix MR diff encoding issues exporting GitLab projects + - Move builds settings out of project settings and rename Pipelines + - Add builds badge to Pipelines settings page + - Export and import avatar as part of project import/export + - Fix migration corrupting import data for old version upgrades + - Show tooltip on GitLab export link in new project page + +v 8.9.6 + - Fix importing of events under notes for GitLab projects. !5154 + - Fix log statements in import/export. !5129 + - Fix commit avatar alignment in compare view. !5128 + - Fix broken migration in MySQL. !5005 + - Overwrite Host and X-Forwarded-Host headers in NGINX !5213 + - Keeps issue number when importing from Gitlab.com + - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) + +v 8.9.7 (unreleased) + - Fix import_data wrongly saved as a result of an invalid import_url + +v 8.9.6 + - Fix importing of events under notes for GitLab projects + +v 8.9.5 + - Add more debug info to import/export and memory killer. !5108 + - Fixed avatar alignment in new MR view. !5095 + - Fix diff comments not showing up in activity feed. !5069 + - Add index on both Award Emoji user and name. !5061 + - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056 + - Re-enable import button when import process fails due to namespace already being taken. !5053 + - Fix snippets comments not displayed. !5045 + - Fix emoji paths in relative root configurations. !5027 + - Fix issues importing events in Import/Export. !4987 + - Fixed 'use shortcuts' button on docs. !4979 + - Admin should be able to turn shared runners into specific ones. !4961 + - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi) + - Improve the request / withdraw access button. !4860 + +v 8.9.4 + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + - Fixed search field blur not removing focus. !4704 + - Resolve "Sub nav isn't showing on file view". !4890 + - Fixes middle click and double request when navigating through the file browser. !4891 + - Fixed URL on label button when filtering. !4897 + - Fixed commit avatar alignment. !4933 + - Do not show build retry link when build is active. !4967 + - Fix restore Rake task warning message output. !4980 + - Handle external issues in IssueReferenceFilter. !4988 + - Expiry date on pinned nav cookie. !5009 + - Updated breakpoint for sidebar pinning. !5019 + +v 8.9.3 + - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 + - Fix rendering of commit notes. !4953 + - Resolve "Pin should show up at 1280px min". !4947 + - Switched mobile button icons to ellipsis and angle. !4944 + - Correctly returns todo ID after creating todo. !4941 + - Better debugging for memory killer middleware. !4936 + - Remove duplicate new page btn from edit wiki. !4904 + - Use clock_gettime for all performance timestamps. !4899 + - Use memorized tags array when searching tags by name. !4859 + - Fixed avatar alignment in new MR view. !4901 + - Removed fade when filtering results. !4932 + - Fix missing avatar on system notes. !4954 + - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 + - Use update_columns to bypass all the dirty code on active_record. !4985 + - Fix restore Rake task warning message output !4980 + +v 8.9.2 + - Fix visibility of snippets when searching. + - Fix an information disclosure when requesting access to a group containing private projects. + - Update omniauth-saml to 1.6.0 !4951 + +v 8.9.1 + - Refactor labels documentation. !3347 + - Eager load award emoji on notes. !4628 + - Fix some CI wording in documentation. !4660 + - Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720 + - Add documentation for the export & import features. !4732 + - Add some docs for Docker Registry configuration. !4738 + - Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744 + - Display group/project access requesters separately in the admin area. !4798 + - Add documentation and examples for configuring cloud storage for registry images. !4812 + - Clarifies documentation about artifact expiry. !4831 + - Fix the Network graph links. !4832 + - Fix MR-auto-close text added to description. !4836 + - Add documentation for award emoji now that comments can be awarded with emojis. !4839 + - Fix typo in export failure email. !4847 + - Fix header vertical centering. !4170 + - Fix subsequent SAML sign ins. !4718 + - Set button label when picking an option from status dropdown. !4771 + - Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775 + - Handle external issues in IssueReferenceFilter. !4789 + - Support for rendering/redacting multiple documents. !4828 + - Update Todos documentation and screenshots to include new functionality. !4840 + - Hide nav arrows by default. !4843 + - Added bottom padding to label color suggestion link. !4845 + - Use jQuery objects in ref dropdown. !4850 + - Fix GitLab project import issues related to notes and builds. !4855 + - Restrict header logo to 36px so it doesn't overflow. !4861 + - Fix unwanted label unassignment. !4863 + - Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869 + - Restore old behavior around diff notes to outdated discussions. !4870 + - Fix merge requests project settings help link anchor. !4873 + - Fix 404 when accessing pipelines as guest user on public projects. !4881 + - Remove width restriction for logo on sign-in page. !4888 + - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884 + - Apply selected value as label. !4886 + - Change Retry to Re-deploy on Deployments page + - Fix temp file being deleted after the request while importing a GitLab project. !4894 + - Fix pagination when sorting by columns with lots of ties (like priority) + - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. + - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) + - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912 + - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 + - Remove duplicate 'New Page' button on edit wiki page + +v 8.9.0 + - Fix group visibility form layout in application settings - Fix builds API response not including commit data - Fix error when CI job variables key specified but not defined - Fix pipeline status when there are no builds in pipeline @@ -63,6 +382,8 @@ v 8.9.0 (unreleased) - Limit email on push diff size to 30 files / 150 KB - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - Changed the Slack build message to use the singular duration if necessary (Aran Koning) + - Fix race condition on merge when build succeeds + - Added shortcut to focus filter search fields and added documentation #18120 - Links from a wiki page to other wiki pages should be rewritten as expected - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) - Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393 @@ -95,6 +416,7 @@ v 8.9.0 (unreleased) - Add Application Setting to configure Container Registry token expire delay (default 5min) - Cache assigned issue and merge request counts in sidebar nav - Use Knapsack only in CI environment + - Updated project creation page to match new UI #2542 - Cache project build count in sidebar nav - Add milestone expire date to the right sidebar - Manually mark a issue or merge request as a todo @@ -136,15 +458,6 @@ v 8.9.0 (unreleased) - Remove tanuki logo from side navigation; center on top nav - Include user relationships when retrieving award_emoji - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - -v 8.8.5 (unreleased) - - Ensure branch cleanup regardless of whether the GitHub import process succeeds - - Fix todos page throwing errors when you have a project pending deletion - - Reduce number of SQL queries when rendering user references - - Import GitHub repositories respecting the API rate limit - - Fix importer for GitHub comments on diff - - Disable Webhooks before proceeding with the GitHub import - - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace - Set inverse_of for Project/Service association to reduce the number of queries - Update tanuki logo highlight/loading colors - Remove explicit Gitlab::Metrics.action assignments, are already automatic. @@ -153,12 +466,21 @@ v 8.8.5 (unreleased) - Filter parameters for request_uri value on instrumented transactions. - Remove duplicated keys add UNIQUE index to keys fingerprint column - ExtractsPath get ref_names from repository cache, if not there access git. + - Show a flash warning about the error detail of XHR requests which failed with status code 404 and 500 - Cache user todo counts from TodoService - Ensure Todos counters doesn't count Todos for projects pending delete - - Add tooltip to pin/unpin navbar - Add left/right arrows horizontal navigation + - Add tooltip to pin/unpin navbar - Add new sub nav style to Wiki and Graphs sub navigation +v 8.8.7 + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +v 8.8.6 + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + v 8.8.5 - Import GitHub repositories respecting the API rate limit !4166 - Fix todos page throwing errors when you have a project pending deletion !4300 @@ -289,6 +611,14 @@ v 8.8.0 - When creating a .gitignore file a dropdown with templates will be provided - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 +v 8.7.9 + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +v 8.7.8 + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + v 8.7.7 - Fix import by `Any Git URL` broken if the URL contains a space - Prevent unauthorized access to other projects build traces @@ -2015,8 +2345,6 @@ v 7.7.0 - Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update - Remove password strength indicator - - v 7.6.0 - Fork repository to groups - New rugged version @@ -2442,13 +2770,13 @@ v 6.5.0 - Files API supports base64 encoded content (sponsored by O'Reilly Media) - Added support for Go's repository retrieval (Bruno Albuquerque) -v6.4.3 +v 6.4.3 - Don't use unicorn worker killer if PhusionPassenger is defined -v6.4.2 +v 6.4.2 - Fixed wrong behaviour of script/upgrade.rb -v6.4.1 +v 6.4.1 - Fixed bug with repository rename - Fixed bug with project transfer diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f447221477..14ff05c9aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -145,7 +145,8 @@ might be edited to make them small and simple. You are encouraged to use the template below for feature proposals. ``` -## Description including problem, use cases, benefits, and/or goals +## Description +Include problem, use cases, benefits, and/or goals ## Proposal diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 4a36342fca..e4604e3afd 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.0.0 +3.2.1 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 8bd6ba8c5c..e7c7d3cc3c 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.5 +0.7.8 diff --git a/Gemfile b/Gemfile index 092ea9d69b..92e666c1bb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ -source "https://rubygems.org" +source 'https://rubygems.org' -gem 'rails', '4.2.6' +gem 'rails', '4.2.7' gem 'rails-deprecated_sanitizer', '~> 1.0.3' # Responders respond_to and respond_with @@ -11,15 +11,15 @@ gem 'responders', '~> 2.0' gem 'sprockets', '~> 3.6.0' # Default values for AR models -gem "default_value_for", "~> 3.0.0" +gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem "mysql2", '~> 0.3.16', group: :mysql -gem "pg", '~> 0.18.2', group: :postgres +gem 'mysql2', '~> 0.3.16', group: :mysql +gem 'pg', '~> 0.18.2', group: :postgres # Authentication libraries gem 'devise', '~> 4.0' -gem 'doorkeeper', '~> 3.1' +gem 'doorkeeper', '~> 4.0' gem 'omniauth', '~> 1.3.1' gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' @@ -28,9 +28,9 @@ gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-facebook', '~> 3.0.0' gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' -gem 'omniauth-google-oauth2', '~> 0.2.0' +gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos -gem 'omniauth-saml', '~> 1.5.0' +gem 'omniauth-saml', '~> 1.6.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' @@ -48,24 +48,24 @@ gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' # Browser detection -gem "browser", '~> 2.2' +gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 10.2' +gem 'gitlab_git', '~> 10.3.2' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master -gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" +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 -gem "github-linguist", "~> 4.7.0", require: "linguist" +gem 'github-linguist', '~> 4.7.0', require: 'linguist' # API gem 'grape', '~> 0.13.0' @@ -73,13 +73,13 @@ gem 'grape-entity', '~> 0.4.2' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' # Pagination -gem "kaminari", "~> 0.17.0" +gem 'kaminari', '~> 0.17.0' # HAML -gem "haml-rails", '~> 0.9.0' +gem 'hamlit', '~> 2.5' # Files attachments -gem "carrierwave", '~> 0.10.0' +gem 'carrierwave', '~> 0.10.0' # Drag and Drop UI gem 'dropzonejs-rails', '~> 0.7.1' @@ -91,28 +91,29 @@ gem 'fog-core', '~> 1.40' gem 'fog-local', '~> 0.3' gem 'fog-google', '~> 0.3' gem 'fog-openstack', '~> 0.1' +gem 'fog-rackspace', '~> 0.1.1' # for aws storage -gem "unf", '~> 0.1.4' +gem 'unf', '~> 0.1.4' # Authorization -gem "six", '~> 0.2.0' +gem 'six', '~> 0.2.0' # Seed data -gem "seed-fu", '~> 2.3.5' +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.2.9' +gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' 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 @@ -123,29 +124,29 @@ gem 'diffy', '~> 3.0.3' # Application server group :unicorn do - gem "unicorn", '~> 4.9.0' + gem 'unicorn', '~> 4.9.0' gem 'unicorn-worker-killer', '~> 0.4.2' end # State machine -gem "state_machines-activerecord", '~> 0.4.0' +gem 'state_machines-activerecord', '~> 0.4.0' # Run events after state machine commits -gem 'after_commit_queue' +gem 'after_commit_queue', '~> 1.3.0' # Issue tags gem 'acts-as-taggable-on', '~> 3.4' # Background jobs -gem 'sinatra', '~> 1.4.4', require: nil +gem 'sinatra', '~> 1.4.4', require: false gem 'sidekiq', '~> 4.0' gem 'sidekiq-cron', '~> 0.4.0' -gem 'redis-namespace' +gem 'redis-namespace', '~> 1.5.2' # HTTP requests -gem "httparty", '~> 0.13.3' +gem 'httparty', '~> 0.13.3' # Colored output to console -gem "rainbow", '~> 2.1.0' +gem 'rainbow', '~> 2.1.0' # GitLab settings gem 'settingslogic', '~> 2.0.9' @@ -155,7 +156,7 @@ gem 'settingslogic', '~> 2.0.9' gem 'version_sorter', '~> 2.0.0' # Cache -gem "redis-rails", '~> 4.0.0' +gem 'redis-rails', '~> 4.0.0' # Redis gem 'redis', '~> 3.2' @@ -168,13 +169,13 @@ gem 'tinder', '~> 1.10.0' gem 'hipchat', '~> 1.5.0' # Flowdock integration -gem "gitlab-flowdock-git-hook", "~> 1.0.1" +gem 'gitlab-flowdock-git-hook', '~> 1.0.1' # Gemnasium integration -gem "gemnasium-gitlab-service", "~> 0.2" +gem 'gemnasium-gitlab-service', '~> 0.2' # Slack integration -gem "slack-notifier", "~> 1.2.0" +gem 'slack-notifier', '~> 1.2.0' # Asana integration gem 'asana', '~> 0.4.0' @@ -186,20 +187,20 @@ gem 'ruby-fogbugz', '~> 0.2.1' gem 'd3_rails', '~> 3.5.0' # underscore-rails -gem "underscore-rails", "~> 1.8.0" +gem 'underscore-rails', '~> 1.8.0' # Sanitize user input -gem "sanitize", '~> 2.0' +gem 'sanitize', '~> 2.0' gem 'babosa', '~> 1.0.2' # Sanitizes SVG input -gem "loofah", "~> 2.0.3" +gem 'loofah', '~> 2.0.3' # Working with license gem 'licensee', '~> 8.0.0' # Protect against bruteforcing -gem "rack-attack", '~> 4.3.1' +gem 'rack-attack', '~> 4.3.1' # Ace editor gem 'ace-rails-ap', '~> 4.0.2' @@ -213,16 +214,16 @@ gem 'charlock_holmes', '~> 0.7.3' # Parse duration gem 'chronic_duration', '~> 0.10.6' -gem "sass-rails", '~> 5.0.0' -gem "coffee-rails", '~> 4.1.0' -gem "uglifier", '~> 2.7.2' +gem 'sass-rails', '~> 5.0.0' +gem 'coffee-rails', '~> 4.1.0' +gem 'uglifier', '~> 2.7.2' gem 'turbolinks', '~> 2.5.0' gem 'jquery-turbolinks', '~> 2.1.0' gem 'addressable', '~> 2.3.8' gem 'bootstrap-sass', '~> 3.3.0' gem 'font-awesome-rails', '~> 4.6.1' -gem 'gitlab_emoji', '~> 0.3.0' +gem 'gemojione', '~> 3.0' gem 'gon', '~> 6.0.1' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' @@ -234,7 +235,7 @@ gem 'net-ssh', '~> 3.0.1' gem 'base32', '~> 0.3.0' # Sentry integration -gem 'sentry-raven', '~> 0.15' +gem 'sentry-raven', '~> 1.1.0' gem 'premailer-rails', '~> 1.9.0' @@ -246,14 +247,13 @@ group :metrics do end group :development do - gem "foreman" + gem 'foreman', '~> 0.78.0' gem 'brakeman', '~> 3.3.0', require: false gem 'letter_opener_web', '~> 1.3.0' - gem 'quiet_assets', '~> 1.0.2' gem 'rerun', '~> 0.11.0' - gem 'bullet', require: false - gem 'rblineprof', platform: :mri, require: false + gem 'bullet', '~> 5.0.0', require: false + gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'web-console', '~> 2.0' # Better errors handler @@ -261,23 +261,23 @@ group :development do gem 'binding_of_caller', '~> 0.7.2' # Docs generator - gem "sdoc", '~> 0.3.20' + gem 'sdoc', '~> 0.3.20' # thin instead webrick - gem 'thin', '~> 1.6.1' + gem 'thin', '~> 1.7.0' end group :development, :test do - gem 'byebug', platform: :mri - gem 'pry-rails' + gem 'byebug', '~> 8.2.1', platform: :mri + gem 'pry-rails', '~> 0.3.4' gem 'awesome_print', '~> 1.2.0', require: false gem 'fuubar', '~> 2.0.0' gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails', '~> 4.6.0' - gem 'rspec-rails', '~> 3.4.0' - gem 'rspec-retry' + gem 'rspec-rails', '~> 3.5.0' + gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' @@ -299,19 +299,18 @@ 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 'coveralls', '~> 0.8.2', require: false gem 'simplecov', '~> 0.11.0', require: false - gem 'flog', require: false - gem 'flay', require: false - gem 'bundler-audit', require: false + gem 'flog', '~> 4.3.2', require: false + gem 'flay', '~> 2.6.1', require: false + gem 'bundler-audit', '~> 0.5.0', require: false - gem 'benchmark-ips', require: false + gem 'benchmark-ips', '~> 2.3.0', require: false - gem "license_finder", require: false - gem 'knapsack' + gem 'license_finder', '~> 2.1.0', require: false + gem 'knapsack', '~> 1.11.0' end group :test do @@ -319,30 +318,34 @@ group :test do gem 'email_spec', '~> 1.6.0' gem 'webmock', '~> 1.21.0' gem 'test_after_commit', '~> 0.4.2' - gem 'sham_rack' + gem 'sham_rack', '~> 1.3.6' end group :production do - gem "gitlab_meta", '7.0' + gem 'gitlab_meta', '7.0' end -gem "newrelic_rpm", '~> 3.14' +gem 'newrelic_rpm', '~> 3.14' gem 'octokit', '~> 4.3.0' -gem "mail_room", "~> 0.8" +gem 'mail_room', '~> 0.8' gem 'email_reply_parser', '~> 0.5.8' ## CI gem 'activerecord-session_store', '~> 1.0.0' -gem "nested_form", '~> 0.3.2' +gem 'nested_form', '~> 0.3.2' # OAuth -gem 'oauth2', '~> 1.0.0' +gem 'oauth2', '~> 1.2.0' # Soft deletion -gem "paranoia", "~> 2.0" +gem 'paranoia', '~> 2.0' # Health check -gem 'health_check', '~> 1.5.1' +gem 'health_check', '~> 2.1.0' + +# System information +gem 'vmstat', '~> 2.1.1' +gem 'sys-filesystem', '~> 1.1.6' diff --git a/Gemfile.lock b/Gemfile.lock index ba16e4bf33..e2b3d55ee0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,36 +1,36 @@ GEM remote: https://rubygems.org/ specs: - RedCloth (4.2.9) + RedCloth (4.3.2) ace-rails-ap (4.0.2) - actionmailer (4.2.6) - actionpack (= 4.2.6) - actionview (= 4.2.6) - activejob (= 4.2.6) + actionmailer (4.2.7) + actionpack (= 4.2.7) + actionview (= 4.2.7) + activejob (= 4.2.7) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.6) - actionview (= 4.2.6) - activesupport (= 4.2.6) + actionpack (4.2.7) + actionview (= 4.2.7) + activesupport (= 4.2.7) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.6) - activesupport (= 4.2.6) + actionview (4.2.7) + activesupport (= 4.2.7) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.6) - activesupport (= 4.2.6) + activejob (4.2.7) + activesupport (= 4.2.7) globalid (>= 0.3.0) - activemodel (4.2.6) - activesupport (= 4.2.6) + activemodel (4.2.7) + activesupport (= 4.2.7) builder (~> 3.1) - activerecord (4.2.6) - activemodel (= 4.2.6) - activesupport (= 4.2.6) + activerecord (4.2.7) + activemodel (= 4.2.7) + activesupport (= 4.2.7) arel (~> 6.0) activerecord-session_store (1.0.0) actionpack (>= 4.0, < 5.1) @@ -38,7 +38,7 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 1.5.2, < 3) railties (>= 4.0, < 5.1) - activesupport (4.2.6) + activesupport (4.2.7) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) @@ -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) @@ -141,12 +141,6 @@ GEM colorize (0.7.7) concurrent-ruby (1.0.2) connection_pool (2.2.0) - coveralls (0.8.13) - json (~> 1.8) - simplecov (~> 0.11.0) - term-ansicolor (~> 1.3) - thor (~> 0.19.1) - tins (~> 1.6.0) crack (0.4.3) safe_yaml (~> 1.0.0) creole (0.5.0) @@ -177,8 +171,8 @@ GEM diff-lcs (1.2.5) diffy (3.0.7) docile (1.1.5) - doorkeeper (3.1.0) - railties (>= 3.2) + doorkeeper (4.0.0) + railties (>= 4.2) dropzonejs-rails (0.7.2) rails (> 3.1) email_reply_parser (0.5.8) @@ -243,6 +237,11 @@ GEM fog-core (>= 1.39) fog-json (>= 1.0) ipaddress (>= 0.8) + fog-rackspace (0.1.1) + fog-core (>= 1.35) + fog-json (>= 1.0) + fog-xml (>= 0.1) + ipaddress (>= 0.8) fog-xml (0.1.2) fog-core nokogiri (~> 1.5, >= 1.5.11) @@ -256,7 +255,7 @@ GEM ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) - gemojione (2.2.1) + gemojione (3.0.1) json get_process_mem (0.2.0) gherkin-ruby (0.3.2) @@ -265,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) @@ -275,9 +274,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_emoji (0.3.1) - gemojione (~> 2.2, >= 2.2.1) - gitlab_git (10.2.0) + gitlab_git (10.3.2) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -290,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) @@ -320,28 +317,19 @@ GEM grape-entity (0.4.8) activesupport multi_json (>= 1.3.2) - haml (4.0.7) + hamlit (2.5.0) + temple (~> 0.7.6) + thor tilt - haml-rails (0.9.0) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - haml (>= 4.0.6, < 5.0) - html2haml (>= 1.0.1) - railties (>= 4.0.1) hashie (3.4.3) - health_check (1.5.1) - rails (>= 2.3.0) + health_check (2.1.0) + rails (>= 4.0) hipchat (1.5.2) httparty mimemagic html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) - html2haml (2.0.0) - erubis (~> 2.7.0) - haml (~> 4.0.0) - nokogiri (~> 1.6.0) - ruby_parser (~> 3.5) htmlentities (4.3.4) http_parser.rb (0.5.3) httparty (0.13.7) @@ -365,7 +353,7 @@ GEM jquery-ui-rails (5.0.5) railties (>= 3.2.16) json (1.8.3) - jwt (1.5.2) + jwt (1.5.4) kaminari (0.17.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) @@ -405,7 +393,7 @@ GEM mini_portile2 (2.1.0) minitest (5.7.0) mousetrap-rails (1.4.6) - multi_json (1.11.2) + multi_json (1.12.1) multi_xml (0.5.5) multipart-post (2.0.0) mysql2 (0.3.20) @@ -418,12 +406,12 @@ GEM pkg-config (~> 1.1.7) numerizer (0.1.1) oauth (0.4.7) - oauth2 (1.0.0) + oauth2 (1.2.0) faraday (>= 0.8, < 0.10) jwt (~> 1.0) multi_json (~> 1.3) multi_xml (~> 0.5) - rack (~> 1.2) + rack (>= 1.2, < 3) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) omniauth (1.3.1) @@ -451,7 +439,7 @@ GEM omniauth-gitlab (1.0.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.0) - omniauth-google-oauth2 (0.2.10) + omniauth-google-oauth2 (0.4.1) addressable (~> 2.3) jwt (~> 1.0) multi_json (~> 1.3) @@ -468,9 +456,9 @@ GEM omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-saml (1.5.0) + omniauth-saml (1.6.0) omniauth (~> 1.3) - ruby-saml (~> 1.1, >= 1.1.1) + ruby-saml (~> 1.3) omniauth-shibboleth (1.2.1) omniauth (>= 1.0.0) omniauth-twitter (1.2.1) @@ -485,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) @@ -509,8 +497,6 @@ GEM pry-rails (0.3.4) pry (>= 0.9.10) pyu-ruby-sasl (0.0.3.3) - quiet_assets (1.0.3) - railties (>= 3.1, < 5.0) rack (1.6.4) rack-accept (0.4.5) rack (>= 0.4) @@ -529,16 +515,16 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.6) - actionmailer (= 4.2.6) - actionpack (= 4.2.6) - actionview (= 4.2.6) - activejob (= 4.2.6) - activemodel (= 4.2.6) - activerecord (= 4.2.6) - activesupport (= 4.2.6) + rails (4.2.7) + actionmailer (= 4.2.7) + actionpack (= 4.2.7) + actionview (= 4.2.7) + activejob (= 4.2.7) + activemodel (= 4.2.7) + activerecord (= 4.2.7) + activesupport (= 4.2.7) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.6) + railties (= 4.2.7) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -548,9 +534,9 @@ GEM rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.6) - actionpack (= 4.2.6) - activesupport (= 4.2.6) + railties (4.2.7) + actionpack (= 4.2.7) + activesupport (= 4.2.7) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.1.0) @@ -566,7 +552,7 @@ GEM recaptcha (3.0.0) json redcarpet (3.3.3) - redis (3.3.0) + redis (3.2.2) redis-actionpack (4.0.1) actionpack (~> 4) redis-rack (~> 1.5.0) @@ -590,38 +576,38 @@ GEM listen (~> 3.0) responders (2.1.1) railties (>= 4.2.0, < 5.1) - rinku (1.7.3) + rinku (2.0.0) rotp (2.1.2) - rouge (1.11.0) + rouge (2.0.5) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) - rspec (3.4.0) - rspec-core (~> 3.4.0) - rspec-expectations (~> 3.4.0) - rspec-mocks (~> 3.4.0) - rspec-core (3.4.4) - rspec-support (~> 3.4.0) - rspec-expectations (3.4.0) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.0) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.4.0) - rspec-mocks (3.4.1) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.4.0) - rspec-rails (3.4.2) - actionpack (>= 3.0, < 4.3) - activesupport (>= 3.0, < 4.3) - railties (>= 3.0, < 4.3) - rspec-core (~> 3.4.0) - rspec-expectations (~> 3.4.0) - rspec-mocks (~> 3.4.0) - rspec-support (~> 3.4.0) + rspec-support (~> 3.5.0) + rspec-rails (3.5.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-support (~> 3.5.0) rspec-retry (0.4.5) rspec-core - rspec-support (3.4.1) - rubocop (0.40.0) - parser (>= 2.3.1.0, < 3.0) + rspec-support (3.5.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) @@ -631,9 +617,8 @@ GEM ruby-fogbugz (0.2.1) crack (~> 0.4) ruby-progressbar (1.8.1) - ruby-saml (1.1.2) + ruby-saml (1.3.0) nokogiri (>= 1.5.10) - uuid (~> 2.3) ruby_parser (3.8.2) sexp_processor (~> 4.1) rubyntlm (0.5.2) @@ -645,8 +630,8 @@ GEM sanitize (2.1.0) nokogiri (>= 1.4.4) sass (3.4.22) - sass-rails (5.0.4) - railties (>= 4.0.0, < 5.0) + sass-rails (5.0.5) + railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) @@ -660,12 +645,12 @@ GEM sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) - seed-fu (2.3.5) - activerecord (>= 3.1, < 4.3) - activesupport (>= 3.1, < 4.3) + seed-fu (2.3.6) + activerecord (>= 3.1) + activesupport (>= 3.1) select2-rails (3.5.9.3) thor (~> 0.14) - sentry-raven (0.15.6) + sentry-raven (1.1.0) faraday (>= 0.7.6) settingslogic (2.0.9) sexp_processor (4.7.0) @@ -673,10 +658,11 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.1.2) + sidekiq (4.1.4) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) redis (~> 3.2, >= 3.2.1) + sinatra (>= 1.4.7) sidekiq-cron (0.4.0) redis-namespace (>= 1.5.2) rufus-scheduler (>= 2.0.24) @@ -687,8 +673,8 @@ GEM json (~> 1.8) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - sinatra (1.4.6) - rack (~> 1.4) + sinatra (1.4.7) + rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) six (0.2.0) @@ -704,17 +690,17 @@ GEM spinach (>= 0.4) spinach-rerun-reporter (0.0.2) spinach (~> 0.8) - spring (1.7.1) + spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.1.0) spring (>= 0.9.1) spring-commands-teaspoon (0.0.2) spring (>= 0.9.1) - sprockets (3.6.0) + sprockets (3.6.3) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.0.4) + sprockets-rails (3.1.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) @@ -726,6 +712,8 @@ GEM activerecord (>= 4.1, < 5.1) state_machines-activemodel (>= 0.3.0) stringex (2.5.2) + sys-filesystem (1.1.6) + ffi systemu (2.6.5) task_list (1.0.2) html-pipeline @@ -733,14 +721,13 @@ GEM railties (>= 3.2.5, < 6) teaspoon-jasmine (2.2.0) teaspoon (>= 1.0.0) - term-ansicolor (1.3.2) - tins (~> 1.0) + temple (0.7.7) test_after_commit (0.4.2) activerecord (>= 3.2) - thin (1.6.4) + thin (1.7.0) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) - rack (~> 1.0) + rack (>= 1, < 3) thor (0.19.1) thread_safe (0.3.5) tilt (2.0.5) @@ -755,7 +742,6 @@ GEM mime-types multi_json (~> 1.7) twitter-stream (~> 0.1) - tins (1.6.0) turbolinks (2.5.3) coffee-rails twitter-stream (0.1.16) @@ -772,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 @@ -789,6 +775,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) + vmstat (2.1.1) warden (1.2.6) rack (>= 1.0) web-console (2.3.0) @@ -814,12 +801,12 @@ PLATFORMS ruby DEPENDENCIES - RedCloth (~> 4.2.9) + RedCloth (~> 4.3.2) ace-rails-ap (~> 4.0.2) activerecord-session_store (~> 1.0.0) acts-as-taggable-on (~> 3.4) addressable (~> 2.3.8) - after_commit_queue + after_commit_queue (~> 1.3.0) akismet (~> 2.0) allocations (~> 1.0) asana (~> 0.4.0) @@ -828,15 +815,15 @@ DEPENDENCIES awesome_print (~> 1.2.0) babosa (~> 1.0.2) base32 (~> 0.3.0) - benchmark-ips + benchmark-ips (~> 2.3.0) better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) brakeman (~> 3.3.0) browser (~> 2.2) - bullet - bundler-audit - byebug + bullet (~> 5.0.0) + bundler-audit (~> 0.5.0) + byebug (~> 8.2.1) capybara (~> 2.6.2) capybara-screenshot (~> 1.0.0) carrierwave (~> 0.10.0) @@ -844,7 +831,6 @@ DEPENDENCIES chronic_duration (~> 0.10.6) coffee-rails (~> 4.1.0) connection_pool (~> 2.0) - coveralls (~> 0.8.2) creole (~> 0.5.0) d3_rails (~> 3.5.0) database_cleaner (~> 1.4.0) @@ -852,38 +838,39 @@ DEPENDENCIES devise (~> 4.0) devise-two-factor (~> 3.0.0) diffy (~> 3.0.3) - doorkeeper (~> 3.1) + doorkeeper (~> 4.0) dropzonejs-rails (~> 0.7.1) email_reply_parser (~> 0.5.8) email_spec (~> 1.6.0) factory_girl_rails (~> 4.6.0) ffaker (~> 2.0.0) - flay - flog + flay (~> 2.6.1) + flog (~> 4.3.2) fog-aws (~> 0.9) fog-azure (~> 0.0) fog-core (~> 1.40) fog-google (~> 0.3) fog-local (~> 0.3) fog-openstack (~> 0.1) + fog-rackspace (~> 0.1.1) font-awesome-rails (~> 4.6.1) - foreman + foreman (~> 0.78.0) fuubar (~> 2.0.0) gemnasium-gitlab-service (~> 0.2) + gemojione (~> 3.0) github-linguist (~> 4.7.0) - github-markup (~> 1.3.1) + github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_emoji (~> 0.3.0) - gitlab_git (~> 10.2) + gitlab_git (~> 10.3.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) grape-entity (~> 0.4.2) - haml-rails (~> 0.9.0) - health_check (~> 1.5.1) + hamlit (~> 2.5) + health_check (~> 2.1.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty (~> 0.13.3) @@ -894,9 +881,9 @@ DEPENDENCIES jquery-ui-rails (~> 5.0.0) jwt kaminari (~> 0.17.0) - knapsack + knapsack (~> 1.11.0) letter_opener_web (~> 1.3.0) - license_finder + license_finder (~> 2.1.0) licensee (~> 8.0.0) loofah (~> 2.0.3) mail_room (~> 0.8) @@ -908,7 +895,7 @@ DEPENDENCIES net-ssh (~> 3.0.1) newrelic_rpm (~> 3.14) nokogiri (~> 1.6.7, >= 1.6.7.2) - oauth2 (~> 1.0.0) + oauth2 (~> 1.2.0) octokit (~> 4.3.0) omniauth (~> 1.3.1) omniauth-auth0 (~> 1.4.1) @@ -918,9 +905,9 @@ DEPENDENCIES omniauth-facebook (~> 3.0.0) omniauth-github (~> 1.1.1) omniauth-gitlab (~> 1.0.0) - omniauth-google-oauth2 (~> 0.2.0) + omniauth-google-oauth2 (~> 0.4.1) omniauth-kerberos (~> 0.3.0) - omniauth-saml (~> 1.5.0) + omniauth-saml (~> 1.6.0) omniauth-shibboleth (~> 1.2.0) omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) @@ -929,29 +916,28 @@ DEPENDENCIES pg (~> 0.18.2) poltergeist (~> 1.9.0) premailer-rails (~> 1.9.0) - pry-rails - quiet_assets (~> 1.0.2) + pry-rails (~> 0.3.4) rack-attack (~> 4.3.1) rack-cors (~> 0.4.0) rack-oauth2 (~> 1.2.1) - rails (= 4.2.6) + rails (= 4.2.7) rails-deprecated_sanitizer (~> 1.0.3) rainbow (~> 2.1.0) - rblineprof + rblineprof (~> 0.3.6) rdoc (~> 3.6) recaptcha (~> 3.0) redcarpet (~> 3.3.3) redis (~> 3.2) - redis-namespace + redis-namespace (~> 1.5.2) redis-rails (~> 4.0.0) 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.4.0) - rspec-retry - rubocop (~> 0.40.0) + rspec-rails (~> 3.5.0) + rspec-retry (~> 0.4.5) + rubocop (~> 0.41.2) rubocop-rspec (~> 1.5.0) ruby-fogbugz (~> 0.2.1) sanitize (~> 2.0) @@ -960,9 +946,9 @@ DEPENDENCIES sdoc (~> 0.3.20) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) - sentry-raven (~> 0.15) + sentry-raven (~> 1.1.0) settingslogic (~> 2.0.9) - sham_rack + sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) sidekiq (~> 4.0) sidekiq-cron (~> 0.4.0) @@ -978,11 +964,12 @@ DEPENDENCIES spring-commands-teaspoon (~> 0.0.2) sprockets (~> 3.6.0) state_machines-activerecord (~> 0.4.0) + sys-filesystem (~> 1.1.6) task_list (~> 1.0.2) teaspoon (~> 1.1.0) teaspoon-jasmine (~> 2.2.0) test_after_commit (~> 0.4.2) - thin (~> 1.6.1) + thin (~> 1.7.0) tinder (~> 1.10.0) turbolinks (~> 2.5.0) u2f (~> 0.2.1) @@ -993,6 +980,7 @@ DEPENDENCIES unicorn-worker-killer (~> 0.4.2) version_sorter (~> 2.0.0) virtus (~> 1.0.1) + vmstat (~> 2.1.1) web-console (~> 2.0) webmock (~> 1.21.0) wikicloth (= 0.8.1) diff --git a/MAINTENANCE.md b/MAINTENANCE.md index d3d3667069..1efb2a35f6 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -1,15 +1,35 @@ # GitLab Maintenance Policy -GitLab is a fast moving and evolving project. We currently don't have the resources to support many releases concurrently. We support exactly one stable release at any given time. +GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: +`(Major).(Minor).(Patch)` in a [pragmatic way]. -GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e). +- **Major version**: Whenever there is something significant or any backwards + incompatible changes are introduced to the public API. +- **Minor version**: When new, backwards compatible functionality is introduced + to the public API or a minor feature is introduced, or when a set of smaller + features is rolled out. +- **Patch number**: When backwards compatible bug fixes are introduced that fix + incorrect behavior. -- **Major version**: Whenever there is something significant or any backwards incompatible changes are introduced to the public API. -- **Minor version**: When new, backwards compatible functionality is introduced to the public API or a minor feature is introduced, or when a set of smaller features is rolled out. -- **Patch number**: When backwards compatible bug fixes are introduced that fix incorrect behavior. +The current stable release will receive security patches and bug fixes +(eg. `8.9.0` -> `8.9.1`). Feature releases will mark the next supported stable +release where the minor version is increased numerically by increments of one +(eg. `8.9 -> 8.10`). -The current stable release will receive security patches and bug fixes (eg. `5.0` -> `5.0.1`). Feature releases will mark the next supported stable release where the minor version is increased numerically by increments of one (eg. `5.0 -> 5.1`). +Our current policy is to support one stable release at any given time, but for +medium-level security issues, we may consider [backporting to the previous two +monthly releases][rel-sec]. -We encourage everyone to run the latest stable release to ensure that you can easily upgrade to the most secure and feature rich GitLab experience. In order to make sure you can easily run the most recent stable release, we are working hard to keep the update process simple and reliable. +We encourage everyone to run the latest stable release to ensure that you can +easily upgrade to the most secure and feature-rich GitLab experience. In order +to make sure you can easily run the most recent stable release, we are working +hard to keep the update process simple and reliable. -More information about the release procedures can be found in the doc/release directory. +More information about the release procedures can be found in our +[release-tools documentation][rel]. You may also want to read our +[Responsible Disclosure Policy][disclosure]. + +[rel-sec]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/security.md#backporting +[rel]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/ +[disclosure]: https://about.gitlab.com/disclosure/ +[pragmatic way]: https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e diff --git a/VERSION b/VERSION index e5c15102d9..c92c95d0fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.9.0 +8.10.5 diff --git a/app/assets/images/auth_buttons/azure_64.png b/app/assets/images/auth_buttons/azure_64.png index a82c751e00..85de779344 100644 Binary files a/app/assets/images/auth_buttons/azure_64.png and b/app/assets/images/auth_buttons/azure_64.png differ diff --git a/app/assets/images/auth_buttons/bitbucket_64.png b/app/assets/images/auth_buttons/bitbucket_64.png index 4b90a57bc7..b3d022a5a7 100644 Binary files a/app/assets/images/auth_buttons/bitbucket_64.png and b/app/assets/images/auth_buttons/bitbucket_64.png differ diff --git a/app/assets/images/auth_buttons/facebook_64.png b/app/assets/images/auth_buttons/facebook_64.png index 1f1a80d736..71ffb1c6a1 100644 Binary files a/app/assets/images/auth_buttons/facebook_64.png and b/app/assets/images/auth_buttons/facebook_64.png differ diff --git a/app/assets/images/auth_buttons/github_64.png b/app/assets/images/auth_buttons/github_64.png index 182a1a3f73..1fa19c55d2 100644 Binary files a/app/assets/images/auth_buttons/github_64.png and b/app/assets/images/auth_buttons/github_64.png differ diff --git a/app/assets/images/auth_buttons/gitlab_64.png b/app/assets/images/auth_buttons/gitlab_64.png index 99a40583b3..f675678dc9 100644 Binary files a/app/assets/images/auth_buttons/gitlab_64.png and b/app/assets/images/auth_buttons/gitlab_64.png differ diff --git a/app/assets/images/auth_buttons/google_64.png b/app/assets/images/auth_buttons/google_64.png index fb64f8bee6..720824230a 100644 Binary files a/app/assets/images/auth_buttons/google_64.png and b/app/assets/images/auth_buttons/google_64.png differ diff --git a/app/assets/images/auth_buttons/twitter_64.png b/app/assets/images/auth_buttons/twitter_64.png index e3bd9169a3..a4f14de57a 100644 Binary files a/app/assets/images/auth_buttons/twitter_64.png and b/app/assets/images/auth_buttons/twitter_64.png differ diff --git a/app/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png index e5fe659ba6..5c55bc79de 100644 Binary files a/app/assets/images/bg_fallback.png and b/app/assets/images/bg_fallback.png differ diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png index 2ef58e5254..8855babf14 100644 Binary files a/app/assets/images/dark-scheme-preview.png and b/app/assets/images/dark-scheme-preview.png differ diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png index 1e7cf79ea4..6f1a34a559 100644 Binary files a/app/assets/images/emoji.png and b/app/assets/images/emoji.png differ diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png index 74d67f7520..dc9cae1d44 100644 Binary files a/app/assets/images/emoji@2x.png and b/app/assets/images/emoji@2x.png differ diff --git a/app/assets/images/gitlab_logo.png b/app/assets/images/gitlab_logo.png index 0c157546b9..ca30b45901 100644 Binary files a/app/assets/images/gitlab_logo.png and b/app/assets/images/gitlab_logo.png differ diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png index 78f17a9af7..4a55fdc225 100644 Binary files a/app/assets/images/gitorious-logo-black.png and b/app/assets/images/gitorious-logo-black.png differ diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png index 4962cffba3..5eaa327d3d 100644 Binary files a/app/assets/images/gitorious-logo-blue.png and b/app/assets/images/gitorious-logo-blue.png differ diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png index 7d89da97e1..5b55e12571 100644 Binary files a/app/assets/images/icon-link.png and b/app/assets/images/icon-link.png differ diff --git a/app/assets/images/images.png b/app/assets/images/images.png index ad146246ca..bd60de994c 100644 Binary files a/app/assets/images/images.png and b/app/assets/images/images.png differ diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png index fbb339c6a9..d9c7d2d804 100644 Binary files a/app/assets/images/monokai-scheme-preview.png and b/app/assets/images/monokai-scheme-preview.png differ diff --git a/app/assets/images/msapplication-tile.png b/app/assets/images/msapplication-tile.png index 58bbf2b20c..1e0e2ed73c 100644 Binary files a/app/assets/images/msapplication-tile.png and b/app/assets/images/msapplication-tile.png differ diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png index 8287acbce1..5383d687b5 100644 Binary files a/app/assets/images/no_avatar.png and b/app/assets/images/no_avatar.png differ diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png index bfb31bb218..71612d5528 100644 Binary files a/app/assets/images/no_group_avatar.png and b/app/assets/images/no_group_avatar.png differ diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png index 884378ec96..52ad11ab7a 100644 Binary files a/app/assets/images/slider_handles.png and b/app/assets/images/slider_handles.png differ diff --git a/app/assets/images/touch-icon-ipad-retina.png b/app/assets/images/touch-icon-ipad-retina.png index feb32b48ec..516dc2f471 100644 Binary files a/app/assets/images/touch-icon-ipad-retina.png and b/app/assets/images/touch-icon-ipad-retina.png differ diff --git a/app/assets/images/touch-icon-ipad.png b/app/assets/images/touch-icon-ipad.png index a6ddc54350..b2093d015b 100644 Binary files a/app/assets/images/touch-icon-ipad.png and b/app/assets/images/touch-icon-ipad.png differ diff --git a/app/assets/images/touch-icon-iphone-retina.png b/app/assets/images/touch-icon-iphone-retina.png index 8bf7ccb753..438654e0d2 100644 Binary files a/app/assets/images/touch-icon-iphone-retina.png and b/app/assets/images/touch-icon-iphone-retina.png differ diff --git a/app/assets/images/touch-icon-iphone.png b/app/assets/images/touch-icon-iphone.png index 87da550f8b..e5f87fbbcf 100644 Binary files a/app/assets/images/touch-icon-iphone.png and b/app/assets/images/touch-icon-iphone.png differ diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index b2b8e1b7ff..90c09619f8 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -38,3 +38,14 @@ class @Admin $('li.group_member').bind 'ajax:success', -> Turbolinks.visit(location.href) + + showBlacklistType = -> + if $("input[name='blacklist_type']:checked").val() == 'file' + $('.blacklist-file').show() + $('.blacklist-raw').hide() + else + $('.blacklist-file').hide() + $('.blacklist-raw').show() + + $("input[name='blacklist_type']").click showBlacklistType + showBlacklistType() diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index cf46f15a15..89b0ac697e 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -3,7 +3,7 @@ groupPath: "/api/:version/groups/:id.json" namespacesPath: "/api/:version/namespaces.json" groupProjectsPath: "/api/:version/groups/:id/projects.json" - projectsPath: "/api/:version/projects.json" + projectsPath: "/api/:version/projects.json?simple=true" labelsPath: "/api/:version/projects/:id/labels" licensePath: "/api/:version/licenses/:key" gitignorePath: "/api/:version/gitignores/:key" diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 397892d15c..eceff6d91d 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -47,15 +47,12 @@ #= require date.format #= require_directory ./behaviors #= require_directory ./blob -#= require_directory ./ci #= require_directory ./commit #= require_directory ./extensions -#= require_directory ./lib +#= require_directory ./lib/utils #= require_directory ./u2f #= require_directory . #= require fuzzaldrin-plus -#= require cropper -#= require u2f window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() @@ -121,6 +118,11 @@ window.onload = -> setTimeout shiftWindow, 100 $ -> + + $document = $(document) + $window = $(window) + $body = $('body') + gl.utils.preventDisabledButtons() bootstrapBreakpoint = bp.getBreakpointSize() @@ -152,7 +154,7 @@ $ -> ), 1 # Initialize tooltips - $('body').tooltip( + $body.tooltip( selector: '.has-tooltip, [data-toggle="tooltip"]' placement: (_, el) -> $el = $(el) @@ -171,7 +173,7 @@ $ -> flash.show() # Disable form buttons while a form is submitting - $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> + $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> buttons = $('[type="submit"]', @) switch e.type @@ -180,11 +182,20 @@ $ -> else buttons.enable() + $(document).ajaxError (e, xhrObj, xhrSetting, xhrErrorText) -> + + if xhrObj.status is 401 + new Flash 'You need to be logged in.', 'alert' + + else if xhrObj.status in [ 404, 500 ] + new Flash 'Something went wrong on our end.', 'alert' + + # Show/Hide the profile menu when hovering the account box $('.account-box').hover -> $(@).toggleClass('hover') # Commit show suppressed diff - $(document).on 'click', '.diff-content .js-show-suppressed-diff', -> + $document.on 'click', '.diff-content .js-show-suppressed-diff', -> $container = $(@).parent() $container.next('table').show() $container.remove() @@ -194,16 +205,15 @@ $ -> $('.header-content .header-logo').toggle() $('.header-content .navbar-collapse').toggle() $('.navbar-toggle').toggleClass('active') - $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") # Show/hide comments on diff - $("body").on "click", ".js-toggle-diff-comments", (e) -> + $body.on "click", ".js-toggle-diff-comments", (e) -> $(@).toggleClass('active') $(@).closest(".diff-file").find(".notes_holder").toggle() e.preventDefault() - $(document).off "click", '.js-confirm-danger' - $(document).on "click", '.js-confirm-danger', (e) -> + $document.off "click", '.js-confirm-danger' + $document.on "click", '.js-confirm-danger', (e) -> e.preventDefault() btn = $(e.target) text = btn.data("confirm-danger-message") @@ -211,7 +221,7 @@ $ -> new ConfirmDangerModal(form, text) - $(document).on 'click', 'button', -> + $document.on 'click', 'button', -> $(this).blur() $('input[type="search"]').each -> @@ -219,7 +229,7 @@ $ -> $this.attr 'value', $this.val() return - $(document) + $document .off 'keyup', 'input[type="search"]' .on 'keyup', 'input[type="search"]' , (e) -> $this = $(this) @@ -227,7 +237,7 @@ $ -> $sidebarGutterToggle = $('.js-sidebar-toggle') - $(document) + $document .off 'breakpoint:change' .on 'breakpoint:change', (e, breakpoint) -> if breakpoint is 'sm' or breakpoint is 'xs' @@ -239,14 +249,14 @@ $ -> oldBootstrapBreakpoint = bootstrapBreakpoint bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint != oldBootstrapBreakpoint - $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + $document.trigger('breakpoint:change', [bootstrapBreakpoint]) checkInitialSidebarSize = -> bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint is "xs" or "sm" - $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + $document.trigger('breakpoint:change', [bootstrapBreakpoint]) - $(window) + $window .off "resize.app" .on "resize.app", (e) -> fitSidebarForSize() @@ -256,14 +266,14 @@ $ -> new Aside() # Sidenav pinning - if $(window).width() < 1440 and $.cookie('pin_nav') is 'true' - $.cookie('pin_nav', 'false', { path: '/' }) + if $window.width() < 1024 and $.cookie('pin_nav') is 'true' + $.cookie('pin_nav', 'false', { path: '/', expires: 365 * 10 }) $('.page-with-sidebar') .toggleClass('page-sidebar-collapsed page-sidebar-expanded') .removeClass('page-sidebar-pinned') $('.navbar-fixed-top').removeClass('header-pinned-nav') - $(document) + $document .off 'click', '.js-nav-pin' .on 'click', '.js-nav-pin', (e) -> e.preventDefault() @@ -288,7 +298,7 @@ $ -> .toggleClass('header-collapsed header-expanded') # Save settings - $.cookie 'pin_nav', doPinNav, { path: '/' } + $.cookie 'pin_nav', doPinNav, { path: '/', expires: 365 * 10 } if $.cookie('pin_nav') is 'true' or doPinNav tooltipText = 'Unpin navigation' diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 030f156486..37d0adaa62 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -341,7 +341,9 @@ class @AwardsHandler for emoji in frequentlyUsedEmojis $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul) - $('input.emoji-search').after(ul).after($('
#{highlighted}
)
end
def no_highlight_files
@@ -29,7 +26,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
- link_to "Edit", edit_path, class: 'btn btn-file-option'
+ link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: edit_path,
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index c533659b60..3fc85dc6b2 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -9,10 +9,21 @@ module BranchesHelper
end
end
+ def filter_branches_path(options = {})
+ exist_opts = {
+ search: params[:search],
+ sort: params[:sort]
+ }
+
+ options = exist_opts.merge(options)
+
+ namespace_project_branches_path(@project.namespace, @project, @id, options)
+ end
+
def can_push_branch?(project, branch_name)
return false unless project.repository.branch_exists?(branch_name)
- ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
+ ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch_name)
end
def project_branches
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 9051a493b9..b478580978 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -15,58 +15,42 @@ module ButtonHelper
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
+ data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard'),
class: "btn btn-clipboard",
data: data,
- type: :button
+ type: :button,
+ title: "Copy to Clipboard"
end
- # Output a "Copy to Clipboard" button with a custom CSS class
- #
- # data - Data attributes passed to `content_tag`
- # css_class - Class passed to the `content_tag`
- #
- # Examples:
- #
- # # Define the target element
- # clipboard_button_with_class({clipboard_target: "div#foo"}, css_class: "btn-clipboard")
- # # => "repo
scope, so we can display a
+ list of your public and private repositories which are available for import.
+
+= form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do
+ .form-group
+ = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40
+ = submit_tag 'List Your GitHub Repositories', class: 'btn btn-success'
+
+- unless github_import_configured?
+ %hr
+ %p
+ Note:
+ - if current_user.admin?
+ As an administrator you may like to configure
+ - else
+ Consider asking your GitLab administrator to configure
+ = link_to 'GitHub integration', help_page_path("integration/github")
+ which will allow login via GitHub and allow importing projects without
+ generating a Personal Access Token.
diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml
deleted file mode 100644
index 8c140a5943..0000000000
--- a/app/views/layouts/_collapse_button.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-= link_to '#', class: 'nav-header-btn text-center toggle-nav-collapse', title: "Open/Close" do
- %span.sr-only Toggle navigation
- = icon('bars')
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index cc8ea066cb..3612f1ce5c 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -1,4 +1,4 @@
-.flash-container
+.flash-container.flash-container-page
- if alert
.flash-alert
= alert
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index e0ed657919..757de92d6d 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -30,8 +30,8 @@
= javascript_include_tag "application"
- - if page_specific_javascripts
- = javascript_include_tag page_specific_javascripts, {"data-turbolinks-track" => true}
+ - if content_for?(:page_specific_javascripts)
+ = yield :page_specific_javascripts
= csrf_meta_tags
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 96b3848542..351100f352 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,6 +1,7 @@
- project = @target_project || @project
+- noteable_class = @noteable.class if @noteable.present?
-- if @noteable
- :javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}"
- GitLab.GfmAutoComplete.setup();
+:javascript
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_class, type_id: params[:id])}"
+ GitLab.GfmAutoComplete.cachedData = undefined;
+ GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 2234bf79c8..a1a71c2fb3 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,6 +1,13 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
- = render partial: 'layouts/collapse_button'
+ .sidebar-action-buttons
+ = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do
+ %span.sr-only Toggle navigation
+ = icon('bars')
+ = link_to '#', class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
+ %span.sr-only Toggle navigation pinning
+ = icon('fw thumb-tack')
+
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
@@ -8,14 +15,6 @@
- else
= render 'layouts/nav/explore'
- - if current_user
- = link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
- = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
- .username
- = current_user.username
- = link_to '#', class: "nav-header-btn text-center pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
- %span.sr-only Toggle navigation pinning
- = icon('thumb-tack')
- if defined?(nav) && nav
.layout-nav
.container-fluid
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 245b9c3b4d..f7580f0015 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/layouts/ci/_info.html.haml b/app/views/layouts/ci/_info.html.haml
deleted file mode 100644
index 24c68a6dbf..0000000000
--- a/app/views/layouts/ci/_info.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- if current_user && current_user.is_admin? && Ci::Runner.count.zero?
- = render 'ci/shared/no_runners'
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
deleted file mode 100644
index 2e56d0ac6a..0000000000
--- a/app/views/layouts/ci/_page.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-.page-with-sidebar{ class: page_sidebar_class }
- = render "layouts/broadcast"
- .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
-
- - if defined?(sidebar) && sidebar
- = render "layouts/ci/#{sidebar}"
- - elsif current_user
- = render 'layouts/nav/dashboard'
- .collapse-nav
- = render partial: 'layouts/collapse_button'
- - if current_user
- = link_to current_user, class: 'sidebar-user', title: "Profile" do
- = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
- .username
- = current_user.username
- .content-wrapper
- = render "layouts/flash"
- = render 'layouts/ci/info'
- %div{ class: container_class }
- .content
- .clearfix
- = yield
diff --git a/app/views/layouts/ci/notify.html.haml b/app/views/layouts/ci/notify.html.haml
deleted file mode 100644
index 270b206df5..0000000000
--- a/app/views/layouts/ci/notify.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-%html{lang: "en"}
- %head
- %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
- %title
- GitLab CI
-
- %body
- = yield :header
-
- %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- = yield
- %br
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- %p{style: "font-size:small;color:#777"}
- - if @project
- You're receiving this notification because you are the one who triggered a build on the #{@project.name} project.
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 40a2c81eeb..94c5388262 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,46 +1,56 @@
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content
- %button.side-nav-toggle{type: 'button'}
+ %button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" }
%span.sr-only Toggle navigation
= icon('bars')
%button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation
- = icon('angle-left')
+ = icon('ellipsis-v')
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm.visible-xs
- = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if current_user
- if session[:impersonator_id]
%li.impersonation
- = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = link_to admin_impersonation_path, method: :delete, title: "Stop Impersonation", aria: { label: 'Stop Impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
- = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
%li
- = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('bell fw')
%span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) }
= todos_pending_count
- if current_user.can_create_project?
%li
- = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to new_project_path, title: 'New project', aria: { label: "New project" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
- %li
- = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('sign-out')
+ %li.header-user.dropdown
+ = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
+ = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
+ %span.caret
+ .dropdown-menu-nav.dropdown-menu-align-right
+ %ul
+ %li
+ = link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username }
+ %li
+ = link_to "Profile Settings", profile_path, aria: { label: "Profile Settings" }
+ %li.divider
+ %li
+ = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" }
- else
%li
%div
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 66e5ec1ad1..5ee8772882 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,15 +1,16 @@
-%div{ class: nav_control_class }
+.scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/admin_settings'
-
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
%ul.nav-links.scrolling-tabs
- %li.fade-left
- = icon('arrow-left')
= nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span
Overview
- = nav_link(controller: %w(background_jobs logs health_check)) do
- = link_to admin_background_jobs_path, title: 'Monitoring' do
+ = nav_link(controller: %w(system_info background_jobs logs health_check)) do
+ = link_to admin_system_info_path, title: 'Monitoring' do
%span
Monitoring
= nav_link(controller: :broadcast_messages) do
@@ -37,5 +38,3 @@
= link_to admin_spam_logs_path, title: "Spam Logs" do
%span
Spam Logs
- %li.fade-right
- = icon('arrow-right')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 52e41b1a85..3a14751ea8 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,64 +1,44 @@
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- .icon-container
- = navbar_icon('project')
%span
Projects
= nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do
- .icon-container
- = icon('bell fw')
%span
Todos
%span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
- .icon-container
- = navbar_icon('activity')
%span
Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
- .icon-container
- = navbar_icon('group')
%span
Groups
= nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
- .icon-container
- = navbar_icon('milestones')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
- .icon-container
- = navbar_icon('issues')
%span
Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
- .icon-container
- = navbar_icon('mr')
%span
Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
- = nav_link(controller: :snippets) do
+ = nav_link(controller: 'dashboard/snippets') do
= link_to dashboard_snippets_path, title: 'Snippets' do
- .icon-container
- = icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
- .icon-container
- = icon('question-circle fw')
%span
Help
= nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
- .icon-container
- = icon('user fw')
%span
Profile Settings
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 3b40006a0c..e5bda7b3a6 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,21 +1,17 @@
%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do
- = icon('bookmark fw')
%span
Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do
- = icon('group fw')
%span
Groups
= nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets' do
- = icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
- = icon('question-circle fw')
%span
Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index f7aa9fab7c..d7d36c84b6 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,9 +1,10 @@
-%div{ class: nav_control_class }
+.scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/group_settings'
-
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
%ul.nav-links.scrolling-tabs
- %li.fade-left
- = icon('arrow-left')
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
%span
@@ -32,5 +33,3 @@
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
- %li.fade-right
- = icon('arrow-right')
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index dac46648b9..bf9a7ecb78 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,16 +1,22 @@
- if current_user
- - if access = @group.users.find_by(id: current_user.id)
- .controls
- .dropdown.group-settings-dropdown
- %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
- = icon('cog')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- - if can?(current_user, :admin_group, @group)
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects' do
- Projects
- %li.divider
- %li
- = link_to edit_group_path(@group) do
- Edit Group
+ - can_edit = can?(current_user, :admin_group, @group)
+ - member = @group.members.find_by(user_id: current_user.id)
+ - can_leave = member && can?(current_user, :destroy_group_member, member)
+
+ .controls
+ .dropdown.group-settings-dropdown
+ %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ = nav_link(path: 'groups#projects') do
+ = link_to 'Projects', projects_group_path(@group), title: 'Projects'
+ %li.divider
+ - if can_edit
+ %li
+ = link_to 'Edit Group', edit_group_path(@group)
+ - if can_leave
+ %li
+ = link_to polymorphic_path([:leave, @group, :members]),
+ data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
+ Leave Group
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 44ea939b7e..6d514f669d 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,48 +1,49 @@
-%ul.nav-links.scrolling-tabs
- %li.fade-left
- = icon('arrow-left')
- = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
- = link_to profile_path, title: 'Profile Settings' do
- %span
- Profile
- = nav_link(controller: [:accounts, :two_factor_auths]) do
- = link_to profile_account_path, title: 'Account' do
- %span
- Account
- - if current_application_settings.user_oauth_applications?
- = nav_link(controller: 'oauth/applications') do
- = link_to applications_profile_path, title: 'Applications' do
+.scrolling-tabs-container
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
+ %ul.nav-links.scrolling-tabs
+ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ = link_to profile_path, title: 'Profile Settings' do
%span
- Applications
- = nav_link(controller: :personal_access_tokens) do
- = link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do
- %span
- Personal Access Tokens
- = nav_link(controller: :emails) do
- = link_to profile_emails_path, title: 'Emails' do
- %span
- Emails
- - unless current_user.ldap_user?
- = nav_link(controller: :passwords) do
- = link_to edit_profile_password_path, title: 'Password' do
+ Profile
+ = nav_link(controller: [:accounts, :two_factor_auths]) do
+ = link_to profile_account_path, title: 'Account' do
%span
- Password
- = nav_link(controller: :notifications) do
- = link_to profile_notifications_path, title: 'Notifications' do
- %span
- Notifications
+ Account
+ - if current_application_settings.user_oauth_applications?
+ = nav_link(controller: 'oauth/applications') do
+ = link_to applications_profile_path, title: 'Applications' do
+ %span
+ Applications
+ = nav_link(controller: :personal_access_tokens) do
+ = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
+ %span
+ Access Tokens
+ = nav_link(controller: :emails) do
+ = link_to profile_emails_path, title: 'Emails' do
+ %span
+ Emails
+ - unless current_user.ldap_user?
+ = nav_link(controller: :passwords) do
+ = link_to edit_profile_password_path, title: 'Password' do
+ %span
+ Password
+ = nav_link(controller: :notifications) do
+ = link_to profile_notifications_path, title: 'Notifications' do
+ %span
+ Notifications
- = nav_link(controller: :keys) do
- = link_to profile_keys_path, title: 'SSH Keys' do
- %span
- SSH Keys
- = nav_link(controller: :preferences) do
- = link_to profile_preferences_path, title: 'Preferences' do
- %span
- Preferences
- = nav_link(path: 'profiles#audit_log') do
- = link_to audit_log_profile_path, title: 'Audit Log' do
- %span
- Audit Log
- %li.fade-right
- = icon('arrow-right')
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path, title: 'SSH Keys' do
+ %span
+ SSH Keys
+ = nav_link(controller: :preferences) do
+ = link_to profile_preferences_path, title: 'Preferences' do
+ %span
+ Preferences
+ = nav_link(path: 'profiles#audit_log') do
+ = link_to audit_log_profile_path, title: 'Audit Log' do
+ %span
+ Audit Log
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 7762746f84..9e65d94186 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -5,28 +5,31 @@
= icon('cog')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- - is_project_member = @project.users.exists?(current_user.id)
- - access = @project.team.max_member_access(current_user.id)
- can_edit = can?(current_user, :admin_project, @project)
+ -# We don't use @project.team.find_member because it searches for group members too...
+ - member = @project.members.find_by(user_id: current_user.id)
+ - can_leave = member && can?(current_user, :destroy_project_member, member)
- = render 'layouts/nav/project_settings', access: access, can_edit: can_edit
+ = render 'layouts/nav/project_settings', can_edit: can_edit
- - if can_edit || is_project_member
+ - if can_edit || can_leave
%li.divider
- if can_edit
%li
= link_to edit_project_path(@project) do
Edit Project
- - if is_project_member
+ - if can_leave
%li
= link_to polymorphic_path([:leave, @project, :members]),
data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do
Leave Project
-%div{ class: nav_control_class }
+.scrolling-tabs-container{ class: nav_control_class }
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
%ul.nav-links.scrolling-tabs
- %li.fade-left
- = icon('arrow-left')
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
%span
@@ -110,5 +113,3 @@
%li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits
- %li.fade-right
- = icon('arrow-right')
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 13d32bd135..52a5bdc1a1 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -3,7 +3,7 @@
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
%span
Members
-- if access && can_edit
+- if can_edit
- if @project.allowed_to_share_with_group?
= nav_link(controller: :group_links) do
= link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
@@ -39,7 +39,7 @@
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
%span
Triggers
- = nav_link(controller: :badges) do
- = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
+ = nav_link(controller: :pipelines_settings) do
+ = link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
%span
- Badges
+ CI/CD Pipelines
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 2049b20495..ee9c0366f2 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -6,7 +6,7 @@
- content_for :scripts_body_top do
- project = @target_project || @project
- if @project_wiki && @page
- - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, params[:id])
+ - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, @page.slug)
- else
- markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
- if current_user
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index a3643a00cf..ea7e3d199f 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,7 +1,7 @@
-- if @note.legacy_diff_note?
+- if @note.diff_note? && @note.diff_file
%p.details
New comment on diff for
- = link_to @note.diff_file_path, @target_url
+ = link_to @note.diff_file.file_path, @target_url
\:
= render 'note_message'
diff --git a/app/views/notify/project_was_not_exported_email.html.haml b/app/views/notify/project_was_not_exported_email.html.haml
index c9e9ade2cf..c888da29c1 100644
--- a/app/views/notify/project_was_not_exported_email.html.haml
+++ b/app/views/notify/project_was_not_exported_email.html.haml
@@ -6,4 +6,4 @@
%ul
- @errors.each do |error|
%li
- error
+ #{error}
diff --git a/app/views/notify/project_was_not_exported_email.text.erb b/app/views/notify/project_was_not_exported_email.text.erb
deleted file mode 100644
index a07f6edacf..0000000000
--- a/app/views/notify/project_was_not_exported_email.text.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Project <%= @project.name %> couldn't be exported.
-
-The errors we encountered were:
-
-- @errors.each do |error|
-<%= error %>
\ No newline at end of file
diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml
new file mode 100644
index 0000000000..b27cb620b9
--- /dev/null
+++ b/app/views/notify/project_was_not_exported_email.text.haml
@@ -0,0 +1,6 @@
+= "Project #{@project.name} couldn't be exported."
+
+= "The errors we encountered were:"
+
+- @errors.each do |error|
+ #{error}
\ No newline at end of file
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index f1532371b2..c161ecc346 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -72,12 +72,11 @@
The diff for this file was not included because it is too large.
- else
%hr
- - diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last
- - blob = @message.project.repository.blob_for_diff(diff_commit, diff_file)
+ - blob = diff_file.blob
- if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob)
%table.code.white
- diff_file.highlighted_diff_lines.each do |line|
- = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true}
+ = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
- else
No preview for this file type
%br
diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml
new file mode 100644
index 0000000000..943ebdaeff
--- /dev/null
+++ b/app/views/profiles/_head.html.haml
@@ -0,0 +1,3 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/cropper.js')
+ = page_specific_javascript_tag('profile/profile_bundle.js')
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 8efe486e01..57d16d2915 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Account"
+= render 'profiles/head'
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index 9c404b6935..9fe86e6b29 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,4 +1,5 @@
- page_title "Audit Log"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 6f7fefdb46..dc499be885 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Emails"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index b3ed59a1a4..6ea358d9f6 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -4,7 +4,7 @@
.form-group
= f.label :key, class: 'label-light'
- = f.text_area :key, class: "form-control", rows: 8, required: true
+ = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the SSH key. Paste the public part, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'."
.form-group
= f.label :title, class: 'label-light'
= f.text_field :title, class: "form-control", required: true
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 6a067a0353..a42b3b8eb3 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -11,7 +11,7 @@
Add an SSH key
%p.profile-settings-content
Before you can add an SSH key you need to
- = link_to "generate it.", help_page_path("ssh", "README")
+ = link_to "generate it.", help_page_path("ssh/README")
= render 'form'
%hr
%h5
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 89f6f01581..6283ceebf1 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,2 +1,3 @@
- page_title @key.title, "SSH Keys"
+= render 'profiles/head'
= render "key_details"
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index f77738f97f..844fce5970 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Notifications"
+= render 'profiles/head'
%div
- if @user.errors.any?
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 1b45548bd0..71ac367830 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Personal Access Tokens"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 1b1b16d656..2afa026847 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,4 +1,5 @@
- page_title 'Preferences'
+= render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar
@@ -42,12 +43,12 @@
.form-group
= f.label :dashboard, class: 'label-light' do
Default Dashboard
- = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
+ = link_to('(?)', help_page_path('profile/preferences') + '#default-dashboard', target: '_blank')
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
.form-group
= f.label :project_view, class: 'label-light' do
Project view
- = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank')
+ = link_to('(?)', help_page_path('profile/preferences') + '#default-project-view', target: '_blank')
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
Choose what content you want to see on a project's home page.
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index eef50d887c..d9fa74fad9 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,3 +1,5 @@
+= render 'profiles/head'
+
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
= form_errors(@user)
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 593be2617c..366f1fed35 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,5 +1,10 @@
- page_title 'Two-Factor Authentication', 'Account'
- 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
@@ -13,7 +18,7 @@
- else
%p
Download the Google Authenticator application from App Store or Google Play Store and scan this code.
- More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
+ More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}.
.row.append-bottom-10
.col-md-3
= raw @qr_code
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 48b0dd6b12..ac50ce83f6 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -5,7 +5,8 @@
%i.fa.fa-rss
= render 'shared/event_filter'
-.content_list{:"data-href" => activity_project_path(@project)}
+
+.content_list.project-activity{:"data-href" => activity_project_path(@project)}
= spinner
:javascript
diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml
index 2987f6b5b2..e74fd5b93e 100644
--- a/app/views/projects/_bitbucket_import_modal.html.haml
+++ b/app/views/projects/_bitbucket_import_modal.html.haml
@@ -10,4 +10,4 @@
as administrator you need to configure
- else
ask your GitLab administrator to configure
- == #{link_to 'OAuth integration', help_page_path("integration", "bitbucket")}.
+ == #{link_to 'OAuth integration', help_page_path("integration/bitbucket")}.
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
deleted file mode 100644
index 0568c2d305..0000000000
--- a/app/views/projects/_builds_settings.html.haml
+++ /dev/null
@@ -1,65 +0,0 @@
-%fieldset.builds-feature
- %h5.prepend-top-0
- Builds
- - unless @repository.gitlab_ci_yml
- .form-group
- %p Builds need to be configured before you can begin using Continuous Integration.
- = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
- .form-group
- %p Get recent application code using the following command:
- .radio
- = f.label :build_allow_git_fetch_false do
- = f.radio_button :build_allow_git_fetch, 'false'
- %strong git clone
- %br
- %span.descr Slower but makes sure you have a clean dir before every build
- .radio
- = f.label :build_allow_git_fetch_true do
- = f.radio_button :build_allow_git_fetch, 'true'
- %strong git fetch
- %br
- %span.descr Faster
-
- .form-group
- = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
- = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
- %p.help-block per build in minutes
- .form-group
- = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
- .input-group
- %span.input-group-addon /
- = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
- %span.input-group-addon /
- %p.help-block
- We will use this regular expression to find test coverage output in build trace.
- Leave blank if you want to disable this feature
- .bs-callout.bs-callout-info
- %p Below are examples of regex for existing tools:
- %ul
- %li
- Simplecov (Ruby) -
- %code \(\d+.\d+\%\) covered
- %li
- pytest-cov (Python) -
- %code \d+\%\s*$
- %li
- phpunit --coverage-text --colors=never (PHP) -
- %code ^\s*Lines:\s*\d+.\d+\%
- %li
- gcovr (C/C++) -
- %code ^TOTAL.*\s+(\d+\%)$
- %li
- tap --coverage-report=text-summary (Node.js) -
- %code ^Statements\s*:\s*([^%]+)
-
- .form-group
- .checkbox
- = f.label :public_builds do
- = f.check_box :public_builds
- %strong Public builds
- .help-block Allow everyone to access builds for Public and Internal projects
-
- .form-group.append-bottom-0
- = f.label :runners_token, "Runners token", class: 'label-light'
- = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
- %p.help-block The secure token used to checkout project.
diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml
deleted file mode 100644
index 46ad155935..0000000000
--- a/app/views/projects/_github_import_modal.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-%div#github_import_modal.modal
- .modal-dialog
- .modal-content
- .modal-header
- %a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3 Import projects from GitHub
- .modal-body
- To enable importing projects from GitHub,
- - if current_user.admin?
- as administrator you need to configure
- - else
- ask your Gitlab administrator to configure
- == #{link_to 'OAuth integration', help_page_path("integration", "github")}.
diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml
index 377cf0187b..e9f39b16aa 100644
--- a/app/views/projects/_gitlab_import_modal.html.haml
+++ b/app/views/projects/_gitlab_import_modal.html.haml
@@ -10,4 +10,4 @@
as administrator you need to configure
- else
ask your GitLab administrator to configure
- == #{link_to 'OAuth integration', help_page_path("integration", "gitlab")}.
+ == #{link_to 'OAuth integration', help_page_path("integration/gitlab")}.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 86ea08dd22..cf11723dc8 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,41 +1,29 @@
- empty_repo = @project.empty_repo?
-.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
- %div{ class: (container_class) }
- .row
- .project-image-container
- = project_icon(@project, alt: '', class: 'project-avatar avatar s70')
- .project-info
- .cover-title.project-home-desc
- %h1
- = @project.name
- %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
- = visibility_level_icon(@project.visibility_level, fw: false)
+.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
+ %div{ class: container_class }
+ = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70')
+ %h1.project-title
+ = @project.name
+ %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
+ = visibility_level_icon(@project.visibility_level, fw: false)
- - if @project.description.present?
- .cover-desc.project-home-desc
- = markdown(@project.description, pipeline: :description)
+ .project-home-desc
+ - if @project.description.present?
+ = markdown(@project.description, pipeline: :description)
- - if forked_from_project = @project.forked_from_project
- .cover-desc
- Forked from
- = link_to project_path(forked_from_project) do
- = forked_from_project.namespace.try(:name)
+ - if forked_from_project = @project.forked_from_project
+ %p
+ Forked from
+ = link_to project_path(forked_from_project) do
+ = forked_from_project.namespace.try(:name)
- .project-repo-buttons.project-action-buttons
- .count-buttons
- = render 'projects/buttons/star'
- = render 'projects/buttons/fork'
+ .project-repo-buttons.project-action-buttons
+ .count-buttons
+ = render 'projects/buttons/star'
+ = render 'projects/buttons/fork'
- .project-clone-holder
- = render "shared/clone_panel"
-
- .project-repo-buttons.btn-group.project-right-buttons
- - if current_user
- = render 'shared/members/access_request_buttons', source: @project
-
- = render "projects/buttons/download"
- = render 'projects/buttons/dropdown'
- = render 'shared/notifications/button', notification_setting: @notification_setting
+ .project-clone-holder
+ = render "shared/clone_panel"
:javascript
new Star();
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 66c30283c7..630ae7d614 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -1,11 +1,10 @@
-.project-last-commit
- - if commit.status
- = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
- = ci_icon_for_status(commit.status)
- = ci_label_for_status(commit.status)
+- if commit.status
+ = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
+ = ci_icon_for_status(commit.status)
+ = ci_label_for_status(commit.status)
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
- ·
- #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
- = commit_author_link(commit, avatar: true, size: 24)
+= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
+= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
+·
+#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
+= commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index e0ca2a3109..3c6b931f41 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,13 +1,15 @@
- if event = last_push_event
- if show_last_push_widget?(event)
.row-content-block.top-block.clear-block.hidden-xs
- %div{ class: (container_class) }
+ %div{ class: container_class }
.event-last-push
.event-last-push-text
%span You pushed to
= link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
%strong= event.ref_name
- branch
+ - if @project && event.project != @project
+ %span at
+ %strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)}
.pull-right
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 28a28282fd..58d961d93c 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -12,10 +12,19 @@
%li.confidential-issue-warning
= icon('warning')
%span This is a confidential issue. Your comment will not be visible to the public.
-
+
%li.pull-right
- %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
- Go full screen
+ .toolbar-group
+ = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
+ = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" })
+ = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
+ = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" })
+ = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
+ = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
+ = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
+ .toolbar-group
+ %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
+ =icon("arrows-alt fw")
.md-write-holder
= yield
@@ -24,7 +33,7 @@
- if defined?(referenced_users) && referenced_users
%div.referenced-users.hide
%span
- = icon('exclamation-triangle')
+ = icon("exclamation-triangle")
You are about to add
%strong
%span.js-referenced-users-count 0
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index da522b5341..19b4249374 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -8,4 +8,4 @@
%strong Only allow merge requests to be merged if the build succeeds
.help-block
Builds need to be configured to enable this feature.
- = link_to icon('question-circle'), help_page_path('workflow', 'merge_requests#only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+ = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml
deleted file mode 100644
index ac80951dd4..0000000000
--- a/app/views/projects/badges/index.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-- page_title 'Badges'
-- badges_path = namespace_project_badges_path(@project.namespace, @project)
-
-.prepend-top-10
- .panel.panel-default
- .panel-heading
- %b Builds badge ·
- = @build_badge.to_html
- .pull-right
- = render 'shared/ref_switcher', destination: 'badges', align_right: true
- .panel-body
- .row
- .col-md-2.text-center
- Markdown
- .col-md-10.code.js-syntax-highlight
- = highlight('.md', @build_badge.to_markdown)
- .row
- %hr
- .row
- .col-md-2.text-center
- HTML
- .col-md-10.code.js-syntax-highlight
- = highlight('.html', @build_badge.to_html)
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 29c7d45074..ff379bafb2 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -4,7 +4,9 @@
= icon('code-fork')
= ref
%span.editor-file-name
- = @path
+ - if current_action?(:edit) || current_action?(:update)
+ = text_field_tag 'file_path', (params[:file_path] || @path),
+ class: 'form-control new-file-path'
- if current_action?(:new) || current_action?(:create)
%span.editor-file-name
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index b1769759dc..58524418a6 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -16,4 +16,4 @@
.file-content.code
.nothing-here-block Empty file
- else
- = render 'shared/file_highlight', blob: blob
+ = render 'shared/file_highlight', blob: blob, repository: @repository
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index e4f04ca776..b1c9895f43 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -4,12 +4,10 @@
%ul.nav-links.no-bottom.js-edit-mode
%li.active
= link_to '#editor' do
- = icon('edit')
Edit File
%li
= link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
- = icon('eye')
= editing_preview_title(@blob.name)
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index ed670dae88..0ab78a39cf 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,12 +1,15 @@
+- @no_container = true
- page_title @blob.path, @ref
+= render "projects/commits/head"
-= render 'projects/last_push'
+%div{ class: container_class }
+ = render 'projects/last_push'
-%div#tree-holder.tree-holder
- = render 'blob', blob: @blob
+ %div#tree-holder.tree-holder
+ = render 'blob', blob: @blob
-- if can_edit_blob?(@blob)
- = render 'projects/blob/remove'
+ - if can_edit_blob?(@blob)
+ = render 'projects/blob/remove'
- - title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
+ - title = "Replace #{@blob.name}"
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index e0367c4027..6f806e3ce5 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -2,33 +2,37 @@
- page_title "Branches"
= render "projects/commits/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
.nav-text
Protected branches can be managed in project settings
- if can? current_user, :push_code, @project
.nav-controls
- = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
- New branch
+ = form_tag(filter_branches_path, method: :get) do
+ = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
- - if @sort.present?
- = @sort.humanize
+ - if params[:sort].present?
+ = params[:sort].humanize
- else
Name
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
- = link_to namespace_project_branches_path(sort: nil) do
- Name
- = link_to namespace_project_branches_path(sort: 'recently_updated') do
+ = link_to filter_branches_path(sort: nil) do
+ = sort_title_name
+ = link_to filter_branches_path(sort: 'recently_updated') do
= sort_title_recently_updated
- = link_to namespace_project_branches_path(sort: 'last_updated') do
+ = link_to filter_branches_path(sort: 'last_updated') do
= sort_title_oldest_updated
- - unless @branches.empty?
+ = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
+ New branch
+ - if @branches.any?
%ul.content-list.all-branches
- @branches.each do |branch|
= render "projects/branches/branch", branch: branch
= paginate @branches, theme: 'gitlab'
+ - else
+ .nothing-here-block No branches to show
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index cab21f0cf1..b89183c40d 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -40,7 +40,7 @@
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
.title
Build details
- - if @build.retryable?
+ - if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
- if @build.merge_request
%p.build-detail-row
@@ -49,7 +49,7 @@
- if @build.duration
%p.build-detail-row
%span.build-light-text Duration:
- #{duration_in_words(@build.finished_at, @build.started_at)}
+ = time_interval_in_words(@build.duration)
- if @build.finished_at
%p.build-detail-row
%span.build-light-text Finished:
@@ -94,9 +94,9 @@
.block
.title
- Commit message
+ Commit title
%p.build-light-text.append-bottom-0
- #{@build.pipeline.git_commit_message}
+ #{@build.pipeline.git_commit_title}
- if @build.tags.any?
.block
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 181547316a..2af625f69c 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Builds"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
@@ -11,17 +11,22 @@
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_builds.count(:id))
+ %li{class: ('active' if @scope == 'pending')}
+ = link_to project_builds_path(@project, scope: :pending) do
+ Pending
+ %span.badge
+ = number_with_delimiter(@all_builds.pending.count(:id))
%li{class: ('active' if @scope == 'running')}
= link_to project_builds_path(@project, scope: :running) do
Running
- %span.badge.js-running-count
- = number_with_delimiter(@all_builds.running_or_pending.count(:id))
+ %span.badge
+ = number_with_delimiter(@all_builds.running.count(:id))
%li{class: ('active' if @scope == 'finished')}
= link_to project_builds_path(@project, scope: :finished) do
Finished
- %span.badge.js-running-count
+ %span.badge
= number_with_delimiter(@all_builds.finished.count(:id))
.nav-controls
@@ -31,12 +36,12 @@
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- unless @repository.gitlab_ci_yml
- = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+ = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
- %ul.content-list
+ %ul.content-list.builds-content-list
- if @builds.blank?
%li
.nothing-here-block No builds to show
@@ -46,14 +51,10 @@
%thead
%tr
%th Status
- %th Build ID
%th Commit
- %th Ref
%th Stage
%th Name
- %th Tags
- %th Duration
- %th Finished at
+ %th
- if @project.build_coverage_enabled?
%th Coverage
%th
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 4e2702c2e4..4421f3b956 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -67,4 +67,4 @@
= render "sidebar"
:javascript
- new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace_with_state[:state]}")
+ new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}")
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 34ad9fe2c4..a098a08285 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -2,7 +2,7 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
%div.count-with-arrow
%span.arrow
@@ -10,10 +10,9 @@
= @project.forks_count
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
%div.count-with-arrow
%span.arrow
- %span.count
- = link_to namespace_project_forks_path(@project.namespace, @project) do
- = @project.forks_count
+ = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
+ = @project.forks_count
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 5bd6e3f0eb..a3114771a4 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -1,32 +1,52 @@
-%tr.build
+%tr.build.commit
%td.status
- if can?(current_user, :read_build, build)
= ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
- else
= ci_status_with_icon(build.status)
- %td.build-link
- - if can?(current_user, :read_build, build)
- = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
- %strong ##{build.id}
- - else
- %strong ##{build.id}
-
- - if build.stuck?
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
- - if defined?(retried) && retried
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
-
- - if defined?(commit_sha) && commit_sha
- %td
- = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
-
- - if defined?(ref) && ref
- %td
- - if build.ref
- = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
+ %td
+ .branch-commit
+ - if can?(current_user, :read_build, build)
+ = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
+ %span ##{build.id}
- else
- .light none
+ %span ##{build.id}
+
+ - if build.stuck?
+ .icon-container
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+ - if defined?(retried) && retried
+ .icon-container
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
+
+ - if defined?(ref) && ref
+ - if build.ref
+ .icon-container
+ = build.tag? ? icon('tag') : icon('code-fork')
+ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
+ - else
+ .light none
+ .icon-container
+ = custom_icon("icon_commit")
+
+ - if defined?(commit_sha) && commit_sha
+ = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
+
+ .label-container
+ - if build.tags.any?
+ - build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.try(:trigger_request)
+ %span.label.label-info triggered
+ - if build.try(:allow_failure)
+ %span.label.label-danger allowed to fail
+ - if defined?(retried) && retried
+ %span.label.label-warning retried
+ - if build.manual?
+ %span.label.label-info manual
+
- if defined?(runner) && runner
%td
@@ -43,25 +63,14 @@
= build.name
%td
- .label-container
- - if build.tags.any?
- - build.tags.each do |tag|
- %span.label.label-primary
- = tag
- - if build.try(:trigger_request)
- %span.label.label-info triggered
- - if build.try(:allow_failure)
- %span.label.label-danger allowed to fail
- - if defined?(retried) && retried
- %span.label.label-warning retried
-
- %td.duration
- if build.duration
- #{duration_in_words(build.finished_at, build.started_at)}
-
- %td.timestamp
+ %p.duration
+ = custom_icon("icon_timer")
+ = duration_in_numbers(build.finished_at, build.started_at)
- if build.finished_at
- %span #{time_ago_with_tooltip(build.finished_at)}
+ %p.finished-at
+ = icon("calendar")
+ %span #{time_ago_with_tooltip(build.finished_at)}
- if defined?(coverage) && coverage
%td.coverage
@@ -77,6 +86,10 @@
- if build.active?
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- - elsif defined?(allow_retry) && allow_retry && build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
- = icon('refresh')
+ - elsif defined?(allow_retry) && allow_retry
+ - if build.retryable?
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = icon('repeat')
+ - elsif build.playable?
+ = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+ = icon('play')
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index e38d1ff5ff..2f7d54f0bd 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -1,20 +1,22 @@
- status = pipeline.status
%tr.commit
%td.commit-link
- = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: "ci-status ci-#{status}" do
- = ci_icon_for_status(status)
- %strong ##{pipeline.id}
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
+ = ci_status_with_icon(status)
+
%td
- %div.branch-commit
+ .branch-commit
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
+ %span ##{pipeline.id}
- if pipeline.ref
- = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace"
- ·
+ .icon-container
+ = pipeline.tag? ? icon('tag') : icon('code-fork')
+ = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
+ .icon-container
+ = custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
-
- - if pipeline.tag?
- %span.label.label-primary tag
- - elsif pipeline.latest?
+ - if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered?
%span.label.label-primary triggered
@@ -25,14 +27,15 @@
%p.commit-title
- if commit = pipeline.commit
+ = author_avatar(commit, size: 20)
= link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
- else
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
+ %td.stage-cell
- status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status
@@ -45,27 +48,47 @@
%td
- if pipeline.started_at && pipeline.finished_at
%p.duration
- #{duration_in_words(pipeline.finished_at, pipeline.started_at)}
+ = custom_icon("icon_timer")
+ = duration_in_numbers(pipeline.finished_at, pipeline.started_at)
+ - if pipeline.finished_at
+ %p.finished-at
+ = icon("calendar")
+ #{time_ago_with_tooltip(pipeline.finished_at)}
- %td
+ %td.pipeline-actions
.controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- - if artifacts.present?
- .dropdown.inline.build-artifacts
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- = icon('download')
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- - artifacts.each do |build|
- %li
- = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
- = icon("download")
- %span Download '#{build.name}' artifacts
+ - actions = pipeline.manual_actions
+ - if artifacts.present? || actions.any?
+ .btn-group.inline
+ - if actions.any?
+ .btn-group
+ %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
+ = icon("play")
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - actions.each do |build|
+ %li
+ = link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do
+ = icon("play")
+ %span= build.name.humanize
+ - if artifacts.present?
+ .btn-group
+ %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
+ = icon("download")
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - artifacts.each do |build|
+ %li
+ = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
+ = icon("download")
+ %span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project)
- - 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")
- - if pipeline.cancelable?
- = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
- = icon("remove")
+ .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")
+ - if pipeline.cancelable?
+ = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
+ = icon("remove")
diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml
index ae7bb01223..9d925cacc0 100644
--- a/app/views/projects/commit/_ci_stage.html.haml
+++ b/app/views/projects/commit/_ci_stage.html.haml
@@ -7,7 +7,7 @@
= ci_icon_for_status(status)
- if stage
- = stage.titleize.pluralize
+ = stage.titleize
= render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true
= render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true
%tr
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index b117517c0d..3ad866bb2f 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -6,10 +6,10 @@
.pull-right.commit-action-buttons
- if defined?(@notes_count) && @notes_count > 0
- %span.btn.disabled.btn-grouped.hidden-xs
+ %span.btn.disabled.btn-grouped.hidden-xs.append-right-10
= icon('comment')
= @notes_count
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped hidden-xs hidden-sm" do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
Browse Files
.dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 0411137b7c..540689f4a6 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -35,16 +35,14 @@
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
-.table-holder
- %table.table.builds
+.table-holder.pipeline-holder
+ %table.table.builds.pipeline
%thead
%tr
%th Status
%th Build ID
%th Name
- %th Tags
- %th Duration
- %th Finished at
+ %th
- if pipeline.project.build_coverage_enabled?
%th Coverage
%th
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 401cb4f7e3..d0da260658 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -7,8 +7,7 @@
= render "ci_menu"
- else
%div.block-connector
-= render "projects/diffs/diffs", diffs: @diffs, project: @project,
- diff_refs: @diff_refs
+= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @commit.diff_refs
= render "projects/notes/notes_with_form"
- if can_collaborate_with_project?
- %w(revert cherry-pick).each do |type|
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 929496f81d..fd888f41b1 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,7 +9,8 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
- = commit_author_avatar(commit, size: 36)
+ = author_avatar(commit, size: 36)
+
.commit-info-block
.commit-row-title
%span.item-title
@@ -18,14 +19,15 @@
·
= commit.short_id
- if commit.status
- = render_commit_status(commit, cssclass: 'visible-xs-inline')
+ .visible-xs-inline
+ = render_commit_status(commit)
- if commit.description?
%a.text-expander.hidden-xs.js-toggle-button ...
.commit-actions.hidden-xs
- if commit.status
- = render_commit_status(commit, cssclass: 'btn btn-transparent')
- = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
+ = render_commit_status(commit)
+ = clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 54dab4bff0..6115264990 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,8 +1,10 @@
-.scrolling-tabs-container
+.scrolling-tabs-container.sub-nav-scroll
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
- %li.fade-left
- = icon('arrow-left')
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_files_path(@project) do
Files
@@ -26,5 +28,3 @@
= nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
- %li.fade-right
- = icon('arrow-right')
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 51ca4eb903..9a44ba9497 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -7,7 +7,7 @@
= render "head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.row-content-block.second-block.content-component-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index dd590a4b8e..af09b3418e 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -2,15 +2,17 @@
.clearfix
- if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
- .form-group
+ .form-group.dropdown.compare-form-group.js-compare-from-dropdown
.input-group.inline-input-group
%span.input-group-addon from
- = text_field_tag :from, params[:from], class: "form-control", required: true
+ = text_field_tag :from, params[:from], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from].presence }
+ = render "ref_dropdown"
= "..."
- .form-group
+ .form-group.dropdown.compare-form-group.js-compare-to-dropdown
.input-group.inline-input-group
%span.input-group-addon to
- = text_field_tag :to, params[:to], class: "form-control", required: true
+ = text_field_tag :to, params[:to], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to].presence }
+ = render "ref_dropdown"
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present?
@@ -19,11 +21,3 @@
= link_to create_mr_path, class: 'prepend-left-10 btn' do
= icon("plus")
Create Merge Request
-
-:javascript
- var availableTags = #{@project.repository.ref_names.to_json};
-
- $("#from, #to").autocomplete({
- source: availableTags,
- minLength: 1
- });
diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml
new file mode 100644
index 0000000000..c604c6d013
--- /dev/null
+++ b/app/views/projects/compare/_ref_dropdown.html.haml
@@ -0,0 +1,4 @@
+.dropdown-menu.dropdown-menu-selectable
+ = dropdown_title "Select branch/tag"
+ = dropdown_content
+ = dropdown_loading
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index b22285c11e..e9ff8e90dd 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Compare"
= render "projects/commits/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.sub-header-block
Compare branches, tags or commit ranges.
%br
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index f4ec7b767f..28a50e7031 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -2,7 +2,7 @@
- page_title "#{params[:from]}...#{params[:to]}"
= render "projects/commits/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.sub-header-block.no-bottom-space
= render "form"
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 894c36a96d..901605f7ca 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -9,5 +9,5 @@
.form-group
%p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it
- = link_to "here", help_page_path("ssh", "README")
+ = link_to "here", help_page_path("ssh/README")
= f.submit "Add key", class: "btn-create btn"
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
new file mode 100644
index 0000000000..f70dba224f
--- /dev/null
+++ b/app/views/projects/deployments/_actions.haml
@@ -0,0 +1,22 @@
+- if can?(current_user, :create_deployment, deployment) && deployment.deployable
+ .pull-right
+ - actions = deployment.manual_actions
+ - if actions.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
+ - actions.each do |action|
+ %li
+ = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
+ = icon("play")
+ %span= action.name.humanize
+
+ - if local_assigns.fetch(:allow_rollback, false)
+ = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
+ - if deployment.last?
+ Re-deploy
+ - else
+ Rollback
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index d08dd92f1f..baf02f1e6a 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -7,17 +7,11 @@
%td
- if deployment.deployable
- = link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do
+ = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td
#{time_ago_with_tooltip(deployment.created_at)}
%td
- - if can?(current_user, :create_deployment, deployment) && deployment.deployable
- .pull-right
- = link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do
- - if deployment.last?
- Retry
- - else
- Rollback
+ = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
new file mode 100644
index 0000000000..a1b071f130
--- /dev/null
+++ b/app/views/projects/diffs/_content.html.haml
@@ -0,0 +1,29 @@
+.diff-content.diff-wrap-lines
+ - # Skip all non non-supported blobs
+ - return unless blob.respond_to?(:text?)
+ - if diff_file.too_large?
+ .nothing-here-block This diff could not be displayed because it is too large.
+ - elsif blob.only_display_raw?
+ .nothing-here-block This file is too large to display.
+ - elsif blob_text_viewable?(blob)
+ - if !project.repository.diffable?(blob)
+ .nothing-here-block This diff was suppressed by a .gitattributes entry.
+ - elsif diff_file.collapsed?
+ - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path))
+ .nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
+ This diff is collapsed. Click to expand it.
+ - elsif diff_file.diff_lines.length > 0
+ - if diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
+ - else
+ = render "projects/diffs/text_file", diff_file: diff_file
+ - else
+ - if diff_file.mode_changed?
+ .nothing-here-block File mode changed
+ - elsif diff_file.renamed_file
+ .nothing-here-block File moved
+ - elsif blob.image?
+ - old_blob = diff_file.old_blob(diff_commit)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index f18bc8c41b..4bf3ccace2 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,10 +2,12 @@
- if diff_view == 'parallel'
- fluid_layout true
-- diff_files = safe_diff_files(diffs, diff_refs)
+- diff_files = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository)
.content-block.oneline-block.files-changed
.inline-parallel-buttons
+ - if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
+ = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
@@ -21,10 +23,10 @@
- if diff_files.overflow?
= render 'projects/diffs/warning', diff_files: diff_files
-.files
+.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, @project))}}
- diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file)
- - blob = project.repository.blob_for_diff(diff_commit, diff_file)
+ - blob = diff_file.blob(diff_commit)
- next unless blob
- blob.load_all_data!(project.repository) unless blob.only_display_raw?
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 2395ea3c27..c306909fb1 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,29 +1,8 @@
-.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
+.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_file)}
.file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- - if diff_file.diff.submodule?
- %span
- = icon('archive fw')
- %span
- = submodule_link(blob, @commit.id, project.repository)
- - else
- = blob_icon blob.mode, blob.name
-
- = link_to "#diff-#{i}" do
- - if diff_file.renamed_file
- - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- = old_path
- →
- = new_path
- - else
- %span
- = diff_file.new_path
- - if diff_file.deleted_file
- deleted
-
- - if diff_file.mode_changed?
- %small
- = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
+ = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{i}"
+ - unless diff_file.submodule?
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do
@@ -37,22 +16,4 @@
= view_file_btn(diff_commit.id, diff_file, project)
- .diff-content.diff-wrap-lines
- - # Skip all non non-supported blobs
- - return unless blob.respond_to?(:text?)
- - if diff_file.too_large?
- .nothing-here-block This diff could not be displayed because it is too large.
- - elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
- .nothing-here-block This diff was suppressed by a .gitattributes entry.
- - elsif blob_text_viewable?(blob)
- - if diff_view == 'parallel'
- = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- - else
- = render "projects/diffs/text_file", diff_file: diff_file, index: i
- - elsif blob.only_display_raw?
- .nothing-here-block This file is too large to display.
- - elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
- - else
- .nothing-here-block No preview for this file type
+ = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
new file mode 100644
index 0000000000..95a2772fd0
--- /dev/null
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -0,0 +1,25 @@
+- if defined?(blob) && blob && diff_file.submodule?
+ %span
+ = icon('archive fw')
+ %span
+ = submodule_link(blob, diff_commit.id, project.repository)
+- else
+ = conditional_link_to url.present?, url do
+ = blob_icon diff_file.b_mode, diff_file.file_path
+
+ - if diff_file.renamed_file
+ - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
+ %strong
+ = old_path
+ →
+ %strong
+ = new_path
+ - else
+ %strong
+ = diff_file.new_path
+ - if diff_file.deleted_file
+ deleted
+
+ - if diff_file.mode_changed?
+ %small
+ = "#{diff_file.a_mode} → #{diff_file.b_mode}"
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 2731219cca..9ec6a7aa5c 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,9 +1,8 @@
- diff = diff_file.diff
-- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
+- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))
// diff_refs will be nil for orphaned commits (e.g. first commit in repo)
-- if diff_refs
- - old_commit_id = diff_refs.first.id
- - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+- if diff_file.old_ref
+ - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
@@ -16,7 +15,7 @@
%div.two-up.view
%span.wrap
.frame.deleted
- %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))}
%img{src: old_file_raw_path}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
@@ -28,7 +27,7 @@
%span.meta-height
%span.wrap
.frame.added
- %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))}
%img{src: file_raw_path}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index f1577e8a47..5a8a131d10 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,3 +1,6 @@
+- plain = local_assigns.fetch(:plain, false)
+- line_code = diff_file.line_code(line)
+- position = diff_file.position(line)
- type = line.type
%tr.line_holder{ id: line_code, class: type }
- case type
@@ -9,18 +12,16 @@
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
- %td.old_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- - link_text = type == "new" ? " ".html_safe : line.old_pos
- - if defined?(plain) && plain
+ %td.old_line.diff-line-num{ class: type, data: { linenumber: line.old_pos } }
+ - link_text = type == "new" ? " " : line.old_pos
+ - if plain
= link_text
- else
- = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(line_code)
+ %a{href: "##{line_code}", data: { linenumber: link_text }}
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- - link_text = type == "old" ? " ".html_safe : line.new_pos
- - if defined?(plain) && plain
+ - link_text = type == "old" ? " " : line.new_pos
+ - if plain
= link_text
- else
- = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- %td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code } }= diff_line_content(line.text, type)
+ %a{href: "##{line_code}", data: { linenumber: link_text }}
+ %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, position, type) unless plain) }= diff_line_content(line.text, type)
diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml
index 0cd888876e..b9c0d9dcdf 100644
--- a/app/views/projects/diffs/_match_line_parallel.html.haml
+++ b/app/views/projects/diffs/_match_line_parallel.html.haml
@@ -1,4 +1,4 @@
-%td.old_line.diff-line-num
+%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.match= line
-%td.new_line.diff-line-num
+%td.new_line.diff-line-num.empty-cell
%td.line_content.parallel.match= line
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 4ecc9528bd..d208fcee10 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,43 +1,36 @@
/ Side-by-side diff view
-%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight
+%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data }
%table
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
%tr.line_holder.parallel
- if left[:type] == 'match'
- = render "projects/diffs/match_line_parallel", { line: left[:text],
- line_old: left[:number], line_new: right[:number] }
+ = render "projects/diffs/match_line_parallel", { line: left[:text] }
- elsif left[:type] == 'nonewline'
- %td.old_line.diff-line-num
+ %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.match= left[:text]
- %td.new_line.diff-line-num
+ %td.new_line.diff-line-num.empty-cell
%td.line_content.parallel.match= left[:text]
- else
- %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
- = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(left[:line_code], 'old')
- %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
+ %td.old_line.diff-line-num{id: left[:line_code], class: [left[:type], ('empty-cell' unless left[:number])], data: { linenumber: left[:number] }}
+ %a{href: "##{left[:line_code]}" }= raw(left[:number])
+ %td.line_content.parallel.noteable_line{class: [left[:type], ('empty-cell' if left[:text].empty?)], data: diff_view_line_data(left[:line_code], left[:position], 'old')}= diff_line_content(left[:text])
- if right[:type] == 'new'
- - new_line_class = 'new'
+ - new_line_type = 'new'
- new_line_code = right[:line_code]
+ - new_position = right[:position]
- else
- - new_line_class = nil
+ - new_line_type = nil
- new_line_code = left[:line_code]
+ - new_position = left[:position]
- %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
- = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(new_line_code, 'new')
- %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
+ %td.new_line.diff-line-num{id: new_line_code, class: [new_line_type, ('empty-cell' unless right[:number])], data: { linenumber: right[:number] }}
+ %a{href: "##{new_line_code}" }= raw(right[:number])
+ %td.line_content.parallel.noteable_line{class: [new_line_type, ('empty-cell' if right[:text].empty?)], data: diff_view_line_data(new_line_code, new_position, 'new')}= diff_line_content(right[:text])
- unless @diff_notes_disabled
- notes_left, notes_right = organize_comments(left, right)
- if notes_left.present? || notes_right.present?
= render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right
-
-- if diff_file.diff.diff.blank? && diff_file.mode_changed?
- .file-mode-changed
- File mode changed
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 068593a7dd..196f8122db 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -3,23 +3,18 @@
.suppressed-container
%a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
-%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
-
+%table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' }
- last_line = 0
- - diff_file.highlighted_diff_lines.each_with_index do |line, index|
- - line_code = generate_line_code(diff_file.file_path, line)
+ - diff_file.highlighted_diff_lines.each do |line|
- last_line = line.new_pos
- = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
+ = render "projects/diffs/line", line: line, diff_file: diff_file
- unless @diff_notes_disabled
- - diff_notes = @grouped_diff_notes[line_code]
+ - line_code = diff_file.line_code(line)
+ - diff_notes = @grouped_diff_notes[line_code] if line_code
- if diff_notes
= render "projects/notes/diff_notes_with_reply", notes: diff_notes
- if last_line > 0
= render "projects/diffs/match_line", { line: "",
line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file }
-
-- if diff_file.diff.blank? && diff_file.mode_changed?
- .file-mode-changed
- File mode changed
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 15536c17f8..10fa1ddf2e 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -2,9 +2,6 @@
%h4
Too many changes to show.
.pull-right
- - unless diff_hard_limit_enabled?
- = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm"
-
- if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit)
= link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 27a94fe02d..921155e970 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -23,7 +23,7 @@
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'label-light' do
Visibility Level
- = link_to "(?)", help_page_path("public_access", "public_access")
+ = link_to "(?)", help_page_path("public_access/public_access")
- if can_change_visibility_level?(@project, current_user)
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project)
- else
@@ -32,6 +32,10 @@
%strong
= visibility_level_label(@project.visibility_level)
.light= visibility_level_description(@project.visibility_level, @project)
+
+ .form-group
+ = render 'shared/allow_request_access', form: f
+
.form-group
= f.label :tag_list, "Tags", class: 'label-light'
= f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
@@ -86,8 +90,6 @@
%hr
= render 'merge_request_settings', f: f
%hr
- = render 'builds_settings', f: f
- %hr
%fieldset.features.append-bottom-default
%h5.prepend-top-0
Project avatar
diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml
index eafa246d05..e245339560 100644
--- a/app/views/projects/environments/_environment.html.haml
+++ b/app/views/projects/environments/_environment.html.haml
@@ -15,3 +15,6 @@
%td
- if last_deployment
#{time_ago_with_tooltip(last_deployment.created_at)}
+
+ %td
+ = render 'projects/deployments/actions', deployment: last_deployment
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index a03f117291..a6dd34653a 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Environments"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
- if can?(current_user, :create_environment, @project) && !@environments.blank?
.top-area
.nav-controls
@@ -17,7 +17,7 @@
Environments are places where code gets deployed, such as staging or production.
%br
= succeed "." do
- = link_to "Read more about environments", help_page_path("ci", "environments")
+ = link_to "Read more about environments", help_page_path("ci/environments")
- if can?(current_user, :create_environment, @project)
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
@@ -28,4 +28,5 @@
%th Environment
%th Last deployment
%th Date
+ %th
= render @environments
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index da325efecd..89e0656719 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -7,6 +7,6 @@
%p
Environments allow you to track deployments of your application
= succeed "." do
- = link_to "Read more about environments", help_page_path("ci", "environments")
+ = link_to "Read more about environments", help_page_path("ci/environments")
= render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 4c15e2759d..b8b1ce52a9 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -2,10 +2,10 @@
- page_title "Environments"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
.col-md-9
- %h3.page-title= @environment.name.titleize
+ %h3.page-title= @environment.name.capitalize
.col-md-3
.nav-controls
@@ -20,7 +20,7 @@
Define environments in the deploy stage(s) in
%code .gitlab-ci.yml
to track deployments here.
- = link_to "Read more", help_page_path("ci", "environments"), class: "btn btn-success"
+ = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- else
.table-holder
%table.table.environments
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index dbe9ddfde2..a1d79bdabd 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -31,11 +31,11 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 73a7fc0e1a..5242bc72b7 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -1,45 +1,54 @@
- page_title "Fork project"
-- if @namespaces.present?
- %h3.page-title Fork project
- %p.lead
- Click to fork the project to a user or group
- %hr
- .fork-namespaces
- - @namespaces.in_groups_of(6, false) do |group|
- .row
- - group.each do |namespace|
- .col-md-2.col-sm-3
- - if fork = namespace.find_fork_of(@project)
- .fork-thumbnail
- = link_to project_path(fork), title: "Visit project fork", class: 'has-tooltip' do
- = image_tag namespace_icon(namespace, 100)
- .caption
- %strong
- = namespace.human_name
- %div.text-primary
- Already forked
-
- - else
- .fork-thumbnail
- = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has-tooltip' do
- = image_tag namespace_icon(namespace, 100)
- .caption
- %strong
- = namespace.human_name
-
- %p.light
- Fork is a copy of a project repository.
+.row.prepend-top-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Fork project
+ %p
+ A fork is a copy of a project.
%br
- Forking a repository allows you to do changes without affecting the original project.
-- else
- %h3 No available namespaces to fork the project
- %p.slead
- You must have permission to create a project in a namespace before forking.
+ Forking a repository allows you to make changes without affecting the original project.
+ .col-lg-9
+ .fork-namespaces
+ - if @namespaces.present?
+ %label.label-light
+ %span
+ Click to fork the project to a user or group
+ - @namespaces.in_groups_of(6, false) do |group|
+ .row
+ - group.each do |namespace|
+ - avatar = namespace_icon(namespace, 100)
+ - if fork = namespace.find_fork_of(@project)
+ .fork-thumbnail.forked
+ = link_to project_path(fork) do
+ - if /no_((\w*)_)*avatar/.match(avatar)
+ .no-avatar
+ = icon 'question'
+ - else
+ = image_tag avatar
+ .caption
+ = namespace.human_name
+ - else
+ .fork-thumbnail
+ = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), method: "POST" do
+ - if /no_((\w*)_)*avatar/.match(avatar)
+ .no-avatar
+ = icon 'question'
+ - else
+ = image_tag avatar
+ .caption
+ = namespace.human_name
+ - else
+ %label.label-light
+ %span
+ No available namespaces to fork the project.
+ %br
+ %small
+ You must have permission to create a project in a namespace before forking.
-.save-project-loader.hide
- .center
- %h2
- %i.fa.fa-spinner.fa-spin
- Forking repository
- %p Please wait a moment, this page will automatically refresh when ready.
+ .save-project-loader.hide
+ .center
+ %h2
+ %i.fa.fa-spinner.fa-spin
+ Forking repository
+ %p Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 5bc5c71283..331dc1fcc2 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -50,10 +50,12 @@
%td.duration
- if generic_commit_status.duration
- #{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)}
+ = icon("clock-o")
+ = time_interval_in_words(generic_commit_status.duration)
%td.timestamp
- if generic_commit_status.finished_at
+ = icon("calendar")
%span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
- if defined?(coverage) && coverage
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index a388d9a0a6..45e51389c0 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,7 +1,9 @@
.nav-links.sub-nav
%ul{ class: (container_class) }
- - page_specific_javascripts asset_path("graphs/application.js")
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/chart.js')
+ = page_specific_javascript_tag('graphs/graphs_bundle.js')
= nav_link(action: :show) do
= link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml
index e695d3ae36..6be4273b6a 100644
--- a/app/views/projects/graphs/ci.html.haml
+++ b/app/views/projects/graphs/ci.html.haml
@@ -2,7 +2,7 @@
- page_title "Continuous Integration", "Graphs"
= render 'head'
-%div{ class: (container_class) }
+%div{ class: container_class }
.sub-header-block
.oneline
A collection of graphs for Continuous Integration
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 0daffe68f6..65db8af494 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -2,7 +2,7 @@
- page_title "Commits", "Graphs"
= render 'head'
-%div{ class: (container_class) }
+%div{ class: container_class }
.sub-header-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs_commits'
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
index 6d97f552a8..fcfcae0be2 100644
--- a/app/views/projects/graphs/languages.html.haml
+++ b/app/views/projects/graphs/languages.html.haml
@@ -2,7 +2,7 @@
- page_title "Languages", "Graphs"
= render 'head'
-%div{ class: (container_class) }
+%div{ class: container_class }
.sub-header-block
.oneline
Programming languages used in this repository
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 9f7e2a361f..a985b442b2 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -2,7 +2,7 @@
- page_title "Contributors", "Graphs"
= render 'head'
-%div{ class: (container_class) }
+%div{ class: container_class }
.sub-header-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs'
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index a8a8caf728..2cd8d03e30 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -10,7 +10,7 @@
.panel-body
%pre
:preserve
- #{sanitize_repo_path(@project.import_error)}
+ #{sanitize_repo_path(@project, @project.import_error)}
= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
= render "shared/import_form", f: f
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index cd876b5ea6..7612fe3719 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -7,20 +7,36 @@
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
%div{ class: (container_class) }
- .top-area
- = render 'shared/issuable/nav', type: :issues
- .nav-controls
- - if current_user
- = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
- = icon('rss')
- %span.icon-label
- Subscribe
- = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
+ - if @project.issues.any?
+ .top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls
+ - if current_user
+ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ = icon('rss')
+ %span.icon-label
+ Subscribe
+ = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
+ - if can? current_user, :create_issue, @project
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ New Issue
+ = render 'shared/issuable/filter', type: :issues
+
+ .issues-holder
+ = render "issues"
+ - else
+ .blank-state.blank-state-welcome
+ %h2.blank-state-title.blank-state-welcome-title
+ Welcome to GitLab Issues
+ %p.blank-state-text
+ Code, test, and deploy together
+ .blank-state
+ .blank-state-icon
+ = custom_icon("issues", size: 50)
+ %h3.blank-state-title
+ You don't have any issues right now.
+ %p.blank-state-text
+ Issues are the best way to track your project progress
- if can? current_user, :create_issue, @project
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
New Issue
-
- = render 'shared/issuable/filter', type: :issues
-
- .issues-holder
- = render "issues"
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index aa4d69550e..db66a0edbd 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -3,7 +3,7 @@
- hide_class = ''
= render "projects/issues/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 2ec96308fd..873ed9b59e 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -42,7 +42,7 @@
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- - if @commits.present?
+ - if @commits_count.nonzero?
%ul.merge-request-tabs.nav-links.no-top.no-bottom
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
@@ -51,7 +51,7 @@
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
- %span.badge= @commits.size
+ %span.badge= @commits_count
- if @pipeline
%li.builds-tab
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 9f948d41dd..ace275c689 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -3,7 +3,7 @@
= render "projects/issues/head"
= render 'projects/last_push'
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index b3bea900d4..b727efaa6a 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -8,7 +8,7 @@
%p
%strong Step 1.
Fetch and check out the branch for this merge request
- = clipboard_button_with_class({clipboard_target: "pre#merge-info-1"}, css_class: "btn-clipboard")
+ = clipboard_button(clipboard_target: "pre#merge-info-1")
%pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
@@ -25,7 +25,7 @@
%p
%strong Step 3.
Merge the branch and fix any conflicts that come up
- = clipboard_button_with_class({clipboard_target: "pre#merge-info-3"}, css_class: "btn-clipboard")
+ = clipboard_button(clipboard_target: "pre#merge-info-3")
%pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
@@ -38,7 +38,7 @@
%p
%strong Step 4.
Push the result of the merge to GitLab
- = clipboard_button_with_class({clipboard_target: "pre#merge-info-4"}, css_class: "btn-clipboard")
+ = clipboard_button(clipboard_target: "pre#merge-info-4")
%pre.dark#merge-info-4
:preserve
git push origin #{h @merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 5bf5210aea..b24bdf22ce 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -19,13 +19,13 @@
Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- %li{ class: issue_button_visibility(@merge_request, true) }
+ %li{ class: merge_request_button_visibility(@merge_request, true) }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
- %li{ class: issue_button_visibility(@merge_request, false) }
+ %li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
%li
= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
Edit
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 08a38d283d..6ef640bb65 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,13 +1,13 @@
- if @pipeline
.mr-widget-heading
- - %w[success skipped canceled failed running pending].each do |status|
+ - %w[success success_with_warnings skipped canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) }
= ci_icon_for_status(status)
%span
CI build
= ci_label_for_status(status)
for
- - commit = @merge_request.last_commit
+ - commit = @merge_request.diff_head_commit
= succeed "." do
= link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
%span.ci-coverage
@@ -24,7 +24,7 @@
CI build
= ci_label_for_status(status)
for
- - commit = @merge_request.last_commit
+ - commit = @merge_request.diff_head_commit
= succeed "." do
= link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
%span.ci-coverage
@@ -33,12 +33,12 @@
.ci_widget
= icon("spinner spin")
- Checking CI status for #{@merge_request.last_commit_short_sha}…
+ Checking CI status for #{@merge_request.diff_head_commit.short_id}…
.ci_widget.ci-not_found{style: "display:none"}
= icon("times-circle")
- Could not find CI status for #{@merge_request.last_commit_short_sha}.
+ Could not find CI status for #{@merge_request.diff_head_commit.short_id}.
.ci_widget.ci-error{style: "display:none"}
= icon("times-circle")
- Could not connect to the CI server. Please check your settings and try again.
\ No newline at end of file
+ Could not connect to the CI server. Please check your settings and try again.
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 0e0af57d76..dc18f715f2 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -22,10 +22,10 @@
- elsif @merge_request.can_be_merged?
= render 'projects/merge_requests/widget/open/accept'
- - if @closes_issues.present?
+ - if mr_closes_issues.present?
.mr-widget-footer
%span
%i.fa.fa-check
- Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
+ Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)}
= succeed '.' do
- != markdown issues_sentence(@closes_issues), pipeline: :gfm, author: @merge_request.author
+ != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 941513febb..bf2e76f008 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -2,7 +2,7 @@
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-quick-submit js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
- = hidden_field_tag :sha, @merge_request.source_sha
+ = hidden_field_tag :sha, @merge_request.diff_head_sha
.accept-merge-holder.clearfix.js-toggle-container
.clearfix
.accept-action
diff --git a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
index 06ab0a3fa0..f000cc38a6 100644
--- a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
@@ -4,7 +4,7 @@
%p
Please resolve these conflicts or
- - if @merge_request.can_be_merged_by?(current_user)
+ - if @merge_request.can_be_merged_via_command_line_by?(current_user)
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
- else
ask someone with write access to this repository to merge this request manually.
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
index ad898ff153..2b6b5e05e8 100644
--- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -16,7 +16,7 @@
- if remove_source_branch_button || user_can_cancel_automatic_merge
.clearfix.prepend-top-10
- if remove_source_branch_button
- = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.source_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
+ = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.diff_head_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
= icon('times')
Remove Source Branch When Merged
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index b0e0bdfff5..ad2bfbec91 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Milestones"
= render "projects/issues/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
= render 'shared/milestones_filter'
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index 86295a3d01..8f6805268d 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
-%div{ class: (container_class) }
+%div{ class: container_class }
.row-content-block.second-block.content-component-block
.tree-ref-holder
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index e4ab064eda..b2ece44d96 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,8 +1,10 @@
- page_title "Network", @ref
-- page_specific_javascripts asset_path("network/application.js")
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/raphael.js')
+ = page_specific_javascript_tag('network/network_bundle.js')
= render "projects/commits/head"
= render "head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.project-network
.controls
= form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
@@ -15,5 +17,5 @@
= check_box_tag :filter_ref, 1, @options[:filter_ref]
%span Begin with the selected commit
- .network-graph{ data: { url: '#{escape_javascript(@url)}', commit_url: '#{escape_javascript(@commit_url)}', ref: '#{escape_javascript(@ref)}', commit_id: '#{escape_javascript(@commit.id)}' } }
+ .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
= spinner nil, true
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 3c1c606050..facdfcc944 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,110 +1,115 @@
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
-%h3.page-title
- New Project
-%hr
-
.project-edit-container
.project-edit-errors
= render 'projects/errors'
- .project-edit-content
+ .row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ New project
+ %p
+ Create or Import your project from popular Git services
+ .col-lg-9
+ = form_for @project, html: { class: 'new_project' } do |f|
+ %fieldset.append-bottom-0
+ .form-group.col-xs-12.col-sm-6
+ = f.label :namespace_id, class: 'label-light' do
+ %span
+ Project path
+ .form-group
+ .input-group
+ - if current_user.can_select_namespace?
+ .input-group-addon
+ = root_url
+ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1}
- = form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f|
- .form-group
- = f.label :path, class: 'control-label' do
- Project owner
- .col-sm-10
- = f.select :namespace_id, namespaces_options(:current_user), {}, {class: 'select2 js-select-namespace', tabindex: 1}
-
- - if current_user.can_create_group?
- .help-block
- Want to house several dependent projects under the same namespace?
- = link_to "Create a group", new_group_path
-
- .form-group
- = f.label :path, class: 'control-label' do
- Project name
- .col-sm-10
- = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
-
- - if import_sources_enabled?
- .project-import.js-toggle-container
- .form-group
- %label.control-label Import project from
- .col-sm-10
- - if github_import_enabled?
- - if github_import_configured?
- = link_to status_import_github_path, class: 'btn import_github' do
- %i.fa.fa-github
- GitHub
- else
- = link_to '#', class: 'how_to_import_link btn import_github' do
- %i.fa.fa-github
- GitHub
- = render 'github_import_modal'
+ .input-group-addon.static-namespace
+ #{root_url}#{current_user.username}/
+ .form-group.col-xs-12.col-sm-6.project-path
+ = f.label :namespace_id, class: 'label-light' do
+ %span
+ Project name
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
+ - if current_user.can_create_group?
+ .help-block
+ Want to house several dependent projects under the same namespace?
+ = link_to "Create a group", new_group_path
- - if bitbucket_import_enabled?
- - if bitbucket_import_configured?
- = link_to status_import_bitbucket_path, class: 'btn import_bitbucket', "data-no-turbolink" => "true" do
- %i.fa.fa-bitbucket
- Bitbucket
- - else
- = link_to status_import_bitbucket_path, class: 'how_to_import_link btn import_bitbucket', "data-no-turbolink" => "true" do
- %i.fa.fa-bitbucket
- Bitbucket
- = render 'bitbucket_import_modal'
+ - if import_sources_enabled?
+ .project-import.js-toggle-container
+ .form-group.clearfix
+ = f.label :visibility_level, class: 'label-light' do
+ Import project from
+ .col-sm-12.import-buttons
+ %div
+ - if github_import_enabled?
+ = link_to new_import_github_path, class: 'btn import_github' do
+ = icon 'github', text: 'GitHub'
+ %div
+ - if bitbucket_import_enabled?
+ - if bitbucket_import_configured?
+ = link_to status_import_bitbucket_path, class: 'btn import_bitbucket', "data-no-turbolink" => "true" do
+ %i.fa.fa-bitbucket
+ Bitbucket
+ - else
+ = link_to status_import_bitbucket_path, class: 'how_to_import_link btn import_bitbucket', "data-no-turbolink" => "true" do
+ %i.fa.fa-bitbucket
+ Bitbucket
+ = render 'bitbucket_import_modal'
+ %div
+ - if gitlab_import_enabled?
+ - if gitlab_import_configured?
+ = link_to status_import_gitlab_path, class: 'btn import_gitlab' do
+ %i.fa.fa-heart
+ GitLab.com
+ - else
+ = link_to status_import_gitlab_path, class: 'how_to_import_link btn import_gitlab' do
+ %i.fa.fa-heart
+ GitLab.com
+ = render 'gitlab_import_modal'
+ %div
+ - if gitorious_import_enabled?
+ = link_to new_import_gitorious_path, class: 'btn import_gitorious' do
+ %i.icon-gitorious.icon-gitorious-small
+ Gitorious.org
+ %div
+ - if google_code_import_enabled?
+ = link_to new_import_google_code_path, class: 'btn import_google_code' do
+ %i.fa.fa-google
+ Google Code
+ %div
+ - if fogbugz_import_enabled?
+ = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
+ %i.fa.fa-bug
+ Fogbugz
+ %div
+ - if git_import_enabled?
+ = link_to "#", class: 'btn js-toggle-button import_git' do
+ %i.fa.fa-git
+ %span Repo by URL
+ %div{ class: 'import_gitlab_project' }
+ - if gitlab_project_import_enabled?
+ = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
+ %i.fa.fa-gitlab
+ %span GitLab export
- - if gitlab_import_enabled?
- - if gitlab_import_configured?
- = link_to status_import_gitlab_path, class: 'btn import_gitlab' do
- %i.fa.fa-heart
- GitLab.com
- - else
- = link_to status_import_gitlab_path, class: 'how_to_import_link btn import_gitlab' do
- %i.fa.fa-heart
- GitLab.com
- = render 'gitlab_import_modal'
+ .js-toggle-content.hide
+ = render "shared/import_form", f: f
- - if gitorious_import_enabled?
- = link_to new_import_gitorious_path, class: 'btn import_gitorious' do
- %i.icon-gitorious.icon-gitorious-small
- Gitorious.org
+ .form-group
+ = f.label :description, class: 'label-light' do
+ Project description
+ %span.light (optional)
+ = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250
- - if google_code_import_enabled?
- = link_to new_import_google_code_path, class: 'btn import_google_code' do
- %i.fa.fa-google
- Google Code
+ .form-group.project-visibility-level-holder
+ = f.label :visibility_level, class: 'label-light' do
+ Visibility Level
+ = link_to "(?)", help_page_path("public_access/public_access")
+ = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project)
- - if fogbugz_import_enabled?
- = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
- %i.fa.fa-bug
- Fogbugz
-
- - if git_import_enabled?
- = link_to "#", class: 'btn js-toggle-button import_git' do
- %i.fa.fa-git
- %span Repo by URL
-
- - if gitlab_project_import_enabled?
- = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do
- %i.fa.fa-gitlab
- %span GitLab export
-
- .js-toggle-content.hide
- = render "shared/import_form", f: f
-
- .prepend-botton-10
-
- .form-group
- = f.label :description, class: 'control-label' do
- Description
- %span.light (optional)
- .col-sm-10
- = f.text_area :description, class: "form-control", rows: 3, maxlength: 250, tabindex: 3
- = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
-
- .form-actions
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
@@ -125,28 +130,33 @@
$(".modal").hide();
});
- $('.import_gitlab_project').bind('click', function() {
- var _href = $("a.import_gitlab_project").attr("href");
- $(".import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
+ $('.btn_import_gitlab_project').bind('click', function() {
+ var _href = $("a.btn_import_gitlab_project").attr("href");
+ $(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
});
- $('.import_gitlab_project').attr('disabled',true)
- $('.import_gitlab_project').attr('title', 'Project path required.');
+ $('.btn_import_gitlab_project').attr('disabled',true)
+ $('.import_gitlab_project').attr('title', 'Project path and name required.');
$('.import_gitlab_project').click(function( event ) {
- if($('.import_gitlab_project').attr('disabled')) {
+ if($('.btn_import_gitlab_project').attr('disabled')) {
event.preventDefault();
- new Flash("Please enter a path for the project to be imported to.");
+ new Flash("Please enter path and name for the project to be imported to.");
}
});
$('#project_path').keyup(function(){
if($(this).val().length !=0) {
- $('.import_gitlab_project').attr('disabled', false);
+ $('.btn_import_gitlab_project').attr('disabled', false);
$('.import_gitlab_project').attr('title','');
$(".flash-container").html("")
} else {
- $('.import_gitlab_project').attr('disabled',true);
- $('.import_gitlab_project').attr('title', 'Project path required.');
+ $('.btn_import_gitlab_project').attr('disabled',true);
+ $('.import_gitlab_project').attr('title', 'Project path and name required.');
}
- })
+ });
+
+ $('.import_git').click(function( event ) {
+ $projectImportUrl = $('#project_import_url')
+ $projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'))
+ });
\ No newline at end of file
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 8144c1ba49..ec6c4938ef 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -4,5 +4,4 @@
%td.notes_content
%ul.notes{ data: { discussion_id: note.discussion_id } }
= render partial: "projects/notes/note", collection: notes, as: :note
- .discussion-reply-holder
- = link_to_reply_discussion(note)
+ = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index 45986b0d1e..e50a4f86d0 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -8,8 +8,7 @@
%ul.notes{ data: { discussion_id: note_left.discussion_id } }
= render partial: "projects/notes/note", collection: notes_left, as: :note
- .discussion-reply-holder
- = link_to_reply_discussion(note_left, 'old')
+ = link_to_reply_discussion(note_left, 'old')
- else
%td.notes_line.old= ""
%td.notes_content.parallel.old= ""
@@ -20,8 +19,7 @@
%ul.notes{ data: { discussion_id: note_right.discussion_id } }
= render partial: "projects/notes/note", collection: notes_right, as: :note
- .discussion-reply-holder
- = link_to_reply_discussion(note_right, 'new')
+ = link_to_reply_discussion(note_right, 'new')
- else
%td.notes_line.new= ""
%td.notes_content.parallel.new= ""
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 03b3f6935d..7c61ba750f 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -7,6 +7,7 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
= f.hidden_field :type
+ = f.hidden_field :position
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml
index 0b00204340..25466e7562 100644
--- a/app/views/projects/notes/_hints.html.haml
+++ b/app/views/projects/notes/_hints.html.haml
@@ -1,8 +1,8 @@
.comment-toolbar.clearfix
.toolbar-text
Styling with
- = link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1
+ = link_to 'Markdown', help_page_path('markdown/markdown'), target: '_blank', tabindex: -1
is supported
%button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' }
= icon('file-image-o', class: 'toolbar-button-icon')
- Attach a file
+ Attach a file
\ No newline at end of file
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index c04d291412..71da8ac9d7 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -17,7 +17,7 @@
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
.note-actions
- - access = note.project.team.human_max_access(note.author.id)
+ - access = note_max_access_for_user(note)
- if access and not note.system
%span.note-role.hidden-xs= access
- if current_user and not note.system
@@ -30,9 +30,9 @@
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do
= icon('trash-o')
.note-body{class: note_editable ? 'js-task-list-container' : ''}
- .note-text
+ .note-text.md
= preserve do
- = markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author)
+ = note.note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable
= render 'projects/notes/edit_form', note: note
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 1c39ce897a..74538a9723 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -2,6 +2,8 @@
= render "projects/notes/notes"
%ul.notes.notes-form.timeline
%li.timeline-entry
+ .flash-container.timeline-content
+
- if can? current_user, :create_note, @project
.timeline-icon.hidden-xs.hidden-sm
%a.author_link{ href: user_path(current_user) }
@@ -12,9 +14,9 @@
.disabled-comment.text-center
.disabled-comment-text.inline
Please
- = link_to "register",new_user_session_path
+ = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
or
- = link_to "login",new_user_session_path
+ = link_to "login", new_session_path(:user, redirect_to_referer: 'yes')
to post a comment
:javascript
diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
index 6401245bf7..4a69b8f884 100644
--- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml
+++ b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
@@ -1,30 +1,17 @@
- note = discussion_notes.first
-- diff = note.diff
-- return unless diff
+- diff_file = note.diff_file
+- return unless diff_file
+
+- blob = note.blob
+
+.diff-file.file-holder
+ .file-title
+ = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: note.project, url: diff_note_path(note)
-.diff-file
- .diff-header
- %span
- - if diff.deleted_file
- = diff.old_path
- - else
- = diff.new_path
- - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
- %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
.diff-content.code.js-syntax-highlight
%table
- note.truncated_diff_lines.each do |line|
- - type = line.type
- - line_code = generate_line_code(note.diff_file_path, line)
- %tr.line_holder{ id: line_code, class: "#{type}" }
- - if type == "match"
- %td.old_line.diff-line-num= "..."
- %td.new_line.diff-line-num= "..."
- %td.line_content.match= line.text
- - else
- %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? " ".html_safe : line.old_pos } }
- %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? " ".html_safe : line.new_pos } }
- %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
+ = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
- - if line_code == note.line_code
- = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
+ - if note.for_line?(line)
+ = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_notes.html.haml b/app/views/projects/notes/discussions/_notes.html.haml
index e598e3c7c6..a785149549 100644
--- a/app/views/projects/notes/discussions/_notes.html.haml
+++ b/app/views/projects/notes/discussions/_notes.html.haml
@@ -3,5 +3,4 @@
.notes{ data: { discussion_id: note.discussion_id } }
%ul.notes.timeline
= render partial: "projects/notes/note", collection: discussion_notes, as: :note
- .discussion-reply-holder
- = link_to_reply_discussion(note)
+ = link_to_reply_discussion(note)
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index b70693eeb6..5f466bdbac 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Pipelines"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
@@ -28,10 +28,10 @@
.nav-controls
- if can? current_user, :create_pipeline, @project
= link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
- New pipeline
+ Run pipeline
- unless @repository.gitlab_ci_yml
- = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+ = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
@@ -45,13 +45,13 @@
.table-holder
%table.table.builds
%tbody
- %th ID
+ %th Status
%th Commit
- stages.each do |stage|
%th.stage
%span.has-tooltip{ title: "#{stage.titleize}" }
- = stage.titleize.pluralize
- %th Duration
+ = stage.titleize
+ %th
%th
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml
new file mode 100644
index 0000000000..228bad36eb
--- /dev/null
+++ b/app/views/projects/pipelines_settings/show.html.haml
@@ -0,0 +1,103 @@
+- page_title "CI/CD Pipelines"
+
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ .col-lg-9
+ %h5.prepend-top-0
+ Pipelines
+ = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project), remote: true, authenticity_token: true do |f|
+ %fieldset.builds-feature
+ - unless @repository.gitlab_ci_yml
+ .form-group
+ %p Pipelines need to be configured before you can begin using Continuous Integration.
+ = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
+ .form-group
+ %p Get recent application code using the following command:
+ .radio
+ = f.label :build_allow_git_fetch_false do
+ = f.radio_button :build_allow_git_fetch, 'false'
+ %strong git clone
+ %br
+ %span.descr Slower but makes sure you have a clean dir before every build
+ .radio
+ = f.label :build_allow_git_fetch_true do
+ = f.radio_button :build_allow_git_fetch, 'true'
+ %strong git fetch
+ %br
+ %span.descr Faster
+
+ .form-group
+ = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
+ = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
+ %p.help-block per build in minutes
+ .form-group
+ = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
+ .input-group
+ %span.input-group-addon /
+ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ %span.input-group-addon /
+ %p.help-block
+ We will use this regular expression to find test coverage output in build trace.
+ Leave blank if you want to disable this feature
+ .bs-callout.bs-callout-info
+ %p Below are examples of regex for existing tools:
+ %ul
+ %li
+ Simplecov (Ruby) -
+ %code \(\d+.\d+\%\) covered
+ %li
+ pytest-cov (Python) -
+ %code \d+\%\s*$
+ %li
+ phpunit --coverage-text --colors=never (PHP) -
+ %code ^\s*Lines:\s*\d+.\d+\%
+ %li
+ gcovr (C/C++) -
+ %code ^TOTAL.*\s+(\d+\%)$
+ %li
+ tap --coverage-report=text-summary (Node.js) -
+ %code ^Statements\s*:\s*([^%]+)
+
+ .form-group
+ .checkbox
+ = f.label :public_builds do
+ = f.check_box :public_builds
+ %strong Public pipelines
+ .help-block Allow everyone to access pipelines for Public and Internal projects
+
+ .form-group.append-bottom-default
+ = f.label :runners_token, "Runners token", class: 'label-light'
+ = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+ %p.help-block The secure token used to checkout project.
+
+ = f.submit 'Save changes', class: "btn btn-save"
+
+%hr
+
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ Builds Badge
+ .col-lg-9
+ .prepend-top-10
+ .panel.panel-default
+ .panel-heading
+ %b Builds badge ·
+ = @build_badge.to_html
+ .pull-right
+ = render 'shared/ref_switcher', destination: 'badges', align_right: true
+ .panel-body
+ .row
+ .col-md-2.text-center
+ Markdown
+ .col-md-10.code.js-syntax-highlight
+ = highlight('.md', @build_badge.to_markdown)
+ .row
+ %hr
+ .row
+ .col-md-2.text-center
+ HTML
+ .col-md-10.code.js-syntax-highlight
+ = highlight('.html', @build_badge.to_html)
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index cb6136c215..e783d8c72c 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -2,8 +2,7 @@
.panel-heading
%strong #{@group.name}
group members
- %small
- (#{members.count})
+ %span.badge= members.size
- if can?(current_user, :admin_group_member, @group)
.controls
= link_to 'Manage group members',
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 82892a3335..978c4dfc5e 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -12,7 +12,7 @@
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2"
.help-block
Read more about role permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+ %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
.form-actions
= f.submit 'Add users to project', class: "btn btn-create"
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 952844acef..77370c14de 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -1,6 +1,7 @@
- @project_group_links.each do |group_links|
- shared_group = group_links.group
- - shared_group_users_count = group_links.group.group_members.count
+ - shared_group_members = shared_group.members
+ - shared_group_users_count = shared_group_members.size
.panel.panel-default
.panel-heading
Shared with
@@ -15,7 +16,7 @@
Edit group members
%ul.content-list
= render partial: 'shared/members/member',
- collection: shared_group.group_members.order(access_level: :desc).limit(20),
+ collection: shared_group_members.order(access_level: :desc).limit(20),
as: :member,
locals: { show_controls: false, show_roles: false }
- if shared_group_users_count > 20
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 0320761425..b0bfdd235f 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -2,8 +2,7 @@
.panel-heading
%strong #{@project.name}
project members
- %small
- (#{members.count})
+ %span.badge= members.size
.controls
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 357ccccaf1..9031f01b49 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -13,9 +13,9 @@
Users with access to this project are listed below.
= render "new_project_member"
- = render 'shared/members/requests', membership_source: @project, members: @project_members.request
+ = render 'shared/members/requests', membership_source: @project, requesters: @requesters
- = render 'team', members: @project_members.non_request
+ = render 'team', members: @project_members
- if @group
= render "group_members", members: @group_members
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index 565905cbe7..720d67dff7 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,6 +1,6 @@
%h5.prepend-top-0
- Already Protected (#{@branches.size})
-- if @branches.empty?
+ Already Protected (#{@protected_branches.size})
+- if @protected_branches.empty?
%p.settings-message.text-center
No branches are protected, protect a branch with the form above.
- else
@@ -8,34 +8,21 @@
.table-responsive
%table.table.protected-branches-list
%colgroup
+ %col{ width: "20%" }
%col{ width: "30%" }
- %col{ width: "30%" }
+ %col{ width: "25%" }
%col{ width: "25%" }
- if can_admin_project
%col
%thead
%tr
- %th Branch
- %th Last commit
- %th Developers can push
+ %th Protected Branch
+ %th Commit
+ %th Developers Can Push
+ %th Developers Can Merge
- if can_admin_project
%th
%tbody
- - @branches.each do |branch|
- - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
- %tr
- %td
- = link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name))
- - if @project.root_ref?(branch.name)
- %span.label.label-info.prepend-left-5 default
- %td
- - if commit = branch.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
- %td
- = check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url })
- - if can_admin_project
- %td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm"
+ = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
+
+ = paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/_dropdown.html.haml
new file mode 100644
index 0000000000..b803d932e6
--- /dev/null
+++ b/app/views/projects/protected_branches/_dropdown.html.haml
@@ -0,0 +1,17 @@
+= f.hidden_field(:name)
+
+= dropdown_tag("Protected Branch",
+ options: { title: "Pick protected branch", toggle_class: 'js-protected-branch-select js-filter-submit',
+ filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches",
+ footer_content: true,
+ data: { show_no: true, show_any: true, show_upcoming: true,
+ selected: params[:protected_branch_name],
+ project_id: @project.try(:id) } }) do
+
+ %ul.dropdown-footer-list.hidden.protected-branch-select-footer-list
+ %li
+ = link_to '#', title: "New Protected Branch", class: "create-new-protected-branch" do
+ Create new
+
+:javascript
+ new ProtectedBranchSelect();
diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/_matching_branch.html.haml
new file mode 100644
index 0000000000..8a5332ca5b
--- /dev/null
+++ b/app/views/projects/protected_branches/_matching_branch.html.haml
@@ -0,0 +1,9 @@
+%tr
+ %td
+ = link_to matching_branch.name, namespace_project_tree_path(@project.namespace, @project, matching_branch.name)
+ - if @project.root_ref?(matching_branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - commit = @project.commit(matching_branch.name)
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
new file mode 100644
index 0000000000..7fda7f9604
--- /dev/null
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -0,0 +1,23 @@
+- url = namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+%tr
+ %td
+ = protected_branch.name
+ - if @project.root_ref?(protected_branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - if protected_branch.wildcard?
+ - matching_branches = protected_branch.matching(repository.branches)
+ = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+ - else
+ - if commit = protected_branch.commit
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = time_ago_with_tooltip(commit.committed_date)
+ - else
+ (branch was removed from repository)
+ %td
+ = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url })
+ %td
+ = check_box_tag("developers_can_merge", protected_branch.id, protected_branch.developers_can_merge, data: { url: url })
+ - if can_admin_project
+ %td
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index c7d317dbae..950df740bb 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -4,30 +4,47 @@
.col-lg-3
%h4.prepend-top-0
= page_title
- %p Keep stable branches secure and force developers to use Merge Requests
+ %p Keep stable branches secure and force developers to use merge requests.
+ %p.prepend-top-20
+ By default, protected branches are designed to:
+ %ul
+ %li prevent their creation, if not already created, from everybody except Masters
+ %li prevent pushes from everybody except Masters
+ %li prevent anyone from force pushing to the branch
+ %li prevent anyone from deleting the branch
+ %p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
.col-lg-9
%h5.prepend-top-0
Protect a branch
- .account-well.append-bottom-default
- %p.light-header.append-bottom-0 Protected branches are designed to
- %ul
- %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
- %li prevent anyone from force pushing to the branch
- %li prevent anyone from deleting the branch
- %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
= form_errors(@protected_branch)
.form-group
= f.label :name, "Branch", class: "label-light"
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
+ = render partial: "dropdown", locals: { f: f }
+ %p.help-block
+ = link_to "Wildcards", help_page_path('user/project/protected_branches', anchor: "wildcard-protected-branches")
+ such as
+ %code *-stable
+ or
+ %code production/*
+ are supported.
+
.form-group
= f.check_box :developers_can_push, class: "pull-left"
.prepend-left-20
= f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
%p.light.append-bottom-0
Allow developers to push to this branch
- = f.submit "Protect", class: "btn-create btn"
+
+ .form-group
+ = f.check_box :developers_can_merge, class: "pull-left"
+ .prepend-left-20
+ = f.label :developers_can_merge, "Developers can merge", class: "label-light append-bottom-0"
+ %p.light.append-bottom-0
+ Allow developers to accept merge requests to this branch
+ = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
+
%hr
= render "branches_list"
diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml
new file mode 100644
index 0000000000..4d8169815b
--- /dev/null
+++ b/app/views/projects/protected_branches/show.html.haml
@@ -0,0 +1,25 @@
+- page_title @protected_branch.name, "Protected Branches"
+
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ = @protected_branch.name
+
+ .col-lg-9
+ %h5 Matching Branches
+ - if @matching_branches.present?
+ .table-responsive
+ %table.table.protected-branches-list
+ %colgroup
+ %col{ width: "30%" }
+ %col{ width: "30%" }
+ %thead
+ %tr
+ %th Branch
+ %th Last commit
+ %tbody
+ - @matching_branches.each do |matching_branch|
+ = render partial: "matching_branch", object: matching_branch
+ - else
+ %p.settings-message.text-center
+ Couldn't find any matching branches.
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 1f13ea28b4..752fbc21a1 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -8,9 +8,10 @@
.col-lg-9
= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
= render 'shared/service_settings', form: form
+
= form.submit 'Save changes', class: 'btn btn-save'
- if @service.valid? && @service.activated?
- disabled = @service.can_test? ? '':'disabled'
- = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
+ = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled}", title: @service.disabled_title
= link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 15f0d85194..dd1cf680cf 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,60 +12,67 @@
= render 'projects/last_push'
= render "home_panel"
-.project-stats.row-content-block.second-block
- %div{ class: (container_class) }
- %ul.nav
+%nav.project-stats{ class: (container_class) }
+ %ul.nav
+ %li
+ = link_to project_files_path(@project) do
+ Files (#{repository_size})
+ %li
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
+ %li
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
+ %li
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
+
+ - if default_project_view != 'readme' && @repository.readme
%li
- = link_to project_files_path(@project) do
- Files (#{repository_size})
+ = link_to 'Readme', readme_path(@project)
+
+ - if @repository.changelog
%li
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
- #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
+ = link_to 'Changelog', changelog_path(@project)
+
+ - if @repository.license_blob
%li
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
+ = link_to license_short_name(@project), license_path(@project)
+
+ - if @repository.contribution_guide
%li
- = link_to namespace_project_tags_path(@project.namespace, @project) do
- #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
+ = link_to 'Contribution guide', contribution_guide_path(@project)
- - if default_project_view != 'readme' && @repository.readme
- %li
- = link_to 'Readme', readme_path(@project)
+ - if current_user && can_push_branch?(@project, @project.default_branch)
+ - unless @repository.changelog
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
+ Add Changelog
+ - unless @repository.license_blob
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'LICENSE') do
+ Add License
+ - unless @repository.contribution_guide
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
+ Add Contribution guide
+ - unless @repository.gitlab_ci_yml
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
+ Set Up CI
+ %li.project-repo-buttons-right
+ .project-repo-buttons.project-right-buttons
+ - if current_user
+ = render 'shared/members/access_request_buttons', source: @project
- - if @repository.changelog
- %li
- = link_to 'Changelog', changelog_path(@project)
-
- - if @repository.license_blob
- %li
- = link_to license_short_name(@project), license_path(@project)
-
- - if @repository.contribution_guide
- %li
- = link_to 'Contribution guide', contribution_guide_path(@project)
-
- - if current_user && can_push_branch?(@project, @project.default_branch)
- - unless @repository.changelog
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
- Add Changelog
- - unless @repository.license_blob
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'LICENSE') do
- Add License
- - unless @repository.contribution_guide
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
- Add Contribution guide
- - unless @repository.gitlab_ci_yml
- %li.missing
- = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
- Set Up CI
+ .btn-group.project-repo-btn-group
+ = render "projects/buttons/download"
+ = render 'projects/buttons/dropdown'
+ = render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
- .content-block.second-block.white
- %div{ class: container_class }
- = render 'projects/last_commit', commit: @repository.commit, project: @project
+ .project-last-commit{ class: container_class }
+ = render 'projects/last_commit', commit: @repository.commit, project: @project
%div{ class: container_class }
- if @project.archived?
@@ -75,4 +82,4 @@
Archived project! Repository is read-only
%div{class: "project-show-#{default_project_view}"}
- = render default_project_view
+ = render default_project_view
\ No newline at end of file
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index bf57beb9d0..bdbf3e5f4d 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -1,27 +1,29 @@
.hidden-xs
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
+ New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :update_project_snippet, @snippet)
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
Delete
-.visible-xs-block.dropdown
- %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
- Options
- %span.caret
- .dropdown-menu.dropdown-menu-full-width
- %ul
- %li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
- New Snippet
- - if can?(current_user, :update_project_snippet, @snippet)
- %li
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
- Edit
- - if can?(current_user, :update_project_snippet, @snippet)
- %li
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
- Delete
+- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
+ .visible-xs-block.dropdown
+ %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-full-width
+ %ul
+ - if can?(current_user, :create_project_snippet, @project)
+ %li
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
+ New Snippet
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+ Edit
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ Delete
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 96fee3b17b..1646bcf4b8 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,10 +1,10 @@
- page_title "Snippets"
-.row-content-block.top-block
+.sub-header-block
.pull-right
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
+ New Snippet
.oneline
Share code pastes with others out of git repository
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 4ca1f58ac5..368231e73f 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -2,30 +2,30 @@
- page_title "Tags"
= render "projects/commits/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
.nav-text
Tags give the ability to mark specific points in history as being important
- - if can? current_user, :push_code, @project
- .nav-controls
+ .nav-controls
+ - if can? current_user, :push_code, @project
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
New tag
- .dropdown.inline
- %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} }
- %span.light= @sort.humanize
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- %li
- = link_to namespace_project_tags_path(sort: nil) do
- Name
- = link_to namespace_project_tags_path(sort: sort_value_recently_updated) do
- = sort_title_recently_updated
- = link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do
- = sort_title_oldest_updated
+ .dropdown.inline
+ %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} }
+ %span.light= @sort.humanize
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li
+ = link_to namespace_project_tags_path(sort: nil) do
+ Name
+ = link_to namespace_project_tags_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
.tags
- - unless @tags.empty?
+ - if @tags.any?
%ul.content-list
= render partial: 'tag', collection: @tags
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index b7d7d5c538..395d7af6cb 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -1,36 +1,39 @@
+- @no_container = true
- page_title @tag.name, "Tags"
= render "projects/commits/head"
-.row-content-block
- .pull-right
- - if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do
- = icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do
- = icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do
- = icon('history')
- - if can? current_user, :download_code, @project
- = render 'projects/tags/download', ref: @tag.name, project: @project
- - if can?(current_user, :admin_project, @project)
- .pull-right
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
- %i.fa.fa-trash-o
- .title
- %span.item-title= @tag.name
- - if @commit
- = render 'projects/branches/commit', commit: @commit, project: @project
- - else
- Cant find HEAD commit for this tag
- - if @tag.message.present?
- %pre.body
- = strip_gpg_signature(@tag.message)
+%div{ class: container_class }
+ .sub-header-block
+ .pull-right.tag-buttons
+ - if can?(current_user, :push_code, @project)
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
+ = icon("pencil")
+ = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do
+ = icon('files-o')
+ = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
+ = icon('history')
+ - if can? current_user, :download_code, @project
+ = render 'projects/tags/download', ref: @tag.name, project: @project
+ - if can?(current_user, :admin_project, @project)
+ .pull-right
+ = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+ %i.fa.fa-trash-o
+ .tag-info.append-bottom-10
+ .title
+ %span.item-title= @tag.name
+ - if @commit
+ = render 'projects/branches/commit', commit: @commit, project: @project
+ - else
+ Cant find HEAD commit for this tag
+ - if @tag.message.present?
+ %pre.body
+ = strip_gpg_signature(@tag.message)
-.append-bottom-default.prepend-top-default
- - if @release.description.present?
- .description
- .wiki
- = preserve do
- = markdown @release.description
- - else
- This tag has no release notes.
+ .append-bottom-default.prepend-top-default
+ - if @release.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown @release.description
+ - else
+ This tag has no release notes.
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 2abcfcdd7b..bf5360b4de 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -7,7 +7,7 @@
= render 'projects/last_push'
= render "projects/commits/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.tree-controls
= render 'projects/find_file_link'
- if can? current_user, :download_code, @project
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 4f8abcdc8e..c32cb122c2 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
-%div{ class: (container_class) }
+%div{ class: container_class }
%div#modal-new-wiki.modal
.modal-dialog
.modal-content
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index bf5d09d50c..233538bb48 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -2,7 +2,7 @@
- page_title "Edit", @page.title.capitalize, "Wiki"
= render 'nav'
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
.nav-text
%strong
@@ -15,9 +15,10 @@
Edit Page
.nav-controls
- - if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- New Page
+ - if !(@page && @page.persisted?)
+ - if can?(current_user, :create_wiki, @project)
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ New Page
= render 'main_links'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 6caf7230f3..b8811a28dd 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -2,7 +2,7 @@
- page_title "Git Access", "Wiki"
= render 'nav'
-%div{ class: (container_class) }
+%div{ class: container_class }
.sub-header-block
%span.oneline
Git access for
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 630ee35b70..4c0b14e2c4 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,6 +1,6 @@
- page_title "History", @page.title.capitalize, "Wiki"
= render 'nav'
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
.nav-text
%strong
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 81d9f391c1..9c10acd4cb 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -3,7 +3,7 @@
= render 'nav'
-%div{ class: (container_class) }
+%div{ class: container_class }
%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 76f9b1ecd7..5cebb538cf 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -2,7 +2,7 @@
- page_title @page.title.capitalize, "Wiki"
= render 'nav'
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
.nav-text
%strong= @page.title.capitalize
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 0fe8a3b490..290743feb4 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -2,9 +2,10 @@
.blob-result
.file-holder
.file-title
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
+ - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename))
+ = link_to blob_link do
%i.fa.fa-file
%strong
= blob.filename
.file-content.code.term
- = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
+ = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link
diff --git a/app/views/shared/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml
new file mode 100644
index 0000000000..53a99a736c
--- /dev/null
+++ b/app/views/shared/_allow_request_access.html.haml
@@ -0,0 +1,6 @@
+.checkbox
+ = form.label :request_access_enabled do
+ = form.check_box :request_access_enabled
+ %strong Allow users to request access
+ %br
+ %span.descr Allow users to request access if visibility is public or internal.
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 84b3f44c0a..3b82d8e686 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -2,15 +2,20 @@
.git-clone-holder.input-group
.input-group-btn
- %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'}
- %span
- = default_clone_protocol.upcase
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
- %li
- = ssh_clone_button(project)
- %li
- = http_clone_button(project)
+ -if allowed_protocols_present?
+ .clone-dropdown-btn.btn.btn-static
+ %span
+ = enabled_project_button(project, enabled_protocol)
+ - else
+ %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', data: { toggle: 'dropdown' }}
+ %span
+ = default_clone_protocol.upcase
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
+ %li
+ = ssh_clone_button(project)
+ %li
+ = http_clone_button(project)
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index aa18e6f236..8824bcc158 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,9 +1,5 @@
%ul.nav-links.event-filter.scrolling-tabs
- %li.fade-left
- = icon('arrow-left')
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
- %li.fade-right
- = icon('arrow-right')
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 37dcf39c06..e26693bf5b 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,13 +1,16 @@
+- repository = nil unless local_assigns.key?(:repository)
+
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
- link_icon = icon('link')
+ - link = blob_link if defined?(blob_link)
- blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
- %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+ %a.diff-line-num{href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i}
= link_icon
= i
.blob-content{data: {blob_id: blob.id}}
- = highlight(blob.name, blob.data, plain: blob.no_highlighting?)
+ = highlight(blob.path, blob.data, repository: repository, plain: blob.no_highlighting?)
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 627814bcfa..65a3a6bdda 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -2,7 +2,7 @@
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
.well.prepend-top-20
%ul
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
index 87028ececd..dce492352a 100644
--- a/app/views/shared/_labels_row.html.haml
+++ b/app/views/shared/_labels_row.html.haml
@@ -1,10 +1,9 @@
- labels.each do |label|
- %span.label-row.btn-group{ role: "group", aria: { label: escape_once(label.name) }, style: "color: #{text_color_for_bg(label.color)}" }
- = link_to namespace_project_label_path(@project.namespace, @project, label),
+ %span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" }
+ = link_to label.name, label_filter_path(@project, label, type: controller.controller_name),
class: "btn btn-transparent has-tooltip",
style: "background-color: #{label.color};",
title: escape_once(label.description),
- data: { container: "body" } do
- = escape_once label.name
+ data: { container: "body" }
%button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } }
= icon("times")
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 4eaf7c2a02..5254d26591 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -10,69 +10,28 @@
.col-sm-10
= form.check_box :active
-- if @service.supported_events.length > 1
- .form-group
- = form.label :url, "Trigger", class: 'control-label'
- .col-sm-10
- - if @service.supported_events.include?("push")
- %div
- = form.check_box :push_events, class: 'pull-left'
- .prepend-left-20
- = form.label :push_events, class: 'list-label' do
- %strong Push events
- %p.light
- This url will be triggered by a push to the repository
- - if @service.supported_events.include?("tag_push")
- %div
- = form.check_box :tag_push_events, class: 'pull-left'
- .prepend-left-20
- = form.label :tag_push_events, class: 'list-label' do
- %strong Tag push events
- %p.light
- This url will be triggered when a new tag is pushed to the repository
- - if @service.supported_events.include?("note")
- %div
- = form.check_box :note_events, class: 'pull-left'
- .prepend-left-20
- = form.label :note_events, class: 'list-label' do
- %strong Comments
- %p.light
- This url will be triggered when someone adds a comment
- - if @service.supported_events.include?("issue")
- %div
- = form.check_box :issues_events, class: 'pull-left'
- .prepend-left-20
- = form.label :issues_events, class: 'list-label' do
- %strong Issues events
- %p.light
- This url will be triggered when an issue is created/updated/merged
- - if @service.supported_events.include?("merge_request")
- %div
- = form.check_box :merge_requests_events, class: 'pull-left'
- .prepend-left-20
- = form.label :merge_requests_events, class: 'list-label' do
- %strong Merge Request events
- %p.light
- This url will be triggered when a merge request is created/updated/merged
- - if @service.supported_events.include?("build")
- %div
- = form.check_box :build_events, class: 'pull-left'
- .prepend-left-20
- = form.label :build_events, class: 'list-label' do
- %strong Build events
- %p.light
- This url will be triggered when a build status changes
- - if @service.supported_events.include?("wiki_page")
- %div
- = form.check_box :wiki_page_events, class: 'pull-left'
- .prepend-left-20
- = form.label :wiki_page_events, class: 'list-label' do
- %strong Wiki Page events
- %p.light
- This url will be triggered when a wiki page is created/updated
+.form-group
+ = form.label :url, "Trigger", class: 'control-label'
+ .col-sm-10
+ - @service.supported_events.each do |event|
+ %div
+ = form.check_box service_event_field_name(event), class: 'pull-left'
+ .prepend-left-20
+ = form.label service_event_field_name(event), class: 'list-label' do
+ %strong
+ = event.humanize
-- @service.fields.each do |field|
+ - field = @service.event_field(event)
+
+ - if field
+ %p
+ = form.text_field field[:name], class: "form-control", placeholder: field[:placeholder]
+
+ %p.light
+ = service_event_description(event)
+
+- @service.global_fields.each do |field|
- type = field[:type]
- if type == 'fieldset'
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index 1c6ec198d3..107ad19177 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,7 +1,7 @@
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'control-label' do
Visibility Level
- = link_to "(?)", help_page_path("public_access", "public_access")
+ = link_to "(?)", help_page_path("public_access/public_access")
.col-sm-10
- if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
diff --git a/app/views/shared/icons/_group.svg b/app/views/shared/icons/_group.svg.erb
similarity index 83%
rename from app/views/shared/icons/_group.svg
rename to app/views/shared/icons/_group.svg.erb
index 75cae0d16c..5363501690 100644
--- a/app/views/shared/icons/_group.svg
+++ b/app/views/shared/icons/_group.svg.erb
@@ -1,9 +1,4 @@
-
-
diff --git a/app/views/shared/icons/_icon_commit.svg b/app/views/shared/icons/_icon_commit.svg
new file mode 100644
index 0000000000..0e96035b7b
--- /dev/null
+++ b/app/views/shared/icons/_icon_commit.svg
@@ -0,0 +1,3 @@
+>>>
:
+
+```no-highlight
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+```
+
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+
## Code and Syntax Highlighting
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting
+
_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
list of supported languages visit the Rouge website._
-Blocks of code are either fenced by lines with three back-ticks ```
, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
+Blocks of code are either fenced by lines with three back-ticks ```
,
+or are indented with four spaces. Only the fenced code blocks support syntax
+highlighting:
```no-highlight
Inline `code` has `back-ticks around` it.
@@ -156,6 +218,9 @@ But let's throw in a tag.
## Inline Diff
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff
+
With inline diffs tags you can display {+ additions +} or [- deletions -].
The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}.
@@ -169,6 +234,9 @@ However the wrapping tags cannot be mixed as such:
## Emoji
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji
+
Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
:zap: You can use emoji anywhere GFM is supported. :v:
@@ -231,6 +299,9 @@ GFM also recognizes certain cross-project references:
## Task Lists
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists
+
You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so:
```no-highlight
@@ -249,6 +320,23 @@ You can add task lists to issues, merge requests and comments. To create a task
Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
+## Videos
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos
+
+Image tags with a video extension are automatically converted to a video player.
+
+The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`.
+
+ Here's a sample video:
+
+ ![Sample Video](img/video.mp4)
+
+Here's a sample video:
+
+![Sample Video](img/video.mp4)
+
# Standard Markdown
## Headers
@@ -552,7 +640,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also a separate paragraph, but...
This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-This line is also a separate paragraph, and...
+This line is also a separate paragraph, and...
This line is on its own line, because the previous line ends with two
spaces.
```
@@ -564,7 +652,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also begins a separate paragraph, but...
This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-This line is also a separate paragraph, and...
+This line is also a separate paragraph, and...
This line is on its own line, because the previous line ends with two
spaces.
@@ -610,6 +698,7 @@ By including colons in the header row, you can align the text within that column
- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
+[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md
[rouge]: http://rouge.jneen.net/ "Rouge website"
[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com
diff --git a/doc/monitoring/img/health_check_token.png b/doc/monitoring/img/health_check_token.png
index 2daf8606b0..2d7c82a65a 100644
Binary files a/doc/monitoring/img/health_check_token.png and b/doc/monitoring/img/health_check_token.png differ
diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md
index 168bd85c26..7947b0fedc 100644
--- a/doc/monitoring/performance/grafana_configuration.md
+++ b/doc/monitoring/performance/grafana_configuration.md
@@ -44,70 +44,32 @@ on a separate server)
## Apply retention policies and create continuous queries
-If you intend to import the GitLab provided Grafana dashboards, you will need
-to copy and run a set of queries against InfluxDB to create the needed data
-sets.
+If you intend to import the GitLab provided Grafana dashboards, you will need to
+set up the right retention policies and continuous queries. The easiest way of
+doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management)
+repository.
-On the InfluxDB server, run the following command, substituting your InfluxDB
-user and password:
-
-```bash
-influxdb --username admin -password super_secret
-```
-
-This will drop you in to an InfluxDB interactive session. Copy the entire
-contents below and paste it in to the interactive session:
+To use this repository you must first clone it:
```
-CREATE RETENTION POLICY default ON gitlab DURATION 1h REPLICATION 1 DEFAULT
-CREATE RETENTION POLICY downsampled ON gitlab DURATION 7d REPLICATION 1
-CREATE CONTINUOUS QUERY grape_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY grape_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY grape_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method, action END;
-CREATE CONTINUOUS QUERY grape_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY grape_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY grape_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.rails_file_descriptor_counts FROM gitlab."default".rails_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.rails_gc_counts FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.rails_gc_timings FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.rails_memory_usage_overall FROM gitlab."default".rails_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method, action END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY rails_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_overall FROM gitlab."default".rails_object_counts GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_per_type FROM gitlab."default".rails_object_counts GROUP BY time(1m), type END;
-CREATE CONTINUOUS QUERY rails_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_view_timings_per_action_and_view FROM gitlab."default".rails_views WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action, view END;
-CREATE CONTINUOUS QUERY sidekiq_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.sidekiq_file_descriptor_counts FROM gitlab."default".sidekiq_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.sidekiq_gc_counts FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.sidekiq_gc_timings FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_git_timings_per_action FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_overall FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_per_action FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_markdown_timings_overall FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^Banzai/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.sidekiq_memory_usage_overall FROM gitlab."default".sidekiq_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_action_and_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method, action END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY sidekiq_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_overall FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_per_type FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m), type END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_view_timings_per_action_and_view FROM gitlab."default".sidekiq_views GROUP BY time(1m), action, view END;
-CREATE CONTINUOUS QUERY web_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.web_transaction_counts_overall FROM gitlab."default".rails_transactions GROUP BY time(1m) END;
+git clone https://gitlab.com/gitlab-org/influxdb-management.git
+cd influxdb-management
```
+Next you must install the required dependencies:
+
+```
+gem install bundler
+bundle install
+```
+
+Now you must configure the repository by first copying `.env.example` to `.env`
+and then editing the `.env` file to contain the correct InfluxDB settings. Once
+configured you can simply run `bundle exec rake` and the InfluxDB database will
+be configured for you.
+
+For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md).
+
## Import Dashboards
You can now import a set of default dashboards that will give you a good
diff --git a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/monitoring/performance/img/grafana_dashboard_dropdown.png
index b4448c7a09..7e34fad71c 100644
Binary files a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png and b/doc/monitoring/performance/img/grafana_dashboard_dropdown.png differ
diff --git a/doc/monitoring/performance/img/grafana_dashboard_import.png b/doc/monitoring/performance/img/grafana_dashboard_import.png
index 5a2d3c0937..f97624365c 100644
Binary files a/doc/monitoring/performance/img/grafana_dashboard_import.png and b/doc/monitoring/performance/img/grafana_dashboard_import.png differ
diff --git a/doc/monitoring/performance/img/grafana_data_source_configuration.png b/doc/monitoring/performance/img/grafana_data_source_configuration.png
index 7e2e111f57..7d50e4c88c 100644
Binary files a/doc/monitoring/performance/img/grafana_data_source_configuration.png and b/doc/monitoring/performance/img/grafana_data_source_configuration.png differ
diff --git a/doc/monitoring/performance/img/grafana_data_source_empty.png b/doc/monitoring/performance/img/grafana_data_source_empty.png
index 11e27571e6..aa39a53aca 100644
Binary files a/doc/monitoring/performance/img/grafana_data_source_empty.png and b/doc/monitoring/performance/img/grafana_data_source_empty.png differ
diff --git a/doc/monitoring/performance/img/grafana_save_icon.png b/doc/monitoring/performance/img/grafana_save_icon.png
index 3d4265bee8..c740e33cd1 100644
Binary files a/doc/monitoring/performance/img/grafana_save_icon.png and b/doc/monitoring/performance/img/grafana_save_icon.png differ
diff --git a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png
index 14d82b6ac9..e6ed45a038 100644
Binary files a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png and b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png differ
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 963b35de3a..78d67aeec7 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -1,101 +1,3 @@
# Permissions
-Users have different abilities depending on the access level they have in a particular group or project.
-
-If a user is both in a project group and in the project itself, the highest permission level is used.
-
-If a user is a GitLab administrator they receive all permissions.
-
-On public and internal projects the Guest role is not enforced.
-All users will be able to create issues, leave comments, and pull or download the project code.
-
-To add or import a user, you can follow the [project users and members
-documentation](../workflow/add-user/add-user.md).
-
-## Project
-
-| Action | Guest | Reporter | Developer | Master | Owner |
-|---------------------------------------|---------|------------|-------------|----------|--------|
-| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
-| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Pull project code | | ✓ | ✓ | ✓ | ✓ |
-| Download project | | ✓ | ✓ | ✓ | ✓ |
-| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
-| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
-| Manage labels | | ✓ | ✓ | ✓ | ✓ |
-| See a commit status | | ✓ | ✓ | ✓ | ✓ |
-| See a container registry | | ✓ | ✓ | ✓ | ✓ |
-| See environments | | ✓ | ✓ | ✓ | ✓ |
-| Manage merge requests | | | ✓ | ✓ | ✓ |
-| Create new merge request | | | ✓ | ✓ | ✓ |
-| Create new branches | | | ✓ | ✓ | ✓ |
-| Push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Remove non-protected branches | | | ✓ | ✓ | ✓ |
-| Add tags | | | ✓ | ✓ | ✓ |
-| Write a wiki | | | ✓ | ✓ | ✓ |
-| Cancel and retry builds | | | ✓ | ✓ | ✓ |
-| Create or update commit status | | | ✓ | ✓ | ✓ |
-| Update a container registry | | | ✓ | ✓ | ✓ |
-| Remove a container registry image | | | ✓ | ✓ | ✓ |
-| Create new environments | | | ✓ | ✓ | ✓ |
-| Create new milestones | | | | ✓ | ✓ |
-| Add new team members | | | | ✓ | ✓ |
-| Push to protected branches | | | | ✓ | ✓ |
-| Enable/disable branch protection | | | | ✓ | ✓ |
-| Turn on/off prot. branch push for devs| | | | ✓ | ✓ |
-| Rewrite/remove git tags | | | | ✓ | ✓ |
-| Edit project | | | | ✓ | ✓ |
-| Add deploy keys to project | | | | ✓ | ✓ |
-| Configure project hooks | | | | ✓ | ✓ |
-| Manage runners | | | | ✓ | ✓ |
-| Manage build triggers | | | | ✓ | ✓ |
-| Manage variables | | | | ✓ | ✓ |
-| Delete environments | | | | ✓ | ✓ |
-| Switch visibility level | | | | | ✓ |
-| Transfer project to another namespace | | | | | ✓ |
-| Remove project | | | | | ✓ |
-| Force push to protected branches [^2] | | | | | |
-| Remove protected branches [^2] | | | | | |
-
-[^1]: If **Allow guest to access builds** is enabled in CI settings
-[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
-
-## Group
-
-In order for a group to appear as public and be browsable, it must contain at
-least one public project.
-
-Any user can remove themselves from a group, unless they are the last Owner of the group.
-
-| Action | Guest | Reporter | Developer | Master | Owner |
-|-------------------------|-------|----------|-----------|--------|-------|
-| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Edit group | | | | | ✓ |
-| Create project in group | | | | ✓ | ✓ |
-| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
-
-## External Users
-
-In cases where it is desired that a user has access only to some internal or
-private projects, there is the option of creating **External Users**. This
-feature may be useful when for example a contractor is working on a given
-project and should only have access to that project.
-
-External users can only access projects to which they are explicitly granted
-access, thus hiding all other internal or private ones from them. Access can be
-granted by adding the user as member to the project or group.
-
-They will, like usual users, receive a role in the project or group with all
-the abilities that are mentioned in the table above. They cannot however create
-groups or projects, and they have the same access as logged out users in all
-other cases.
-
-An administrator can flag a user as external [through the API](../api/users.md)
-or by checking the checkbox on the admin panel. As an administrator, navigate
-to **Admin > Users** to create a new user or edit an existing one. There, you
-will find the option to flag the user as external.
+This document was moved to [user/permissions.md](../user/permissions.md).
diff --git a/doc/profile/2fa.png b/doc/profile/2fa.png
index bbf415210d..bb464efa68 100644
Binary files a/doc/profile/2fa.png and b/doc/profile/2fa.png differ
diff --git a/doc/profile/2fa_auth.png b/doc/profile/2fa_auth.png
index 4a4fbe6898..0caaed1080 100644
Binary files a/doc/profile/2fa_auth.png and b/doc/profile/2fa_auth.png differ
diff --git a/doc/project_services/bugzilla.md b/doc/project_services/bugzilla.md
new file mode 100644
index 0000000000..215ed6fe9c
--- /dev/null
+++ b/doc/project_services/bugzilla.md
@@ -0,0 +1,17 @@
+# Bugzilla Service
+
+Go to your project's **Settings > Services > Bugzilla** and fill in the required
+details as described in the table below.
+
+| Field | Description |
+| ----- | ----------- |
+| `description` | A name for the issue tracker (to differentiate between instances, for example) |
+| `project_url` | The URL to the project in Bugzilla which is being linked to this GitLab project. Note that the `project_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. |
+| `issues_url` | The URL to the issue in Bugzilla project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
+| `new_issue_url` | This is the URL to create a new issue in Bugzilla for the project linked to this GitLab project. Note that the `new_issue_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. |
+
+Once you have configured and enabled Bugzilla:
+
+- the **Issues** link on the GitLab project pages takes you to the appropriate
+ Bugzilla product page
+- clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue
diff --git a/doc/project_services/img/builds_emails_service.png b/doc/project_services/img/builds_emails_service.png
index e604dd73ff..88943dc410 100644
Binary files a/doc/project_services/img/builds_emails_service.png and b/doc/project_services/img/builds_emails_service.png differ
diff --git a/doc/project_services/img/jira_add_gitlab_commit_message.png b/doc/project_services/img/jira_add_gitlab_commit_message.png
index 85e54861b3..aec472b911 100644
Binary files a/doc/project_services/img/jira_add_gitlab_commit_message.png and b/doc/project_services/img/jira_add_gitlab_commit_message.png differ
diff --git a/doc/project_services/img/jira_add_user_to_group.png b/doc/project_services/img/jira_add_user_to_group.png
index e457643388..0ba737bda9 100644
Binary files a/doc/project_services/img/jira_add_user_to_group.png and b/doc/project_services/img/jira_add_user_to_group.png differ
diff --git a/doc/project_services/img/jira_create_new_group.png b/doc/project_services/img/jira_create_new_group.png
index edaa132605..0609060cb0 100644
Binary files a/doc/project_services/img/jira_create_new_group.png and b/doc/project_services/img/jira_create_new_group.png differ
diff --git a/doc/project_services/img/jira_create_new_group_name.png b/doc/project_services/img/jira_create_new_group_name.png
index 9e518ad784..53d77b17df 100644
Binary files a/doc/project_services/img/jira_create_new_group_name.png and b/doc/project_services/img/jira_create_new_group_name.png differ
diff --git a/doc/project_services/img/jira_create_new_user.png b/doc/project_services/img/jira_create_new_user.png
index 57e433dd81..9eaa444ed2 100644
Binary files a/doc/project_services/img/jira_create_new_user.png and b/doc/project_services/img/jira_create_new_user.png differ
diff --git a/doc/project_services/img/jira_group_access.png b/doc/project_services/img/jira_group_access.png
index 47716ca6d0..8d4657427a 100644
Binary files a/doc/project_services/img/jira_group_access.png and b/doc/project_services/img/jira_group_access.png differ
diff --git a/doc/project_services/img/jira_issue_closed.png b/doc/project_services/img/jira_issue_closed.png
index cabec1ae13..acdd83702d 100644
Binary files a/doc/project_services/img/jira_issue_closed.png and b/doc/project_services/img/jira_issue_closed.png differ
diff --git a/doc/project_services/img/jira_issue_reference.png b/doc/project_services/img/jira_issue_reference.png
index 15739a22dc..1a2d9f04a6 100644
Binary files a/doc/project_services/img/jira_issue_reference.png and b/doc/project_services/img/jira_issue_reference.png differ
diff --git a/doc/project_services/img/jira_issues_workflow.png b/doc/project_services/img/jira_issues_workflow.png
index 28e17be3a8..0703081d77 100644
Binary files a/doc/project_services/img/jira_issues_workflow.png and b/doc/project_services/img/jira_issues_workflow.png differ
diff --git a/doc/project_services/img/jira_merge_request_close.png b/doc/project_services/img/jira_merge_request_close.png
index 1e78daf105..47785e3ba2 100644
Binary files a/doc/project_services/img/jira_merge_request_close.png and b/doc/project_services/img/jira_merge_request_close.png differ
diff --git a/doc/project_services/img/jira_project_name.png b/doc/project_services/img/jira_project_name.png
index 5986fdb63f..e785ec6140 100644
Binary files a/doc/project_services/img/jira_project_name.png and b/doc/project_services/img/jira_project_name.png differ
diff --git a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png
index 0149181dc8..fb270d85e3 100644
Binary files a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png and b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png differ
diff --git a/doc/project_services/img/jira_service.png b/doc/project_services/img/jira_service.png
index 1f6628c437..13aefce6f8 100644
Binary files a/doc/project_services/img/jira_service.png and b/doc/project_services/img/jira_service.png differ
diff --git a/doc/project_services/img/jira_service_close_issue.png b/doc/project_services/img/jira_service_close_issue.png
index 67dfc6144c..eed69e80d2 100644
Binary files a/doc/project_services/img/jira_service_close_issue.png and b/doc/project_services/img/jira_service_close_issue.png differ
diff --git a/doc/project_services/img/jira_service_page.png b/doc/project_services/img/jira_service_page.png
index c225daa81e..a5b49c501b 100644
Binary files a/doc/project_services/img/jira_service_page.png and b/doc/project_services/img/jira_service_page.png differ
diff --git a/doc/project_services/img/jira_submit_gitlab_merge_request.png b/doc/project_services/img/jira_submit_gitlab_merge_request.png
index e935d9362a..77630d39d3 100644
Binary files a/doc/project_services/img/jira_submit_gitlab_merge_request.png and b/doc/project_services/img/jira_submit_gitlab_merge_request.png differ
diff --git a/doc/project_services/img/jira_user_management_link.png b/doc/project_services/img/jira_user_management_link.png
index 2745916972..5f002b59ba 100644
Binary files a/doc/project_services/img/jira_user_management_link.png and b/doc/project_services/img/jira_user_management_link.png differ
diff --git a/doc/project_services/img/jira_workflow_screenshot.png b/doc/project_services/img/jira_workflow_screenshot.png
index 8635a32eb6..937a50a77d 100644
Binary files a/doc/project_services/img/jira_workflow_screenshot.png and b/doc/project_services/img/jira_workflow_screenshot.png differ
diff --git a/doc/project_services/img/redmine_configuration.png b/doc/project_services/img/redmine_configuration.png
index d14e526ad3..e9d8c0d2da 100644
Binary files a/doc/project_services/img/redmine_configuration.png and b/doc/project_services/img/redmine_configuration.png differ
diff --git a/doc/project_services/img/services_templates_redmine_example.png b/doc/project_services/img/services_templates_redmine_example.png
index 384d057fc8..77c2b98e5d 100644
Binary files a/doc/project_services/img/services_templates_redmine_example.png and b/doc/project_services/img/services_templates_redmine_example.png differ
diff --git a/doc/project_services/img/slack_configuration.png b/doc/project_services/img/slack_configuration.png
new file mode 100644
index 0000000000..b8de8a56db
Binary files /dev/null and b/doc/project_services/img/slack_configuration.png differ
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index f81a035f70..4442b7c174 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -30,6 +30,7 @@ further configuration instructions and details. Contributions are welcome.
| [Atlassian Bamboo CI](bamboo.md) | A continuous integration and build server |
| Buildkite | Continuous integration and deployments |
| [Builds emails](builds_emails.md) | Email the builds status to a list of recipients |
+| [Bugzilla](bugzilla.md) | Bugzilla issue tracker |
| Campfire | Simple web-based real-time group chat |
| Custom Issue Tracker | Custom issue tracker |
| Drone CI | Continuous Integration platform built on Docker, written in Go |
@@ -44,7 +45,7 @@ further configuration instructions and details. Contributions are welcome.
| PivotalTracker | Project Management Software (Source Commits Endpoint) |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
| [Redmine](redmine.md) | Redmine issue tracker |
-| Slack | A team communication tool for the 21st century |
+| [Slack](slack.md) | A team communication tool for the 21st century |
## Services Templates
diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md
new file mode 100644
index 0000000000..3cfe77c9f8
--- /dev/null
+++ b/doc/project_services/slack.md
@@ -0,0 +1,50 @@
+# Slack Service
+
+## On Slack
+
+To enable Slack integration you must create an incoming webhook integration on
+Slack:
+
+1. [Sign in to Slack](https://slack.com/signin)
+1. Visit [Incoming WebHooks](https://my.slack.com/services/new/incoming-webhook/)
+1. Choose the channel name you want to send notifications to.
+1. Click **Add Incoming WebHooks Integration**
+1. Copy the **Webhook URL**, we'll need this later for GitLab.
+
+## On GitLab
+
+After you set up Slack, it's time to set up GitLab.
+
+Go to your project's **Settings > Services > Slack** and you will see a
+checkbox with the following events that can be triggered:
+
+- Push
+- Issue
+- Merge request
+- Note
+- Tag push
+- Build
+- Wiki page
+
+Bellow each of these event checkboxes, you will have an input field to insert
+which Slack channel you want to send that event message, with `#general`
+being the default. Enter your preferred channel **without** the hash sign (`#`).
+
+At the end, fill in your Slack details:
+
+| Field | Description |
+| ----- | ----------- |
+| **Webhook** | The [incoming webhook URL][slackhook] which you have to setup on Slack. |
+| **Username** | Optional username which can be on messages sent to slack. Fill this in if you want to change the username of the bot. |
+| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
+
+After you are all done, click **Save changes** for the changes to take effect.
+
+>**Note:**
+You can set "branch,pushed,Compare changes" as highlight words on your Slack
+profile settings, so that you can be aware of new commits when somebody pushes
+them.
+
+![Slack configuration](img/slack_configuration.png)
+
+[slackhook]: https://my.slack.com/services/new/incoming-webhook
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 17bb75ecec..a3921f1b89 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -17,7 +17,7 @@ Public projects can be cloned **without any** authentication.
They will also be listed on the public access directory (`/public`).
-**Any logged in user** will have [Guest](../permissions/permissions)
+**Any logged in user** will have [Guest](../user/permissions.md)
permissions on the repository.
### Internal projects
@@ -27,8 +27,8 @@ Internal projects can be cloned by any logged in user.
They will also be listed on the public access directory (`/public`) for logged
in users.
-Any logged in user will have [Guest](../permissions/permissions) permissions on
-the repository.
+Any logged in user will have [Guest](../user/permissions.md) permissions
+on the repository.
### How to change project visibility
diff --git a/doc/raketasks/backup_hrz.png b/doc/raketasks/backup_hrz.png
index 03e50df1d7..42084717eb 100644
Binary files a/doc/raketasks/backup_hrz.png and b/doc/raketasks/backup_hrz.png differ
diff --git a/doc/raketasks/check_repos_output.png b/doc/raketasks/check_repos_output.png
index 916b168510..1f632566b0 100644
Binary files a/doc/raketasks/check_repos_output.png and b/doc/raketasks/check_repos_output.png differ
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 8a38937062..2b305cb5c9 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -14,7 +14,8 @@
- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed
it in the `/etc/gitlab/gitlab.rb` file.
- For installations from source, it is usually located at: `/home/git/repositories` or you can see where
-your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry.
+your repositories are located by looking at `config/gitlab.yml` under the `repositories => storages` entries
+(you'll usually use the `default` storage path to start).
New folder needs to have git user ownership and read/write/execute access for git user and its group:
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index d9dce2af48..315cb56a08 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -167,3 +167,22 @@ of those assets. Unless you are modifying the JavaScript / CSS code on your
production machine after installing the package, there should be no reason to redo
rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package.
+
+## Tracking Deployments
+
+GitLab provides a Rake task that lets you track deployments in GitLab
+Performance Monitoring. This Rake task simply stores the current GitLab version
+in the GitLab Performance Monitoring database.
+
+For Omnibus-packages:
+
+```
+sudo gitlab-rake gitlab:track_deployment
+```
+
+For installations from source:
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
+```
diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png
index aa51ce030b..6af5feabb1 100644
Binary files a/doc/security/img/two_factor_authentication_settings.png and b/doc/security/img/two_factor_authentication_settings.png differ
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index a1198e5878..d6a0979f6e 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -17,10 +17,6 @@ cat ~/.ssh/id_rsa.pub
If you see a long string starting with `ssh-rsa`, you can skip the `ssh-keygen` step.
-Note: It is a best practice to use a password for an SSH key, but it is not
-required and you can skip creating a password by pressing enter. Note that
-the password you choose here can't be altered or retrieved.
-
To generate a new SSH key, use the following command:
```bash
ssh-keygen -t rsa -C "$your_email"
@@ -30,6 +26,12 @@ pair and for a password. When prompted for the location and filename, just
press enter to use the default. If you use a different name, the key will not
be used automatically.
+Note: It is a best practice to use a password for an SSH key, but it is not
+required and you can skip creating a password by pressing enter.
+
+If you want to change the password of your key later, you can use the following
+command: `ssh-keygen -p
+ # Code blocks:
+ # ```
+ # Anything, including `>>>` blocks which are ignored by this filter
+ # ```
+
+ ^```
+ .+?
+ \n```$
+ )
+ |
+ (?
+ # HTML block:
+ #
+ # Anything, including `>>>` blocks which are ignored by this filter
+ #
+
+ ^<[^>]+?>\n
+ .+?
+ \n<\/[^>]+?>$
+ )
+ |
+ (?:
+ # Blockquote:
+ # >>>
+ # Anything, including code and HTML blocks
+ # >>>
+
+ ^>>>\n
+ (?
+ (?:
+ # Any character that doesn't introduce a code or HTML block
+ (?!
+ ^```
+ |
+ ^<[^>]+?>\n
+ )
+ .
+ |
+ # A code block
+ \g
+ |
+ # An HTML block
+ \g
+ )+?
+ )
+ \n>>>$
+ )
+ }mx.freeze
+
+ def initialize(text, context = nil, result = nil)
+ super text, context, result
+ @text = @text.delete("\r")
+ end
+
+ def call
+ @text.gsub(REGEX) do
+ if $~[:quote]
+ $~[:quote].gsub(/^/, "> ").gsub(/^> $/, ">")
+ else
+ $~[0]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index d25de90067..ae7d31cf19 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -61,7 +61,7 @@ module Banzai
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
- @emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
+ @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
end
def emoji_pattern
@@ -69,7 +69,7 @@ module Banzai
end
def emoji_filename(name)
- "#{Emoji.emoji_filename(name)}.png"
+ "#{Gitlab::Emoji.emoji_filename(name)}.png"
end
end
end
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index ccd106860b..f0fb6084a3 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -8,6 +8,10 @@ module Banzai
# of the anchor, and then replace the img with the link-wrapped version.
def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
+ div = doc.document.create_element(
+ 'div',
+ class: 'image-container'
+ )
link = doc.document.create_element(
'a',
@@ -17,7 +21,10 @@ module Banzai
)
link.children = img.clone
- img.replace(link)
+
+ div.children = link
+
+ img.replace(div)
end
doc
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 2614261f9e..4042e9a4c2 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -31,10 +31,14 @@ module Banzai
projects_per_reference.each do |path, project|
issue_ids = references_per_project[path]
- next unless project.default_issues_tracker?
+ if project.default_issues_tracker?
+ issues = project.issues.where(iid: issue_ids.to_a)
+ else
+ issues = issue_ids.map { |id| ExternalIssue.new(id, project) }
+ end
- project.issues.where(iid: issue_ids.to_a).each do |issue|
- hash[project][issue.iid] = issue
+ issues.each do |issue|
+ hash[project][issue.iid.to_i] = issue
end
end
@@ -42,6 +46,26 @@ module Banzai
end
end
+ def object_link_title(object)
+ if object.is_a?(ExternalIssue)
+ "Issue in #{object.project.external_issue_tracker.title}"
+ else
+ super
+ end
+ end
+
+ def data_attributes_for(text, project, object)
+ if object.is_a?(ExternalIssue)
+ data_attribute(
+ project: project.id,
+ external_issue: object.id,
+ reference_type: ExternalIssueReferenceFilter.reference_type
+ )
+ else
+ super
+ end
+ end
+
def find_projects_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index e4d3f87d0a..e258dc8e2b 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -13,13 +13,13 @@ module Banzai
end
def self.references_in(text, pattern = Label.reference_pattern)
- text.gsub(pattern) do |match|
+ unescape_html_entities(text).gsub(pattern) do |match|
yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~
end
end
def references_in(text, pattern = Label.reference_pattern)
- text.gsub(pattern) do |match|
+ unescape_html_entities(text).gsub(pattern) do |match|
label = find_label($~[:project], $~[:label_id], $~[:label_name])
if label
@@ -66,6 +66,10 @@ module Banzai
LabelsHelper.render_colored_cross_project_label(object)
end
end
+
+ def unescape_html_entities(text)
+ CGI.unescapeHTML(text.to_s)
+ end
end
end
end
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index c753a84a20..c59a80dd1c 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -7,40 +7,13 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
- nodes = Querying.css(doc, 'a.gfm[data-reference-type]')
- visible = nodes_visible_to_user(nodes)
-
- nodes.each do |node|
- unless visible.include?(node)
- # The reference should be replaced by the original text,
- # which is not always the same as the rendered text.
- text = node.attr('data-original') || node.text
- node.replace(text)
- end
- end
+ Redactor.new(project, current_user).redact([doc])
doc
end
private
- def nodes_visible_to_user(nodes)
- per_type = Hash.new { |h, k| h[k] = [] }
- visible = Set.new
-
- nodes.each do |node|
- per_type[node.attr('data-reference-type')] << node
- end
-
- per_type.each do |type, nodes|
- parser = Banzai::ReferenceParser[type].new(project, current_user)
-
- visible.merge(parser.nodes_visible_to_user(current_user, nodes))
- end
-
- visible
- end
-
def current_user
context[:current_user]
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 2d6f34c9cd..bf058241cd 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -29,7 +29,7 @@ module Banzai
def data_attribute(attributes = {})
attributes = attributes.reject { |_, v| v.nil? }
- attributes[:reference_type] = self.class.reference_type
+ attributes[:reference_type] ||= self.class.reference_type
attributes.delete(:original) if context[:no_original_data]
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ")
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index c78da40460..f426fff4bb 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -20,7 +20,7 @@ module Banzai
process_link_attr el.attribute('href')
end
- doc.search('img').each do |el|
+ doc.css('img, video').each do |el|
process_link_attr el.attribute('src')
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 62a79c62e2..91f0159f9a 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -19,24 +19,36 @@ 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 = "#{code}
"
end
- # Replace the parent `pre` element with the entire highlighted block
- node.parent.replace(highlighted)
+ highlighted = %(#{code}
)
+
+ # Extracted to a method to measure it
+ replace_parent_pre_element(node, highlighted)
end
private
+ def replace_parent_pre_element(node, highlighted)
+ # Replace the parent `pre` element with the entire highlighted block
+ node.parent.replace(highlighted)
+ end
+
# 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 5b0a6d8541..e1ca7f4d24 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)
- %(#{escape_once(text)})
+ def link_tag(url, data, text, title)
+ %(#{escape_once(text)})
end
end
end
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
new file mode 100644
index 0000000000..fd8b9a6f0c
--- /dev/null
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -0,0 +1,59 @@
+module Banzai
+ module Filter
+
+ # Find every image that isn't already wrapped in an `a` tag, and that has
+ # a `src` attribute ending with a video extension, add a new video node and
+ # a "Download" link in the case the video cannot be played.
+ class VideoLinkFilter < HTML::Pipeline::Filter
+
+ def call
+ doc.xpath(query).each do |el|
+ el.replace(video_node(doc, el))
+ end
+
+ doc
+ end
+
+ private
+
+ def query
+ @query ||= begin
+ src_query = UploaderHelper::VIDEO_EXT.map do |ext|
+ "'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
+ end
+
+ "descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
+ end
+ end
+
+ def video_node(doc, element)
+ container = doc.document.create_element(
+ 'div',
+ class: 'video-container'
+ )
+
+ video = doc.document.create_element(
+ 'video',
+ src: element['src'],
+ width: '400',
+ controls: true,
+ 'data-setup' => '{}')
+
+ link = doc.document.create_element(
+ 'a',
+ element['title'] || element['alt'],
+ href: element['src'],
+ target: '_blank',
+ title: "Download '#{element['title'] || element['alt']}'")
+ download_paragraph = doc.document.create_element('p')
+ download_paragraph.children = link
+
+ container.add_child(video)
+ container.add_child(download_paragraph)
+
+ container
+ end
+ end
+
+ end
+end
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 37a2779d45..269d5bf74f 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -8,7 +8,6 @@ module Banzai
# Context options:
# :project_wiki
class WikiLinkFilter < HTML::Pipeline::Filter
-
def call
return doc unless project_wiki?
@@ -29,7 +28,7 @@ module Banzai
return if html_attr.blank?
html_attr.value = apply_rewrite_rules(html_attr.value)
- rescue URI::Error
+ rescue URI::Error, Addressable::URI::InvalidURIError
# noop
end
diff --git a/lib/banzai/note_renderer.rb b/lib/banzai/note_renderer.rb
new file mode 100644
index 0000000000..bab6a9934d
--- /dev/null
+++ b/lib/banzai/note_renderer.rb
@@ -0,0 +1,22 @@
+module Banzai
+ module NoteRenderer
+ # Renders a collection of Note instances.
+ #
+ # notes - The notes to render.
+ # project - The project to use for rendering/redacting.
+ # user - The user viewing the notes.
+ # path - The request path.
+ # wiki - The project's wiki.
+ # git_ref - The current Git reference.
+ def self.render(notes, project, user = nil, path = nil, wiki = nil, git_ref = nil)
+ renderer = ObjectRenderer.new(project,
+ user,
+ requested_path: path,
+ project_wiki: wiki,
+ ref: git_ref,
+ pipeline: :note)
+
+ renderer.render(notes, :note)
+ end
+ end
+end
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
new file mode 100644
index 0000000000..9aef807c15
--- /dev/null
+++ b/lib/banzai/object_renderer.rb
@@ -0,0 +1,86 @@
+module Banzai
+ # Class for rendering multiple objects (e.g. Note instances) in a single pass.
+ #
+ # Rendered Markdown is stored in an attribute in every object based on the
+ # name of the attribute containing the Markdown. For example, when the
+ # attribute `note` is rendered the HTML is stored in `note_html`.
+ class ObjectRenderer
+ attr_reader :project, :user
+
+ # Make sure to set the appropriate pipeline in the `raw_context` attribute
+ # (e.g. `:note` for Note instances).
+ #
+ # project - A Project to use for rendering and redacting Markdown.
+ # user - The user viewing the Markdown/HTML documents, if any.
+ # context - A Hash containing extra attributes to use in the rendering
+ # pipeline.
+ def initialize(project, user = nil, raw_context = {})
+ @project = project
+ @user = user
+ @raw_context = raw_context
+ end
+
+ # Renders and redacts an Array of objects.
+ #
+ # objects - The objects to render
+ # attribute - The attribute containing the raw Markdown to render.
+ #
+ # Returns the same input objects.
+ def render(objects, attribute)
+ documents = render_objects(objects, attribute)
+ redacted = redact_documents(documents)
+
+ objects.each_with_index do |object, index|
+ redacted_data = redacted[index]
+ object.__send__("#{attribute}_html=", redacted_data[:document].to_html.html_safe)
+ object.user_visible_reference_count = redacted_data[:visible_reference_count]
+ end
+ end
+
+ # Renders the attribute of every given object.
+ def render_objects(objects, attribute)
+ render_attributes(objects, attribute)
+ end
+
+ # Redacts the list of documents.
+ #
+ # Returns an Array containing the redacted documents.
+ def redact_documents(documents)
+ redactor = Redactor.new(project, user)
+
+ redactor.redact(documents)
+ end
+
+ # Returns a Banzai context for the given object and attribute.
+ def context_for(object, attribute)
+ context = base_context.merge(cache_key: [object, attribute])
+
+ if object.respond_to?(:author)
+ context[:author] = object.author
+ end
+
+ context
+ end
+
+ # Renders the attributes of a set of objects.
+ #
+ # Returns an Array of `Nokogiri::HTML::Document`.
+ def render_attributes(objects, attribute)
+ strings_and_contexts = objects.map do |object|
+ context = context_for(object, attribute)
+
+ string = object.__send__(attribute)
+
+ { text: string, context: context }
+ end
+
+ Banzai.cache_collection_render(strings_and_contexts).each_with_index.map do |html, index|
+ Banzai::Pipeline[:relative_link].to_document(html, strings_and_contexts[index][:context])
+ end
+ end
+
+ def base_context
+ @base_context ||= @raw_context.merge(current_user: user, project: project)
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/full_pipeline.rb b/lib/banzai/pipeline/full_pipeline.rb
index d47ddfda4b..3c974f7317 100644
--- a/lib/banzai/pipeline/full_pipeline.rb
+++ b/lib/banzai/pipeline/full_pipeline.rb
@@ -1,7 +1,6 @@
module Banzai
module Pipeline
class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline)
-
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index b27ecf3c92..8d94b199c6 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
Filter::SanitizationFilter,
Filter::UploadLinkFilter,
+ Filter::VideoLinkFilter,
Filter::ImageLinkFilter,
Filter::EmojiFilter,
Filter::TableOfContentsFilter,
diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb
index 50dc978b45..6cf219661d 100644
--- a/lib/banzai/pipeline/pre_process_pipeline.rb
+++ b/lib/banzai/pipeline/pre_process_pipeline.rb
@@ -3,7 +3,8 @@ module Banzai
class PreProcessPipeline < BasePipeline
def self.filters
FilterArray[
- Filter::YamlFrontMatterFilter
+ Filter::YamlFrontMatterFilter,
+ Filter::BlockquoteFenceFilter,
]
end
diff --git a/lib/banzai/pipeline/relative_link_pipeline.rb b/lib/banzai/pipeline/relative_link_pipeline.rb
new file mode 100644
index 0000000000..270990e7ab
--- /dev/null
+++ b/lib/banzai/pipeline/relative_link_pipeline.rb
@@ -0,0 +1,11 @@
+module Banzai
+ module Pipeline
+ class RelativeLinkPipeline < BasePipeline
+ def self.filters
+ FilterArray[
+ Filter::RelativeLinkFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb
new file mode 100644
index 0000000000..0df3a72d1c
--- /dev/null
+++ b/lib/banzai/redactor.rb
@@ -0,0 +1,82 @@
+module Banzai
+ # Class for removing Markdown references a certain user is not allowed to
+ # view.
+ class Redactor
+ attr_reader :user, :project
+
+ # project - A Project to use for redacting links.
+ # user - The currently logged in user (if any).
+ def initialize(project, user = nil)
+ @project = project
+ @user = user
+ end
+
+ # Redacts the references in the given Array of documents.
+ #
+ # This method modifies the given documents in-place.
+ #
+ # documents - A list of HTML documents containing references to redact.
+ #
+ # Returns the documents passed as the first argument.
+ def redact(documents)
+ all_document_nodes = document_nodes(documents)
+
+ redact_document_nodes(all_document_nodes)
+ end
+
+ # Redacts the given node documents
+ #
+ # data - An Array of a Hashes mapping an HTML document to nodes to redact.
+ def redact_document_nodes(all_document_nodes)
+ all_nodes = all_document_nodes.map { |x| x[:nodes] }.flatten
+ visible = nodes_visible_to_user(all_nodes)
+ metadata = []
+
+ all_document_nodes.each do |entry|
+ nodes_for_document = entry[:nodes]
+ doc_data = { document: entry[:document], visible_reference_count: nodes_for_document.count }
+ metadata << doc_data
+
+ nodes_for_document.each do |node|
+ next if visible.include?(node)
+
+ doc_data[:visible_reference_count] -= 1
+ # The reference should be replaced by the original text,
+ # which is not always the same as the rendered text.
+ text = node.attr('data-original') || node.text
+ node.replace(text)
+ end
+ end
+
+ metadata
+ end
+
+ # Returns the nodes visible to the current user.
+ #
+ # nodes - The input nodes to check.
+ #
+ # Returns a new Array containing the visible nodes.
+ def nodes_visible_to_user(nodes)
+ per_type = Hash.new { |h, k| h[k] = [] }
+ visible = Set.new
+
+ nodes.each do |node|
+ per_type[node.attr('data-reference-type')] << node
+ end
+
+ per_type.each do |type, nodes|
+ parser = Banzai::ReferenceParser[type].new(project, user)
+
+ visible.merge(parser.nodes_visible_to_user(user, nodes))
+ end
+
+ visible
+ end
+
+ def document_nodes(documents)
+ documents.map do |document|
+ { document: document, nodes: Querying.css(document, 'a.gfm[data-reference-type]') }
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 3d7b9c4a02..6cf218aaa0 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -133,8 +133,9 @@ module Banzai
return {} if nodes.empty?
ids = unique_attribute_values(nodes, attribute)
+ rows = collection_objects_for_ids(collection, ids)
- collection.where(id: ids).each_with_object({}) do |row, hash|
+ rows.each_with_object({}) do |row, hash|
hash[row.id] = row
end
end
@@ -153,6 +154,31 @@ module Banzai
values.to_a
end
+ # Queries the collection for the objects with the given IDs.
+ #
+ # If the RequestStore module is enabled this method will only query any
+ # objects that have not yet been queried. For objects that have already
+ # been queried the object is returned from the cache.
+ def collection_objects_for_ids(collection, ids)
+ if RequestStore.active?
+ cache = collection_cache[collection_cache_key(collection)]
+ to_query = ids.map(&:to_i) - cache.keys
+
+ unless to_query.empty?
+ collection.where(id: to_query).each { |row| cache[row.id] = row }
+ end
+
+ cache.values
+ else
+ collection.where(id: ids)
+ end
+ end
+
+ # Returns the cache key to use for a collection.
+ def collection_cache_key(collection)
+ collection.respond_to?(:model) ? collection.model : collection
+ end
+
# Processes the list of HTML documents and returns an Array containing all
# the references.
def process(documents)
@@ -189,7 +215,7 @@ module Banzai
end
def find_projects_for_hash_keys(hash)
- Project.where(id: hash.keys)
+ collection_objects_for_ids(Project, hash.keys)
end
private
@@ -199,6 +225,12 @@ module Banzai
def lazy(&block)
Gitlab::Lazy.new(&block)
end
+
+ def collection_cache
+ RequestStore[:banzai_collection_cache] ||= Hash.new do |hash, key|
+ hash[key] = {}
+ end
+ end
end
end
end
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index a12b0d1956..863f5725d3 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -73,7 +73,7 @@ module Banzai
def find_users(ids)
return [] if ids.empty?
- User.where(id: ids).to_a
+ collection_objects_for_ids(User, ids)
end
def find_users_for_groups(ids)
@@ -85,7 +85,8 @@ module Banzai
def find_users_for_projects(ids)
return [] if ids.empty?
- Project.where(id: ids).flat_map { |p| p.team.members.to_a }
+ collection_objects_for_ids(Project, ids).
+ flat_map { |p| p.team.members.to_a }
end
end
end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 6718acdef7..910687a7b6 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -10,7 +10,7 @@ module Banzai
# requiring XHTML, such as Atom feeds, need to call `post_process` on the
# result, providing the appropriate `pipeline` option.
#
- # markdown - Markdown String
+ # text - Markdown String
# context - Hash of context options passed to our HTML Pipeline
#
# Returns an HTML-safe String
@@ -29,6 +29,58 @@ module Banzai
end
end
+ # Perform multiple render from an Array of Markdown String into an
+ # Array of HTML-safe String of HTML.
+ #
+ # As the rendered Markdown String can be already cached read all the data
+ # from the cache using Rails.cache.read_multi operation. If the Markdown String
+ # is not in the cache or it's not cacheable (no cache_key entry is provided in
+ # the context) the Markdown String is rendered and stored in the cache so the
+ # next render call gets the rendered HTML-safe String from the cache.
+ #
+ # For further explanation see #render method comments.
+ #
+ # texts_and_contexts - An Array of Hashes that contains the Markdown String (:text)
+ # an options passed to our HTML Pipeline (:context)
+ #
+ # If on the :context you specify a :cache_key entry will be used to retrieve it
+ # and cache the result of rendering the Markdown String.
+ #
+ # Returns an Array containing HTML-safe String instances.
+ #
+ # Example:
+ # texts_and_contexts
+ # => [{ text: '### Hello',
+ # context: { cache_key: [note, :note] } }]
+ def self.cache_collection_render(texts_and_contexts)
+ items_collection = texts_and_contexts.each_with_index do |item, index|
+ context = item[:context]
+ cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline])
+
+ item[:cache_key] = cache_key if cache_key
+ end
+
+ cacheable_items, non_cacheable_items = items_collection.partition { |item| item.key?(:cache_key) }
+
+ items_in_cache = []
+ items_not_in_cache = []
+
+ unless cacheable_items.empty?
+ items_in_cache = Rails.cache.read_multi(*cacheable_items.map { |item| item[:cache_key] })
+ items_not_in_cache = cacheable_items.reject do |item|
+ item[:rendered] = items_in_cache[item[:cache_key]]
+ items_in_cache.key?(item[:cache_key])
+ end
+ end
+
+ (items_not_in_cache + non_cacheable_items).each do |item|
+ item[:rendered] = render(item[:text], item[:context])
+ Rails.cache.write(item[:cache_key], item[:rendered]) if item[:cache_key]
+ end
+
+ items_collection.map { |item| item[:rendered] }
+ end
+
def self.render_result(text, context = {})
text = Pipeline[:pre_process].to_html(text, context) if text
@@ -78,5 +130,13 @@ module Banzai
return unless cache_key
["banzai", *cache_key, pipeline_name || :full]
end
+
+ # To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key.
+ # Other option will be to generate stringified keys on our side and don't delegate to Rails.cache.expanded_key
+ # method.
+ def self.full_cache_multi_key(cache_key, pipeline_name)
+ return unless cache_key
+ Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
+ end
end
end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 9f270f7b38..260ac81f5f 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -195,8 +195,7 @@ module Ci
not_found! unless build
authenticate_build_token!(build)
- build.remove_artifacts_file!
- build.remove_artifacts_metadata!
+ build.erase_artifacts!
end
end
end
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index 5270108ef0..1d7126a432 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -13,7 +13,6 @@ module Ci
collect
end
-
def push(from, to, format)
@labels << from.strftime(format)
@total << project.builds.
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index ed86de819e..83afed9f49 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -2,9 +2,8 @@ module Ci
class GitlabCiYamlProcessor
class ValidationError < StandardError; end
- include Gitlab::Ci::Config::Node::ValidationHelpers
+ include Gitlab::Ci::Config::Node::LegacyValidationHelpers
- DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
@@ -14,7 +13,7 @@ module Ci
ALLOWED_CACHE_KEYS = [:key, :untracked, :paths]
ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in]
- attr_reader :after_script, :image, :services, :path, :cache
+ attr_reader :path, :cache, :stages
def initialize(config, path = nil)
@ci_config = Gitlab::Ci::Config.new(config)
@@ -22,50 +21,85 @@ module Ci
@path = path
- initial_parsing
+ unless @ci_config.valid?
+ raise ValidationError, @ci_config.errors.first
+ end
+ initial_parsing
validate!
rescue Gitlab::Ci::Config::Loader::FormatError => e
raise ValidationError, e.message
end
+ def jobs_for_ref(ref, tag = false, trigger_request = nil)
+ @jobs.select do |_, job|
+ process?(job[:only], job[:except], ref, tag, trigger_request)
+ end
+ end
+
+ def jobs_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
+ jobs_for_ref(ref, tag, trigger_request).select do |_, job|
+ job[:stage] == stage
+ end
+ end
+
+ def builds_for_ref(ref, tag = false, trigger_request = nil)
+ jobs_for_ref(ref, tag, trigger_request).map do |name, _|
+ build_attributes(name)
+ end
+ end
+
def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
- builds.select do |build|
- build[:stage] == stage &&
- process?(build[:only], build[:except], ref, tag, trigger_request)
+ jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
+ build_attributes(name)
end
end
def builds
- @jobs.map do |name, job|
- build_job(name, job)
+ @jobs.map do |name, _|
+ build_attributes(name)
end
end
- def stages
- @stages || DEFAULT_STAGES
- end
-
- def global_variables
- @variables
- end
-
- def job_variables(name)
- job = @jobs[name.to_sym]
- return [] unless job
-
- job[:variables] || []
+ def build_attributes(name)
+ job = @jobs[name.to_sym] || {}
+ {
+ stage_idx: @stages.index(job[:stage]),
+ stage: job[:stage],
+ ##
+ # Refactoring note:
+ # - before script behaves differently than after script
+ # - after script returns an array of commands
+ # - before script should be a concatenated command
+ commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
+ tag_list: job[:tags] || [],
+ name: name,
+ allow_failure: job[:allow_failure] || false,
+ when: job[:when] || 'on_success',
+ environment: job[:environment],
+ yaml_variables: yaml_variables(name),
+ options: {
+ image: job[:image] || @image,
+ services: job[:services] || @services,
+ artifacts: job[:artifacts],
+ cache: job[:cache] || @cache,
+ dependencies: job[:dependencies],
+ after_script: job[:after_script] || @after_script,
+ }.compact
+ }
end
private
def initial_parsing
- @after_script = @config[:after_script]
- @image = @config[:image]
- @services = @config[:services]
- @stages = @config[:stages] || @config[:types]
- @variables = @config[:variables] || {}
- @cache = @config[:cache]
+ @before_script = @ci_config.before_script
+ @image = @ci_config.image
+ @after_script = @ci_config.after_script
+ @services = @ci_config.services
+ @variables = @ci_config.variables
+ @stages = @ci_config.stages
+ @cache = @ci_config.cache
+
@jobs = {}
@config.except!(*ALLOWED_YAML_KEYS)
@@ -83,36 +117,25 @@ module Ci
@jobs[name] = { stage: stage }.merge(job)
end
- def build_job(name, job)
- {
- stage_idx: stages.index(job[:stage]),
- stage: job[:stage],
- commands: [job[:before_script] || [@ci_config.before_script], job[:script]].flatten.compact.join("\n"),
- tag_list: job[:tags] || [],
- name: name,
- only: job[:only],
- except: job[:except],
- allow_failure: job[:allow_failure] || false,
- when: job[:when] || 'on_success',
- environment: job[:environment],
- options: {
- image: job[:image] || @image,
- services: job[:services] || @services,
- artifacts: job[:artifacts],
- cache: job[:cache] || @cache,
- dependencies: job[:dependencies],
- after_script: job[:after_script] || @after_script,
- }.compact
- }
+ def yaml_variables(name)
+ variables = global_variables.merge(job_variables(name))
+ variables.map do |key, value|
+ { key: key, value: value, public: true }
+ end
+ end
+
+ def global_variables
+ @variables || {}
+ end
+
+ def job_variables(name)
+ job = @jobs[name.to_sym]
+ return {} unless job
+
+ job[:variables] || {}
end
def validate!
- unless @ci_config.valid?
- raise ValidationError, @ci_config.errors.first
- end
-
- validate_global!
-
@jobs.each do |name, job|
validate_job!(name, job)
end
@@ -120,50 +143,6 @@ module Ci
true
end
- def validate_global!
- unless @after_script.nil? || validate_array_of_strings(@after_script)
- raise ValidationError, "after_script should be an array of strings"
- end
-
- unless @image.nil? || @image.is_a?(String)
- raise ValidationError, "image should be a string"
- end
-
- unless @services.nil? || validate_array_of_strings(@services)
- raise ValidationError, "services should be an array of strings"
- end
-
- unless @stages.nil? || validate_array_of_strings(@stages)
- raise ValidationError, "stages should be an array of strings"
- end
-
- unless @variables.nil? || validate_variables(@variables)
- raise ValidationError, "variables should be a map of key-value strings"
- end
-
- validate_global_cache! if @cache
- end
-
- def validate_global_cache!
- @cache.keys.each do |key|
- unless ALLOWED_CACHE_KEYS.include? key
- raise ValidationError, "#{name} cache unknown parameter #{key}"
- end
- end
-
- if @cache[:key] && !validate_string(@cache[:key])
- raise ValidationError, "cache:key parameter should be a string"
- end
-
- if @cache[:untracked] && !validate_boolean(@cache[:untracked])
- raise ValidationError, "cache:untracked parameter should be an boolean"
- end
-
- if @cache[:paths] && !validate_array_of_strings(@cache[:paths])
- raise ValidationError, "cache:paths parameter should be an array of strings"
- end
- end
-
def validate_job!(name, job)
validate_job_name!(name)
validate_job_keys!(name, job)
@@ -216,8 +195,8 @@ module Ci
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
end
- if job[:when] && !job[:when].in?(%w[on_success on_failure always])
- raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
+ if job[:when] && !job[:when].in?(%w[on_success on_failure always manual])
+ raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual"
end
if job[:environment] && !validate_environment(job[:environment])
@@ -240,8 +219,8 @@ module Ci
end
def validate_job_stage!(name, job)
- unless job[:stage].is_a?(String) && job[:stage].in?(stages)
- raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
+ unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
+ raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
end
end
@@ -305,12 +284,12 @@ module Ci
raise ValidationError, "#{name} job: dependencies parameter should be an array of strings"
end
- stage_index = stages.index(job[:stage])
+ stage_index = @stages.index(job[:stage])
job[:dependencies].each do |dependency|
raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym]
- unless stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
+ unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
end
end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index 42232b7129..2edddb84fc 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -7,50 +7,44 @@ module ContainerRegistry
MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'
+ # Taken from: FaradayMiddleware::FollowRedirects
+ REDIRECT_CODES = Set.new [301, 302, 303, 307]
+
def initialize(base_uri, options = {})
@base_uri = base_uri
- @faraday = Faraday.new(@base_uri) do |conn|
- initialize_connection(conn, options)
- end
+ @options = options
end
def repository_tags(name)
- response_body @faraday.get("/v2/#{name}/tags/list")
+ response_body faraday.get("/v2/#{name}/tags/list")
end
def repository_manifest(name, reference)
- response_body @faraday.get("/v2/#{name}/manifests/#{reference}")
+ response_body faraday.get("/v2/#{name}/manifests/#{reference}")
end
def repository_tag_digest(name, reference)
- response = @faraday.head("/v2/#{name}/manifests/#{reference}")
+ response = faraday.head("/v2/#{name}/manifests/#{reference}")
response.headers['docker-content-digest'] if response.success?
end
def delete_repository_tag(name, reference)
- @faraday.delete("/v2/#{name}/manifests/#{reference}").success?
+ faraday.delete("/v2/#{name}/manifests/#{reference}").success?
end
def blob(name, digest, type = nil)
- headers = {}
- headers['Accept'] = type if type
- response_body @faraday.get("/v2/#{name}/blobs/#{digest}", nil, headers)
+ type ||= 'application/octet-stream'
+ response_body faraday_blob.get("/v2/#{name}/blobs/#{digest}", nil, 'Accept' => type), allow_redirect: true
end
def delete_blob(name, digest)
- @faraday.delete("/v2/#{name}/blobs/#{digest}").success?
+ faraday.delete("/v2/#{name}/blobs/#{digest}").success?
end
-
+
private
-
+
def initialize_connection(conn, options)
conn.request :json
- conn.headers['Accept'] = MANIFEST_VERSION
-
- conn.response :json, content_type: 'application/json'
- conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
- conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
- conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v2+json'
if options[:user] && options[:password]
conn.request(:basic_auth, options[:user].to_s, options[:password].to_s)
@@ -61,8 +55,43 @@ module ContainerRegistry
conn.adapter :net_http
end
- def response_body(response)
- response.body if response.success?
+ def accept_manifest(conn)
+ conn.headers['Accept'] = MANIFEST_VERSION
+
+ conn.response :json, content_type: 'application/json'
+ conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
+ conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
+ conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v2+json'
+ end
+
+ def response_body(response, allow_redirect: false)
+ if allow_redirect && REDIRECT_CODES.include?(response.status)
+ response = redirect_response(response.headers['location'])
+ end
+
+ response.body if response && response.success?
+ end
+
+ def redirect_response(location)
+ return unless location
+
+ # We explicitly remove authorization token
+ faraday_blob.get(location) do |req|
+ req['Authorization'] = ''
+ end
+ end
+
+ def faraday
+ @faraday ||= Faraday.new(@base_uri) do |conn|
+ initialize_connection(conn, @options)
+ accept_manifest(conn)
+ end
+ end
+
+ def faraday_blob
+ @faraday_blob ||= Faraday.new(@base_uri) do |conn|
+ initialize_connection(conn, @options)
+ end
end
end
end
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index 708d01b95a..5904019992 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -53,7 +53,7 @@ module ContainerRegistry
def config
return unless config_blob
- @config ||= ContainerRegistry::Config.new(self, config_blob)
+ @config ||= ContainerRegistry::Config.new(self, config_blob) if config_blob.data
end
def created_at
diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb
index 1b80be112a..cee664b895 100644
--- a/lib/disable_email_interceptor.rb
+++ b/lib/disable_email_interceptor.rb
@@ -1,6 +1,5 @@
# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
class DisableEmailInterceptor
-
def self.delivering_email(message)
message.perform_deliveries = false
Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 37f4c34054..c3064163e0 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -2,6 +2,11 @@ require_dependency 'gitlab/git'
module Gitlab
def self.com?
- Gitlab.config.gitlab.url == 'https://gitlab.com'
+ # Check `staging?` as well to keep parity with gitlab.com
+ Gitlab.config.gitlab.url == 'https://gitlab.com' || staging?
+ end
+
+ def self.staging?
+ Gitlab.config.gitlab.url == 'https://staging.gitlab.com'
end
end
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 6d0e30e916..de41ea415a 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -5,6 +5,8 @@
#
module Gitlab
module Access
+ class AccessDeniedError < StandardError; end
+
GUEST = 10
REPORTER = 20
DEVELOPER = 30
@@ -12,9 +14,10 @@ module Gitlab
OWNER = 50
# Branch protection settings
- PROTECTION_NONE = 0
- PROTECTION_DEV_CAN_PUSH = 1
- PROTECTION_FULL = 2
+ PROTECTION_NONE = 0
+ PROTECTION_DEV_CAN_PUSH = 1
+ PROTECTION_FULL = 2
+ PROTECTION_DEV_CAN_MERGE = 3
class << self
def values
@@ -52,6 +55,7 @@ module Gitlab
def protection_options
{
"Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE,
+ "Protected against pushes: Developers cannot push new commits, but are allowed to accept merge requests to the branch." => PROTECTION_DEV_CAN_MERGE,
"Partially protected: Developers can push new commits, but cannot force push or delete the branch. Masters can do all of those." => PROTECTION_DEV_CAN_PUSH,
"Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL,
}
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index 0b9c2e730f..1a22ad9acf 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -4,7 +4,6 @@ module Gitlab
# Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters
# the resulting HTML through HTML pipeline filters.
module Asciidoc
-
DEFAULT_ADOC_ATTRS = [
'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
'env-gitlab', 'source-highlighter=html-pipeline'
diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb
index 51b1df9ecb..39b43ab548 100644
--- a/lib/gitlab/award_emoji.rb
+++ b/lib/gitlab/award_emoji.rb
@@ -1,24 +1,14 @@
module Gitlab
class AwardEmoji
CATEGORIES = {
- other: "Other",
objects: "Objects",
- places: "Places",
- travel_places: "Travel",
- emoticons: "Emoticons",
- objects_symbols: "Symbols",
+ travel: "Travel",
+ symbols: "Symbols",
nature: "Nature",
- celebration: "Celebration",
people: "People",
activity: "Activity",
flags: "Flags",
- food_drink: "Food"
- }.with_indifferent_access
-
- CATEGORY_ALIASES = {
- symbols: "objects_symbols",
- foods: "food_drink",
- travel: "travel_places"
+ food: "Food"
}.with_indifferent_access
def self.normalize_emoji_name(name)
@@ -35,7 +25,7 @@ module Gitlab
# Skip Fitzpatrick(tone) modifiers
next if data["category"] == "modifier"
- category = CATEGORY_ALIASES[data["category"]] || data["category"]
+ category = data["category"]
@emoji_by_category[category] << data
end
@@ -57,17 +47,26 @@ module Gitlab
def self.aliases
@aliases ||=
begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
- JSON.parse(File.read(json_path))
- end
+ json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
+ JSON.parse(File.read(json_path))
+ end
end
# Returns an Array of Emoji names and their asset URLs.
def self.urls
@urls ||= begin
path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
+ # Construct the full asset path ourselves because
+ # ActionView::Helpers::AssetUrlHelper.asset_url is slow for hundreds
+ # of entries since it has to do a lot of extra work (e.g. regexps).
prefix = Gitlab::Application.config.assets.prefix
digest = Gitlab::Application.config.assets.digest
+ base =
+ if defined?(Gitlab::Application.config.relative_url_root) && Gitlab::Application.config.relative_url_root
+ Gitlab::Application.config.relative_url_root
+ else
+ ''
+ end
JSON.parse(File.read(path)).map do |hash|
if digest
@@ -76,7 +75,7 @@ module Gitlab
fname = hash['unicode']
end
- { name: hash['name'], path: "#{prefix}/#{fname}.png" }
+ { name: hash['name'], path: File.join(base, prefix, "#{fname}.png") }
end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index ab7b811c5d..ab94abeda7 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -8,7 +8,6 @@ module Grack
end
class Auth < Rack::Auth::Basic
-
attr_accessor :user, :project, :env
def call(env)
@@ -22,7 +21,7 @@ module Grack
# Need this if under RELATIVE_URL_ROOT
unless Gitlab.config.gitlab.relative_url_root.empty?
# If website is mounted using relative_url_root need to remove it first
- @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
+ @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root, '')
else
@env['PATH_INFO'] = @request.path
end
@@ -64,7 +63,7 @@ module Grack
def ci_request?(login, password)
matched_login = /(?^[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/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 3e3986d638..839a4fa30d 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -1,3 +1,5 @@
+require 'securerandom'
+
module Gitlab
class Shell
class Error < StandardError; end
@@ -18,77 +20,84 @@ module Gitlab
# Init new repository
#
+ # storage - project's storage path
# name - project path with namespace
#
# Ex.
- # add_repository("gitlab/gitlab-ci")
+ # add_repository("/path/to/storage", "gitlab/gitlab-ci")
#
- def add_repository(name)
+ def add_repository(storage, name)
Gitlab::Utils.system_silent([gitlab_shell_projects_path,
- 'add-project', "#{name}.git"])
+ 'add-project', storage, "#{name}.git"])
end
# Import repository
#
+ # storage - project's storage path
# name - project path with namespace
#
# Ex.
- # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
+ # import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
- def import_repository(name, url)
- output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '900'])
+ def import_repository(storage, name, url)
+ output, status = Popen::popen([gitlab_shell_projects_path, 'import-project',
+ storage, "#{name}.git", url, '900'])
raise Error, output unless status.zero?
true
end
# Move repository
- #
+ # storage - project's storage path
# path - project path with namespace
# new_path - new project path with namespace
#
# Ex.
- # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new")
+ # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new")
#
- def mv_repository(path, new_path)
+ def mv_repository(storage, path, new_path)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
- "#{path}.git", "#{new_path}.git"])
+ storage, "#{path}.git", "#{new_path}.git"])
end
# Fork repository to new namespace
- #
+ # forked_from_storage - forked-from project's storage path
# path - project path with namespace
+ # forked_to_storage - forked-to project's storage path
# fork_namespace - namespace for forked project
#
# Ex.
- # fork_repository("gitlab/gitlab-ci", "randx")
+ # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
#
- def fork_repository(path, fork_namespace)
+ def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
- "#{path}.git", fork_namespace])
+ forked_from_storage, "#{path}.git", forked_to_storage,
+ fork_namespace])
end
# Remove repository from file system
#
+ # storage - project's storage path
# name - project path with namespace
#
# Ex.
- # remove_repository("gitlab/gitlab-ci")
+ # remove_repository("/path/to/storage", "gitlab/gitlab-ci")
#
- def remove_repository(name)
+ def remove_repository(storage, name)
Gitlab::Utils.system_silent([gitlab_shell_projects_path,
- 'rm-project', "#{name}.git"])
+ 'rm-project', storage, "#{name}.git"])
end
# Gc repository
#
+ # storage - project storage path
# path - project path with namespace
#
# Ex.
- # gc("gitlab/gitlab-ci")
+ # gc("/path/to/storage", "gitlab/gitlab-ci")
#
- def gc(path)
+ def gc(storage, path)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'gc',
- "#{path}.git"])
+ storage, "#{path}.git"])
end
# Add new key to gitlab-shell
@@ -133,31 +142,31 @@ module Gitlab
# Add empty directory for storing repositories
#
# Ex.
- # add_namespace("gitlab")
+ # add_namespace("/path/to/storage", "gitlab")
#
- def add_namespace(name)
- FileUtils.mkdir(full_path(name), mode: 0770) unless exists?(name)
+ def add_namespace(storage, name)
+ FileUtils.mkdir(full_path(storage, name), mode: 0770) unless exists?(storage, name)
end
# Remove directory from repositories storage
# Every repository inside this directory will be removed too
#
# Ex.
- # rm_namespace("gitlab")
+ # rm_namespace("/path/to/storage", "gitlab")
#
- def rm_namespace(name)
- FileUtils.rm_r(full_path(name), force: true)
+ def rm_namespace(storage, name)
+ FileUtils.rm_r(full_path(storage, name), force: true)
end
# Move namespace directory inside repositories storage
#
# Ex.
- # mv_namespace("gitlab", "gitlabhq")
+ # mv_namespace("/path/to/storage", "gitlab", "gitlabhq")
#
- def mv_namespace(old_name, new_name)
- return false if exists?(new_name) || !exists?(old_name)
+ def mv_namespace(storage, old_name, new_name)
+ return false if exists?(storage, new_name) || !exists?(storage, old_name)
- FileUtils.mv(full_path(old_name), full_path(new_name))
+ FileUtils.mv(full_path(storage, old_name), full_path(storage, new_name))
end
def url_to_repo(path)
@@ -176,11 +185,26 @@ module Gitlab
# Check if such directory exists in repositories.
#
# Usage:
- # exists?('gitlab')
- # exists?('gitlab/cookies.git')
+ # exists?(storage, 'gitlab')
+ # exists?(storage, 'gitlab/cookies.git')
#
- def exists?(dir_name)
- File.exist?(full_path(dir_name))
+ def exists?(storage, dir_name)
+ File.exist?(full_path(storage, dir_name))
+ end
+
+ # Create (if necessary) and link the secret token file
+ def generate_and_link_secret_token
+ secret_file = Gitlab.config.gitlab_shell.secret_file
+ unless File.exist? secret_file
+ # Generate a new token of 16 random hexadecimal characters and store it in secret_file.
+ token = SecureRandom.hex(16)
+ File.write(secret_file, token)
+ end
+
+ link_path = File.join(gitlab_shell_path, '.gitlab_shell_secret')
+ if File.exist?(gitlab_shell_path) && !File.exist?(link_path)
+ FileUtils.symlink(secret_file, link_path)
+ end
end
protected
@@ -193,14 +217,10 @@ module Gitlab
File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
end
- def repos_path
- Gitlab.config.gitlab_shell.repos_path
- end
-
- def full_path(dir_name)
+ def full_path(storage, dir_name)
raise ArgumentError.new("Directory name can't be blank") if dir_name.blank?
- File.join(repos_path, dir_name)
+ File.join(storage, dir_name)
end
def gitlab_shell_projects_path
diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb
index 997a22779a..d62bc50ce7 100644
--- a/lib/gitlab/blame.rb
+++ b/lib/gitlab/blame.rb
@@ -41,7 +41,8 @@ module Gitlab
def highlighted_lines
@blob.load_all_data!(repository)
- @highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines
+ @highlighted_lines ||=
+ Gitlab::Highlight.highlight(@blob.path, @blob.data, repository: repository).lines
end
def project
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
new file mode 100644
index 0000000000..5551fac4b8
--- /dev/null
+++ b/lib/gitlab/checks/change_access.rb
@@ -0,0 +1,96 @@
+module Gitlab
+ module Checks
+ class ChangeAccess
+ attr_reader :user_access, :project
+
+ def initialize(change, user_access:, project:)
+ @oldrev, @newrev, @ref = change.split(' ')
+ @branch_name = branch_name(@ref)
+ @user_access = user_access
+ @project = project
+ end
+
+ def exec
+ error = protected_branch_checks || tag_checks || push_checks
+
+ if error
+ GitAccessStatus.new(false, error)
+ else
+ GitAccessStatus.new(true)
+ end
+ end
+
+ protected
+
+ def protected_branch_checks
+ return unless project.protected_branch?(@branch_name)
+
+ if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches)
+ return "You are not allowed to force push code to a protected branch on this project."
+ elsif Gitlab::Git.blank_ref?(@newrev) && user_access.cannot_do_action?(:remove_protected_branches)
+ return "You are not allowed to delete protected branches from this project."
+ end
+
+ if matching_merge_request?
+ if user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
+ return
+ else
+ "You are not allowed to merge code into protected branches on this project."
+ end
+ else
+ if user_access.can_push_to_branch?(@branch_name)
+ return
+ else
+ "You are not allowed to push code to protected branches on this project."
+ end
+ end
+ end
+
+ def tag_checks
+ tag_ref = tag_name(@ref)
+
+ if tag_ref && protected_tag?(tag_ref) && user_access.cannot_do_action?(:admin_project)
+ "You are not allowed to change existing tags on this project."
+ end
+ end
+
+ def push_checks
+ if user_access.cannot_do_action?(:push_code)
+ "You are not allowed to push code to this project."
+ end
+ end
+
+ private
+
+ def protected_tag?(tag_name)
+ project.repository.tag_exists?(tag_name)
+ end
+
+ def forced_push?
+ Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
+ end
+
+ def matching_merge_request?
+ Checks::MatchingMergeRequest.new(@newrev, @branch_name, @project).match?
+ end
+
+ def branch_name(ref)
+ ref = @ref.to_s
+ if Gitlab::Git.branch_ref?(ref)
+ Gitlab::Git.ref_name(ref)
+ else
+ nil
+ end
+ end
+
+ def tag_name(ref)
+ ref = @ref.to_s
+ if Gitlab::Git.tag_ref?(ref)
+ Gitlab::Git.ref_name(ref)
+ else
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb
new file mode 100644
index 0000000000..dfa83a0eab
--- /dev/null
+++ b/lib/gitlab/checks/force_push.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module Checks
+ class ForcePush
+ def self.force_push?(project, oldrev, newrev)
+ return false if project.empty_repo?
+
+ # Created or deleted branch
+ if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
+ false
+ else
+ missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
+ missed_refs.split("\n").size > 0
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/matching_merge_request.rb b/lib/gitlab/checks/matching_merge_request.rb
new file mode 100644
index 0000000000..849848515d
--- /dev/null
+++ b/lib/gitlab/checks/matching_merge_request.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module Checks
+ class MatchingMergeRequest
+ def initialize(newrev, branch_name, project)
+ @newrev = newrev
+ @branch_name = branch_name
+ @project = project
+ end
+
+ def match?
+ @project.merge_requests
+ .with_state(:locked)
+ .where(in_progress_merge_commit_sha: @newrev, target_branch: @branch_name)
+ .exists?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index b48d3592f1..e6cc152976 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -4,12 +4,11 @@ module Gitlab
# Base GitLab CI Configuration facade
#
class Config
- delegate :valid?, :errors, to: :@global
-
##
# Temporary delegations that should be removed after refactoring
#
- delegate :before_script, to: :@global
+ delegate :before_script, :image, :services, :after_script, :variables,
+ :stages, :cache, to: :@global
def initialize(config)
@config = Loader.new(config).load!
@@ -18,6 +17,14 @@ module Gitlab
@global.process!
end
+ def valid?
+ @global.valid?
+ end
+
+ def errors
+ @global.errors
+ end
+
def to_hash
@config
end
diff --git a/lib/gitlab/ci/config/node/boolean.rb b/lib/gitlab/ci/config/node/boolean.rb
new file mode 100644
index 0000000000..84b03ee783
--- /dev/null
+++ b/lib/gitlab/ci/config/node/boolean.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a boolean value.
+ #
+ class Boolean < Entry
+ include Validatable
+
+ validations do
+ validates :config, boolean: true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb
new file mode 100644
index 0000000000..cdf8ba2e35
--- /dev/null
+++ b/lib/gitlab/ci/config/node/cache.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a cache configuration
+ #
+ class Cache < Entry
+ include Configurable
+
+ node :key, Node::Key,
+ description: 'Cache key used to define a cache affinity.'
+
+ node :untracked, Node::Boolean,
+ description: 'Cache all untracked files.'
+
+ node :paths, Node::Paths,
+ description: 'Specify which paths should be cached across builds.'
+
+ validations do
+ validates :config, allowed_keys: true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index d60f87f3f9..37936fc824 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -15,43 +15,49 @@ module Gitlab
#
module Configurable
extend ActiveSupport::Concern
+ include Validatable
- def allowed_nodes
- self.class.allowed_nodes || {}
+ included do
+ validations do
+ validates :config, type: Hash
+ end
end
private
- def prevalidate!
- unless @value.is_a?(Hash)
- @errors << 'should be a configuration entry with hash value'
- end
- end
-
def create_node(key, factory)
- factory.with(value: @value[key])
- factory.nullify! unless @value.has_key?(key)
+ factory.with(value: @config[key], key: key, parent: self)
+
factory.create!
end
class_methods do
- def allowed_nodes
- Hash[@allowed_nodes.map { |key, factory| [key, factory.dup] }]
+ def nodes
+ Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }]
end
private
- def allow_node(symbol, entry_class, metadata)
+ def node(symbol, entry_class, metadata)
factory = Node::Factory.new(entry_class)
.with(description: metadata[:description])
- define_method(symbol) do
- raise Entry::InvalidError unless valid?
+ (@nodes ||= {}).merge!(symbol.to_sym => factory)
+ end
- @nodes[symbol].try(:value)
+ def helpers(*nodes)
+ nodes.each do |symbol|
+ define_method("#{symbol}_defined?") do
+ @nodes[symbol].try(:defined?)
+ end
+
+ define_method("#{symbol}_value") do
+ raise Entry::InvalidError unless valid?
+ @nodes[symbol].try(:value)
+ end
+
+ alias_method symbol.to_sym, "#{symbol}_value".to_sym
end
-
- (@allowed_nodes ||= {}).merge!(symbol => factory)
end
end
end
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 52758a962f..9e79e170a4 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -8,14 +8,14 @@ module Gitlab
class Entry
class InvalidError < StandardError; end
- attr_accessor :description
+ attr_reader :config
+ attr_accessor :key, :parent, :description
- def initialize(value)
- @value = value
+ def initialize(config)
+ @config = config
@nodes = {}
- @errors = []
-
- prevalidate!
+ @validator = self.class.validator.new(self)
+ @validator.validate
end
def process!
@@ -23,50 +23,65 @@ module Gitlab
return unless valid?
compose!
-
- nodes.each(&:process!)
- nodes.each(&:validate!)
+ process_nodes!
end
def nodes
@nodes.values
end
+ def leaf?
+ self.class.nodes.none?
+ end
+
+ def ancestors
+ @parent ? @parent.ancestors + [@parent] : []
+ end
+
def valid?
errors.none?
end
- def leaf?
- allowed_nodes.none?
- end
-
def errors
- @errors + nodes.map(&:errors).flatten
- end
-
- def allowed_nodes
- {}
- end
-
- def validate!
- raise NotImplementedError
+ @validator.messages + nodes.flat_map(&:errors)
end
def value
- raise NotImplementedError
+ if leaf?
+ @config
+ else
+ defined = @nodes.select { |_key, value| value.defined? }
+ Hash[defined.map { |key, node| [key, node.value] }]
+ end
+ end
+
+ def defined?
+ true
+ end
+
+ def self.default
+ end
+
+ def self.nodes
+ {}
+ end
+
+ def self.validator
+ Validator
end
private
- def prevalidate!
- end
-
def compose!
- allowed_nodes.each do |key, essence|
+ self.class.nodes.each do |key, essence|
@nodes[key] = create_node(key, essence)
end
end
+ def process_nodes!
+ nodes.each(&:process!)
+ end
+
def create_node(key, essence)
raise NotImplementedError
end
diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb
index 787ca006f5..5919a28328 100644
--- a/lib/gitlab/ci/config/node/factory.rb
+++ b/lib/gitlab/ci/config/node/factory.rb
@@ -5,13 +5,11 @@ module Gitlab
##
# Factory class responsible for fabricating node entry objects.
#
- # It uses Fluent Interface pattern to set all necessary attributes.
- #
class Factory
class InvalidFactory < StandardError; end
- def initialize(entry_class)
- @entry_class = entry_class
+ def initialize(node)
+ @node = node
@attributes = {}
end
@@ -20,18 +18,29 @@ module Gitlab
self
end
- def nullify!
- @entry_class = Node::Null
- self
- end
-
def create!
raise InvalidFactory unless @attributes.has_key?(:value)
- @entry_class.new(@attributes[:value]).tap do |entry|
+ fabricate.tap do |entry|
+ entry.key = @attributes[:key]
+ entry.parent = @attributes[:parent]
entry.description = @attributes[:description]
end
end
+
+ private
+
+ def fabricate
+ ##
+ # We assume that unspecified entry is undefined.
+ # See issue #18775.
+ #
+ if @attributes[:value].nil?
+ Node::Undefined.new(@node)
+ else
+ @node.new(@attributes[:value])
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index 044603423d..f92e1eccbc 100644
--- a/lib/gitlab/ci/config/node/global.rb
+++ b/lib/gitlab/ci/config/node/global.rb
@@ -9,8 +9,36 @@ module Gitlab
class Global < Entry
include Configurable
- allow_node :before_script, Script,
+ node :before_script, Node::Script,
description: 'Script that will be executed before each job.'
+
+ node :image, Node::Image,
+ description: 'Docker image that will be used to execute jobs.'
+
+ node :services, Node::Services,
+ description: 'Docker images that will be linked to the container.'
+
+ node :after_script, Node::Script,
+ description: 'Script that will be executed after each job.'
+
+ node :variables, Node::Variables,
+ description: 'Environment variables that will be used.'
+
+ node :stages, Node::Stages,
+ description: 'Configuration of stages for this pipeline.'
+
+ node :types, Node::Stages,
+ description: 'Deprecated: stages for this pipeline.'
+
+ node :cache, Node::Cache,
+ description: 'Configure caching between build jobs.'
+
+ helpers :before_script, :image, :services, :after_script,
+ :variables, :stages, :types, :cache
+
+ def stages
+ stages_defined? ? stages_value : types_value
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/node/image.rb b/lib/gitlab/ci/config/node/image.rb
new file mode 100644
index 0000000000..5d3c7c5eab
--- /dev/null
+++ b/lib/gitlab/ci/config/node/image.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a Docker image.
+ #
+ class Image < Entry
+ include Validatable
+
+ validations do
+ validates :config, type: String
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/key.rb b/lib/gitlab/ci/config/node/key.rb
new file mode 100644
index 0000000000..f8b461ca09
--- /dev/null
+++ b/lib/gitlab/ci/config/node/key.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a key.
+ #
+ class Key < Entry
+ include Validatable
+
+ validations do
+ validates :config, key: true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/validation_helpers.rb b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
similarity index 97%
rename from lib/gitlab/ci/config/node/validation_helpers.rb
rename to lib/gitlab/ci/config/node/legacy_validation_helpers.rb
index 72f648975d..4d9a508796 100644
--- a/lib/gitlab/ci/config/node/validation_helpers.rb
+++ b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
@@ -2,7 +2,7 @@ module Gitlab
module Ci
class Config
module Node
- module ValidationHelpers
+ module LegacyValidationHelpers
private
def validate_duration(value)
diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb
deleted file mode 100644
index 4f590f6bec..0000000000
--- a/lib/gitlab/ci/config/node/null.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module Gitlab
- module Ci
- class Config
- module Node
- ##
- # This class represents a configuration entry that is not being used
- # in configuration file.
- #
- # This implements Null Object pattern.
- #
- class Null < Entry
- def value
- nil
- end
-
- def validate!
- nil
- end
-
- def method_missing(*)
- nil
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/config/node/paths.rb b/lib/gitlab/ci/config/node/paths.rb
new file mode 100644
index 0000000000..3c6d3a5296
--- /dev/null
+++ b/lib/gitlab/ci/config/node/paths.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents an array of paths.
+ #
+ class Paths < Entry
+ include Validatable
+
+ validations do
+ validates :config, array_of_strings: true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/script.rb b/lib/gitlab/ci/config/node/script.rb
index 5072bf0db7..39328f0fad 100644
--- a/lib/gitlab/ci/config/node/script.rb
+++ b/lib/gitlab/ci/config/node/script.rb
@@ -5,22 +5,11 @@ module Gitlab
##
# Entry that represents a script.
#
- # Each element in the value array is a command that will be executed
- # by GitLab Runner. Currently we concatenate these commands with
- # new line character as a separator, what is compatible with
- # implementation in Runner.
- #
class Script < Entry
- include ValidationHelpers
+ include Validatable
- def value
- @value.join("\n")
- end
-
- def validate!
- unless validate_array_of_strings(@value)
- @errors << 'before_script should be an array of strings'
- end
+ validations do
+ validates :config, array_of_strings: true
end
end
end
diff --git a/lib/gitlab/ci/config/node/services.rb b/lib/gitlab/ci/config/node/services.rb
new file mode 100644
index 0000000000..481e2b66ad
--- /dev/null
+++ b/lib/gitlab/ci/config/node/services.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a configuration of Docker services.
+ #
+ class Services < Entry
+ include Validatable
+
+ validations do
+ validates :config, array_of_strings: true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/stages.rb b/lib/gitlab/ci/config/node/stages.rb
new file mode 100644
index 0000000000..b1fe45357f
--- /dev/null
+++ b/lib/gitlab/ci/config/node/stages.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a configuration for pipeline stages.
+ #
+ class Stages < Entry
+ include Validatable
+
+ validations do
+ validates :config, array_of_strings: true
+ end
+
+ def self.default
+ %w[build test deploy]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb
new file mode 100644
index 0000000000..699605e1e3
--- /dev/null
+++ b/lib/gitlab/ci/config/node/undefined.rb
@@ -0,0 +1,30 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # This class represents an undefined entry node.
+ #
+ # It takes original entry class as configuration and returns default
+ # value of original entry as self value.
+ #
+ #
+ class Undefined < Entry
+ include Validatable
+
+ validations do
+ validates :config, type: Class
+ end
+
+ def value
+ @config.default
+ end
+
+ def defined?
+ false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/validatable.rb b/lib/gitlab/ci/config/node/validatable.rb
new file mode 100644
index 0000000000..f6e2896dfb
--- /dev/null
+++ b/lib/gitlab/ci/config/node/validatable.rb
@@ -0,0 +1,29 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ module Validatable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def validator
+ validator = Class.new(Node::Validator)
+
+ if defined?(@validations)
+ @validations.each { |rules| validator.class_eval(&rules) }
+ end
+
+ validator
+ end
+
+ private
+
+ def validations(&block)
+ (@validations ||= []).append(block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb
new file mode 100644
index 0000000000..758a6cf435
--- /dev/null
+++ b/lib/gitlab/ci/config/node/validator.rb
@@ -0,0 +1,41 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ class Validator < SimpleDelegator
+ include ActiveModel::Validations
+ include Node::Validators
+
+ def initialize(node)
+ super(node)
+ @node = node
+ end
+
+ def messages
+ errors.full_messages.map do |error|
+ "#{location} #{error}".downcase
+ end
+ end
+
+ def self.name
+ 'Validator'
+ end
+
+ def unknown_keys
+ return [] unless config.is_a?(Hash)
+
+ config.keys - @node.class.nodes.keys
+ end
+
+ private
+
+ def location
+ predecessors = ancestors.map(&:key).compact
+ current = key || @node.class.name.demodulize.underscore
+ predecessors.append(current).join(':')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb
new file mode 100644
index 0000000000..7b2f57990b
--- /dev/null
+++ b/lib/gitlab/ci/config/node/validators.rb
@@ -0,0 +1,70 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ module Validators
+ class AllowedKeysValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ if record.unknown_keys.any?
+ unknown_list = record.unknown_keys.join(', ')
+ record.errors.add(:config,
+ "contains unknown keys: #{unknown_list}")
+ end
+ end
+ end
+
+ class ArrayOfStringsValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_array_of_strings(value)
+ record.errors.add(attribute, 'should be an array of strings')
+ end
+ end
+ end
+
+ class BooleanValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_boolean(value)
+ record.errors.add(attribute, 'should be a boolean value')
+ end
+ end
+ end
+
+ class KeyValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_string(value)
+ record.errors.add(attribute, 'should be a string or symbol')
+ end
+ end
+ end
+
+ class TypeValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ type = options[:with]
+ raise unless type.is_a?(Class)
+
+ unless value.is_a?(type)
+ record.errors.add(attribute, "should be a #{type.name}")
+ end
+ end
+ end
+
+ class VariablesValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_variables(value)
+ record.errors.add(attribute, 'should be a hash of key value pairs')
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/variables.rb b/lib/gitlab/ci/config/node/variables.rb
new file mode 100644
index 0000000000..5f813f81f5
--- /dev/null
+++ b/lib/gitlab/ci/config/node/variables.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents environment variables.
+ #
+ class Variables < Entry
+ include Validatable
+
+ validations do
+ validates :config, variables: true
+ end
+
+ def self.default
+ {}
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index 9bef9037ad..58f86abc5c 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -22,7 +22,9 @@ module Gitlab
@extractor.analyze(closing_statements.join(" "))
- @extractor.issues
+ @extractor.issues.reject do |issue|
+ @extractor.project.forked_from?(issue.project) # Don't extract issues on original project
+ end
end
end
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 28c34429c1..735331df66 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -9,10 +9,14 @@ module Gitlab
end
def ensure_application_settings!
- settings = ::ApplicationSetting.cached
+ if connect_to_db?
+ begin
+ settings = ::ApplicationSetting.current
+ # In case Redis isn't running or the Redis UNIX socket file is not available
+ rescue ::Redis::BaseError, ::Errno::ENOENT
+ settings = ::ApplicationSetting.last
+ end
- if !settings && connect_to_db?
- settings = ::ApplicationSetting.current
settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
end
@@ -35,7 +39,7 @@ module Gitlab
session_expire_delay: Settings.gitlab['session_expire_delay'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
- restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
+ domain_whitelist: Settings.gitlab['domain_whitelist'],
import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
@@ -44,6 +48,7 @@ module Gitlab
akismet_enabled: false,
repository_checks_enabled: true,
container_registry_token_expire_delay: 5,
+ user_default_external: false,
)
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index dec20d8659..927f9dad20 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/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
new file mode 100644
index 0000000000..8406ca4269
--- /dev/null
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module Diff
+ class DiffRefs
+ attr_reader :base_sha
+ attr_reader :start_sha
+ attr_reader :head_sha
+
+ def initialize(base_sha:, start_sha: base_sha, head_sha:)
+ @base_sha = base_sha
+ @start_sha = start_sha
+ @head_sha = head_sha
+ end
+
+ def ==(other)
+ other.is_a?(self.class) &&
+ base_sha == other.base_sha &&
+ start_sha == other.start_sha &&
+ head_sha == other.head_sha
+ end
+
+ # There is only one case in which we will have `start_sha` and `head_sha`,
+ # but not `base_sha`, which is when a diff is generated between an
+ # orphaned branch and another branch, which means there _is_ no base, but
+ # we're still able to highlight it, and to create diff notes, which are
+ # the primary things `DiffRefs` are used for.
+ # `DiffRefs` are "complete" when they have `start_sha` and `head_sha`,
+ # because `base_sha` can always be derived from this, to return an actual
+ # sha, or `nil`.
+ # We have `base_sha` directly available on `DiffRefs` because it's faster#
+ # than having to look it up in the repo every time.
+ def complete?
+ start_sha && head_sha
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index d2e85cabf7..b09ca1fb8b 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -1,47 +1,83 @@
module Gitlab
module Diff
class File
- attr_reader :diff, :diff_refs
+ attr_reader :diff, :repository, :diff_refs
delegate :new_file, :deleted_file, :renamed_file,
- :old_path, :new_path, to: :diff, prefix: false
+ :old_path, :new_path, :a_mode, :b_mode,
+ :submodule?, :too_large?, :collapsed?, to: :diff, prefix: false
- def initialize(diff, diff_refs)
+ def initialize(diff, repository:, diff_refs: nil)
@diff = diff
+ @repository = repository
@diff_refs = diff_refs
end
+ def position(line)
+ return unless diff_refs
+
+ Position.new(
+ old_path: old_path,
+ new_path: new_path,
+ old_line: line.old_line,
+ new_line: line.new_line,
+ diff_refs: diff_refs
+ )
+ end
+
+ def line_code(line)
+ return if line.meta?
+
+ Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
+ end
+
+ def line_for_line_code(code)
+ diff_lines.find { |line| line_code(line) == code }
+ end
+
+ def line_for_position(pos)
+ diff_lines.find { |line| position(line) == pos }
+ end
+
+ def position_for_line_code(code)
+ line = line_for_line_code(code)
+ position(line) if line
+ end
+
+ def line_code_for_position(pos)
+ line = line_for_position(pos)
+ line_code(line) if line
+ end
+
+ def content_commit
+ return unless diff_refs
+
+ repository.commit(deleted_file ? old_ref : new_ref)
+ end
+
def old_ref
- diff_refs[0] if diff_refs
+ diff_refs.try(:base_sha)
end
def new_ref
- diff_refs[1] if diff_refs
+ diff_refs.try(:head_sha)
end
- # Array of Gitlab::DIff::Line objects
+ # Array of Gitlab::Diff::Line objects
def diff_lines
- @lines ||= parser.parse(raw_diff.each_line).to_a
- end
-
- def too_large?
- diff.too_large?
+ @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
end
def highlighted_diff_lines
- Gitlab::Diff::Highlight.new(self).highlight
+ @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end
def parallel_diff_lines
- Gitlab::Diff::ParallelDiff.new(self).parallelize
+ @parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
end
def mode_changed?
- !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode)
- end
-
- def parser
- Gitlab::Diff::Parser.new
+ a_mode && b_mode && a_mode != b_mode
end
def raw_diff
@@ -53,17 +89,15 @@ module Gitlab
end
def prev_line(index)
- if index > 0
- diff_lines[index - 1]
- end
+ diff_lines[index - 1] if index > 0
+ end
+
+ def paths
+ [old_path, new_path].compact
end
def file_path
- if diff.new_path.present?
- diff.new_path
- elsif diff.old_path.present?
- diff.old_path
- end
+ new_path.presence || old_path
end
def added_lines
@@ -73,6 +107,21 @@ module Gitlab
def removed_lines
diff_lines.count(&:removed?)
end
+
+ def old_blob(commit = content_commit)
+ return unless commit
+
+ parent_id = commit.parent_id
+ return unless parent_id
+
+ repository.blob_at(parent_id, old_path)
+ end
+
+ def blob(commit = content_commit)
+ return unless commit
+
+ repository.blob_at(commit.id, file_path)
+ end
end
end
end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 9429b3ff88..649a265a02 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -1,11 +1,13 @@
module Gitlab
module Diff
class Highlight
- attr_reader :diff_file, :diff_lines, :raw_lines
+ attr_reader :diff_file, :diff_lines, :raw_lines, :repository
delegate :old_path, :new_path, :old_ref, :new_ref, to: :diff_file, prefix: :diff
- def initialize(diff_lines)
+ def initialize(diff_lines, repository: nil)
+ @repository = repository
+
if diff_lines.is_a?(Gitlab::Diff::File)
@diff_file = diff_lines
@diff_lines = @diff_file.diff_lines
@@ -19,7 +21,7 @@ module Gitlab
@diff_lines.map.with_index do |diff_line, i|
diff_line = diff_line.dup
# ignore highlighting for "match" lines
- next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline'
+ next diff_line if diff_line.meta?
rich_line = highlight_line(diff_line) || diff_line.text
@@ -40,12 +42,12 @@ module Gitlab
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
- case diff_line.type
- when 'new', nil
- rich_line = new_lines[diff_line.new_pos - 1]
- when 'old'
- rich_line = old_lines[diff_line.old_pos - 1]
- end
+ rich_line =
+ if diff_line.unchanged? || diff_line.added?
+ new_lines[diff_line.new_pos - 1]
+ elsif diff_line.removed?
+ old_lines[diff_line.old_pos - 1]
+ end
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
@@ -58,19 +60,12 @@ module Gitlab
def old_lines
return unless diff_file
- @old_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:old))
+ @old_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_old_ref, diff_old_path)
end
def new_lines
return unless diff_file
- @new_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:new))
- end
-
- def processing_args(version)
- ref = send("diff_#{version}_ref")
- path = send("diff_#{version}_path")
-
- [ref.project.repository, ref.id, path]
+ @new_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_new_ref, diff_new_path)
end
end
end
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index 789c14518b..28ad637fda 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -1,16 +1,30 @@
module Gitlab
module Diff
class InlineDiff
+ # Regex to find a run of deleted lines followed by the same number of added lines
+ LINE_PAIRS_PATTERN = %r{
+ # Runs start at the beginning of the string (the first line) or after a space (for an unchanged line)
+ (?:\A|\s)
+
+ # This matches a number of `-`s followed by the same number of `+`s through recursion
+ (?
+ -
+ \g?
+ \+
+ )
+
+ # Runs end at the end of the string (the last line) or before a space (for an unchanged line)
+ (?=\s|\z)
+ }x.freeze
+
attr_accessor :old_line, :new_line, :offset
def self.for_lines(lines)
- local_edit_indexes = self.find_local_edits(lines)
+ changed_line_pairs = self.find_changed_line_pairs(lines)
inline_diffs = []
- local_edit_indexes.each do |index|
- old_index = index
- new_index = index + 1
+ changed_line_pairs.each do |old_index, new_index|
old_line = lines[old_index]
new_line = lines[new_index]
@@ -51,18 +65,28 @@ module Gitlab
private
- def self.find_local_edits(lines)
- line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
- joined_line_prefixes = " #{line_prefixes.join} "
+ # Finds pairs of old/new line pairs that represent the same line that changed
+ def self.find_changed_line_pairs(lines)
+ # Prefixes of all diff lines, indicating their types
+ # For example: `" - + -+ ---+++ --+ -++"`
+ line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
- offset = 0
- local_edit_indexes = []
- while index = joined_line_prefixes.index(" -+ ", offset)
- local_edit_indexes << index
- offset = index + 1
+ changed_line_pairs = []
+ line_prefixes.scan(LINE_PAIRS_PATTERN) do
+ # For `"---+++"`, `begin_index == 0`, `end_index == 6`
+ begin_index, end_index = Regexp.last_match.offset(:del_ins)
+
+ # For `"---+++"`, `changed_line_count == 3`
+ changed_line_count = (end_index - begin_index) / 2
+
+ halfway_index = begin_index + changed_line_count
+ (begin_index...halfway_index).each do |i|
+ # For `"---+++"`, index 1 maps to 1 + 3 = 4
+ changed_line_pairs << [i, i + changed_line_count]
+ end
end
- local_edit_indexes
+ changed_line_pairs
end
def longest_common_prefix(a, b)
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index 03730b435a..c6189d660c 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -9,6 +9,18 @@ module Gitlab
@old_pos, @new_pos = old_pos, new_pos
end
+ def old_line
+ old_pos unless added? || meta?
+ end
+
+ def new_line
+ new_pos unless removed? || meta?
+ end
+
+ def unchanged?
+ type.nil?
+ end
+
def added?
type == 'new'
end
@@ -16,6 +28,10 @@ module Gitlab
def removed?
type == 'old'
end
+
+ def meta?
+ type == 'match' || type == 'nonewline'
+ end
end
end
end
diff --git a/lib/gitlab/diff/line_mapper.rb b/lib/gitlab/diff/line_mapper.rb
new file mode 100644
index 0000000000..576a761423
--- /dev/null
+++ b/lib/gitlab/diff/line_mapper.rb
@@ -0,0 +1,64 @@
+# When provided a diff for a specific file, maps old line numbers to new line
+# numbers and back, to find out where a specific line in a file was moved by the
+# changes.
+module Gitlab
+ module Diff
+ class LineMapper
+ attr_accessor :diff_file
+
+ def initialize(diff_file)
+ @diff_file = diff_file
+ end
+
+ # Find new line number for old line number.
+ def old_to_new(old_line)
+ map_line_number(old_line, from: :old_line, to: :new_line)
+ end
+
+ # Find old line number for new line number.
+ def new_to_old(new_line)
+ map_line_number(new_line, from: :new_line, to: :old_line)
+ end
+
+ private
+
+ def diff_lines
+ @diff_lines ||= @diff_file.diff_lines
+ end
+
+ # Find old/new line number based on its old/new counterpart line number.
+ def map_line_number(from_line, from:, to:)
+ # If no diff file could be found, the file wasn't changed, and the
+ # mapped line number is the same as the specified line number.
+ return from_line unless diff_file
+
+ # To find the mapped line number for the specified line number,
+ # we need to find:
+ # - The diff line with that exact line number, if it is in the diff context
+ # - The first diff line with a higher line number, if it falls between diff contexts
+ # - The last known diff line, if it falls after the last diff context
+ diff_line = diff_lines.find do |diff_line|
+ diff_from_line = diff_line.send(from)
+ diff_from_line && diff_from_line >= from_line
+ end
+ diff_line ||= diff_lines.last
+
+ # If no diff line could be found, the file wasn't changed, and the
+ # mapped line number is the same as the specified line number.
+ return from_line unless diff_line
+
+ diff_from_line = diff_line.send(from)
+ diff_to_line = diff_line.send(to)
+
+ # If the line was removed, there is no mapped line number.
+ return unless diff_to_line
+
+ # Because we may not have the diff line with the exact line number
+ # we were looking for, we need to adjust the mapped line number.
+ distance = diff_from_line - from_line
+
+ diff_to_line - distance
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb
index 74f9b3c050..b069afdd28 100644
--- a/lib/gitlab/diff/parallel_diff.rb
+++ b/lib/gitlab/diff/parallel_diff.rb
@@ -8,112 +8,97 @@ module Gitlab
end
def parallelize
- lines = []
- skip_next = false
+ i = 0
+ free_right_index = nil
+
+ lines = []
highlighted_diff_lines = diff_file.highlighted_diff_lines
highlighted_diff_lines.each do |line|
- full_line = line.text
- type = line.type
- line_code = generate_line_code(diff_file.file_path, line)
- line_new = line.new_pos
- line_old = line.old_pos
+ line_code = diff_file.line_code(line)
+ position = diff_file.position(line)
- next_line = diff_file.next_line(line.index)
-
- if next_line
- next_line = highlighted_diff_lines[next_line.index]
- next_line_code = generate_line_code(diff_file.file_path, next_line)
- next_type = next_line.type
- next_line = next_line.text
- end
-
- case type
+ case line.type
when 'match', nil
# line in the right panel is the same as in the left one
lines << {
left: {
- type: type,
- number: line_old,
- text: full_line,
+ type: line.type,
+ number: line.old_pos,
+ text: line.text,
line_code: line_code,
+ position: position
},
right: {
- type: type,
- number: line_new,
- text: full_line,
- line_code: line_code
+ type: line.type,
+ number: line.new_pos,
+ text: line.text,
+ line_code: line_code,
+ position: position
}
}
+
+ free_right_index = nil
+ i += 1
when 'old'
- case next_type
- when 'new'
- # Left side has text removed, right side has text added
- lines << {
- left: {
- type: type,
- number: line_old,
- text: full_line,
- line_code: line_code,
- },
- right: {
- type: next_type,
- number: line_new,
- text: next_line,
- line_code: next_line_code
- }
+ lines << {
+ left: {
+ type: line.type,
+ number: line.old_pos,
+ text: line.text,
+ line_code: line_code,
+ position: position
+ },
+ right: {
+ type: nil,
+ number: nil,
+ text: "",
+ line_code: line_code,
+ position: position
}
- skip_next = true
- when 'old', 'nonewline', nil
- # Left side has text removed, right side doesn't have any change
- # No next line code, no new line number, no new line text
- lines << {
- left: {
- type: type,
- number: line_old,
- text: full_line,
- line_code: line_code,
- },
- right: {
- type: next_type,
- number: nil,
- text: "",
- line_code: nil
- }
- }
- end
+ }
+
+ # Once we come upon a new line it can be put on the right of this old line
+ free_right_index ||= i
+ i += 1
when 'new'
- if skip_next
- # Change has been already included in previous line so no need to do it again
- skip_next = false
- next
+ data = {
+ type: line.type,
+ number: line.new_pos,
+ text: line.text,
+ line_code: line_code,
+ position: position
+ }
+
+ if free_right_index
+ # If an old line came before this without a line on the right, this
+ # line can be put to the right of it.
+ lines[free_right_index][:right] = data
+
+ # If there are any other old lines on the left that don't yet have
+ # a new counterpart on the right, update the free_right_index
+ next_free_right_index = free_right_index + 1
+ free_right_index = next_free_right_index < i ? next_free_right_index : nil
else
- # Change is only on the right side, left side has no change
lines << {
left: {
type: nil,
number: nil,
text: "",
line_code: line_code,
+ position: position
},
- right: {
- type: type,
- number: line_new,
- text: full_line,
- line_code: line_code
- }
+ right: data
}
+
+ free_right_index = nil
+ i += 1
end
end
end
+
lines
end
-
- private
-
- def generate_line_code(file_path, line)
- Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
- end
end
end
end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 522dd2b942..59a2367b65 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -40,7 +40,6 @@ module Gitlab
line_obj_index += 1
end
-
case line[0]
when "+"
line_new += 1
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
new file mode 100644
index 0000000000..2fdcf8d783
--- /dev/null
+++ b/lib/gitlab/diff/position.rb
@@ -0,0 +1,155 @@
+# Defines a specific location, identified by paths and line numbers,
+# within a specific diff, identified by start, head and base commit ids.
+module Gitlab
+ module Diff
+ class Position
+ attr_reader :old_path
+ attr_reader :new_path
+ attr_reader :old_line
+ attr_reader :new_line
+ attr_reader :base_sha
+ attr_reader :start_sha
+ attr_reader :head_sha
+
+ def initialize(attrs = {})
+ @old_path = attrs[:old_path]
+ @new_path = attrs[:new_path]
+ @old_line = attrs[:old_line]
+ @new_line = attrs[:new_line]
+
+ if attrs[:diff_refs]
+ @base_sha = attrs[:diff_refs].base_sha
+ @start_sha = attrs[:diff_refs].start_sha
+ @head_sha = attrs[:diff_refs].head_sha
+ else
+ @base_sha = attrs[:base_sha]
+ @start_sha = attrs[:start_sha]
+ @head_sha = attrs[:head_sha]
+ end
+ end
+
+ # `Gitlab::Diff::Position` objects are stored as serialized attributes in
+ # `DiffNote`, which use YAML to encode and decode objects.
+ # `#init_with` and `#encode_with` can be used to customize the en/decoding
+ # behavior. In this case, we override these to prevent memoized instance
+ # variables like `@diff_file` and `@diff_line` from being serialized.
+ def init_with(coder)
+ initialize(coder['attributes'])
+
+ self
+ end
+
+ def encode_with(coder)
+ coder['attributes'] = self.to_h
+ end
+
+ def key
+ @key ||= [base_sha, start_sha, head_sha, Digest::SHA1.hexdigest(old_path || ""), Digest::SHA1.hexdigest(new_path || ""), old_line, new_line]
+ end
+
+ def ==(other)
+ other.is_a?(self.class) && key == other.key
+ end
+
+ def to_h
+ {
+ old_path: old_path,
+ new_path: new_path,
+ old_line: old_line,
+ new_line: new_line,
+ base_sha: base_sha,
+ start_sha: start_sha,
+ head_sha: head_sha
+ }
+ end
+
+ def inspect
+ %(#<#{self.class}:#{object_id} #{to_h}>)
+ end
+
+ def complete?
+ file_path.present? &&
+ (old_line || new_line) &&
+ diff_refs.complete?
+ end
+
+ def to_json(opts = nil)
+ JSON.generate(self.to_h, opts)
+ end
+
+ def type
+ if old_line && new_line
+ nil
+ elsif new_line
+ 'new'
+ else
+ 'old'
+ end
+ end
+
+ def unchanged?
+ type.nil?
+ end
+
+ def added?
+ type == 'new'
+ end
+
+ def removed?
+ type == 'old'
+ end
+
+ def paths
+ [old_path, new_path].compact.uniq
+ end
+
+ def file_path
+ new_path.presence || old_path
+ end
+
+ def diff_refs
+ @diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha)
+ end
+
+ def diff_file(repository)
+ @diff_file ||= begin
+ if RequestStore.active?
+ key = {
+ project_id: repository.project.id,
+ start_sha: start_sha,
+ head_sha: head_sha,
+ path: file_path
+ }
+
+ RequestStore.fetch(key) { find_diff_file(repository) }
+ else
+ find_diff_file(repository)
+ end
+ end
+ end
+
+ def diff_line(repository)
+ @diff_line ||= diff_file(repository).line_for_position(self)
+ end
+
+ def line_code(repository)
+ @line_code ||= diff_file(repository).line_code_for_position(self)
+ end
+
+ private
+
+ def find_diff_file(repository)
+ diffs = Gitlab::Git::Compare.new(
+ repository.raw_repository,
+ start_sha,
+ head_sha
+ ).diffs(paths: paths)
+
+ diff = diffs.first
+ return unless diff
+
+ Gitlab::Diff::File.new(diff, repository: repository, diff_refs: diff_refs)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb
new file mode 100644
index 0000000000..4d04f86726
--- /dev/null
+++ b/lib/gitlab/diff/position_tracer.rb
@@ -0,0 +1,168 @@
+# Finds the diff position in the new diff that corresponds to the same location
+# specified by the provided position in the old diff.
+module Gitlab
+ module Diff
+ class PositionTracer
+ attr_accessor :repository
+ attr_accessor :old_diff_refs
+ attr_accessor :new_diff_refs
+ attr_accessor :paths
+
+ def initialize(repository:, old_diff_refs:, new_diff_refs:, paths: nil)
+ @repository = repository
+ @old_diff_refs = old_diff_refs
+ @new_diff_refs = new_diff_refs
+ @paths = paths
+ end
+
+ def trace(old_position)
+ return unless old_diff_refs.complete? && new_diff_refs.complete?
+ return unless old_position.diff_refs == old_diff_refs
+
+ # Suppose we have an MR with source branch `feature` and target branch `master`.
+ # When the MR was created, the head of `master` was commit A, and the
+ # head of `feature` was commit B, resulting in the original diff A->B.
+ # Since creation, `master` was updated to C.
+ # Now `feature` is being updated to D, and the newly generated MR diff is C->D.
+ # It is possible that C and D are direct decendants of A and B respectively,
+ # but this isn't necessarily the case as rebases and merges come into play.
+ #
+ # Suppose we have a diff note on the original diff A->B. Now that the MR
+ # is updated, we need to find out what line in C->D corresponds to the
+ # line the note was originally created on, so that we can update the diff note's
+ # records and continue to display it in the right place in the diffs.
+ # If we cannot find this line in the new diff, this means the diff note is now
+ # outdated, and we will display that fact to the user.
+ #
+ # In the new diff, the file the diff note was originally created on may
+ # have been renamed, deleted or even created, if the file existed in A and B,
+ # but was removed in C, and restored in D.
+ #
+ # Every diff note stores a Position object that defines a specific location,
+ # identified by paths and line numbers, within a specific diff, identified
+ # by start, head and base commit ids.
+ #
+ # For diff notes for diff A->B, the position looks like this:
+ # Position
+ # base_sha - ID of commit A
+ # head_sha - ID of commit B
+ # old_path - path as of A (nil if file was newly created)
+ # new_path - path as of B (nil if file was deleted)
+ # old_line - line number as of A (nil if file was newly created)
+ # new_line - line number as of B (nil if file was deleted)
+ #
+ # We can easily update `base_sha` and `head_sha` to hold the IDs of commits C and D,
+ # but need to find the paths and line numbers as of C and D.
+ #
+ # If the file was unchanged or newly created in A->B, the path as of D can be found
+ # by generating diff B->D ("head to head"), finding the diff file with
+ # `diff_file.old_path == position.new_path`, and taking `diff_file.new_path`.
+ # The path as of C can be found by taking diff C->D, finding the diff file
+ # with that same `new_path` and taking `diff_file.old_path`.
+ # The line number as of D can be found by using the LineMapper on diff B->D
+ # and providing the line number as of B.
+ # The line number as of C can be found by using the LineMapper on diff C->D
+ # and providing the line number as of D.
+ #
+ # If the file was deleted in A->B, the path as of C can be found
+ # by generating diff A->C ("base to base"), finding the diff file with
+ # `diff_file.old_path == position.old_path`, and taking `diff_file.new_path`.
+ # The path as of D can be found by taking diff C->D, finding the diff file
+ # with that same `old_path` and taking `diff_file.new_path`.
+ # The line number as of C can be found by using the LineMapper on diff A->C
+ # and providing the line number as of A.
+ # The line number as of D can be found by using the LineMapper on diff C->D
+ # and providing the line number as of C.
+
+ results = nil
+ results ||= trace_added_line(old_position) if old_position.added? || old_position.unchanged?
+ results ||= trace_removed_line(old_position) if old_position.removed? || old_position.unchanged?
+
+ return unless results
+
+ file_diff, old_line, new_line = results
+
+ Position.new(
+ old_path: file_diff.old_path,
+ new_path: file_diff.new_path,
+ head_sha: new_diff_refs.head_sha,
+ start_sha: new_diff_refs.start_sha,
+ base_sha: new_diff_refs.base_sha,
+ old_line: old_line,
+ new_line: new_line
+ )
+ end
+
+ private
+
+ def trace_added_line(old_position)
+ file_path = old_position.new_path
+
+ return unless diff_head_to_head
+
+ file_head_to_head = diff_head_to_head.find { |diff_file| diff_file.old_path == file_path }
+
+ file_path = file_head_to_head.new_path if file_head_to_head
+
+ new_line = LineMapper.new(file_head_to_head).old_to_new(old_position.new_line)
+
+ return unless new_line
+
+ file_diff = new_diffs.find { |diff_file| diff_file.new_path == file_path }
+ return unless file_diff
+
+ old_line = LineMapper.new(file_diff).new_to_old(new_line)
+
+ [file_diff, old_line, new_line]
+ end
+
+ def trace_removed_line(old_position)
+ file_path = old_position.old_path
+
+ return unless diff_base_to_base
+
+ file_base_to_base = diff_base_to_base.find { |diff_file| diff_file.old_path == file_path }
+
+ file_path = file_base_to_base.old_path if file_base_to_base
+
+ old_line = LineMapper.new(file_base_to_base).old_to_new(old_position.old_line)
+
+ return unless old_line
+
+ file_diff = new_diffs.find { |diff_file| diff_file.old_path == file_path }
+ return unless file_diff
+
+ new_line = LineMapper.new(file_diff).old_to_new(old_line)
+
+ [file_diff, old_line, new_line]
+ end
+
+ def diff_base_to_base
+ @diff_base_to_base ||= diff_files(old_diff_refs.base_sha || old_diff_refs.start_sha, new_diff_refs.base_sha || new_diff_refs.start_sha)
+ end
+
+ def diff_head_to_head
+ @diff_head_to_head ||= diff_files(old_diff_refs.head_sha, new_diff_refs.head_sha)
+ end
+
+ def new_diffs
+ @new_diffs ||= diff_files(new_diff_refs.start_sha, new_diff_refs.head_sha, use_base: true)
+ end
+
+ def diff_files(start_sha, head_sha, use_base: false)
+ base_sha = self.repository.merge_base(start_sha, head_sha) || start_sha
+
+ diffs = self.repository.raw_repository.diff(
+ use_base ? base_sha : start_sha,
+ head_sha,
+ {},
+ *paths
+ )
+
+ diffs.decorate! do |diff|
+ Gitlab::Diff::File.new(diff, repository: self.repository)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/downtime_check.rb b/lib/gitlab/downtime_check.rb
new file mode 100644
index 0000000000..ab9537ed7d
--- /dev/null
+++ b/lib/gitlab/downtime_check.rb
@@ -0,0 +1,71 @@
+module Gitlab
+ # Checks if a set of migrations requires downtime or not.
+ class DowntimeCheck
+ # The constant containing the boolean that indicates if downtime is needed
+ # or not.
+ DOWNTIME_CONST = :DOWNTIME
+
+ # The constant that specifies the reason for the migration requiring
+ # downtime.
+ DOWNTIME_REASON_CONST = :DOWNTIME_REASON
+
+ # Checks the given migration paths and returns an Array of
+ # `Gitlab::DowntimeCheck::Message` instances.
+ #
+ # migrations - The migration file paths to check.
+ def check(migrations)
+ migrations.map do |path|
+ require(path)
+
+ migration_class = class_for_migration_file(path)
+
+ unless migration_class.const_defined?(DOWNTIME_CONST)
+ raise "The migration in #{path} does not specify if it requires " \
+ "downtime or not"
+ end
+
+ if online?(migration_class)
+ Message.new(path)
+ else
+ reason = downtime_reason(migration_class)
+
+ unless reason
+ raise "The migration in #{path} requires downtime but no reason " \
+ "was given"
+ end
+
+ Message.new(path, true, reason)
+ end
+ end
+ end
+
+ # Checks the given migrations and prints the results to STDOUT/STDERR.
+ #
+ # migrations - The migration file paths to check.
+ def check_and_print(migrations)
+ check(migrations).each do |message|
+ puts message.to_s # rubocop: disable Rails/Output
+ end
+ end
+
+ # Returns the class for the given migration file path.
+ def class_for_migration_file(path)
+ File.basename(path, File.extname(path)).split('_', 2).last.camelize.
+ constantize
+ end
+
+ # Returns true if the given migration can be performed without downtime.
+ def online?(migration)
+ migration.const_get(DOWNTIME_CONST) == false
+ end
+
+ # Returns the downtime reason, or nil if none was defined.
+ def downtime_reason(migration)
+ if migration.const_defined?(DOWNTIME_REASON_CONST)
+ migration.const_get(DOWNTIME_REASON_CONST)
+ else
+ nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb
new file mode 100644
index 0000000000..4446e921e0
--- /dev/null
+++ b/lib/gitlab/downtime_check/message.rb
@@ -0,0 +1,28 @@
+module Gitlab
+ class DowntimeCheck
+ class Message
+ attr_reader :path, :offline, :reason
+
+ OFFLINE = "\e[32moffline\e[0m"
+ ONLINE = "\e[31monline\e[0m"
+
+ # path - The file path of the migration.
+ # offline - When set to `true` the migration will require downtime.
+ # reason - The reason as to why the migration requires downtime.
+ def initialize(path, offline = false, reason = nil)
+ @path = path
+ @offline = offline
+ @reason = reason
+ end
+
+ def to_s
+ label = offline ? OFFLINE : ONLINE
+
+ message = "[#{label}]: #{path}"
+ message += ": #{reason}" if reason
+
+ message
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index 047c77c6fc..97701b0cd4 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -33,11 +33,15 @@ module Gitlab
end
def commits
- @commits ||= (Commit.decorate(compare.commits, project) if compare)
+ return unless compare
+
+ @commits ||= Commit.decorate(compare.commits, project)
end
def diffs
- @diffs ||= (safe_diff_files(compare.diffs(max_files: 30), diff_refs) if compare)
+ return unless compare
+
+ @diffs ||= safe_diff_files(compare.diffs(max_files: 30), diff_refs: diff_refs, repository: project.repository)
end
def diffs_count
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 97ef9851d7..1c671a7487 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -104,15 +104,7 @@ module Gitlab
end
def create_note(reply)
- Notes::CreateService.new(
- sent_notification.project,
- sent_notification.recipient,
- note: reply,
- noteable_type: sent_notification.noteable_type,
- noteable_id: sent_notification.noteable_id,
- commit_id: sent_notification.commit_id,
- line_code: sent_notification.line_code
- ).execute
+ sent_notification.create_note(reply)
end
end
end
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
new file mode 100644
index 0000000000..b63213ae20
--- /dev/null
+++ b/lib/gitlab/emoji.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module Emoji
+ extend self
+
+ def emojis
+ Gemojione.index.instance_variable_get(:@emoji_by_name)
+ end
+
+ def emojis_by_moji
+ Gemojione.index.instance_variable_get(:@emoji_by_moji)
+ end
+
+ def emojis_names
+ emojis.keys.sort
+ end
+
+ def emoji_filename(name)
+ emojis[name]["unicode"]
+ end
+ end
+end
diff --git a/lib/gitlab/force_push_check.rb b/lib/gitlab/force_push_check.rb
deleted file mode 100644
index 93c6a5bb7f..0000000000
--- a/lib/gitlab/force_push_check.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module Gitlab
- class ForcePushCheck
- def self.force_push?(project, oldrev, newrev)
- return false if project.empty_repo?
-
- # Created or deleted branch
- if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
- false
- else
- missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
- missed_refs.split("\n").size > 0
- end
- end
- end
-end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index 07b856ca64..9b681e636c 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -1,6 +1,7 @@
module Gitlab
module Git
class Hook
+ GL_PROTOCOL = 'web'.freeze
attr_reader :name, :repo_path, :path
def initialize(name, repo_path)
@@ -14,7 +15,7 @@ module Gitlab
end
def trigger(gl_id, oldrev, newrev, ref)
- return true unless exists?
+ return [true, nil] unless exists?
case name
when "pre-receive", "post-receive"
@@ -29,19 +30,20 @@ module Gitlab
def call_receive_hook(gl_id, oldrev, newrev, ref)
changes = [oldrev, newrev, ref].join(" ")
- # function will return true if succesful
exit_status = false
+ exit_message = nil
vars = {
'GL_ID' => gl_id,
- 'PWD' => repo_path
+ 'PWD' => repo_path,
+ 'GL_PROTOCOL' => GL_PROTOCOL
}
options = {
chdir: repo_path
}
- Open3.popen2(vars, path, options) do |stdin, _, wait_thr|
+ Open3.popen3(vars, path, options) do |stdin, stdout, stderr, wait_thr|
exit_status = true
stdin.sync = true
@@ -60,17 +62,24 @@ module Gitlab
unless wait_thr.value == 0
exit_status = false
+ exit_message = retrieve_error_message(stderr, stdout)
end
end
- exit_status
+ [exit_status, exit_message]
end
def call_update_hook(gl_id, oldrev, newrev, ref)
Dir.chdir(repo_path) do
- system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
+ stdout, stderr, status = Open3.capture3({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
+ [status.success?, stderr.presence || stdout]
end
end
+
+ def retrieve_error_message(stderr, stdout)
+ err_message = stderr.gets
+ err_message.blank? ? stdout.gets : err_message
+ end
end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index d2a0e316cb..8e8f39d9cb 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -1,63 +1,31 @@
+# Check a user's access to perform a git action. All public methods in this
+# class return an instance of `GitlabAccessStatus`
module Gitlab
class GitAccess
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
PUSH_COMMANDS = %w{ git-receive-pack }
- attr_reader :actor, :project
+ attr_reader :actor, :project, :protocol, :user_access
- def initialize(actor, project)
+ def initialize(actor, project, protocol)
@actor = actor
@project = project
- end
-
- def user
- return @user if defined?(@user)
-
- @user =
- case actor
- when User
- actor
- when DeployKey
- nil
- when Key
- actor.user
- end
- end
-
- def deploy_key
- actor if actor.is_a?(DeployKey)
- end
-
- def can_push_to_branch?(ref)
- return false unless user
-
- if project.protected_branch?(ref) && !project.developers_can_push_to_protected_branch?(ref)
- user.can?(:push_code_to_protected_branches, project)
- else
- user.can?(:push_code, project)
- end
- end
-
- def can_read_project?
- if user
- user.can?(:read_project, project)
- elsif deploy_key
- deploy_key.projects.include?(project)
- else
- false
- end
+ @protocol = protocol
+ @user_access = UserAccess.new(user, project: project)
end
def check(cmd, changes = nil)
+ return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed?
+
unless actor
return build_status_object(false, "No user or key was provided.")
end
- if user && !user_allowed?
+ if user && !user_access.allowed?
return build_status_object(false, "Your account has been blocked.")
end
- unless project && can_read_project?
+ unless project && (user_access.can_read_project? || deploy_key_can_read_project?)
return build_status_object(false, 'The project you were looking for could not be found.')
end
@@ -92,7 +60,7 @@ module Gitlab
end
def user_download_access_check
- unless user.can?(:download_code, project)
+ unless user_access.can_do_action?(:download_code)
return build_status_object(false, "You are not allowed to download code from this project.")
end
@@ -122,92 +90,49 @@ module Gitlab
build_status_object(true)
end
- def can_user_do_action?(action)
- @permission_cache ||= {}
- @permission_cache[action] ||= user.can?(action, project)
- end
-
def change_access_check(change)
- oldrev, newrev, ref = change.split(' ')
-
- action =
- if project.protected_branch?(branch_name(ref))
- protected_branch_action(oldrev, newrev, branch_name(ref))
- elsif (tag_ref = tag_name(ref)) && protected_tag?(tag_ref)
- # Prevent any changes to existing git tag unless user has permissions
- :admin_project
- else
- :push_code
- end
-
- unless can_user_do_action?(action)
- status =
- case action
- when :force_push_code_to_protected_branches
- build_status_object(false, "You are not allowed to force push code to a protected branch on this project.")
- when :remove_protected_branches
- build_status_object(false, "You are not allowed to deleted protected branches from this project.")
- when :push_code_to_protected_branches
- build_status_object(false, "You are not allowed to push code to protected branches on this project.")
- when :admin_project
- build_status_object(false, "You are not allowed to change existing tags on this project.")
- else # :push_code
- build_status_object(false, "You are not allowed to push code to this project.")
- end
- return status
- end
-
- build_status_object(true)
+ Checks::ChangeAccess.new(change, user_access: user_access, project: project).exec
end
- def forced_push?(oldrev, newrev)
- Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
+ def protocol_allowed?
+ Gitlab::ProtocolAccess.allowed?(protocol)
end
private
- def protected_branch_action(oldrev, newrev, branch_name)
- # we dont allow force push to protected branch
- if forced_push?(oldrev, newrev)
- :force_push_code_to_protected_branches
- elsif Gitlab::Git.blank_ref?(newrev)
- # and we dont allow remove of protected branch
- :remove_protected_branches
- elsif project.developers_can_push_to_protected_branch?(branch_name)
- :push_code
+ def matching_merge_request?(newrev, branch_name)
+ Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
+ end
+
+ def deploy_key
+ actor if actor.is_a?(DeployKey)
+ end
+
+ def deploy_key_can_read_project?
+ if deploy_key
+ return true if project.public?
+ deploy_key.projects.include?(project)
else
- :push_code_to_protected_branches
- end
- end
-
- def protected_tag?(tag_name)
- project.repository.tag_exists?(tag_name)
- end
-
- def user_allowed?
- Gitlab::UserAccess.allowed?(user)
- end
-
- def branch_name(ref)
- ref = ref.to_s
- if Gitlab::Git.branch_ref?(ref)
- Gitlab::Git.ref_name(ref)
- else
- nil
- end
- end
-
- def tag_name(ref)
- ref = ref.to_s
- if Gitlab::Git.tag_ref?(ref)
- Gitlab::Git.ref_name(ref)
- else
- nil
+ false
end
end
protected
+ def user
+ return @user if defined?(@user)
+
+ @user =
+ case actor
+ when User
+ actor
+ when DeployKey
+ nil
+ when Key
+ actor.user
+ end
+ end
+
def build_status_object(status, message = '')
GitAccessStatus.new(status, message)
end
diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb
index 5a806ff6e0..09bb01be69 100644
--- a/lib/gitlab/git_access_status.rb
+++ b/lib/gitlab/git_access_status.rb
@@ -8,8 +8,8 @@ module Gitlab
@message = message
end
- def to_json
- { status: @status, message: @message }.to_json
+ def to_json(opts = nil)
+ { status: @status, message: @message }.to_json(opts)
end
end
end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 8672cbc0ec..f71d357590 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,7 +1,7 @@
module Gitlab
class GitAccessWiki < GitAccess
def change_access_check(change)
- if user.can?(:create_wiki, project)
+ if user_access.can_do_action?(:create_wiki)
build_status_object(true)
else
build_status_object(false, "You are not allowed to write to this project's wiki.")
diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb
index a15fc84b41..7d2d545b84 100644
--- a/lib/gitlab/github_import/branch_formatter.rb
+++ b/lib/gitlab/github_import/branch_formatter.rb
@@ -4,7 +4,7 @@ module Gitlab
delegate :repo, :sha, :ref, to: :raw_data
def exists?
- project.repository.branch_exists?(ref)
+ branch_exists? && commit_exists?
end
def name
@@ -15,11 +15,15 @@ module Gitlab
repo.present?
end
- def valid?
- repo.present?
+ private
+
+ def branch_exists?
+ project.repository.branch_exists?(ref)
end
- private
+ def commit_exists?
+ project.repository.commit(sha).present?
+ end
def short_id
sha.to_s[0..7]
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index d325eca6d9..084e514492 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -4,26 +4,39 @@ module Gitlab
GITHUB_SAFE_REMAINING_REQUESTS = 100
GITHUB_SAFE_SLEEP_TIME = 500
- attr_reader :client, :api
+ attr_reader :access_token
def initialize(access_token)
- @client = ::OAuth2::Client.new(
+ @access_token = access_token
+
+ if access_token
+ ::Octokit.auto_paginate = false
+ end
+ end
+
+ def api
+ @api ||= ::Octokit::Client.new(
+ access_token: access_token,
+ api_endpoint: github_options[:site],
+ # If there is no config, we're connecting to github.com and we
+ # should verify ssl.
+ connection_options: {
+ ssl: { verify: config ? config['verify_ssl'] : true }
+ }
+ )
+ end
+
+ def client
+ unless config
+ raise Projects::ImportService::Error,
+ 'OAuth configuration for GitHub missing.'
+ end
+
+ @client ||= ::OAuth2::Client.new(
config.app_id,
config.app_secret,
github_options.merge(ssl: { verify: config['verify_ssl'] })
)
-
- if access_token
- ::Octokit.auto_paginate = false
-
- @api = ::Octokit::Client.new(
- access_token: access_token,
- api_endpoint: github_options[:site],
- connection_options: {
- ssl: { verify: config['verify_ssl'] }
- }
- )
- end
end
def authorize_url(redirect_uri)
@@ -56,15 +69,30 @@ module Gitlab
end
def github_options
- config["args"]["client_options"].deep_symbolize_keys
+ if config
+ config["args"]["client_options"].deep_symbolize_keys
+ else
+ OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
+ end
end
def rate_limit
api.rate_limit!
+ # GitHub Rate Limit API returns 404 when the rate limit is
+ # disabled. In this case we just want to return gracefully
+ # instead of spitting out an error.
+ rescue Octokit::NotFound
+ nil
+ end
+
+ def has_rate_limit?
+ return @has_rate_limit if defined?(@has_rate_limit)
+
+ @has_rate_limit = rate_limit.present?
end
def rate_limit_exceed?
- rate_limit.remaining <= GITHUB_SAFE_REMAINING_REQUESTS
+ has_rate_limit? && rate_limit.remaining <= GITHUB_SAFE_REMAINING_REQUESTS
end
def rate_limit_sleep_time
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 2286ac8829..3932fcb1ed 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -131,8 +131,10 @@ module Gitlab
def clean_up_restored_branches(branches)
branches.each do |name, _|
client.delete_ref(repo, "heads/#{name}")
- project.repository.rm_branch(project.creator, name)
+ project.repository.delete_branch(name) rescue Rugged::ReferenceError
end
+
+ project.repository.after_remove_branch
end
def apply_labels(issuable)
@@ -167,7 +169,7 @@ module Gitlab
def import_wiki
unless project.wiki_enabled?
wiki = WikiFormatter.new(project)
- gitlab_shell.import_repository(wiki.path_with_namespace, wiki.import_url)
+ gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url)
project.update_attribute(:wiki_enabled, true)
end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index 498b00cb65..a4ea2210ab 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -11,10 +11,10 @@ module Gitlab
description: description,
source_project: source_branch_project,
source_branch: source_branch_name,
- head_source_sha: source_branch_sha,
+ source_branch_sha: source_branch_sha,
target_project: target_branch_project,
target_branch: target_branch_name,
- base_target_sha: target_branch_sha,
+ target_branch_sha: target_branch_sha,
state: state,
milestone: milestone,
author_id: author_id,
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 3f76ec9797..46d40f75be 100644
--- a/lib/gitlab/gitlab_import/importer.rb
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -15,31 +15,35 @@ module Gitlab
end
def execute
- project_identifier = CGI.escape(project.import_source)
+ ActiveRecord::Base.no_touching do
+ project_identifier = CGI.escape(project.import_source)
- # Issues && Comments
- issues = client.issues(project_identifier)
+ # Issues && Comments
+ issues = client.issues(project_identifier)
- issues.each do |issue|
- body = @formatter.author_line(issue["author"]["name"])
- body += issue["description"]
+ issues.each do |issue|
+ body = @formatter.author_line(issue["author"]["name"])
+ body += issue["description"]
- comments = client.issue_comments(project_identifier, issue["id"])
+ comments = client.issue_comments(project_identifier, issue["id"])
- if comments.any?
- body += @formatter.comments_header
+ 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!(
- description: body,
- title: issue["title"],
- state: issue["state"],
- author_id: gl_user_id(project, issue["author"]["id"])
- )
end
true
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index f751a3a12f..c5a11148d3 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -3,10 +3,9 @@ module Gitlab
def add_gon_variables
gon.api_version = API::API.version
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
- gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
- gon.shortcuts_path = help_shortcuts_path
+ gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.award_menu_url = emojis_path
diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb
index 2122339d2d..3caf903645 100644
--- a/lib/gitlab/graphs/commits.rb
+++ b/lib/gitlab/graphs/commits.rb
@@ -18,7 +18,7 @@ module Gitlab
end
def commit_per_day
- @commit_per_day ||= (@commits.size.to_f / @duration).round(1)
+ @commit_per_day ||= @commits.size / (@duration + 1)
end
def collect_data
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 280120b0f9..9360afedfc 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, nowrap: true, plain: false)
- new(blob_name, blob_content, nowrap: nowrap).
+ 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
@@ -10,35 +10,45 @@ module Gitlab
return [] unless blob
blob.load_all_data!(repository)
- highlight(file_name, blob.data).lines.map!(&:html_safe)
+ highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe)
end
- def initialize(blob_name, blob_content, nowrap: true)
- @formatter = rouge_formatter(nowrap: nowrap)
- @lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText
+ def initialize(blob_name, blob_content, repository: nil)
+ @formatter = Rouge::Formatters::HTMLGitlab.new
+ @repository = repository
+ @blob_name = blob_name
+ @blob_content = blob_content
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 rouge_formatter(options = {})
- options = options.reverse_merge(
- nowrap: true,
- cssclass: 'code highlight',
- lineanchors: true,
- lineanchorsid: 'LC'
- )
+ def custom_language
+ language_name = @repository && @repository.gitattribute(@blob_name, 'gitlab-language')
- Rouge::Formatters::HTMLGitlab.new(options)
+ return nil unless language_name
+
+ Rouge::Lexer.find_fancy(language_name)
end
end
end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index 99cf85d9a3..bb562bdcd2 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -2,7 +2,8 @@ module Gitlab
module ImportExport
extend self
- VERSION = '0.1.0'
+ VERSION = '0.1.3'
+ FILENAME_LIMIT = 50
def export_path(relative_path:)
File.join(storage_path, relative_path)
@@ -12,6 +13,10 @@ module Gitlab
File.join(Settings.shared['path'], 'tmp/project_exports')
end
+ def import_upload_path(filename:)
+ File.join(storage_path, 'uploads', filename)
+ end
+
def project_filename
"project.json"
end
@@ -28,6 +33,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/avatar_restorer.rb b/lib/gitlab/import_export/avatar_restorer.rb
new file mode 100644
index 0000000000..352539eb59
--- /dev/null
+++ b/lib/gitlab/import_export/avatar_restorer.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module ImportExport
+ class AvatarRestorer
+
+ def initialize(project:, shared:)
+ @project = project
+ @shared = shared
+ end
+
+ def restore
+ return true unless avatar_export_file
+
+ @project.avatar = File.open(avatar_export_file)
+ @project.save!
+ rescue => e
+ @shared.error(e)
+ false
+ end
+
+ private
+
+ def avatar_export_file
+ @avatar_export_file ||= Dir["#{avatar_export_path}/*"].first
+ end
+
+ def avatar_export_path
+ File.join(@shared.export_path, 'avatar')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/avatar_saver.rb b/lib/gitlab/import_export/avatar_saver.rb
new file mode 100644
index 0000000000..998c21e258
--- /dev/null
+++ b/lib/gitlab/import_export/avatar_saver.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module ImportExport
+ class AvatarSaver
+ include Gitlab::ImportExport::CommandLineUtil
+
+ def initialize(project:, shared:)
+ @project = project
+ @shared = shared
+ end
+
+ def save
+ return true unless @project.avatar.exists?
+
+ copy_files(avatar_path, avatar_export_path)
+ rescue => e
+ @shared.error(e)
+ false
+ end
+
+ private
+
+ def avatar_export_path
+ File.join(@shared.export_path, 'avatar', @project.avatar_identifier)
+ end
+
+ def avatar_path
+ @project.avatar.path
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index 78664f076e..e522a0fc8f 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -17,6 +17,10 @@ module Gitlab
execute(%W(#{git_bin_path} clone --bare #{bundle_path} #{repo_path}))
end
+ def git_restore_hooks
+ execute(%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
+ end
+
private
def tar_with_options(archive:, dir:, options:)
@@ -28,13 +32,27 @@ module Gitlab
end
def execute(cmd)
- _output, status = Gitlab::Popen.popen(cmd)
+ output, status = Gitlab::Popen.popen(cmd)
+ @shared.error(Gitlab::ImportExport::Error.new(output.to_s)) unless status.zero?
status.zero?
end
def git_bin_path
Gitlab.config.git.bin_path
end
+
+ def copy_files(source, destination)
+ # if we are copying files, create the destination folder
+ destination_folder = File.file?(source) ? File.dirname(destination) : destination
+
+ FileUtils.mkdir_p(destination_folder)
+ FileUtils.copy_entry(source, destination)
+ true
+ end
+
+ def repository_storage_paths_args
+ Gitlab.config.repositories.storages.values
+ end
end
end
end
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 0e70d9282d..eca6e5b6d5 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -3,6 +3,8 @@ module Gitlab
class FileImporter
include Gitlab::ImportExport::CommandLineUtil
+ MAX_RETRIES = 8
+
def self.import(*args)
new(*args).import
end
@@ -14,7 +16,10 @@ module Gitlab
def import
FileUtils.mkdir_p(@shared.export_path)
- decompress_archive
+
+ wait_for_archived_file do
+ decompress_archive
+ end
rescue => e
@shared.error(e)
false
@@ -22,8 +27,23 @@ module Gitlab
private
+ # Exponentially sleep until I/O finishes copying the file
+ def wait_for_archived_file
+ MAX_RETRIES.times do |retry_number|
+ break if File.exist?(@archive_file)
+
+ sleep(2**retry_number)
+ end
+
+ yield
+ end
+
def decompress_archive
- untar_zxf(archive: @archive_file, dir: @shared.export_path)
+ result = untar_zxf(archive: @archive_file, dir: @shared.export_path)
+
+ raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
+
+ true
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 164ab6238c..1da5104361 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -1,24 +1,34 @@
# Model relationships to be included in the project import/export
project_tree:
- issues:
+ - :events
- notes:
- :author
- - :labels
- - :milestones
+ - :author
+ - :events
+ - label_links:
+ - :label
+ - milestone:
+ - :events
- snippets:
- notes:
:author
- :releases
- - :events
- project_members:
- :user
- merge_requests:
- notes:
- :author
+ - :author
+ - :events
- :merge_request_diff
+ - :events
+ - label_links:
+ - :label
+ - milestone:
+ - :events
- pipelines:
- notes:
- :author
+ - :author
+ - :events
- :statuses
- :variables
- :triggers
@@ -26,6 +36,9 @@ project_tree:
- :services
- :hooks
- :protected_branches
+ - :labels
+ - milestones:
+ - :events
# Only include the following attributes for the models specified.
included_attributes:
@@ -48,7 +61,15 @@ included_attributes:
excluded_attributes:
snippets:
- :expired_at
+ merge_request_diff:
+ - :st_diffs
+ issues:
+ - :milestone_id
+ merge_requests:
+ - :milestone_id
methods:
statuses:
- - :type
\ No newline at end of file
+ - :type
+ merge_request_diff:
+ - :utf8_st_diffs
\ No newline at end of file
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index d209e04f7b..e9ee47fc09 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class Importer
-
def initialize(project)
@archive_file = project.import_source
@current_user = project.creator
@@ -10,17 +9,22 @@ module Gitlab
end
def execute
- Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file,
- shared: @shared)
- if check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
+ if import_file && check_version! && [project_tree, avatar_restorer, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
project_tree.restored_project
else
raise Projects::ImportService::Error.new(@shared.errors.join(', '))
end
+
+ remove_import_file
end
private
+ def import_file
+ Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file,
+ shared: @shared)
+ end
+
def check_version!
Gitlab::ImportExport::VersionChecker.check!(shared: @shared)
end
@@ -31,6 +35,10 @@ module Gitlab
project: @project)
end
+ def avatar_restorer
+ Gitlab::ImportExport::AvatarRestorer.new(project: project_tree.restored_project, shared: @shared)
+ end
+
def repo_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path,
shared: @shared,
@@ -40,8 +48,7 @@ module Gitlab
def wiki_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: wiki_repo_path,
shared: @shared,
- project: ProjectWiki.new(project_tree.restored_project),
- wiki: true)
+ project: ProjectWiki.new(project_tree.restored_project))
end
def uploads_restorer
@@ -59,6 +66,10 @@ module Gitlab
def wiki_repo_path
File.join(@shared.export_path, 'project.wiki.bundle')
end
+
+ def remove_import_file
+ FileUtils.rm_rf(@archive_file)
+ end
end
end
end
diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb
new file mode 100644
index 0000000000..008300bde4
--- /dev/null
+++ b/lib/gitlab/import_export/json_hash_builder.rb
@@ -0,0 +1,110 @@
+module Gitlab
+ module ImportExport
+ # Generates a hash that conforms with http://apidock.com/rails/Hash/to_json
+ # and its peculiar options.
+ class JsonHashBuilder
+ def self.build(model_objects, attributes_finder)
+ new(model_objects, attributes_finder).build
+ end
+
+ def initialize(model_objects, attributes_finder)
+ @model_objects = model_objects
+ @attributes_finder = attributes_finder
+ end
+
+ def build
+ process_model_objects(@model_objects)
+ end
+
+ private
+
+ # Called when the model is actually a hash containing other relations (more models)
+ # Returns the config in the right format for calling +to_json+
+ #
+ # +model_object_hash+ - A model relationship such as:
+ # {:merge_requests=>[:merge_request_diff, :notes]}
+ def process_model_objects(model_object_hash)
+ json_config_hash = {}
+ current_key = model_object_hash.keys.first
+
+ model_object_hash.values.flatten.each do |model_object|
+ @attributes_finder.parse(current_key) { |hash| json_config_hash[current_key] ||= hash }
+ handle_model_object(current_key, model_object, json_config_hash)
+ end
+
+ json_config_hash
+ end
+
+ # Creates or adds to an existing hash an individual model or list
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +model_object+ model or list of models to include in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def handle_model_object(current_key, model_object, json_config_hash)
+ model_or_sub_model = model_object.is_a?(Hash) ? process_model_objects(model_object) : model_object
+
+ if json_config_hash[current_key]
+ add_model_value(current_key, model_or_sub_model, json_config_hash)
+ else
+ create_model_value(current_key, model_or_sub_model, json_config_hash)
+ end
+ end
+
+ # Constructs a new hash that will hold the configuration for that particular object
+ # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +value+ existing model to be included in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def create_model_value(current_key, value, json_config_hash)
+ parsed_hash = { include: value }
+ parse_hash(value, parsed_hash)
+
+ json_config_hash[current_key] = parsed_hash
+ end
+
+ # Calls attributes finder to parse the hash and add any attributes to it
+ #
+ # +value+ existing model to be included in the hash
+ # +parsed_hash+ the original hash
+ def parse_hash(value, parsed_hash)
+ @attributes_finder.parse(value) do |hash|
+ parsed_hash = { include: hash_or_merge(value, hash) }
+ end
+ end
+
+ # Adds new model configuration to an existing hash with key +current_key+
+ # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +value+ existing model to be included in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def add_model_value(current_key, value, json_config_hash)
+ @attributes_finder.parse(value) { |hash| value = { value => hash } }
+
+ add_to_array(current_key, json_config_hash, value)
+ end
+
+ # Adds new model configuration to an existing hash with key +current_key+
+ # it creates a new array if it was previously a single value
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +value+ existing model to be included in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def add_to_array(current_key, json_config_hash, value)
+ old_values = json_config_hash[current_key][:include]
+
+ json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
+ end
+
+ # Construct a new hash or merge with an existing one a model configuration
+ # This is to fulfil +to_json+ requirements.
+ #
+ # +hash+ hash containing configuration generated mainly from +@attributes_finder+
+ # +value+ existing model to be included in the hash
+ def hash_or_merge(value, hash)
+ value.is_a?(Hash) ? value.merge(hash) : { value => hash }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index c569a35a48..b459054c19 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class MembersMapper
-
attr_reader :missing_author_ids
def initialize(exported_members:, user:, project:)
diff --git a/lib/gitlab/import_export/project_creator.rb b/lib/gitlab/import_export/project_creator.rb
index 89388d1984..77bb3ca658 100644
--- a/lib/gitlab/import_export/project_creator.rb
+++ b/lib/gitlab/import_export/project_creator.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class ProjectCreator
-
def initialize(namespace_id, current_user, file, project_path)
@namespace_id = namespace_id
@current_user = current_user
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index dd71b92c52..c7b3551b84 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class ProjectTreeRestorer
-
def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json')
@user = user
@@ -13,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
@@ -45,7 +47,7 @@ module Gitlab
relation_key = relation.is_a?(Hash) ? relation.keys.first : relation
relation_hash = create_relation(relation_key, @tree_hash[relation_key.to_s])
- saved << restored_project.update_attribute(relation_key, relation_hash)
+ saved << restored_project.append_or_update_attribute(relation_key, relation_hash)
end
saved.all?
end
@@ -70,10 +72,19 @@ module Gitlab
# Example:
# +relation_key+ issues, loops through the list of *issues* and for each individual
# issue, finds any subrelations such as notes, creates them and assign them back to the hash
+ #
+ # Recursively calls this method if the sub-relation is a hash containing more sub-relations
def create_sub_relations(relation, tree_hash)
relation_key = relation.keys.first.to_s
- tree_hash[relation_key].each do |relation_item|
+ return if tree_hash[relation_key].blank?
+
+ [tree_hash[relation_key]].flatten.each do |relation_item|
relation.values.flatten.each do |sub_relation|
+ # We just use author to get the user ID, do not attempt to create an instance.
+ next if sub_relation == :author
+
+ create_sub_relations(sub_relation, relation_item) if sub_relation.is_a?(Hash)
+
relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation)
relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank?
end
diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb
index 19defd8f03..5021a1a14c 100644
--- a/lib/gitlab/import_export/reader.rb
+++ b/lib/gitlab/import_export/reader.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class Reader
-
attr_reader :tree
def initialize(shared:)
@@ -30,88 +29,12 @@ module Gitlab
def build_hash(model_list)
model_list.map do |model_objects|
if model_objects.is_a?(Hash)
- build_json_config_hash(model_objects)
+ Gitlab::ImportExport::JsonHashBuilder.build(model_objects, @attributes_finder)
else
@attributes_finder.find(model_objects)
end
end
end
-
- # Called when the model is actually a hash containing other relations (more models)
- # Returns the config in the right format for calling +to_json+
- # +model_object_hash+ - A model relationship such as:
- # {:merge_requests=>[:merge_request_diff, :notes]}
- def build_json_config_hash(model_object_hash)
- @json_config_hash = {}
-
- model_object_hash.values.flatten.each do |model_object|
- current_key = model_object_hash.keys.first
-
- @attributes_finder.parse(current_key) { |hash| @json_config_hash[current_key] ||= hash }
-
- handle_model_object(current_key, model_object)
- process_sub_model(current_key, model_object) if model_object.is_a?(Hash)
- end
- @json_config_hash
- end
-
-
- # If the model is a hash, process the sub_models, which could also be hashes
- # If there is a list, add to an existing array, otherwise use hash syntax
- # +current_key+ main model that will be a key in the hash
- # +model_object+ model or list of models to include in the hash
- def process_sub_model(current_key, model_object)
- sub_model_json = build_json_config_hash(model_object).dup
- @json_config_hash.slice!(current_key)
-
- if @json_config_hash[current_key] && @json_config_hash[current_key][:include]
- @json_config_hash[current_key][:include] << sub_model_json
- else
- @json_config_hash[current_key] = { include: sub_model_json }
- end
- end
-
- # Creates or adds to an existing hash an individual model or list
- # +current_key+ main model that will be a key in the hash
- # +model_object+ model or list of models to include in the hash
- def handle_model_object(current_key, model_object)
- if @json_config_hash[current_key]
- add_model_value(current_key, model_object)
- else
- create_model_value(current_key, model_object)
- end
- end
-
- # Constructs a new hash that will hold the configuration for that particular object
- # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
- # +current_key+ main model that will be a key in the hash
- # +value+ existing model to be included in the hash
- def create_model_value(current_key, value)
- parsed_hash = { include: value }
-
- @attributes_finder.parse(value) do |hash|
- parsed_hash = { include: hash_or_merge(value, hash) }
- end
- @json_config_hash[current_key] = parsed_hash
- end
-
- # Adds new model configuration to an existing hash with key +current_key+
- # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
- # +current_key+ main model that will be a key in the hash
- # +value+ existing model to be included in the hash
- def add_model_value(current_key, value)
- @attributes_finder.parse(value) { |hash| value = { value => hash } }
- old_values = @json_config_hash[current_key][:include]
- @json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
- end
-
- # Construct a new hash or merge with an existing one a model configuration
- # This is to fulfil +to_json+ requirements.
- # +value+ existing model to be included in the hash
- # +hash+ hash containing configuration generated mainly from +@attributes_finder+
- def hash_or_merge(value, hash)
- value.is_a?(Hash) ? value.merge(hash) : { value => hash }
- end
end
end
end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index b872780f20..e9c1b79fa4 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class RelationFactory
-
OVERRIDES = { snippets: :project_snippets,
pipelines: 'Ci::Pipeline',
statuses: 'commit_status',
@@ -12,6 +11,12 @@ module Gitlab
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze
+ BUILD_MODELS = %w[Ci::Build commit_status].freeze
+
+ IMPORTED_OBJECT_MAX_RETRIES = 5.freeze
+
+ EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze
+
def self.create(*args)
new(*args).create
end
@@ -21,22 +26,35 @@ module Gitlab
@relation_hash = relation_hash.except('id', 'noteable_id')
@members_mapper = members_mapper
@user = user
+ @imported_object_retries = 0
end
# Creates an object from an actual model with name "relation_sym" with params from
# the relation_hash, updating references with new object IDs, mapping users using
# the "members_mapper" object, also updating notes if required.
def create
- set_note_author if @relation_name == :notes
- update_user_references
- update_project_references
- reset_ci_tokens if @relation_name == 'Ci::Trigger'
+ setup_models
generate_imported_object
end
private
+ def setup_models
+ if @relation_name == :notes
+ set_note_author
+
+ # TODO: note attatchments not supported yet
+ @relation_hash['attachment'] = nil
+ end
+
+ update_user_references
+ update_project_references
+ reset_ci_tokens if @relation_name == 'Ci::Trigger'
+ @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
+ set_st_diffs if @relation_name == :merge_request_diff
+ end
+
def update_user_references
USER_REFERENCES.each do |reference|
if @relation_hash[reference]
@@ -70,7 +88,7 @@ module Gitlab
end
def generate_imported_object
- if @relation_sym == 'commit_status' # call #trace= method after assigning the other attributes
+ if BUILD_MODELS.include?(@relation_name) # call #trace= method after assigning the other attributes
trace = @relation_hash.delete('trace')
imported_object do |object|
object.trace = trace
@@ -85,7 +103,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']
@@ -109,10 +127,14 @@ module Gitlab
end
def imported_object
- imported_object = relation_class.new(@relation_hash)
- yield(imported_object) if block_given?
- imported_object.importing = true if imported_object.respond_to?(:importing)
- imported_object
+ yield(existing_or_new_object) if block_given?
+ existing_or_new_object.importing = true if existing_or_new_object.respond_to?(:importing)
+ existing_or_new_object
+ rescue ActiveRecord::RecordNotUnique
+ # as the operation is not atomic, retry in the unlikely scenario an INSERT is
+ # performed on the same object between the SELECT and the INSERT
+ @imported_object_retries += 1
+ retry if @imported_object_retries < IMPORTED_OBJECT_MAX_RETRIES
end
def update_note_for_missing_author(author_name)
@@ -123,6 +145,28 @@ module Gitlab
def admin_user?
@user.is_admin?
end
+
+ def parsed_relation_hash
+ @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
+ end
+
+ def set_st_diffs
+ @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
+ end
+
+ def existing_or_new_object
+ # Only find existing records to avoid mapping tables such as milestones
+ # Otherwise always create the record, skipping the extra SELECT clause.
+ @existing_or_new_object ||= begin
+ if EXISTING_OBJECT_CHECK.include?(@relation_name)
+ existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id'))
+ existing_object.assign_attributes(parsed_relation_hash)
+ existing_object
+ else
+ relation_class.new(parsed_relation_hash)
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 546dae4d12..6d9379acf2 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -3,19 +3,18 @@ module Gitlab
class RepoRestorer
include Gitlab::ImportExport::CommandLineUtil
- def initialize(project:, shared:, path_to_bundle:, wiki: false)
+ def initialize(project:, shared:, path_to_bundle:)
@project = project
@path_to_bundle = path_to_bundle
@shared = shared
- @wiki = wiki
end
def restore
- return wiki? unless File.exist?(@path_to_bundle)
+ return true unless File.exist?(@path_to_bundle)
FileUtils.mkdir_p(path_to_repo)
- git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle)
+ git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) && repo_restore_hooks
rescue => e
@shared.error(e)
false
@@ -31,8 +30,14 @@ module Gitlab
@project.repository.path_to_repo
end
+ def repo_restore_hooks
+ return true if wiki?
+
+ git_restore_hooks
+ end
+
def wiki?
- @wiki
+ @project.class.name == 'ProjectWiki'
end
end
end
diff --git a/lib/gitlab/import_export/repo_saver.rb b/lib/gitlab/import_export/repo_saver.rb
index cce43fe994..331e14021e 100644
--- a/lib/gitlab/import_export/repo_saver.rb
+++ b/lib/gitlab/import_export/repo_saver.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def save
- return false if @project.empty_repo?
+ return true if @project.empty_repo? # it's ok to have no repo
@full_path = File.join(@shared.export_path, ImportExport.project_bundle_filename)
bundle_to_disk
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index f38229c6c5..6130c124dd 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
@@ -17,6 +18,7 @@ module Gitlab
Rails.logger.info("Saved project export #{archive_file}")
archive_file
else
+ @shared.error(Gitlab::ImportExport::Error.new("Unable to save #{archive_file} into #{@shared.export_path}"))
false
end
rescue => e
@@ -35,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/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 6aff05b886..5d6de8bc47 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class Shared
-
attr_reader :errors, :opts
def initialize(opts)
diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb
index 7292e9d971..62a2553675 100644
--- a/lib/gitlab/import_export/uploads_saver.rb
+++ b/lib/gitlab/import_export/uploads_saver.rb
@@ -1,6 +1,7 @@
module Gitlab
module ImportExport
class UploadsSaver
+ include Gitlab::ImportExport::CommandLineUtil
def initialize(project:, shared:)
@project = project
@@ -18,12 +19,6 @@ module Gitlab
private
- def copy_files(source, destination)
- FileUtils.mkdir_p(destination)
- FileUtils.copy_entry(source, destination)
- true
- end
-
def uploads_export_path
File.join(@shared.export_path, 'uploads')
end
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index cf5c62c5e3..de3fe6d822 100644
--- a/lib/gitlab/import_export/version_checker.rb
+++ b/lib/gitlab/import_export/version_checker.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class VersionChecker
-
def self.check!(*args)
new(*args).check!
end
@@ -26,7 +25,7 @@ module Gitlab
def verify_version!(version)
if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version)
- raise Gitlab::ImportExport::Error("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+ raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
else
true
end
diff --git a/lib/gitlab/import_export/version_saver.rb b/lib/gitlab/import_export/version_saver.rb
index f7f73dc934..9b642d740b 100644
--- a/lib/gitlab/import_export/version_saver.rb
+++ b/lib/gitlab/import_export/version_saver.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class VersionSaver
-
def initialize(shared:)
@shared = shared
end
diff --git a/lib/gitlab/import_export/wiki_repo_saver.rb b/lib/gitlab/import_export/wiki_repo_saver.rb
index 1eedae39f8..6107420e4d 100644
--- a/lib/gitlab/import_export/wiki_repo_saver.rb
+++ b/lib/gitlab/import_export/wiki_repo_saver.rb
@@ -4,6 +4,7 @@ module Gitlab
def save
@wiki = ProjectWiki.new(@project)
return true unless wiki_repository_exists? # it's okay to have no Wiki
+
bundle_to_disk(File.join(@shared.export_path, project_filename))
end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 948d43582c..59a05411fe 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -24,8 +24,6 @@ module Gitlab
'GitLab export' => 'gitlab_project'
}
end
-
end
-
end
end
diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb
index 8684b4636e..b75ae512d9 100644
--- a/lib/gitlab/key_fingerprint.rb
+++ b/lib/gitlab/key_fingerprint.rb
@@ -39,7 +39,7 @@ module Gitlab
# OpenSSH 6.8 introduces a new default output format for fingerprints.
# Check the version and decide which command to use.
- version_output, version_status = popen(%W(ssh -V))
+ version_output, version_status = popen(%w(ssh -V))
return false unless version_status.zero?
version_matches = version_output.match(/OpenSSH_(?\d+)\.(?\d+)/)
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index e3ed2f6791..a1ee1aa81f 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -1,7 +1,6 @@
module Gitlab
module Lfs
class Response
-
def initialize(project, user, ci, request)
@origin_project = project
@project = storage_project(project)
@@ -48,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 69bd5e6230..f2a76a56b8 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/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index faf0d9b631..c048fe20ba 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -18,12 +18,12 @@ module Gitlab
# Measures the real and CPU execution time of the supplied block.
def measure
- start_real = Time.now
+ start_real = System.monotonic_time
start_cpu = System.cpu_time
retval = yield
- @real_time += (Time.now - start_real) * 1000.0
- @cpu_time += System.cpu_time.to_f - start_cpu
+ @real_time += System.monotonic_time - start_real
+ @cpu_time += System.cpu_time - start_cpu
@call_count += 1
retval
diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb
index 1cd1ca30f7..f23d67e1e3 100644
--- a/lib/gitlab/metrics/metric.rb
+++ b/lib/gitlab/metrics/metric.rb
@@ -4,16 +4,15 @@ module Gitlab
class Metric
JITTER_RANGE = 0.000001..0.001
- attr_reader :series, :values, :tags, :created_at
+ attr_reader :series, :values, :tags
# series - The name of the series (as a String) to store the metric in.
# values - A Hash containing the values to store.
# tags - A Hash containing extra tags to add to the metrics.
def initialize(series, values, tags = {})
- @values = values
- @series = series
- @tags = tags
- @created_at = Time.now.utc
+ @values = values
+ @series = series
+ @tags = tags
end
# Returns a Hash in a format that can be directly written to InfluxDB.
@@ -27,20 +26,20 @@ module Gitlab
#
# Due to the way InfluxDB is set up there's no solution to this problem,
# all we can do is lower the amount of collisions. We do this by using
- # Time#to_f which returns the seconds as a Float providing greater
- # accuracy. We then add a small random value that is large enough to
- # distinguish most timestamps but small enough to not alter the amount
- # of seconds.
+ # System.real_time which returns the nanoseconds as a Float providing
+ # greater accuracy. We then add a small random value that is large
+ # enough to distinguish most timestamps but small enough to not alter
+ # the timestamp significantly.
#
# See https://gitlab.com/gitlab-com/operations/issues/175 for more
# information.
- time = @created_at.to_f + rand(JITTER_RANGE)
+ time = System.real_time(:nanosecond) + rand(JITTER_RANGE)
{
series: @series,
tags: @tags,
values: @values,
- timestamp: (time * 1_000_000_000).to_i
+ timestamp: time.to_i
}
end
end
diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb
index fd98aa3412..a1240fd33e 100644
--- a/lib/gitlab/metrics/sidekiq_middleware.rb
+++ b/lib/gitlab/metrics/sidekiq_middleware.rb
@@ -8,6 +8,8 @@ module Gitlab
trans = Transaction.new("#{worker.class.name}#perform")
begin
+ # Old gitlad-shell messages don't provide enqueued_at/created_at attributes
+ trans.set(:sidekiq_queue_duration, Time.now.to_f - (message['enqueued_at'] || message['created_at'] || 0))
trans.run { yield }
ensure
trans.finish
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index 8e345e8ae4..aaed2184f4 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -2,11 +2,21 @@ module Gitlab
module Metrics
module Subscribers
# Class for tracking the total time spent in Rails cache calls
+ # http://guides.rubyonrails.org/active_support_instrumentation.html
class RailsCache < ActiveSupport::Subscriber
attach_to :active_support
def cache_read(event)
increment(:cache_read, event.duration)
+
+ return unless current_transaction
+ return if event.payload[:super_operation] == :fetch
+
+ if event.payload[:hit]
+ current_transaction.increment(:cache_read_hit_count, 1)
+ else
+ current_transaction.increment(:cache_read_miss_count, 1)
+ end
end
def cache_write(event)
@@ -21,6 +31,18 @@ module Gitlab
increment(:cache_exists, event.duration)
end
+ def cache_fetch_hit(event)
+ return unless current_transaction
+
+ current_transaction.increment(:cache_read_hit_count, 1)
+ end
+
+ def cache_generate(event)
+ return unless current_transaction
+
+ current_transaction.increment(:cache_read_miss_count, 1)
+ end
+
def increment(key, duration)
return unless current_transaction
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index a7d183b2f9..82c18bb108 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -34,13 +34,29 @@ module Gitlab
# THREAD_CPUTIME is not supported on OS X
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time
- Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
+ Process.
+ clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f
end
else
def self.cpu_time
- Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
+ Process.
+ clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f
end
end
+
+ # Returns the current real time in a given precision.
+ #
+ # Returns the time as a Float.
+ def self.real_time(precision = :millisecond)
+ Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f
+ end
+
+ # Returns the current monotonic clock time in a given precision.
+ #
+ # Returns the time as a Float.
+ def self.monotonic_time(precision = :millisecond)
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, precision).to_f
+ end
end
end
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 4bc5081aa0..bded245da4 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -30,7 +30,7 @@ module Gitlab
end
def duration
- @finished_at ? (@finished_at - @started_at) * 1000.0 : 0.0
+ @finished_at ? (@finished_at - @started_at) : 0.0
end
def allocated_memory
@@ -41,12 +41,12 @@ module Gitlab
Thread.current[THREAD_KEY] = self
@memory_before = System.memory_usage
- @started_at = Time.now
+ @started_at = System.monotonic_time
yield
ensure
@memory_after = System.memory_usage
- @finished_at = Time.now
+ @finished_at = System.monotonic_time
Thread.current[THREAD_KEY] = nil
end
diff --git a/lib/gitlab/o_auth/auth_hash.rb b/lib/gitlab/o_auth/auth_hash.rb
index 36e5c2670b..7d6911a1ab 100644
--- a/lib/gitlab/o_auth/auth_hash.rb
+++ b/lib/gitlab/o_auth/auth_hash.rb
@@ -66,7 +66,7 @@ module Gitlab
# Get the first part of the email address (before @)
# In addtion in removes illegal characters
def generate_username(email)
- email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/,'').to_s
+ email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/, '').to_s
end
def generate_temporarily_email(username)
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 78f3ecb4cb..0a91d3918d 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -56,8 +56,6 @@ module Gitlab
if external_provider? && @user
@user.external = true
- elsif @user
- @user.external = false
end
@user
@@ -74,7 +72,7 @@ module Gitlab
if user
# Case when a LDAP user already exists in Gitlab. Add the OAuth identity to existing account.
log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity."
- user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
+ user.identities.find_or_initialize_by(extern_uid: auth_hash.uid, provider: auth_hash.provider)
else
log.info "No existing LDAP account was found in GitLab. Checking for #{auth_hash.provider} account."
user = find_by_uid_and_provider
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index 746ec28333..4e2f8ed558 100644
--- a/lib/gitlab/other_markup.rb
+++ b/lib/gitlab/other_markup.rb
@@ -1,7 +1,6 @@
module Gitlab
# Parser/renderer for markups without other special support code.
module OtherMarkup
-
# Public: Converts the provided markup into HTML.
#
# input - the source text in a markup format
diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb
new file mode 100644
index 0000000000..21aefc884b
--- /dev/null
+++ b/lib/gitlab/protocol_access.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module ProtocolAccess
+ def self.allowed?(protocol)
+ if protocol == 'web'
+ true
+ elsif current_application_settings.enabled_git_access_protocol.blank?
+ true
+ else
+ protocol == current_application_settings.enabled_git_access_protocol
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index c84c68f96f..ffad5e17c7 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -13,7 +13,6 @@ module Gitlab
"Cannot start with '-' or end in '.'." \
end
-
def namespace_name_regex
@namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze
end
@@ -22,7 +21,6 @@ module Gitlab
"can contain only letters, digits, '_', '.', dash and space."
end
-
def project_name_regex
@project_name_regex ||= /\A[\p{Alnum}_][\p{Alnum}\p{Pd}_\. ]*\z/.freeze
end
@@ -32,7 +30,6 @@ module Gitlab
"It must start with letter, digit or '_'."
end
-
def project_path_regex
@project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(? repository.path_to_repo,
- 'ShaFrom' => from.sha,
- 'ShaTo' => to.sha
+ 'ShaFrom' => diff_refs.start_sha,
+ 'ShaTo' => diff_refs.head_sha
}
[
@@ -52,6 +50,31 @@ module Gitlab
]
end
+ def send_git_patch(repository, diff_refs)
+ params = {
+ 'RepoPath' => repository.path_to_repo,
+ 'ShaFrom' => diff_refs.start_sha,
+ 'ShaTo' => diff_refs.head_sha
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "git-format-patch:#{encode(params)}"
+ ]
+ end
+
+ def send_artifacts_entry(build, entry)
+ params = {
+ 'Archive' => build.artifacts_file.path,
+ 'Entry' => Base64.encode64(entry.path)
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "artifacts-entry:#{encode(params)}"
+ ]
+ end
+
protected
def encode(hash)
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
index 8ddc351129..068a95790c 100644
--- a/lib/repository_cache.rb
+++ b/lib/repository_cache.rb
@@ -1,14 +1,15 @@
# Interface to the Redis-backed cache store used by the Repository model
class RepositoryCache
- attr_reader :namespace, :backend
+ attr_reader :namespace, :backend, :project_id
- def initialize(namespace, backend = Rails.cache)
+ def initialize(namespace, project_id, backend = Rails.cache)
@namespace = namespace
@backend = backend
+ @project_id = project_id
end
def cache_key(type)
- "#{type}:#{namespace}"
+ "#{type}:#{namespace}:#{project_id}"
end
def expire(key)
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index 8c309efc7b..4edfd01507 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -1,175 +1,27 @@
-require 'cgi'
-
module Rouge
module Formatters
- class HTMLGitlab < Rouge::Formatter
+ class HTMLGitlab < Rouge::Formatters::HTML
tag 'html_gitlab'
# Creates a new Rouge::Formatter::HTMLGitlab instance.
#
- # [+nowrap+] If set to True, don't wrap the output at all, not
- # even inside a tag (default: false).
- # [+cssclass+] CSS class for the wrapping 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 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
- # tags. Used in combination with linenos and lineanchors
- # (default: false).
- # [+inline_theme+] Inline CSS styles for the 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)
+ @line_number = linenostart
end
- def render(tokens)
- case @linenos
- when 'table'
- render_tableized(tokens)
- when 'inline'
- render_untableized(tokens)
- else
- render_untableized(tokens)
- end
- end
+ def stream(tokens, &b)
+ is_first = true
+ token_lines(tokens) do |line|
+ yield "\n" unless is_first
+ is_first = false
- alias_method :format, :render
+ yield %()
+ line.each { |token, value| yield span(token, value.chomp) }
+ yield %()
- private
-
- def render_untableized(tokens)
- data = process_tokens(tokens)
-
- html = ''
- html << "" unless @nowrap
- html << wrap_lines(data[:code])
- html << "
\n" unless @nowrap
- html
- end
-
- def render_tableized(tokens)
- data = process_tokens(tokens)
-
- html = ''
- html << "" unless @nowrap
- html << ''
- html << ""
- html << wrap_linenos(data[:numbers])
- html << '
'
- html << ""
- html << wrap_lines(data[:code])
- html << '
'
- html << '
'
- html << '' 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|
- "#{number}"
- 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'
- "" \
- "#{number}" \
- "#{line}" \
- ''
- else
- "#{line}" \
- ''
- end
- end
- lines.join("\n")
- else
- if @linenos == 'inline'
- lines = lines.each_with_index.map do |line, index|
- number = index + @linenostart
- "#{number}#{line}"
- end
- lines.join("\n")
- else
- lines.join("\n")
- end
- end
- end
-
- def span(tok, val)
- # http://stackoverflow.com/a/1600584/2587286
- val = CGI.escapeHTML(val)
-
- if tok.shortname.empty?
- val
- else
- if @inline_theme
- rules = @inline_theme.style_for(tok).rendered_rules
- ""
- else
- "#{val}"
- end
+ @line_number += 1
end
end
end
diff --git a/lib/tasks/downtime_check.rake b/lib/tasks/downtime_check.rake
new file mode 100644
index 0000000000..30a2e9be5c
--- /dev/null
+++ b/lib/tasks/downtime_check.rake
@@ -0,0 +1,26 @@
+desc 'Checks if migrations in a branch require downtime'
+task downtime_check: :environment do
+ # First we'll want to make sure we're comparing with the right upstream
+ # repository/branch.
+ current_branch = `git rev-parse --abbrev-ref HEAD`.strip
+
+ # Either the developer ran this task directly on the master branch, or they're
+ # making changes directly on the master branch.
+ if current_branch == 'master'
+ if defined?(Gitlab::License)
+ repo = 'gitlab-ee'
+ else
+ repo = 'gitlab-ce'
+ end
+
+ `git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1`
+
+ compare_with = 'FETCH_HEAD'
+ # The developer is working on a different branch, in this case we can just
+ # compare with the master branch.
+ else
+ compare_with = 'master'
+ end
+
+ Rake::Task['gitlab:db:downtime_check'].invoke(compare_with)
+end
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
index 030ee8bafc..993112aee3 100644
--- a/lib/tasks/gemojione.rake
+++ b/lib/tasks/gemojione.rake
@@ -4,7 +4,7 @@ namespace :gemojione do
require 'digest/sha2'
require 'json'
- dir = Gemojione.index.images_path
+ dir = Gemojione.images_path
digests = []
aliases = Hash.new { |hash, key| hash[key] = [] }
aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
@@ -13,7 +13,7 @@ namespace :gemojione do
aliases[real_name] << alias_name
end
- AwardEmoji.emojis.map do |name, emoji_hash|
+ Gitlab::AwardEmoji.emojis.map do |name, emoji_hash|
fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
digest = Digest::SHA256.file(fpath).hexdigest
@@ -50,9 +50,14 @@ namespace :gemojione do
SIZE = 20
RETINA = SIZE * 2
+ # Update these values to the width and height of the spritesheet when
+ # new emoji are added.
+ SPRITESHEET_WIDTH = 860
+ SPRITESHEET_HEIGHT = 840
+
Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+ FileUtils.cp_r(Gemojione.images_path, tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
@@ -64,7 +69,7 @@ namespace :gemojione do
# Combine the resized assets into a packed sprite and re-generate the SCSS
SpriteFactory.cssurl = "image-url('$IMAGE')"
- SpriteFactory.run!(File.join(tmpdir, 'images'), {
+ SpriteFactory.run!(File.join(tmpdir, 'png'), {
output_style: style_path,
output_image: "app/assets/images/emoji.png",
selector: '.emoji-',
@@ -97,7 +102,7 @@ namespace :gemojione do
only screen and (min-resolution: 192dpi),
only screen and (min-resolution: 2dppx) {
background-image: image-url('emoji@2x.png');
- background-size: 840px 820px;
+ background-size: #{SPRITESHEET_WIDTH}px #{SPRITESHEET_HEIGHT}px;
}
}
CSS
@@ -107,7 +112,7 @@ namespace :gemojione do
# Now do it again but for Retina
Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+ FileUtils.cp_r(Gemojione.images_path, tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
@@ -116,7 +121,7 @@ namespace :gemojione do
end
# Combine the resized assets into a packed sprite and re-generate the SCSS
- SpriteFactory.run!(File.join(tmpdir, 'images'), {
+ SpriteFactory.run!(File.join(tmpdir), {
output_image: "app/assets/images/emoji@2x.png",
style: false,
nocomments: true,
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 9ee72fde92..b43ee5b338 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -33,12 +33,13 @@ namespace :gitlab do
unless backup.skipped?('db')
unless ENV['force'] == 'yes'
- warning = warning = <<-MSG.strip_heredoc
+ warning = <<-MSG.strip_heredoc
Before restoring the database we recommend removing all existing
tables to avoid future upgrade problems. Be aware that if you have
custom tables in the GitLab database these tables and all data will be
removed.
MSG
+ puts warning.color(:red)
ask_to_continue
puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.color(:yellow)
sleep(5)
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 12d6ac45fb..60f4636e73 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -356,97 +356,108 @@ namespace :gitlab do
########################
def check_repo_base_exists
- print "Repo base directory exists? ... "
+ puts "Repo base directory exists?"
- repo_base_path = Gitlab.config.gitlab_shell.repos_path
+ Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ print "#{name}... "
- if File.exists?(repo_base_path)
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- puts "#{repo_base_path} is missing".color(:red)
- try_fixing_it(
- "This should have been created when setting up GitLab Shell.",
- "Make sure it's set correctly in config/gitlab.yml",
- "Make sure GitLab Shell is installed correctly."
- )
- for_more_information(
- see_installation_guide_section "GitLab Shell"
- )
- fix_and_rerun
+ if File.exists?(repo_base_path)
+ puts "yes".color(:green)
+ else
+ puts "no".color(:red)
+ puts "#{repo_base_path} is missing".color(:red)
+ try_fixing_it(
+ "This should have been created when setting up GitLab Shell.",
+ "Make sure it's set correctly in config/gitlab.yml",
+ "Make sure GitLab Shell is installed correctly."
+ )
+ for_more_information(
+ see_installation_guide_section "GitLab Shell"
+ )
+ fix_and_rerun
+ end
end
end
def check_repo_base_is_not_symlink
- print "Repo base directory is a symlink? ... "
+ puts "Repo storage directories are symlinks?"
- repo_base_path = Gitlab.config.gitlab_shell.repos_path
- unless File.exists?(repo_base_path)
- puts "can't check because of previous errors".color(:magenta)
- return
- end
+ Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ print "#{name}... "
- unless File.symlink?(repo_base_path)
- puts "no".color(:green)
- else
- puts "yes".color(:red)
- try_fixing_it(
- "Make sure it's set to the real directory in config/gitlab.yml"
- )
- fix_and_rerun
+ unless File.exists?(repo_base_path)
+ puts "can't check because of previous errors".color(:magenta)
+ return
+ end
+
+ unless File.symlink?(repo_base_path)
+ puts "no".color(:green)
+ else
+ puts "yes".color(:red)
+ try_fixing_it(
+ "Make sure it's set to the real directory in config/gitlab.yml"
+ )
+ fix_and_rerun
+ end
end
end
def check_repo_base_permissions
- print "Repo base access is drwxrws---? ... "
+ puts "Repo paths access is drwxrws---?"
- repo_base_path = Gitlab.config.gitlab_shell.repos_path
- unless File.exists?(repo_base_path)
- puts "can't check because of previous errors".color(:magenta)
- return
- end
+ Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ print "#{name}... "
- if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770")
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- try_fixing_it(
- "sudo chmod -R ug+rwX,o-rwx #{repo_base_path}",
- "sudo chmod -R ug-s #{repo_base_path}",
- "sudo find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s"
- )
- for_more_information(
- see_installation_guide_section "GitLab Shell"
- )
- fix_and_rerun
+ unless File.exists?(repo_base_path)
+ puts "can't check because of previous errors".color(:magenta)
+ return
+ end
+
+ if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770")
+ puts "yes".color(:green)
+ else
+ puts "no".color(:red)
+ try_fixing_it(
+ "sudo chmod -R ug+rwX,o-rwx #{repo_base_path}",
+ "sudo chmod -R ug-s #{repo_base_path}",
+ "sudo find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s"
+ )
+ for_more_information(
+ see_installation_guide_section "GitLab Shell"
+ )
+ fix_and_rerun
+ end
end
end
def check_repo_base_user_and_group
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
- print "Repo base owned by #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group}? ... "
+ puts "Repo paths owned by #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group}?"
- repo_base_path = Gitlab.config.gitlab_shell.repos_path
- unless File.exists?(repo_base_path)
- puts "can't check because of previous errors".color(:magenta)
- return
- end
+ Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ print "#{name}... "
- uid = uid_for(gitlab_shell_ssh_user)
- gid = gid_for(gitlab_shell_owner_group)
- if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".color(:blue)
- try_fixing_it(
- "sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}"
- )
- for_more_information(
- see_installation_guide_section "GitLab Shell"
- )
- fix_and_rerun
+ unless File.exists?(repo_base_path)
+ puts "can't check because of previous errors".color(:magenta)
+ return
+ end
+
+ uid = uid_for(gitlab_shell_ssh_user)
+ gid = gid_for(gitlab_shell_owner_group)
+ if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid
+ puts "yes".color(:green)
+ else
+ puts "no".color(:red)
+ puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".color(:blue)
+ try_fixing_it(
+ "sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}"
+ )
+ for_more_information(
+ see_installation_guide_section "GitLab Shell"
+ )
+ fix_and_rerun
+ end
end
end
@@ -473,7 +484,7 @@ namespace :gitlab do
else
puts "wrong or missing hooks".color(:red)
try_fixing_it(
- sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')}"),
+ sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')} #{repository_storage_paths_args.join(' ')}"),
'Check the hooks_path in config/gitlab.yml',
'Check your gitlab-shell installation'
)
@@ -773,7 +784,7 @@ namespace :gitlab do
servers.each do |server|
puts "Server: #{server}"
Gitlab::LDAP::Adapter.open(server) do |adapter|
- users = adapter.users(adapter.config.uid, '*', 100)
+ users = adapter.users(adapter.config.uid, '*', limit)
users.each do |user|
puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
end
@@ -785,13 +796,13 @@ namespace :gitlab do
namespace :repo do
desc "GitLab | Check the integrity of the repositories managed by GitLab"
task check: :environment do
- namespace_dirs = Dir.glob(
- File.join(Gitlab.config.gitlab_shell.repos_path, '*')
- )
+ Gitlab.config.repositories.storages.each do |name, path|
+ namespace_dirs = Dir.glob(File.join(path, '*'))
- namespace_dirs.each do |namespace_dir|
- repo_dirs = Dir.glob(File.join(namespace_dir, '*'))
- repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
+ namespace_dirs.each do |namespace_dir|
+ repo_dirs = Dir.glob(File.join(namespace_dir, '*'))
+ repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
+ end
end
end
end
@@ -799,12 +810,12 @@ namespace :gitlab do
namespace :user do
desc "GitLab | Check the integrity of a specific user's repositories"
task :check_repos, [:username] => :environment do |t, args|
- username = args[:username] || prompt("Check repository integrity for which username? ".color(:blue))
+ username = args[:username] || prompt("Check repository integrity for fsername? ".color(:blue))
user = User.find_by(username: username)
if user
repo_dirs = user.authorized_projects.map do |p|
File.join(
- Gitlab.config.gitlab_shell.repos_path,
+ p.repository_storage_path,
"#{p.path_with_namespace}.git"
)
end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index ab0028d660..b7cbdc6cd7 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -5,36 +5,36 @@ namespace :gitlab do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
-
namespaces = Namespace.pluck(:path)
- git_base_path = Gitlab.config.gitlab_shell.repos_path
- all_dirs = Dir.glob(git_base_path + '/*')
+ Gitlab.config.repositories.storages.each do |name, git_base_path|
+ all_dirs = Dir.glob(git_base_path + '/*')
- puts git_base_path.color(:yellow)
- puts "Looking for directories to remove... "
+ puts git_base_path.color(:yellow)
+ puts "Looking for directories to remove... "
- all_dirs.reject! do |dir|
- # skip if git repo
- dir =~ /.git$/
- end
+ all_dirs.reject! do |dir|
+ # skip if git repo
+ dir =~ /.git$/
+ end
- all_dirs.reject! do |dir|
- dir_name = File.basename dir
+ all_dirs.reject! do |dir|
+ dir_name = File.basename dir
- # skip if namespace present
- namespaces.include?(dir_name)
- end
+ # skip if namespace present
+ namespaces.include?(dir_name)
+ end
- all_dirs.each do |dir_path|
+ all_dirs.each do |dir_path|
- if remove_flag
- if FileUtils.rm_rf dir_path
- puts "Removed...#{dir_path}".color(:red)
+ if remove_flag
+ if FileUtils.rm_rf dir_path
+ puts "Removed...#{dir_path}".color(:red)
+ else
+ puts "Cannot remove #{dir_path}".color(:red)
+ end
else
- puts "Cannot remove #{dir_path}".color(:red)
+ puts "Can be removed: #{dir_path}".color(:red)
end
- else
- puts "Can be removed: #{dir_path}".color(:red)
end
end
@@ -48,20 +48,21 @@ namespace :gitlab do
warn_user_is_not_gitlab
move_suffix = "+orphaned+#{Time.now.to_i}"
- repo_root = Gitlab.config.gitlab_shell.repos_path
- # Look for global repos (legacy, depth 1) and normal repos (depth 2)
- IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
- find.each_line do |path|
- path.chomp!
- repo_with_namespace = path.
- sub(repo_root, '').
- sub(%r{^/*}, '').
- chomp('.git').
- chomp('.wiki')
- next if Project.find_with_namespace(repo_with_namespace)
- new_path = path + move_suffix
- puts path.inspect + ' -> ' + new_path.inspect
- File.rename(path, new_path)
+ Gitlab.config.repositories.storages.each do |name, repo_root|
+ # Look for global repos (legacy, depth 1) and normal repos (depth 2)
+ IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
+ find.each_line do |path|
+ path.chomp!
+ repo_with_namespace = path.
+ sub(repo_root, '').
+ sub(%r{^/*}, '').
+ chomp('.git').
+ chomp('.wiki')
+ next if Project.find_with_namespace(repo_with_namespace)
+ new_path = path + move_suffix
+ puts path.inspect + ' -> ' + new_path.inspect
+ File.rename(path, new_path)
+ end
end
end
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 7230b9485b..7c96bc864c 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -25,6 +25,10 @@ namespace :gitlab do
desc 'Drop all tables'
task :drop_tables => :environment do
connection = ActiveRecord::Base.connection
+
+ # If MySQL, turn off foreign key checks
+ connection.execute('SET FOREIGN_KEY_CHECKS=0') if Gitlab::Database.mysql?
+
tables = connection.tables
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
@@ -35,6 +39,9 @@ namespace :gitlab do
# MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
# Add `IF EXISTS` because cascade could have already deleted a table.
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
+
+ # If MySQL, re-enable foreign key checks
+ connection.execute('SET FOREIGN_KEY_CHECKS=1') if Gitlab::Database.mysql?
end
desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
@@ -46,5 +53,20 @@ namespace :gitlab do
Rake::Task['db:seed_fu'].invoke
end
end
+
+ desc 'Checks if migrations require downtime or not'
+ task :downtime_check, [:ref] => :environment do |_, args|
+ abort 'You must specify a Git reference to compare with' unless args[:ref]
+
+ require 'shellwords'
+
+ ref = Shellwords.escape(args[:ref])
+
+ migrations = `git diff #{ref}.. --name-only -- db/migrate`.lines.
+ map { |file| Rails.root.join(file.strip).to_s }.
+ select { |file| File.file?(file) }
+
+ Gitlab::DowntimeCheck.new.check_and_print(migrations)
+ end
end
end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 4753f00c26..dbdd4e977e 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -2,73 +2,73 @@ namespace :gitlab do
namespace :import do
# How to use:
#
- # 1. copy the bare repos under the repos_path (commonly /home/git/repositories)
+ # 1. copy the bare repos under the repository storage paths (commonly the default path is /home/git/repositories)
# 2. run: bundle exec rake gitlab:import:repos RAILS_ENV=production
#
# Notes:
# * The project owner will set to the first administator of the system
# * Existing projects will be skipped
#
- desc "GitLab | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance"
+ desc "GitLab | Import bare repositories from repositories -> storages into GitLab project instance"
task repos: :environment do
+ Gitlab.config.repositories.storages.each do |name, git_base_path|
+ repos_to_import = Dir.glob(git_base_path + '/**/*.git')
- git_base_path = Gitlab.config.gitlab_shell.repos_path
- repos_to_import = Dir.glob(git_base_path + '/**/*.git')
+ repos_to_import.each do |repo_path|
+ # strip repo base path
+ repo_path[0..git_base_path.length] = ''
- repos_to_import.each do |repo_path|
- # strip repo base path
- repo_path[0..git_base_path.length] = ''
+ path = repo_path.sub(/\.git$/, '')
+ group_name, name = File.split(path)
+ group_name = nil if group_name == '.'
- path = repo_path.sub(/\.git$/, '')
- group_name, name = File.split(path)
- group_name = nil if group_name == '.'
+ puts "Processing #{repo_path}".color(:yellow)
- puts "Processing #{repo_path}".color(:yellow)
-
- if path.end_with?('.wiki')
- puts " * Skipping wiki repo"
- next
- end
-
- project = Project.find_with_namespace(path)
-
- if project
- puts " * #{project.name} (#{repo_path}) exists"
- else
- user = User.admins.reorder("id").first
-
- project_params = {
- name: name,
- path: name
- }
-
- # find group namespace
- if group_name
- group = Namespace.find_by(path: group_name)
- # create group namespace
- unless group
- group = Group.new(:name => group_name)
- group.path = group_name
- group.owner = user
- if group.save
- puts " * Created Group #{group.name} (#{group.id})".color(:green)
- else
- puts " * Failed trying to create group #{group.name}".color(:red)
- end
- end
- # set project group
- project_params[:namespace_id] = group.id
+ if path.end_with?('.wiki')
+ puts " * Skipping wiki repo"
+ next
end
- project = Projects::CreateService.new(user, project_params).execute
+ project = Project.find_with_namespace(path)
- if project.persisted?
- puts " * Created #{project.name} (#{repo_path})".color(:green)
- project.update_repository_size
- project.update_commit_count
+ if project
+ puts " * #{project.name} (#{repo_path}) exists"
else
- puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red)
- puts " Errors: #{project.errors.messages}".color(:red)
+ user = User.admins.reorder("id").first
+
+ project_params = {
+ name: name,
+ path: name
+ }
+
+ # find group namespace
+ if group_name
+ group = Namespace.find_by(path: group_name)
+ # create group namespace
+ unless group
+ group = Group.new(:name => group_name)
+ group.path = group_name
+ group.owner = user
+ if group.save
+ puts " * Created Group #{group.name} (#{group.id})".color(:green)
+ else
+ puts " * Failed trying to create group #{group.name}".color(:red)
+ end
+ end
+ # set project group
+ project_params[:namespace_id] = group.id
+ end
+
+ project = Projects::CreateService.new(user, project_params).execute
+
+ if project.persisted?
+ puts " * Created #{project.name} (#{repo_path})".color(:green)
+ project.update_repository_size
+ project.update_commit_count
+ else
+ puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red)
+ puts " Errors: #{project.errors.messages}".color(:red)
+ end
end
end
end
diff --git a/lib/tasks/gitlab/import_export.rake b/lib/tasks/gitlab/import_export.rake
new file mode 100644
index 0000000000..c2c6031db6
--- /dev/null
+++ b/lib/tasks/gitlab/import_export.rake
@@ -0,0 +1,13 @@
+namespace :gitlab do
+ namespace :import_export do
+ desc "GitLab | Show Import/Export version"
+ task version: :environment do
+ puts "Import/Export v#{Gitlab::ImportExport.version}"
+ end
+
+ desc "GitLab | Display exported DB structure"
+ task data: :environment do
+ puts YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].to_yaml(:SortKeys => true)
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index 352b566df2..fe43d40e6d 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -62,7 +62,10 @@ namespace :gitlab do
puts ""
puts "GitLab Shell".color(:yellow)
puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}"
- puts "Repositories:\t#{Gitlab.config.gitlab_shell.repos_path}"
+ puts "Repository storage paths:"
+ Gitlab.config.repositories.storages.each do |name, path|
+ puts "- #{name}: \t#{path}"
+ end
puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
diff --git a/lib/tasks/gitlab/list_repos.rake b/lib/tasks/gitlab/list_repos.rake
index c7596e7abc..ffcc76e549 100644
--- a/lib/tasks/gitlab/list_repos.rake
+++ b/lib/tasks/gitlab/list_repos.rake
@@ -9,7 +9,7 @@ namespace :gitlab do
scope = scope.where('id IN (?) OR namespace_id in (?)', project_ids, namespace_ids)
end
scope.find_each do |project|
- base = File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace)
+ base = File.join(project.repository_storage_path, project.path_with_namespace)
puts base + '.git'
puts base + '.wiki.git'
end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index b1648a4602..c85ebdf861 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -12,7 +12,6 @@ namespace :gitlab do
gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url
gitlab_url += '/' unless gitlab_url.end_with?('/')
- repos_path = Gitlab.config.gitlab_shell.repos_path
target_dir = Gitlab.config.gitlab_shell.path
# Clone if needed
@@ -35,7 +34,6 @@ namespace :gitlab do
user: user,
gitlab_url: gitlab_url,
http_settings: {self_signed_cert: false}.stringify_keys,
- repos_path: repos_path,
auth_file: File.join(home_dir, ".ssh", "authorized_keys"),
redis: {
bin: %x{which redis-cli}.chomp,
@@ -58,10 +56,10 @@ namespace :gitlab do
File.open("config.yml", "w+") {|f| f.puts config.to_yaml}
# Launch installation process
- system(*%W(bin/install))
+ system(*%W(bin/install) + repository_storage_paths_args)
# (Re)create hooks
- system(*%W(bin/create-hooks))
+ system(*%W(bin/create-hooks) + repository_storage_paths_args)
end
# Required for debian packaging with PKGR: Setup .ssh/environment with
@@ -73,6 +71,8 @@ namespace :gitlab do
File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f|
f.puts "PATH=#{ENV['PATH']}"
end
+
+ Gitlab::Shell.new.generate_and_link_secret_token
end
desc "GitLab | Setup gitlab-shell"
@@ -87,7 +87,8 @@ namespace :gitlab do
if File.exists?(path_to_repo)
print '-'
else
- if Gitlab::Shell.new.add_repository(project.path_with_namespace)
+ if Gitlab::Shell.new.add_repository(project.repository_storage_path,
+ project.path_with_namespace)
print '.'
else
print 'F'
@@ -138,4 +139,3 @@ namespace :gitlab do
system(*%W(#{Gitlab.config.git.bin_path} reset --hard #{tag}))
end
end
-
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index d0c019044b..ab96b1d359 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -125,10 +125,16 @@ namespace :gitlab do
end
def all_repos
- IO.popen(%W(find #{Gitlab.config.gitlab_shell.repos_path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
- find.each_line do |path|
- yield path.chomp
+ Gitlab.config.repositories.storages.each do |name, path|
+ IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
+ find.each_line do |path|
+ yield path.chomp
+ end
end
end
end
+
+ def repository_storage_paths_args
+ Gitlab.config.repositories.storages.values
+ end
end
diff --git a/lib/tasks/gitlab/track_deployment.rake b/lib/tasks/gitlab/track_deployment.rake
new file mode 100644
index 0000000000..84aa2e8507
--- /dev/null
+++ b/lib/tasks/gitlab/track_deployment.rake
@@ -0,0 +1,9 @@
+namespace :gitlab do
+ desc 'GitLab | Tracks a deployment in GitLab Performance Monitoring'
+ task track_deployment: :environment do
+ metric = Gitlab::Metrics::Metric.
+ new('deployments', version: Gitlab::VERSION)
+
+ Gitlab::Metrics.submit_metrics([metric.to_hash])
+ end
+end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index c5666d49e6..21c0e5f1d4 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -6,8 +6,6 @@ task :test do
end
unless Rails.env.production?
- require 'coveralls/rake/task'
- Coveralls::RakeTask.new
desc "GitLab | Run all tests on CI with simplecov"
- task :test_ci => [:rubocop, :brakeman, 'teaspoon', :spinach, :spec, 'coveralls:push']
+ task test_ci: [:rubocop, :brakeman, 'teaspoon', :spinach, :spec]
end
diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb
index d4291f012d..41dee5fdc0 100644
--- a/lib/uploaded_file.rb
+++ b/lib/uploaded_file.rb
@@ -3,7 +3,6 @@ require "fileutils"
# Taken from: Rack::Test::UploadedFile
class UploadedFile
-
# The filename, *not* including the path, of the "uploaded" file
attr_reader :original_filename
diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png
index 7da5f23ed9..05c8b0d0cc 100644
Binary files a/public/apple-touch-icon-precomposed.png and b/public/apple-touch-icon-precomposed.png differ
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
index 7da5f23ed9..05c8b0d0cc 100644
Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ
diff --git a/rubocop/cop/migration/add_index.rb b/rubocop/cop/migration/add_index.rb
new file mode 100644
index 0000000000..d9247a1f7e
--- /dev/null
+++ b/rubocop/cop/migration/add_index.rb
@@ -0,0 +1,46 @@
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if indexes are added in a concurrent manner.
+ class AddIndex < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = 'add_index requires downtime, use add_concurrent_index instead'
+
+ def on_def(node)
+ return unless in_migration?(node)
+
+ new_tables = []
+
+ node.each_descendant(:send) do |send_node|
+ first_arg = first_argument(send_node)
+
+ # The first argument of "create_table" / "add_index" is the table
+ # name.
+ new_tables << first_arg if create_table?(send_node)
+
+ next if method_name(send_node) != :add_index
+
+ # Using "add_index" is fine for newly created tables as there's no
+ # data in these tables yet.
+ next if new_tables.include?(first_arg)
+
+ add_offense(send_node, :selector)
+ end
+ end
+
+ def create_table?(node)
+ method_name(node) == :create_table
+ end
+
+ def method_name(node)
+ node.children[1]
+ end
+
+ def first_argument(node)
+ node.children[2] ? node.children[0] : nil
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/migration/column_with_default.rb b/rubocop/cop/migration/column_with_default.rb
new file mode 100644
index 0000000000..97ee8b1104
--- /dev/null
+++ b/rubocop/cop/migration/column_with_default.rb
@@ -0,0 +1,50 @@
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if columns are added in a way that doesn't require
+ # downtime.
+ class ColumnWithDefault < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ WHITELISTED_TABLES = [:application_settings]
+
+ MSG = 'add_column with a default value requires downtime, ' \
+ 'use add_column_with_default instead'
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ return unless name == :add_column
+
+ # Ignore whitelisted tables.
+ return if table_whitelisted?(node.children[2])
+
+ opts = node.children.last
+
+ return unless opts && opts.type == :hash
+
+ opts.each_node(:pair) do |pair|
+ if hash_key_type(pair) == :sym && hash_key_name(pair) == :default
+ add_offense(node, :selector)
+ end
+ end
+ end
+
+ def table_whitelisted?(symbol)
+ symbol && symbol.type == :sym &&
+ WHITELISTED_TABLES.include?(symbol.children[0])
+ end
+
+ def hash_key_type(pair)
+ pair.children[0].type
+ end
+
+ def hash_key_name(pair)
+ pair.children[0].children[0]
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/migration_helpers.rb b/rubocop/migration_helpers.rb
new file mode 100644
index 0000000000..3160a784a0
--- /dev/null
+++ b/rubocop/migration_helpers.rb
@@ -0,0 +1,10 @@
+module RuboCop
+ # Module containing helper methods for writing migration cops.
+ module MigrationHelpers
+ # Returns true if the given node originated from the db/migrate directory.
+ def in_migration?(node)
+ File.dirname(node.location.expression.source_buffer.name).
+ end_with?('db/migrate')
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
new file mode 100644
index 0000000000..7922e19768
--- /dev/null
+++ b/rubocop/rubocop.rb
@@ -0,0 +1,3 @@
+require_relative 'migration_helpers'
+require_relative 'cop/migration/add_index'
+require_relative 'cop/migration/column_with_default'
diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb
index eb82476b17..d5f0b289b5 100644
--- a/spec/controllers/admin/impersonations_controller_spec.rb
+++ b/spec/controllers/admin/impersonations_controller_spec.rb
@@ -22,7 +22,7 @@ describe Admin::ImpersonationsController do
it "responds with status 404" do
delete :destroy
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
it "doesn't sign us in" do
@@ -46,7 +46,7 @@ describe Admin::ImpersonationsController do
it "responds with status 404" do
delete :destroy
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
it "doesn't sign us in as the impersonator" do
@@ -65,7 +65,7 @@ describe Admin::ImpersonationsController do
it "responds with status 404" do
delete :destroy
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
it "doesn't sign us in as the impersonator" do
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index 4cb8b8da15..8eaacef202 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -11,12 +11,12 @@ describe Admin::ProjectsController do
render_views
it 'retrieves the project for the given visibility level' do
- get :index, visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]
+ get :index, visibility_level: [Gitlab::VisibilityLevel::PUBLIC]
expect(response.body).to match(project.name)
end
it 'does not retrieve the project' do
- get :index, visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]
+ get :index, visibility_level: [Gitlab::VisibilityLevel::INTERNAL]
expect(response.body).not_to match(project.name)
end
end
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
index b51b303a71..520a4f6f9c 100644
--- a/spec/controllers/admin/spam_logs_controller_spec.rb
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -14,7 +14,7 @@ describe Admin::SpamLogsController do
it 'lists all spam logs' do
get :index
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -22,14 +22,14 @@ describe Admin::SpamLogsController do
it 'removes only the spam log when removing log' do
expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1)
expect(User.find(user.id)).to be_truthy
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
it 'removes user and his spam logs when removing the user' do
delete :destroy, id: first_spam.id, remove_user: true
expect(flash[:notice]).to eq "User #{user.username} was successfully removed."
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(SpamLog.count).to eq(0)
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 6caf37ddc2..ab9aa65f7b 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -17,7 +17,7 @@ describe Admin::UsersController do
it 'deletes user' do
delete :destroy, id: user.username, format: :json
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound)
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index ff5b391627..8bd210cbc3 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -44,17 +44,16 @@ describe ApplicationController do
context "when the 'private_token' param is populated with the private token" do
it "logs the user in" do
get :index, private_token: user.private_token
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response.body).to eq("authenticated")
end
end
-
context "when the 'PRIVATE-TOKEN' header is populated with the private token" do
it "logs the user in" do
@request.headers['PRIVATE-TOKEN'] = user.private_token
get :index
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response.body).to eq("authenticated")
end
end
@@ -80,7 +79,7 @@ describe ApplicationController do
context "when the 'personal_access_token' param is populated with the personal access token" do
it "logs the user in" do
get :index, private_token: personal_access_token.token
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response.body).to eq('authenticated')
end
end
@@ -89,7 +88,7 @@ describe ApplicationController do
it "logs the user in" do
@request.headers["PRIVATE-TOKEN"] = personal_access_token.token
get :index
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response.body).to eq('authenticated')
end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 28cf804c1b..60c654f622 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -29,7 +29,7 @@ describe AutocompleteController do
get(:users, project_id: 'unknown')
end
- it { expect(response.status).to eq(404) }
+ it { expect(response).to have_http_status(404) }
end
end
@@ -58,7 +58,7 @@ describe AutocompleteController do
get(:users, group_id: 'unknown')
end
- it { expect(response.status).to eq(404) }
+ it { expect(response).to have_http_status(404) }
end
end
@@ -114,7 +114,7 @@ describe AutocompleteController do
get(:users, project_id: project.id)
end
- it { expect(response.status).to eq(404) }
+ it { expect(response).to have_http_status(404) }
end
describe 'GET #users with unknown project' do
@@ -122,7 +122,7 @@ describe AutocompleteController do
get(:users, project_id: 'unknown')
end
- it { expect(response.status).to eq(404) }
+ it { expect(response).to have_http_status(404) }
end
describe 'GET #users with inaccessible group' do
@@ -131,7 +131,7 @@ describe AutocompleteController do
get(:users, group_id: user.namespace.id)
end
- it { expect(response.status).to eq(404) }
+ it { expect(response).to have_http_status(404) }
end
describe 'GET #users with no project' do
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
deleted file mode 100644
index cf5c606c72..0000000000
--- a/spec/controllers/commit_controller_spec.rb
+++ /dev/null
@@ -1,246 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CommitController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:commit) { project.commit("master") }
- let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
- let(:master_pickable_commit) { project.commit(master_pickable_sha) }
-
- before do
- sign_in(user)
- project.team << [user, :master]
- end
-
- describe "#show" do
- shared_examples "export as" do |format|
- it "should generally work" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response).to be_success
- end
-
- it "should generate it" do
- expect_any_instance_of(Commit).to receive(:"to_#{format}")
-
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
- end
-
- it "should render it" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
-
- expect(response.body).to eq(commit.send(:"to_#{format}"))
- end
-
- it "should not escape Html" do
- allow_any_instance_of(Commit).to receive(:"to_#{format}").
- and_return('HTML entities &<>" ')
-
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
-
- expect(response.body).not_to include('&')
- expect(response.body).not_to include('>')
- expect(response.body).not_to include('<')
- expect(response.body).not_to include('"')
- end
- end
-
- describe "as diff" do
- include_examples "export as", :diff
- let(:format) { :diff }
-
- it "should really only be a git diff" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to start_with("diff --git")
- end
-
- it "should really only be a git diff without whitespace changes" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: '66eceea0db202bb39c4e445e8ca28689645366c5',
- # id: commit.id,
- format: format,
- w: 1)
-
- expect(response.body).to start_with("diff --git")
- # without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs).first.diff.split("\n")
- expect(diff_splits.length).to be <= 2
- end
- end
-
- describe "as patch" do
- include_examples "export as", :patch
- let(:format) { :patch }
-
- it "should really be a git email patch" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to start_with("From #{commit.id}")
- end
-
- it "should contain a git diff" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to match(/^diff --git/)
- end
- end
-
- context 'commit that removes a submodule' do
- render_views
-
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:commit) { fork_project.commit('remove-submodule') }
-
- before do
- fork_project.team << [user, :master]
- end
-
- it 'renders it' do
- get(:show,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project.to_param,
- id: commit.id)
-
- expect(response).to be_success
- end
- end
- end
-
- describe "#branches" do
- it "contains branch and tags information" do
- get(:branches,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id)
-
- expect(assigns(:branches)).to include("master", "feature_conflict")
- expect(assigns(:tags)).to include("v1.1.0")
- end
- end
-
- describe '#revert' do
- context 'when target branch is not provided' do
- it 'should render the 404 page' do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id)
-
- expect(response).not_to be_success
- expect(response.status).to eq(404)
- end
- end
-
- context 'when the revert was successful' do
- it 'should redirect to the commits page' do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
-
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
- expect(flash[:notice]).to eq('The commit has been successfully reverted.')
- end
- end
-
- context 'when the revert failed' do
- before do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
- end
-
- it 'should redirect to the commit page' do
- # Reverting a commit that has been already reverted.
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
-
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
- expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
- end
- end
- end
-
- describe '#cherry_pick' do
- context 'when target branch is not provided' do
- it 'should render the 404 page' do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: master_pickable_commit.id)
-
- expect(response).not_to be_success
- expect(response.status).to eq(404)
- end
- end
-
- context 'when the cherry-pick was successful' do
- it 'should redirect to the commits page' do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
-
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
- expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
- end
- end
-
- context 'when the cherry_pick failed' do
- before do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
- end
-
- it 'should redirect to the commit page' do
- # Cherry-picking a commit that has been already cherry-picked.
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
-
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
- expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
- end
- end
- end
-end
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 89c2c26a36..c34475976c 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -13,7 +13,7 @@ describe Groups::GroupMembersController do
it 'renders index with group members' do
get :index, group_id: group
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response).to render_template(:index)
end
end
@@ -26,7 +26,7 @@ describe Groups::GroupMembersController do
delete :destroy, group_id: group,
id: 42
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
end
end
@@ -48,7 +48,7 @@ describe Groups::GroupMembersController do
delete :destroy, group_id: group,
id: member
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
expect(group.users).to include group_user
end
end
@@ -89,7 +89,7 @@ describe Groups::GroupMembersController do
it 'returns 403' do
delete :leave, group_id: group
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
end
end
@@ -118,9 +118,7 @@ describe Groups::GroupMembersController do
it 'cannot removes himself from the group' do
delete :leave, group_id: group
- expect(response).to redirect_to(group_path(group))
- expect(response).to set_flash[:alert].to "You can not leave the \"#{group.name}\" group. Transfer or delete the group."
- expect(group.users).to include user
+ expect(response).to have_http_status(403)
end
end
@@ -134,8 +132,8 @@ describe Groups::GroupMembersController do
delete :leave, group_id: group
expect(response).to set_flash.to 'Your access request to the group has been withdrawn.'
- expect(response).to redirect_to(dashboard_groups_path)
- expect(group.members.request).to be_empty
+ expect(response).to redirect_to(group_path(group))
+ expect(group.requesters).to be_empty
expect(group.users).not_to include user
end
end
@@ -155,7 +153,7 @@ describe Groups::GroupMembersController do
expect(response).to set_flash.to 'Your request for access has been queued for review.'
expect(response).to redirect_to(group_path(group))
- expect(group.members.request.exists?(user_id: user)).to be_truthy
+ expect(group.requesters.exists?(user_id: user)).to be_truthy
expect(group.users).not_to include user
end
end
@@ -168,7 +166,7 @@ describe Groups::GroupMembersController do
post :approve_access_request, group_id: group,
id: 42
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
end
end
@@ -177,7 +175,7 @@ describe Groups::GroupMembersController do
let(:group_requester) { create(:user) }
let(:member) do
group.request_access(group_requester)
- group.members.request.find_by(user_id: group_requester)
+ group.requesters.find_by(user_id: group_requester)
end
context 'when user does not have enough rights' do
@@ -190,7 +188,7 @@ describe Groups::GroupMembersController do
post :approve_access_request, group_id: group,
id: member
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
expect(group.users).not_to include group_requester
end
end
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 0d8a68bb51..56ecf2bb64 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -65,21 +65,21 @@ describe HealthCheckController do
it 'supports passing the token in the header' do
request.headers['TOKEN'] = token
get :index
- expect(response.status).to eq(500)
+ expect(response).to have_http_status(500)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
it 'supports failure plaintest response' do
get :index, token: token
- expect(response.status).to eq(500)
+ expect(response).to have_http_status(500)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
it 'supports failure json response' do
get :index, token: token, format: :json
- expect(response.status).to eq(500)
+ expect(response).to have_http_status(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('The server is on fire')
@@ -87,7 +87,7 @@ describe HealthCheckController do
it 'supports failure xml response' do
get :index, token: token, format: :xml
- expect(response.status).to eq(500)
+ expect(response).to have_http_status(500)
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be false
expect(xml_response['message']).to include('The server is on fire')
@@ -95,7 +95,7 @@ describe HealthCheckController do
it 'supports failure responses for specific checks' do
get :index, token: token, checks: 'email', format: :json
- expect(response.status).to eq(500)
+ expect(response).to have_http_status(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('Email is on fire')
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 1f7fd51734..33c75e7584 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -11,7 +11,7 @@ describe HelpController do
context 'for Markdown formats' do
context 'when requested file exists' do
before do
- get :show, category: 'ssh', file: 'README', format: :md
+ get :show, path: 'ssh/README', format: :md
end
it 'assigns to @markdown' do
@@ -26,7 +26,7 @@ describe HelpController do
context 'when requested file is missing' do
it 'renders not found' do
- get :show, category: 'foo', file: 'bar', format: :md
+ get :show, path: 'foo/bar', format: :md
expect(response).to be_not_found
end
end
@@ -36,8 +36,7 @@ describe HelpController do
context 'when requested file exists' do
it 'renders the raw file' do
get :show,
- category: 'workflow/protected_branches',
- file: 'protected_branches1',
+ path: 'user/project/img/labels_filter',
format: :png
expect(response).to be_success
expect(response.content_type).to eq 'image/png'
@@ -48,8 +47,7 @@ describe HelpController do
context 'when requested file is missing' do
it 'renders not found' do
get :show,
- category: 'foo',
- file: 'bar',
+ path: 'foo/bar',
format: :png
expect(response).to be_not_found
end
@@ -59,11 +57,19 @@ describe HelpController do
context 'for other formats' do
it 'always renders not found' do
get :show,
- category: 'ssh',
- file: 'README',
+ path: 'ssh/README',
format: :foo
expect(response).to be_not_found
end
end
end
+
+ describe 'GET #ui' do
+ context 'for UI Development Kit' do
+ it 'renders found' do
+ get :ui
+ expect(response).to have_http_status(200)
+ end
+ end
+ end
end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index c55a3c2820..51d5952685 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -16,6 +16,24 @@ describe Import::GithubController do
allow(controller).to receive(:github_import_enabled?).and_return(true)
end
+ describe "GET new" do
+ it "redirects to GitHub for an access token if logged in with GitHub" do
+ allow(controller).to receive(:logged_in_with_github?).and_return(true)
+ expect(controller).to receive(:go_to_github_for_permissions)
+
+ get :new
+ end
+
+ it "redirects to status if we already have a token" do
+ assign_session_token
+ allow(controller).to receive(:logged_in_with_github?).and_return(false)
+
+ get :new
+
+ expect(controller).to redirect_to(status_import_github_url)
+ end
+ end
+
describe "GET callback" do
it "updates access token" do
token = "asdasd12345"
@@ -32,6 +50,20 @@ describe Import::GithubController do
end
end
+ describe "POST personal_access_token" do
+ it "updates access token" do
+ token = "asdfasdf9876"
+
+ allow_any_instance_of(Gitlab::GithubImport::Client).
+ to receive(:user).and_return(true)
+
+ post :personal_access_token, personal_access_token: token
+
+ expect(session[:github_access_token]).to eq(token)
+ expect(controller).to redirect_to(status_import_github_url)
+ end
+ end
+
describe "GET status" do
before do
@repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim')
@@ -59,6 +91,17 @@ describe Import::GithubController do
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([])
end
+
+ it "handles an invalid access token" do
+ allow_any_instance_of(Gitlab::GithubImport::Client).
+ to receive(:repos).and_raise(Octokit::Unauthorized)
+
+ get :status
+
+ expect(session[:github_access_token]).to eq(nil)
+ expect(controller).to redirect_to(new_import_github_url)
+ expect(flash[:alert]).to eq('Access denied to your GitHub account.')
+ end
end
describe "POST create" do
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 3c6e54839b..e478a253b3 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -15,7 +15,7 @@ describe InvitesController do
get :accept, id: token
member.reload
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(member.user).to eq(user)
expect(flash[:notice]).to include 'You have been granted'
end
@@ -26,7 +26,7 @@ describe InvitesController do
get :decline, id: token
expect{member.reload}.to raise_error ActiveRecord::RecordNotFound
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(flash[:notice]).to include 'You have declined the invitation to join'
end
end
diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb
index 27e9afe582..2b334ed117 100644
--- a/spec/controllers/namespaces_controller_spec.rb
+++ b/spec/controllers/namespaces_controller_spec.rb
@@ -86,7 +86,7 @@ describe NamespacesController do
it "responds with status 404" do
get :show, id: group.path
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -102,7 +102,7 @@ describe NamespacesController do
it "responds with status 404" do
get :show, id: "doesntexist"
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb
index 15d155833b..79b819a137 100644
--- a/spec/controllers/notification_settings_controller_spec.rb
+++ b/spec/controllers/notification_settings_controller_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe NotificationSettingsController do
let(:project) { create(:empty_project) }
+ let(:group) { create(:group, :internal) }
let(:user) { create(:user) }
before do
@@ -12,7 +13,7 @@ describe NotificationSettingsController do
context 'when not authorized' do
it 'redirects to sign in page' do
post :create,
- project: { id: project.id },
+ project_id: project.id,
notification_setting: { level: :participating }
expect(response).to redirect_to(new_user_session_path)
@@ -20,33 +21,73 @@ describe NotificationSettingsController do
end
context 'when authorized' do
+ let(:custom_events) do
+ events = {}
+
+ NotificationSetting::EMAIL_EVENTS.each do |event|
+ events[event.to_s] = true
+ end
+
+ events
+ end
+
before do
sign_in(user)
end
- it 'returns success' do
- post :create,
- project: { id: project.id },
- notification_setting: { level: :participating }
+ context 'for projects' do
+ let(:notification_setting) { user.notification_settings_for(project) }
- expect(response.status).to eq 200
- end
-
- context 'and setting custom notification setting' do
- let(:custom_events) do
- events = {}
-
- NotificationSetting::EMAIL_EVENTS.each do |event|
- events[event] = "true"
- end
- end
-
- it 'returns success' do
+ it 'creates notification setting' do
post :create,
- project: { id: project.id },
- notification_setting: { level: :participating, events: custom_events }
+ project_id: project.id,
+ notification_setting: { level: :participating }
expect(response.status).to eq 200
+ expect(notification_setting.level).to eq("participating")
+ expect(notification_setting.user_id).to eq(user.id)
+ expect(notification_setting.source_id).to eq(project.id)
+ expect(notification_setting.source_type).to eq("Project")
+ end
+
+ context 'with custom settings' do
+ it 'creates notification setting' do
+ post :create,
+ project_id: project.id,
+ notification_setting: { level: :custom }.merge(custom_events)
+
+ expect(response.status).to eq 200
+ expect(notification_setting.level).to eq("custom")
+ expect(notification_setting.events).to eq(custom_events)
+ end
+ end
+ end
+
+ context 'for groups' do
+ let(:notification_setting) { user.notification_settings_for(group) }
+
+ it 'creates notification setting' do
+ post :create,
+ namespace_id: group.id,
+ notification_setting: { level: :watch }
+
+ expect(response.status).to eq 200
+ expect(notification_setting.level).to eq("watch")
+ expect(notification_setting.user_id).to eq(user.id)
+ expect(notification_setting.source_id).to eq(group.id)
+ expect(notification_setting.source_type).to eq("Namespace")
+ end
+
+ context 'with custom settings' do
+ it 'creates notification setting' do
+ post :create,
+ namespace_id: group.id,
+ notification_setting: { level: :custom }.merge(custom_events)
+
+ expect(response.status).to eq 200
+ expect(notification_setting.level).to eq("custom")
+ expect(notification_setting.events).to eq(custom_events)
+ end
end
end
end
@@ -57,10 +98,10 @@ describe NotificationSettingsController do
it 'returns 404' do
post :create,
- project: { id: private_project.id },
+ project_id: private_project.id,
notification_setting: { level: :participating }
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -118,7 +159,7 @@ describe NotificationSettingsController do
id: notification_setting,
notification_setting: { level: :participating }
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index af37830489..552899eb36 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -12,7 +12,7 @@ describe Oauth::ApplicationsController do
it 'shows list of applications' do
get :index
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
it 'redirects back to profile page if OAuth applications are disabled' do
@@ -21,7 +21,7 @@ describe Oauth::ApplicationsController do
get :index
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(response).to redirect_to(profile_path)
end
end
diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb
index 4eafc11aba..18148acde3 100644
--- a/spec/controllers/profiles/accounts_controller_spec.rb
+++ b/spec/controllers/profiles/accounts_controller_spec.rb
@@ -1,7 +1,6 @@
require 'spec_helper'
describe Profiles::AccountsController do
-
let(:user) { create(:omniauth_user, provider: 'saml') }
before do
@@ -13,7 +12,7 @@ describe Profiles::AccountsController do
delete :unlink, provider: 'saml'
updated_user = User.find(user.id)
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(updated_user.identities.size).to eq(1)
expect(updated_user.identities).to include(identity)
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
new file mode 100644
index 0000000000..9444a50b1c
--- /dev/null
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -0,0 +1,40 @@
+require 'rails_helper'
+
+describe Projects::BlobController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ user = create(:user)
+ project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ describe 'GET diff' do
+ render_views
+
+ def do_get(opts = {})
+ params = { namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: 'master/CHANGELOG' }
+ get :diff, params.merge(opts)
+ end
+
+ context 'when essential params are missing' do
+ it 'renders nothing' do
+ do_get
+
+ expect(response.body).to be_blank
+ end
+ end
+
+ context 'when essential params are present' do
+ it 'renders the diff content' do
+ do_get(since: 1, to: 5, offset: 10)
+
+ expect(response.body).to be_present
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index c4b4a888b4..644de308c6 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -68,7 +68,6 @@ describe Projects::BranchesController do
let(:branch) { "1-feature-branch" }
let!(:issue) { create(:issue, project: project) }
-
it 'redirects' do
post :create,
namespace_id: project.namespace.to_param,
@@ -89,7 +88,6 @@ describe Projects::BranchesController do
branch_name: branch,
issue_iid: issue.iid
end
-
end
end
@@ -103,7 +101,7 @@ describe Projects::BranchesController do
namespace_id: project.namespace.to_param,
project_id: project.to_param
- expect(response.status).to eq(303)
+ expect(response).to have_http_status(303)
end
end
@@ -121,24 +119,24 @@ describe Projects::BranchesController do
context "valid branch name, valid source" do
let(:branch) { "feature" }
- it { expect(response.status).to eq(200) }
+ it { expect(response).to have_http_status(200) }
end
context "valid branch name with unencoded slashes" do
let(:branch) { "improve/awesome" }
- it { expect(response.status).to eq(200) }
+ it { expect(response).to have_http_status(200) }
end
context "valid branch name with encoded slashes" do
let(:branch) { "improve%2Fawesome" }
- it { expect(response.status).to eq(200) }
+ it { expect(response).to have_http_status(200) }
end
context "invalid branch name, valid ref" do
let(:branch) { "no-branch" }
- it { expect(response.status).to eq(404) }
+ it { expect(response).to have_http_status(404) }
end
end
end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 6e3db10e45..3001d32e71 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -1,9 +1,29 @@
-require 'rails_helper'
+require 'spec_helper'
describe Projects::CommitController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:commit) { project.commit("master") }
+ let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
+ let(:master_pickable_commit) { project.commit(master_pickable_sha) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
describe 'GET show' do
render_views
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :show, params.merge(extra_params)
+ end
+
let(:project) { create(:project) }
before do
@@ -15,7 +35,7 @@ describe Projects::CommitController do
context 'with valid id' do
it 'responds with 200' do
- go id: project.commit.id
+ go(id: commit.id)
expect(response).to be_ok
end
@@ -23,27 +43,274 @@ describe Projects::CommitController do
context 'with invalid id' do
it 'responds with 404' do
- go id: project.commit.id.reverse
+ go(id: commit.id.reverse)
expect(response).to be_not_found
end
end
it 'handles binary files' do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: TestEnv::BRANCH_SHA['binary-encoding'],
- format: "html")
+ go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html')
expect(response).to be_success
end
- def go(id:)
- get :show,
+ shared_examples "export as" do |format|
+ it "should generally work" do
+ go(id: commit.id, format: format)
+
+ expect(response).to be_success
+ end
+
+ it "should generate it" do
+ expect_any_instance_of(Commit).to receive(:"to_#{format}")
+
+ go(id: commit.id, format: format)
+ end
+
+ it "should render it" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to eq(commit.send(:"to_#{format}"))
+ end
+
+ it "should not escape Html" do
+ allow_any_instance_of(Commit).to receive(:"to_#{format}").
+ and_return('HTML entities &<>" ')
+
+ go(id: commit.id, format: format)
+
+ expect(response.body).not_to include('&')
+ expect(response.body).not_to include('>')
+ expect(response.body).not_to include('<')
+ expect(response.body).not_to include('"')
+ end
+ end
+
+ describe "as diff" do
+ include_examples "export as", :diff
+ let(:format) { :diff }
+
+ it "should really only be a git diff" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to start_with("diff --git")
+ end
+
+ it "should really only be a git diff without whitespace changes" do
+ go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1)
+
+ expect(response.body).to start_with("diff --git")
+ # without whitespace option, there are more than 2 diff_splits
+ diff_splits = assigns(:diffs).first.diff.split("\n")
+ expect(diff_splits.length).to be <= 2
+ end
+ end
+
+ describe "as patch" do
+ include_examples "export as", :patch
+ let(:format) { :patch }
+
+ it "should really be a git email patch" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to start_with("From #{commit.id}")
+ end
+
+ it "should contain a git diff" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to match(/^diff --git/)
+ end
+ end
+
+ context 'commit that removes a submodule' do
+ render_views
+
+ let(:fork_project) { create(:forked_project_with_submodules, visibility_level: 20) }
+ let(:commit) { fork_project.commit('remove-submodule') }
+
+ it 'renders it' do
+ get(:show,
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project.to_param,
+ id: commit.id)
+
+ expect(response).to be_success
+ end
+ end
+ end
+
+ describe "GET branches" do
+ it "contains branch and tags information" do
+ get(:branches,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: commit.id)
+
+ expect(assigns(:branches)).to include("master", "feature_conflict")
+ expect(assigns(:tags)).to include("v1.1.0")
+ end
+ end
+
+ describe 'POST revert' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: commit.id)
+
+ expect(response).not_to be_success
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the revert was successful' do
+ it 'should redirect to the commits page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully reverted.')
+ end
+ end
+
+ context 'when the revert failed' do
+ before do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Reverting a commit that has been already reverted.
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
+ end
+ end
+ end
+
+ describe 'POST cherry_pick' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: master_pickable_commit.id)
+
+ expect(response).not_to be_success
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the cherry-pick was successful' do
+ it 'should redirect to the commits page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
+ end
+ end
+
+ context 'when the cherry_pick failed' do
+ before do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Cherry-picking a commit that has been already cherry-picked.
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
+ end
+ end
+ end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: id
+ project_id: project.to_param
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { '.gitmodules' }
+
+ context 'when the commit exists' do
+ context 'when the user has access to the project' do
+ context 'when the path exists in the diff' do
+ it 'enables diff notes' do
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_falsey
+ expect(assigns(:comments_target)).to eq(noteable_type: 'Commit',
+ commit_id: commit.id)
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ before do
+ project.team.truncate
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the commit does not exist' do
+ before { diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
end
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 4018dac95a..4058d5e245 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -64,4 +64,73 @@ describe Projects::CompareController do
expect(assigns(:commits)).to eq(nil)
end
end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { 'files/ruby/feature.rb' }
+
+ context 'when the from and to refs exist' do
+ context 'when the user has access to the project' do
+ context 'when the path exists in the diff' do
+ it 'disables diff notes' do
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ before do
+ project.team.truncate
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the from ref does not exist' do
+ before { diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the to ref does not exist' do
+ before { diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index 70ed8f3a62..f66bcb8099 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -64,9 +64,7 @@ describe Projects::ForksController do
expect(assigns[:forks]).to be_present
end
end
-
end
end
end
-
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index cbaa3e0b7b..7cf09fa4a4 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -14,7 +14,7 @@ describe Projects::IssuesController do
it "returns index" do
get :index, namespace_id: project.namespace.path, project_id: project.path
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
it "return 301 if request path doesn't match project path" do
@@ -28,7 +28,7 @@ describe Projects::IssuesController do
project.save
get :index, namespace_id: project.namespace.path, project_id: project.path
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
it "returns 404 when external issue tracker is enabled" do
@@ -36,7 +36,7 @@ describe Projects::IssuesController do
allow(project).to receive(:default_issues_tracker?).and_return(false)
get :index, namespace_id: project.namespace.path, project_id: project.path
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -248,7 +248,7 @@ describe Projects::IssuesController do
before { sign_in(user) }
it "rejects a developer to destroy an issue" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -262,7 +262,7 @@ describe Projects::IssuesController do
it "deletes the issue" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
end
end
@@ -280,7 +280,7 @@ describe Projects::IssuesController do
project_id: project.path, id: issue.iid, name: "thumbsup")
end.to change { issue.award_emoji.count }.by(1)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index ab1dd34ed5..3492b6ffbb 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -18,7 +18,6 @@ describe Projects::LabelsController do
15.times { |i| create_label(priority: (i % 3) + 1, title: "label #{15 - i}") }
5.times { |i| create_label(title: "label #{100 - i}") }
-
get :index, namespace_id: project.namespace.to_param, project_id: project.to_param
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 4b408c0370..210085e3b1 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -10,7 +10,7 @@ describe Projects::MergeRequestsController do
project.team << [user, :master]
end
- describe '#new' do
+ describe 'GET new' do
context 'merge request that removes a submodule' do
render_views
@@ -34,7 +34,7 @@ describe Projects::MergeRequestsController do
end
end
- describe "#show" do
+ describe "GET show" do
shared_examples "export merge as" do |format|
it "should generally work" do
get(:show,
@@ -96,31 +96,19 @@ describe Projects::MergeRequestsController do
end
describe "as patch" do
- include_examples "export merge as", :patch
- let(:format) { :patch }
-
- it "should really be a git email patch with commit" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid, format: format)
-
- expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}")
- end
-
- it "should contain git diffs" do
+ it 'triggers workhorse to serve the request' do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
- format: format)
+ format: :patch)
- expect(response.body).to match(/^diff --git/)
+ expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-format-patch:")
end
end
end
- describe 'GET #index' do
+ describe 'GET index' do
def get_merge_requests
get :index,
namespace_id: project.namespace.to_param,
@@ -129,7 +117,6 @@ describe Projects::MergeRequestsController do
end
context 'when filtering by opened state' do
-
context 'with opened merge requests' do
it 'should list those merge requests' do
get_merge_requests
@@ -150,11 +137,10 @@ describe Projects::MergeRequestsController do
expect(assigns(:merge_requests)).to include(merge_request)
end
end
-
end
end
- describe 'PUT #update' do
+ describe 'PUT update' do
context 'there is no source project' do
let(:project) { create(:project) }
let(:fork_project) { create(:forked_project_with_submodules) }
@@ -182,7 +168,7 @@ describe Projects::MergeRequestsController do
end
end
- describe 'POST #merge' do
+ describe 'POST merge' do
let(:base_params) do
{
namespace_id: project.namespace.path,
@@ -226,7 +212,7 @@ describe Projects::MergeRequestsController do
context 'when the sha parameter matches the source SHA' do
def merge_with_sha
- post :merge, base_params.merge(sha: merge_request.source_sha)
+ post :merge, base_params.merge(sha: merge_request.diff_head_sha)
end
it 'returns :success' do
@@ -243,11 +229,11 @@ describe Projects::MergeRequestsController do
context 'when merge_when_build_succeeds is passed' do
def merge_when_build_succeeds
- post :merge, base_params.merge(sha: merge_request.source_sha, merge_when_build_succeeds: '1')
+ post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_build_succeeds: '1')
end
before do
- create(:ci_empty_pipeline, project: project, sha: merge_request.source_sha, ref: merge_request.source_branch)
+ create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch)
end
it 'returns :merge_when_build_succeeds' do
@@ -264,15 +250,27 @@ describe Projects::MergeRequestsController do
merge_when_build_succeeds
end
+
+ context 'when project.only_allow_merge_if_build_succeeds? is true' do
+ before do
+ project.update_column(:only_allow_merge_if_build_succeeds, true)
+ end
+
+ it 'returns :merge_when_build_succeeds' do
+ merge_when_build_succeeds
+
+ expect(assigns(:status)).to eq(:merge_when_build_succeeds)
+ end
+ end
end
end
end
- describe "DELETE #destroy" do
+ describe "DELETE destroy" do
it "denies access to users unless they're admin or project owner" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
context "when the user is owner" do
@@ -285,103 +283,217 @@ describe Projects::MergeRequestsController do
it "deletes the merge request" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
end
end
end
describe 'GET diffs' do
- def go(format: 'html')
- get :diffs,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid,
- format: format
- end
-
- context 'as html' do
- it 'renders the diff template' do
- go
-
- expect(response).to render_template('diffs')
- end
- end
-
- context 'as json' do
- it 'renders the diffs template to a string' do
- go format: 'json'
-
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
- end
- end
-
- context 'with forked projects with submodules' do
- render_views
-
- let(:project) { create(:project) }
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
-
- before do
- fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
- fork_project.save
- merge_request.reload
- end
-
- it 'renders' do
- go format: 'json'
-
- expect(response).to be_success
- expect(response.body).to have_content('Subproject commit')
- end
- end
- end
-
- describe 'GET diffs with ignore_whitespace_change' do
- def go(format: 'html')
- get :diffs,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid,
- format: format,
- w: 1
- end
-
- context 'as html' do
- it 'renders the diff template' do
- go
-
- expect(response).to render_template('diffs')
- end
- end
-
- context 'as json' do
- it 'renders the diffs template to a string' do
- go format: 'json'
-
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
- end
- end
- end
-
- describe 'GET diffs with view' do
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid
+ project_id: project.to_param,
+ id: merge_request.iid
}
get :diffs, params.merge(extra_params)
end
- it 'saves the preferred diff view in a cookie' do
- go view: 'parallel'
+ context 'with default params' do
+ context 'as html' do
+ before { go(format: 'html') }
- expect(response.cookies['diff_view']).to eq('parallel')
+ it 'renders the diff template' do
+ expect(response).to render_template('diffs')
+ end
+ end
+
+ context 'as json' do
+ before { go(format: 'json') }
+
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/show/_diffs')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
+ end
+
+ context 'with forked projects with submodules' do
+ render_views
+
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+ let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
+
+ before do
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+ merge_request.reload
+ go(format: 'json')
+ end
+
+ it 'renders' do
+ expect(response).to be_success
+ expect(response.body).to have_content('Subproject commit')
+ end
+ end
+ end
+
+ context 'with ignore_whitespace_change' do
+ context 'as html' do
+ before { go(format: 'html', w: 1) }
+
+ it 'renders the diff template' do
+ expect(response).to render_template('diffs')
+ end
+ end
+
+ context 'as json' do
+ before { go(format: 'json', w: 1) }
+
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/show/_diffs')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
+ end
+ end
+
+ context 'with view' do
+ before { go(view: 'parallel') }
+
+ it 'saves the preferred diff view in a cookie' do
+ expect(response.cookies['diff_view']).to eq('parallel')
+ end
+ end
+ end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ context 'when an ID param is passed' do
+ let(:existing_path) { 'files/ruby/popen.rb' }
+
+ context 'when the merge request exists' do
+ context 'when the user can view the merge request' do
+ context 'when the path exists in the diff' do
+ it 'enables diff notes' do
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_falsey
+ expect(assigns(:comments_target)).to eq(noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id)
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user cannot view the merge request' do
+ before do
+ project.team.truncate
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the merge request does not exist' do
+ before { diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the merge request belongs to a different project' do
+ let(:other_project) { create(:empty_project) }
+
+ before do
+ other_project.team << [user, :master]
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project.to_param)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when source and target params are passed' do
+ let(:existing_path) { 'files/ruby/feature.rb' }
+
+ context 'when both branches are in the same project' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the source branch is in a different project to the target' do
+ let(:other_project) { create(:project) }
+
+ before { other_project.team << [user, :master] }
+
+ context 'when the path exists in the diff' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 00bc38b607..75590c1ed4 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::NotesController do
project_id: project.path, id: note.id, name: "thumbsup")
end.to change { note.award_emoji.count }.by(1)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
it "removes the already awarded emoji" do
@@ -30,7 +30,7 @@ describe Projects::NotesController do
project_id: project.path, id: note.id, name: "thumbsup")
end.to change { AwardEmoji.count }.by(-1)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index fc5f458e79..5e2a8cf384 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -58,7 +58,7 @@ describe Projects::ProjectMembersController do
get :index, namespace_id: project.namespace, project_id: project
end
- it { expect(response.status).to eq(200) }
+ it { expect(response).to have_http_status(200) }
end
end
@@ -71,7 +71,7 @@ describe Projects::ProjectMembersController do
project_id: project,
id: 42
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -94,7 +94,7 @@ describe Projects::ProjectMembersController do
project_id: project,
id: member
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
expect(project.users).to include team_user
end
end
@@ -139,7 +139,7 @@ describe Projects::ProjectMembersController do
delete :leave, namespace_id: project.namespace,
project_id: project
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
end
end
@@ -171,11 +171,7 @@ describe Projects::ProjectMembersController do
delete :leave, namespace_id: project.namespace,
project_id: project
- expect(response).to redirect_to(
- namespace_project_path(project.namespace, project)
- )
- expect(response).to set_flash[:alert].to "You can not leave the \"#{project.human_name}\" project. Transfer or delete the project."
- expect(project.users).to include user
+ expect(response).to have_http_status(403)
end
end
@@ -190,8 +186,8 @@ describe Projects::ProjectMembersController do
project_id: project
expect(response).to set_flash.to 'Your access request to the project has been withdrawn.'
- expect(response).to redirect_to(dashboard_projects_path)
- expect(project.members.request).to be_empty
+ expect(response).to redirect_to(namespace_project_path(project.namespace, project))
+ expect(project.requesters).to be_empty
expect(project.users).not_to include user
end
end
@@ -214,7 +210,7 @@ describe Projects::ProjectMembersController do
expect(response).to redirect_to(
namespace_project_path(project.namespace, project)
)
- expect(project.members.request.exists?(user_id: user)).to be_truthy
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
expect(project.users).not_to include user
end
end
@@ -228,7 +224,7 @@ describe Projects::ProjectMembersController do
project_id: project,
id: 42
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -237,7 +233,7 @@ describe Projects::ProjectMembersController do
let(:team_requester) { create(:user) }
let(:member) do
project.request_access(team_requester)
- project.members.request.find_by(user_id: team_requester.id)
+ project.requesters.find_by(user_id: team_requester.id)
end
context 'when user does not have enough rights' do
@@ -251,7 +247,7 @@ describe Projects::ProjectMembersController do
project_id: project,
id: member
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
expect(project.users).not_to include team_requester
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 33c35161da..48f799d8ca 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::RawController do
project_id: public_project.to_param,
id: id)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition']).
to eq("inline")
@@ -30,7 +30,7 @@ describe Projects::RawController do
project_id: public_project.to_param,
id: id)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response.header['Content-Type']).to eq('image/jpeg')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:")
end
@@ -54,7 +54,7 @@ describe Projects::RawController do
project_id: public_project.to_param,
id: id)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -65,7 +65,7 @@ describe Projects::RawController do
project_id: public_project.to_param,
id: id)
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index aad62cf20e..2fe3c26352 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -28,7 +28,6 @@ describe Projects::RepositoriesController do
end
context "when the service raises an error" do
-
before do
allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
end
@@ -36,7 +35,7 @@ describe Projects::RepositoriesController do
it "renders Not Found" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 0f32a30f18..b8a28f4370 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::SnippetsController do
get :index, namespace_id: project.namespace.path, project_id: project.path
expect(assigns(:snippets)).not_to include(project_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -30,7 +30,7 @@ describe Projects::SnippetsController do
get :index, namespace_id: project.namespace.path, project_id: project.path
expect(assigns(:snippets)).to include(project_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -41,7 +41,7 @@ describe Projects::SnippetsController do
get :index, namespace_id: project.namespace.path, project_id: project.path
expect(assigns(:snippets)).to include(project_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -56,7 +56,7 @@ describe Projects::SnippetsController do
it 'responds with status 404' do
get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -67,7 +67,7 @@ describe Projects::SnippetsController do
get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
expect(assigns(:snippet)).to eq(project_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -78,7 +78,7 @@ describe Projects::SnippetsController do
get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param
expect(assigns(:snippet)).to eq(project_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -88,7 +88,7 @@ describe Projects::SnippetsController do
it 'responds with status 404' do
get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -98,7 +98,7 @@ describe Projects::SnippetsController do
it 'responds with status 404' do
get action, namespace_id: project.namespace.path, project_id: project.path, id: 42
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
new file mode 100644
index 0000000000..a6995145cc
--- /dev/null
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Projects::TagsController do
+ let(:project) { create(:project, :public) }
+ let!(:release) { create(:release, project: project) }
+ let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
+
+ describe 'GET index' do
+ before { get :index, namespace_id: project.namespace.to_param, project_id: project.to_param }
+
+ it 'returns the tags for the page' do
+ expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
+ end
+
+ it 'returns releases matching those tags' do
+ expect(assigns(:releases)).to include(release)
+ expect(assigns(:releases)).not_to include(invalid_release)
+ end
+ end
+end
diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb
index 40a3403b66..936320a370 100644
--- a/spec/controllers/projects/todo_controller_spec.rb
+++ b/spec/controllers/projects/todo_controller_spec.rb
@@ -1,6 +1,8 @@
require('spec_helper')
describe Projects::TodosController do
+ include ApiHelpers
+
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
@@ -8,46 +10,54 @@ describe Projects::TodosController do
context 'Issues' do
describe 'POST create' do
+ def go
+ post :create,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ issuable_id: issue.id,
+ issuable_type: 'issue',
+ format: 'html'
+ end
+
context 'when authorized' do
before do
sign_in(user)
project.team << [user, :developer]
end
- it 'should create todo for issue' do
+ it 'creates todo for issue' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: issue.id,
- issuable_type: 'issue')
+ go
end.to change { user.todos.count }.by(1)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns todo path and pending count' do
+ go
+
+ expect(response).to have_http_status(200)
+ expect(json_response['count']).to eq 1
+ expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
end
end
context 'when not authorized' do
- it 'should not create todo for issue that user has no access to' do
+ it 'does not create todo for issue that user has no access to' do
sign_in(user)
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: issue.id,
- issuable_type: 'issue')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
- it 'should not create todo for issue when user not logged in' do
+ it 'does not create todo for issue when user not logged in' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: issue.id,
- issuable_type: 'issue')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
end
end
end
@@ -55,46 +65,54 @@ describe Projects::TodosController do
context 'Merge Requests' do
describe 'POST create' do
+ def go
+ post :create,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ issuable_id: merge_request.id,
+ issuable_type: 'merge_request',
+ format: 'html'
+ end
+
context 'when authorized' do
before do
sign_in(user)
project.team << [user, :developer]
end
- it 'should create todo for merge request' do
+ it 'creates todo for merge request' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: merge_request.id,
- issuable_type: 'merge_request')
+ go
end.to change { user.todos.count }.by(1)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns todo path and pending count' do
+ go
+
+ expect(response).to have_http_status(200)
+ expect(json_response['count']).to eq 1
+ expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
end
end
context 'when not authorized' do
- it 'should not create todo for merge request user has no access to' do
+ it 'does not create todo for merge request user has no access to' do
sign_in(user)
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: merge_request.id,
- issuable_type: 'merge_request')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
- it 'should not create todo for merge request user has no access to' do
+ it 'does not create todo for merge request user has no access to' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: merge_request.id,
- issuable_type: 'merge_request')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
end
end
end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index e74731c9ed..1cc050247c 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -64,9 +64,8 @@ describe Projects::TreeController do
context "valid SHA commit ID with path" do
let(:id) { '6d39438/.gitignore' }
- it { expect(response.status).to eq(302) }
+ it { expect(response).to have_http_status(302) }
end
-
end
describe 'GET show with blob path' do
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 93c4494c66..71d0e4be83 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -14,11 +14,11 @@ describe Projects::UploadsController do
context "without params['file']" do
it "returns an error" do
- post :create,
+ post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project.to_param,
format: :json
- expect(response.status).to eq(422)
+ expect(response).to have_http_status(422)
end
end
@@ -34,23 +34,21 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"rails_sample\"'
expect(response.body).to match "\"url\":\"/uploads"
- expect(response.body).to match '\"is_image\":true'
end
end
context 'with valid non-image file' do
before do
- post :create,
+ post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- file: txt,
+ project_id: project.to_param,
+ file: txt,
format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"/uploads"
- expect(response.body).to match '\"is_image\":false'
end
end
end
@@ -79,7 +77,7 @@ describe Projects::UploadsController do
it "responds with status 200" do
go
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -87,7 +85,7 @@ describe Projects::UploadsController do
it "responds with status 404" do
go
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -106,7 +104,7 @@ describe Projects::UploadsController do
it "responds with status 200" do
go
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -114,7 +112,7 @@ describe Projects::UploadsController do
it "responds with status 404" do
go
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -140,7 +138,7 @@ describe Projects::UploadsController do
it "responds with status 200" do
go
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -192,7 +190,7 @@ describe Projects::UploadsController do
it "responds with status 200" do
go
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -224,7 +222,7 @@ describe Projects::UploadsController do
it "responds with status 200" do
go
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -232,7 +230,7 @@ describe Projects::UploadsController do
it "responds with status 404" do
go
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -253,7 +251,7 @@ describe Projects::UploadsController do
it "responds with status 200" do
go
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -261,7 +259,7 @@ describe Projects::UploadsController do
it "responds with status 404" do
go
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -270,7 +268,7 @@ describe Projects::UploadsController do
it "responds with status 404" do
go
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 146b2c2e13..3edce4d339 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -43,6 +43,26 @@ describe ProjectsController do
end
end
+ context "project with empty repo" do
+ let(:empty_project) { create(:project_empty_repo, :public) }
+
+ before { sign_in(user) }
+
+ User.project_views.keys.each do |project_view|
+ context "with #{project_view} view set" do
+ before do
+ user.update_attributes(project_view: project_view)
+
+ get :show, namespace_id: empty_project.namespace.path, id: empty_project.path
+ end
+
+ it "renders the empty project view" do
+ expect(response).to render_template('empty')
+ end
+ end
+ end
+ end
+
context "rendering default project view" do
render_views
@@ -77,7 +97,7 @@ describe ProjectsController do
get :show, namespace_id: public_project.namespace.path, id: public_project.path
expect(assigns(:project)).to eq(public_project)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -89,19 +109,16 @@ describe ProjectsController do
expect(response).to redirect_to("/#{public_project.path_with_namespace}")
end
-
# MySQL queries are case insensitive by default, so this spec would fail.
if Gitlab::Database.postgresql?
context "when there is also a match with the same casing" do
-
let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) }
it "loads the exactly matched project" do
-
get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
expect(assigns(:project)).to eq(other_project)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -146,7 +163,7 @@ describe ProjectsController do
expect(project.repository.path).to include(new_path)
expect(assigns(:repository).path).to eq(project.repository.path)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -161,7 +178,7 @@ describe ProjectsController do
delete :destroy, namespace_id: project.namespace.path, id: project.path
expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(response).to redirect_to(dashboard_projects_path)
end
end
@@ -234,7 +251,7 @@ describe ProjectsController do
delete(:remove_fork,
namespace_id: project.namespace.to_param,
id: project.to_param, format: :js)
- expect(response.status).to eq(401)
+ expect(response).to have_http_status(401)
end
end
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 209fa37d97..026f41c926 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -14,8 +14,7 @@ describe RegistrationsController do
before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) }
it 'logs user in directly' do
- post(:create, user_params)
- expect(ActionMailer::Base.deliveries.last).to be_nil
+ expect { post(:create, user_params) }.not_to change{ ActionMailer::Base.deliveries.size }
expect(subject.current_user).not_to be_nil
end
end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index b3dcb52c50..2a89159c07 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -19,7 +19,7 @@ describe SnippetsController do
it 'responds with status 404' do
get :show, id: other_personal_snippet.to_param
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -28,7 +28,7 @@ describe SnippetsController do
get :show, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -54,7 +54,7 @@ describe SnippetsController do
get :show, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -79,7 +79,7 @@ describe SnippetsController do
get :show, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -88,7 +88,7 @@ describe SnippetsController do
get :show, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -102,7 +102,7 @@ describe SnippetsController do
it 'responds with status 404' do
get :show, id: 'doesntexist'
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -110,7 +110,7 @@ describe SnippetsController do
it 'responds with status 404' do
get :show, id: 'doesntexist'
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -134,7 +134,7 @@ describe SnippetsController do
it 'responds with status 404' do
get :raw, id: other_personal_snippet.to_param
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -143,7 +143,7 @@ describe SnippetsController do
get :raw, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -169,7 +169,7 @@ describe SnippetsController do
get :raw, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -194,7 +194,7 @@ describe SnippetsController do
get :raw, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -203,7 +203,7 @@ describe SnippetsController do
get :raw, id: personal_snippet.to_param
expect(assigns(:snippet)).to eq(personal_snippet)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -217,7 +217,7 @@ describe SnippetsController do
it 'responds with status 404' do
get :raw, id: 'doesntexist'
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -225,7 +225,7 @@ describe SnippetsController do
it 'responds with status 404' do
get :raw, id: 'doesntexist'
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 73858e6f06..69124ab06b 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -26,7 +26,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -35,7 +35,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -52,7 +52,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -64,7 +64,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -109,7 +109,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -118,7 +118,7 @@ describe UploadsController do
it "responds with status 404" do
get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -133,7 +133,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -145,7 +145,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -181,7 +181,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -190,7 +190,7 @@ describe UploadsController do
it "responds with status 404" do
get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
@@ -210,7 +210,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -222,7 +222,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -267,7 +267,7 @@ describe UploadsController do
it "responds with status 200" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -276,7 +276,7 @@ describe UploadsController do
it "responds with status 404" do
get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index c61ec17466..54a2d3d946 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -33,7 +33,7 @@ describe UsersController do
it 'renders the show template' do
get :show, username: user.username
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response).to render_template('show')
end
end
@@ -47,7 +47,7 @@ describe UsersController do
context 'when logged out' do
it 'renders 404' do
get :show, username: user.username
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -56,7 +56,7 @@ describe UsersController do
it 'renders show' do
get :show, username: user.username
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response).to render_template('show')
end
end
@@ -64,7 +64,6 @@ describe UsersController do
end
describe 'GET #calendar' do
-
it 'renders calendar' do
sign_in(user)
@@ -121,7 +120,7 @@ describe UsersController do
context 'format html' do
it 'renders snippets page' do
get :snippets, username: user.username
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(response).to render_template('show')
end
end
@@ -129,7 +128,7 @@ describe UsersController do
context 'format json' do
it 'response with snippets json data' do
get :snippets, username: user.username, format: :json
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
expect(JSON.parse(response.body)).to have_key('html')
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index fe05a0cfc0..5e19e403c6 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
+ stage 'test'
+ stage_idx 0
ref 'master'
tag false
created_at 'Di 29. Okt 09:50:00 CET 2013'
@@ -15,6 +17,11 @@ FactoryGirl.define do
services: ["postgres"]
}
end
+ yaml_variables do
+ [
+ { key: :DB_NAME, value: 'postgres', public: true }
+ ]
+ end
pipeline factory: :ci_pipeline
@@ -38,6 +45,11 @@ FactoryGirl.define do
status 'pending'
end
+ trait :manual do
+ status 'skipped'
+ self.when 'manual'
+ end
+
trait :allowed_to_fail do
allow_failure true
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 696cf276e5..83e38095fe 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -10,21 +10,46 @@ FactoryGirl.define do
on_issue
factory :note_on_commit, traits: [:on_commit]
- factory :note_on_commit_diff, traits: [:on_commit, :on_diff], class: LegacyDiffNote
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request]
- factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff], class: LegacyDiffNote
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system]
+ factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote
+ factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote
+
+ factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do
+ position do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: noteable.diff_refs
+ )
+ end
+ end
+
+ factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
+ position do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: project.commit(commit_id).try(:diff_refs)
+ )
+ end
+ end
+
trait :on_commit do
noteable nil
- noteable_id nil
noteable_type 'Commit'
+ noteable_id nil
commit_id RepoHelpers.sample_commit.id
end
- trait :on_diff do
+ trait :legacy_diff_note do
line_code "0_184_184"
end
diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb
new file mode 100644
index 0000000000..b5e96d18b8
--- /dev/null
+++ b/spec/factories/notification_settings.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :notification_setting do
+ source factory: :empty_project
+ user
+ level 3
+ events []
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 5c8ddbebf0..b682ced75a 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -2,7 +2,7 @@ FactoryGirl.define do
# Project without repository
#
# Project does not have bare repository.
- # Use this factory if you dont need repository in tests
+ # Use this factory if you don't need repository in tests
factory :empty_project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index f426e27afe..866e663f02 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -22,5 +22,13 @@ FactoryGirl.define do
trait :build_failed do
action { Todo::BUILD_FAILED }
end
+
+ trait :approval_required do
+ action { Todo::APPROVAL_REQUIRED }
+ end
+
+ trait :done do
+ state :done
+ end
end
end
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
new file mode 100644
index 0000000000..16baf7e951
--- /dev/null
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe "Admin::AbuseReports", feature: true, js: true do
+ let(:user) { create(:user) }
+
+ context 'as an admin' do
+ describe 'if a user has been reported for abuse' do
+ before do
+ create(:abuse_report, user: user)
+ login_as :admin
+ end
+
+ describe 'in the abuse report view' do
+ it "should present a link to the user's profile" do
+ visit admin_abuse_reports_path
+
+ expect(page).to have_link user.name, href: user_path(user)
+ end
+ end
+
+ describe 'in the profile page of the user' do
+ it 'should show a link to the admin view of the user' do
+ visit user_path(user)
+
+ expect(page).to have_link '', href: admin_user_path(user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index a6198389f0..e177059d95 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -36,12 +36,45 @@ describe 'Admin Builds' do
end
end
+ context 'Pending tab' do
+ context 'when have pending builds' do
+ it 'shows pending builds' do
+ build1 = create(:ci_build, pipeline: pipeline, status: :pending)
+ build2 = create(:ci_build, pipeline: pipeline, status: :running)
+ build3 = create(:ci_build, pipeline: pipeline, status: :success)
+ build4 = create(:ci_build, pipeline: pipeline, status: :failed)
+
+ visit admin_builds_path(scope: :pending)
+
+ expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+ expect(page.find('.build-link')).to have_content(build1.id)
+ expect(page.find('.build-link')).not_to have_content(build2.id)
+ expect(page.find('.build-link')).not_to have_content(build3.id)
+ expect(page.find('.build-link')).not_to have_content(build4.id)
+ expect(page).to have_link 'Cancel all'
+ end
+ end
+
+ context 'when have no builds pending' do
+ it 'shows a message' do
+ create(:ci_build, pipeline: pipeline, status: :success)
+
+ visit admin_builds_path(scope: :pending)
+
+ expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+ expect(page).to have_content 'No builds to show'
+ expect(page).not_to have_link 'Cancel all'
+ end
+ end
+ end
+
context 'Running tab' do
context 'when have running builds' do
it 'shows running builds' do
- build1 = create(:ci_build, pipeline: pipeline, status: :pending)
+ build1 = create(:ci_build, pipeline: pipeline, status: :running)
build2 = create(:ci_build, pipeline: pipeline, status: :success)
build3 = create(:ci_build, pipeline: pipeline, status: :failed)
+ build4 = create(:ci_build, pipeline: pipeline, status: :pending)
visit admin_builds_path(scope: :running)
@@ -49,6 +82,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
+ expect(page.find('.build-link')).not_to have_content(build4.id)
expect(page).to have_link 'Cancel all'
end
end
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
new file mode 100644
index 0000000000..5b1c046027
--- /dev/null
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -0,0 +1,66 @@
+require 'rails_helper'
+
+feature 'Admin disables Git access protocol', feature: true do
+ let(:project) { create(:empty_project, :empty_repo) }
+ let(:admin) { create(:admin) }
+
+ background do
+ login_as(admin)
+ end
+
+ context 'with HTTP disabled' do
+ background do
+ disable_http_protocol
+ end
+
+ scenario 'shows only SSH url' do
+ visit_project
+
+ expect(page).to have_content("git clone #{project.ssh_url_to_repo}")
+ expect(page).not_to have_selector('#clone-dropdown')
+ end
+ end
+
+ context 'with SSH disabled' do
+ background do
+ disable_ssh_protocol
+ end
+
+ scenario 'shows only HTTP url' do
+ visit_project
+
+ expect(page).to have_content("git clone #{project.http_url_to_repo}")
+ expect(page).not_to have_selector('#clone-dropdown')
+ end
+ end
+
+ context 'with nothing disabled' do
+ background do
+ create(:personal_key, user: admin)
+ end
+
+ scenario 'shows default SSH url and protocol selection dropdown' do
+ visit_project
+
+ expect(page).to have_content("git clone #{project.ssh_url_to_repo}")
+ expect(page).to have_selector('#clone-dropdown')
+ end
+
+ end
+
+ def visit_project
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ def disable_http_protocol
+ visit admin_application_settings_path
+ find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[2]').select_option
+ click_on 'Save'
+ end
+
+ def disable_ssh_protocol
+ visit admin_application_settings_path
+ find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[3]').select_option
+ click_on 'Save'
+ end
+end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 31633817d5..7964951ae9 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -6,7 +6,6 @@ describe "Admin::Hooks", feature: true do
login_as :admin
@system_hook = create(:system_hook)
-
end
describe "GET /admin/hooks" do
@@ -49,5 +48,4 @@ describe "Admin::Hooks", feature: true do
it { expect(current_path).to eq(admin_hooks_path) }
end
-
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 2d297776cb..2f82fafc13 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -62,19 +62,45 @@ describe "Admin Runners" do
end
describe 'enable/create' do
- before do
- @project1.runners << runner
- visit admin_runner_path(runner)
+ shared_examples 'assignable runner' do
+ it 'enables a runner for a project' do
+ within '.unassigned-projects' do
+ click_on 'Enable'
+ end
+
+ assigned_project = page.find('.assigned-projects')
+
+ expect(assigned_project).to have_content(@project2.path)
+ end
end
- it 'enables specific runner for project' do
- within '.unassigned-projects' do
- click_on 'Enable'
+ context 'with specific runner' do
+ before do
+ @project1.runners << runner
+ visit admin_runner_path(runner)
end
- assigned_project = page.find('.assigned-projects')
+ it_behaves_like 'assignable runner'
+ end
- expect(assigned_project).to have_content(@project2.path)
+ context 'with locked runner' do
+ before do
+ runner.update(locked: true)
+ @project1.runners << runner
+ visit admin_runner_path(runner)
+ end
+
+ it_behaves_like 'assignable runner'
+ end
+
+ context 'with shared runner' do
+ before do
+ @project1.destroy
+ runner.update(is_shared: true)
+ visit admin_runner_path(runner)
+ end
+
+ it_behaves_like 'assignable runner'
end
end
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
new file mode 100644
index 0000000000..f4e5c26b51
--- /dev/null
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe 'Admin System Info' do
+ before do
+ login_as :admin
+ end
+
+ describe 'GET /admin/system_info' do
+ it 'shows system info page' do
+ visit admin_system_info_path
+
+ expect(page).to have_content 'CPU'
+ expect(page).to have_content 'Memory'
+ expect(page).to have_content 'Disks'
+ end
+ end
+end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 1cb709c1de..767504df25 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -144,9 +144,7 @@ describe "Admin::Users", feature: true do
before { click_link 'Impersonate' }
it 'logs in as the user when impersonate is clicked' do
- page.within '.sidebar-wrapper' do
- expect(page.find('.sidebar-user')['data-user']).to eql(another_user.username)
- end
+ expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
end
it 'sees impersonation log out icon' do
@@ -158,9 +156,7 @@ describe "Admin::Users", feature: true do
it 'can log out of impersonated user back to original user' do
find(:css, 'li.impersonation a').click
- page.within '.sidebar-wrapper' do
- expect(page.find('.sidebar-user')['data-user']).to eql(@user.username)
- end
+ expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username)
end
it 'is redirected back to the impersonated users page in the admin after stopping' do
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index de6aed74fb..91704377a0 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -61,7 +61,7 @@ describe "User Feed", feature: true do
end
it 'should have XHTML summaries in merge request descriptions' do
- expect(body).to match /Here is the fix: ]*>
]*\/><\/a>/
+ expect(body).to match /Here is the fix: <\/p>]*>]*>
]*\/><\/a><\/div>/
end
end
end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index 16832c297a..0cfeb2e57d 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -13,17 +13,33 @@ describe "Builds" do
end
describe "GET /:project/builds" do
+ context "Pending scope" do
+ before do
+ visit namespace_project_builds_path(@project.namespace, @project, scope: :pending)
+ end
+
+ it "shows Pending tab builds" do
+ expect(page).to have_link 'Cancel running'
+ expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+ expect(page).to have_content @build.short_sha
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ end
+ end
+
context "Running scope" do
before do
@build.run!
visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end
- it { expect(page).to have_selector('.nav-links li.active', text: 'Running') }
- it { expect(page).to have_link 'Cancel running' }
- it { expect(page).to have_content @build.short_sha }
- it { expect(page).to have_content @build.ref }
- it { expect(page).to have_content @build.name }
+ it "shows Running tab builds" do
+ expect(page).to have_selector('.nav-links li.active', text: 'Running')
+ expect(page).to have_link 'Cancel running'
+ expect(page).to have_content @build.short_sha
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ end
end
context "Finished scope" do
@@ -32,9 +48,11 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end
- it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') }
- it { expect(page).to have_content 'No builds to show' }
- it { expect(page).to have_link 'Cancel running' }
+ it "shows Finished tab builds" do
+ expect(page).to have_selector('.nav-links li.active', text: 'Finished')
+ expect(page).to have_content 'No builds to show'
+ expect(page).to have_link 'Cancel running'
+ end
end
context "All builds" do
@@ -43,11 +61,13 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project)
end
- it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
- it { expect(page).to have_content @build.short_sha }
- it { expect(page).to have_content @build.ref }
- it { expect(page).to have_content @build.name }
- it { expect(page).not_to have_link 'Cancel running' }
+ it "shows All tab builds" do
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
+ expect(page).to have_content @build.short_sha
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ expect(page).not_to have_link 'Cancel running'
+ end
end
end
@@ -179,9 +199,13 @@ describe "Builds" do
click_link 'Retry'
end
- it { expect(page.status_code).to eq(200) }
- it { expect(page).to have_content 'pending' }
- it { expect(page).to have_content 'Cancel' }
+ it 'shows the right status and buttons' do
+ expect(page).to have_http_status(200)
+ expect(page).to have_content 'pending'
+ page.within('aside.right-sidebar') do
+ expect(page).to have_content 'Cancel'
+ end
+ end
end
context "Build from other project" do
@@ -192,7 +216,25 @@ describe "Builds" do
page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
end
- it { expect(page.status_code).to eq(404) }
+ it { expect(page).to have_http_status(404) }
+ end
+
+ context "Build that current user is not allowed to retry" do
+ before do
+ @build.run!
+ @build.cancel!
+ @project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ logout_direct
+ login_with(create(:user))
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it 'does not show the Retry button' do
+ page.within('aside.right-sidebar') do
+ expect(page).not_to have_content 'Retry'
+ end
+ end
end
end
diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb
new file mode 100644
index 0000000000..c62556948e
--- /dev/null
+++ b/spec/features/compare_spec.rb
@@ -0,0 +1,42 @@
+require "spec_helper"
+
+describe "Compare", js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_compare_index_path(project.namespace, project, from: "master", to: "master")
+ end
+
+ describe "branches" do
+ it "should pre-populate fields" do
+ expect(page.find_field("from").value).to eq("master")
+ end
+
+ it "should compare branches" do
+ fill_in "from", with: "fea"
+ find("#from").click
+
+ click_link "feature"
+ expect(page.find_field("from").value).to eq("feature")
+
+ click_button "Compare"
+ expect(page).to have_content "Commits"
+ end
+ end
+
+ describe "tags" do
+ it "should compare tags" do
+ fill_in "from", with: "v1.0"
+ find("#from").click
+
+ click_link "v1.0.0"
+ expect(page.find_field("from").value).to eq("v1.0.0")
+
+ click_button "Compare"
+ expect(page).to have_content "Commits"
+ end
+ end
+end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index cf86e2c85e..c2e0612aef 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -1,7 +1,6 @@
require 'spec_helper'
describe "Dashboard > User filters projects", feature: true do
-
describe 'filtering personal projects' do
before do
user = create(:user)
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 7fb28f4174..a7d9f2a0c7 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -13,6 +13,7 @@ feature 'Environments', feature: true do
describe 'when showing environments' do
given!(:environment) { }
given!(:deployment) { }
+ given!(:manual) { }
before do
visit namespace_project_environments_path(project.namespace, project)
@@ -43,6 +44,24 @@ feature 'Environments', feature: true do
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
end
+
+ context 'with build and manual actions' do
+ given(:pipeline) { create(:ci_pipeline, project: project) }
+ given(:build) { create(:ci_build, pipeline: pipeline) }
+ given(:deployment) { create(:deployment, environment: environment, deployable: build) }
+ given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
+
+ scenario 'does show a play button' do
+ expect(page).to have_link(manual.name.humanize)
+ end
+
+ scenario 'does allow to play manual action' do
+ expect(manual).to be_skipped
+ expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
+ expect(page).to have_content(manual.name)
+ expect(manual.reload).to be_pending
+ end
+ end
end
end
@@ -54,6 +73,7 @@ feature 'Environments', feature: true do
describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) }
given!(:deployment) { }
+ given!(:manual) { }
before do
visit namespace_project_environment_path(project.namespace, project, environment)
@@ -72,20 +92,36 @@ feature 'Environments', feature: true do
expect(page).to have_link(deployment.short_sha)
end
- scenario 'does not show a retry button for deployment without build' do
- expect(page).not_to have_link('Retry')
+ scenario 'does not show a re-deploy button for deployment without build' do
+ expect(page).not_to have_link('Re-deploy')
end
context 'with build' do
- given(:build) { create(:ci_build, project: project) }
+ given(:pipeline) { create(:ci_pipeline, project: project) }
+ given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show build name' do
expect(page).to have_link("#{build.name} (##{build.id})")
end
- scenario 'does show retry button' do
- expect(page).to have_link('Retry')
+ scenario 'does show re-deploy button' do
+ expect(page).to have_link('Re-deploy')
+ end
+
+ context 'with manual action' do
+ given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
+
+ scenario 'does show a play button' do
+ expect(page).to have_link(manual.name.humanize)
+ end
+
+ scenario 'does allow to play manual action' do
+ expect(manual).to be_skipped
+ expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
+ expect(page).to have_content(manual.name)
+ expect(manual.reload).to be_pending
+ end
end
end
end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
new file mode 100644
index 0000000000..688f68d3cf
--- /dev/null
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -0,0 +1,248 @@
+require 'spec_helper'
+
+feature 'Expand and collapse diffs', js: true, feature: true do
+ include WaitForAjax
+
+ let(:branch) { 'expand-collapse-diffs' }
+
+ before do
+ login_as :admin
+ project = create(:project)
+
+ # Ensure that undiffable.md is in .gitattributes
+ project.repository.copy_gitattributes(branch)
+ visit namespace_project_commit_path(project.namespace, project, project.commit(branch))
+ execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
+ end
+
+ def file_container(filename)
+ find("[data-blob-diff-path*='#{filename}']")
+ end
+
+ # Use define_method instead of let (which is memoized) so that this just works across a
+ # reload.
+ #
+ files = [
+ 'small_diff.md', 'large_diff.md', 'large_diff_renamed.md', 'undiffable.md',
+ 'too_large.md', 'too_large_image.jpg'
+ ]
+
+ files.each do |file|
+ define_method(file.split('.').first) { file_container(file) }
+ end
+
+ context 'visiting a commit with collapsed diffs' do
+ it 'shows small diffs immediately' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'collapses large diffs by default' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+ end
+
+ it 'collapses large diffs for renamed files by default' do
+ expect(large_diff_renamed).not_to have_selector('.code')
+ expect(large_diff_renamed).to have_selector('.nothing-here-block')
+ expect(large_diff_renamed).to have_selector('.file-title .deletion')
+ expect(large_diff_renamed).to have_selector('.file-title .addition')
+ end
+
+ it 'shows non-renderable diffs as such immediately, regardless of their size' do
+ expect(undiffable).not_to have_selector('.code')
+ expect(undiffable).to have_selector('.nothing-here-block')
+ expect(undiffable).to have_content('gitattributes')
+ end
+
+ it 'does not allow diffs that are larger than the maximum size to be expanded' do
+ expect(too_large).not_to have_selector('.code')
+ expect(too_large).to have_selector('.nothing-here-block')
+ expect(too_large).to have_content('too large')
+ end
+
+ it 'shows image diffs immediately, regardless of their size' do
+ expect(too_large_image).not_to have_selector('.nothing-here-block')
+ expect(too_large_image).to have_selector('.image')
+ end
+
+ context 'expanding a diff for a renamed file' do
+ before do
+ large_diff_renamed.find('.nothing-here-block').click
+ wait_for_ajax
+ end
+
+ it 'shows the old content' do
+ old_line = large_diff_renamed.find('.line_content.old')
+
+ expect(old_line).to have_content('two copies')
+ end
+
+ it 'shows the new content' do
+ new_line = large_diff_renamed.find('.line_content.new', match: :prefer_exact)
+
+ expect(new_line).to have_content('three copies')
+ end
+ end
+
+ context 'expanding a large diff' do
+ before do
+ click_link('large_diff.md')
+ wait_for_ajax
+ end
+
+ it 'makes a request to get the content' do
+ ajax_uris = evaluate_script('ajaxUris')
+
+ expect(ajax_uris).not_to be_empty
+ expect(ajax_uris.first).to include('large_diff.md')
+ end
+
+ it 'shows the diff content' do
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ context 'adding a comment to the expanded diff' do
+ let(:comment_text) { 'A comment' }
+
+ before do
+ large_diff.find('.diff-line-num', match: :prefer_exact).hover
+ large_diff.find('.add-diff-note').click
+ large_diff.find('.note-textarea').send_keys comment_text
+ large_diff.find_button('Comment').click
+ wait_for_ajax
+ end
+
+ it 'adds the comment' do
+ expect(large_diff.find('.notes')).to have_content comment_text
+ end
+
+ context 'reloading the page' do
+ before { refresh }
+
+ it 'collapses the large diff by default' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 'expanding the diff' do
+ before do
+ click_link('large_diff.md')
+ wait_for_ajax
+ end
+
+ it 'shows the diff content' do
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'shows the diff comment' do
+ expect(large_diff.find('.notes')).to have_content comment_text
+ end
+ end
+ end
+ end
+ end
+
+ context 'collapsing an expanded diff' do
+ before { click_link('small_diff.md') }
+
+ it 'hides the diff content' do
+ expect(small_diff).not_to have_selector('.code')
+ expect(small_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 're-expanding the same diff' do
+ before { click_link('small_diff.md') }
+
+ it 'shows the diff content' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'does not make a new HTTP request' do
+ expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md'))
+ end
+ end
+ end
+ end
+
+ context 'visiting a commit without collapsed diffs' do
+ let(:branch) { 'feature' }
+
+ it 'does not show Expand all button' do
+ expect(page).not_to have_link('Expand all')
+ end
+ end
+
+ context 'visiting a commit with more than safe files' do
+ let(:branch) { 'expand-collapse-files' }
+
+ # safe-files -> 100 | safe-lines -> 5000 | commit-files -> 105
+ it 'does collapsing from the safe number of files to the end on small files' do
+ expect(page).to have_link('Expand all')
+
+ expect(page).to have_selector('.diff-content', count: 105)
+ expect(page).to have_selector('.diff-collapsed', count: 5)
+
+ %w(file-95.txt file-96.txt file-97.txt file-98.txt file-99.txt).each do |filename|
+ expect(find("[data-blob-diff-path*='#{filename}']")).to have_selector('.diff-collapsed')
+ end
+ end
+ end
+
+ context 'visiting a commit with more than safe lines' do
+ let(:branch) { 'expand-collapse-lines' }
+
+ # safe-files -> 100 | safe-lines -> 5000 | commit_files -> 8 (each 1250 lines)
+ it 'does collapsing from the safe number of lines to the end' do
+ expect(page).to have_link('Expand all')
+
+ expect(page).to have_selector('.diff-content', count: 6)
+ expect(page).to have_selector('.diff-collapsed', count: 2)
+
+ %w(file-4.txt file-5.txt).each do |filename|
+ expect(find("[data-blob-diff-path*='#{filename}']")).to have_selector('.diff-collapsed')
+ end
+ end
+ end
+
+ context 'expanding all diffs' do
+ before do
+ click_link('Expand all')
+ wait_for_ajax
+ execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
+ end
+
+ it 'reloads the page with all diffs expanded' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ context 'collapsing an expanded diff' do
+ before { click_link('small_diff.md') }
+
+ it 'hides the diff content' do
+ expect(small_diff).not_to have_selector('.code')
+ expect(small_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 're-expanding the same diff' do
+ before { click_link('small_diff.md') }
+
+ it 'shows the diff content' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'does not make a new HTTP request' do
+ expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md'))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 7852c39fee..a89ac09f23 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -81,7 +81,6 @@ describe "GitLab Flavored Markdown", feature: true do
end
end
-
describe "for merge requests" do
before do
@merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix #{issue.to_reference}")
@@ -100,7 +99,6 @@ describe "GitLab Flavored Markdown", feature: true do
end
end
-
describe "for milestones" do
before do
@milestone = create(:milestone,
diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
new file mode 100644
index 0000000000..33bf6d3752
--- /dev/null
+++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Last owner cannot leave group', feature: true do
+ let(:owner) { create(:user) }
+ let(:group) { create(:group) }
+
+ background do
+ group.add_owner(owner)
+ login_as(owner)
+ visit group_path(group)
+ end
+
+ scenario 'user does not see a "Leave Group" link' do
+ expect(page).not_to have_content 'Leave Group'
+ end
+end
diff --git a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb
new file mode 100644
index 0000000000..37c433cc09
--- /dev/null
+++ b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Member cannot request access to his project', feature: true do
+ let(:member) { create(:user) }
+ let(:group) { create(:group) }
+
+ background do
+ group.add_developer(member)
+ login_as(member)
+ visit group_path(group)
+ end
+
+ scenario 'member does not see the request access button' do
+ expect(page).not_to have_content 'Request Access'
+ end
+end
diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb
new file mode 100644
index 0000000000..3185ff924b
--- /dev/null
+++ b/spec/features/groups/members/member_leaves_group_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Member leaves group', feature: true do
+ let(:user) { create(:user) }
+ let(:owner) { create(:user) }
+ let(:group) { create(:group, :public) }
+
+ background do
+ group.add_owner(owner)
+ group.add_developer(user)
+ login_as(user)
+ visit group_path(group)
+ end
+
+ scenario 'user leaves group' do
+ click_link 'Leave Group'
+
+ expect(current_path).to eq(dashboard_groups_path)
+ expect(group.users.exists?(user.id)).to be_falsey
+ end
+end
diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb
index 22525ce530..10d3713f19 100644
--- a/spec/features/groups/members/owner_manages_access_requests_spec.rb
+++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb
@@ -39,10 +39,9 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was denied"
end
-
def expect_visible_access_request(group, user)
- expect(group.members.request.exists?(user_id: user)).to be_truthy
- expect(page).to have_content "#{group.name} access requests (1)"
+ expect(group.requesters.exists?(user_id: user)).to be_truthy
+ expect(page).to have_content "#{group.name} access requests 1"
expect(page).to have_content user.name
end
end
diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb
index a878a96b6e..b3baa2ab57 100644
--- a/spec/features/groups/members/user_requests_access_spec.rb
+++ b/spec/features/groups/members/user_requests_access_spec.rb
@@ -4,6 +4,7 @@ feature 'Groups > Members > User requests access', feature: true do
let(:user) { create(:user) }
let(:owner) { create(:user) }
let(:group) { create(:group, :public) }
+ let!(:project) { create(:project, :private, namespace: group) }
background do
group.add_owner(owner)
@@ -11,22 +12,44 @@ feature 'Groups > Members > User requests access', feature: true do
visit group_path(group)
end
+ scenario 'request access feature is disabled' do
+ group.update_attributes(request_access_enabled: false)
+ visit group_path(group)
+
+ expect(page).not_to have_content 'Request Access'
+ end
+
scenario 'user can request access to a group' do
perform_enqueued_jobs { click_link 'Request Access' }
expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email]
expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group"
- expect(group.members.request.exists?(user_id: user)).to be_truthy
+ expect(group.requesters.exists?(user_id: user)).to be_truthy
expect(page).to have_content 'Your request for access has been queued for review.'
expect(page).to have_content 'Withdraw Access Request'
+ expect(page).not_to have_content 'Leave Group'
+ end
+
+ scenario 'user does not see private projects' do
+ perform_enqueued_jobs { click_link 'Request Access' }
+
+ expect(page).not_to have_content project.name
+ end
+
+ scenario 'user does not see group in the Dashboard > Groups page' do
+ perform_enqueued_jobs { click_link 'Request Access' }
+
+ visit dashboard_groups_path
+
+ expect(page).not_to have_content group.name
end
scenario 'user is not listed in the group members page' do
click_link 'Request Access'
- expect(group.members.request.exists?(user_id: user)).to be_truthy
+ expect(group.requesters.exists?(user_id: user)).to be_truthy
click_link 'Members'
@@ -38,11 +61,11 @@ feature 'Groups > Members > User requests access', feature: true do
scenario 'user can withdraw its request for access' do
click_link 'Request Access'
- expect(group.members.request.exists?(user_id: user)).to be_truthy
+ expect(group.requesters.exists?(user_id: user)).to be_truthy
click_link 'Withdraw Access Request'
- expect(group.members.request.exists?(user_id: user)).to be_falsey
+ expect(group.requesters.exists?(user_id: user)).to be_falsey
expect(page).to have_content 'Your access request to the group has been withdrawn.'
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 891df65216..2d8b59472e 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/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index 8c6b669ce7..1e2306d7f5 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -6,7 +6,7 @@ describe 'Help Pages', feature: true do
login_as :user
end
it 'replace the variable $your_email with the email of the user' do
- visit help_page_path('ssh', 'README')
+ visit help_page_path('ssh/README')
expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"")
end
end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 7143d0e40f..afc093cc1f 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -10,7 +10,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
let!(:bug) { create(:label, project: project, title: 'bug') }
let!(:feature) { create(:label, project: project, title: 'feature') }
- context 'as a allowed user', js: true do
+ context 'as an allowed user', js: true do
before do
project.team << [user, :master]
@@ -164,6 +164,133 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
end
end
+
+ context 'toggling a milestone' do
+ let!(:milestone) { create(:milestone, project: project, title: 'First Release') }
+
+ context 'setting a milestone' do
+ before do
+ issue1.labels << bug
+ issue2.labels << feature
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'labels are kept' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+
+ check 'check_all_issues'
+ open_milestone_dropdown(['First Release'])
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+ end
+ end
+
+ context 'setting a milestone and adding another label' do
+ before do
+ issue1.labels << bug
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'existing label is kept and new label is present' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+
+ check 'check_all_issues'
+ open_milestone_dropdown ['First Release']
+ open_labels_dropdown ['feature']
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+ end
+ end
+
+ context 'setting a milestone and removing existing label' do
+ before do
+ issue1.labels << bug
+ issue1.labels << feature
+ issue2.labels << feature
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'existing label is kept and new label is present' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+
+ check 'check_all_issues'
+ open_milestone_dropdown ['First Release']
+ unmark_labels_in_dropdown ['feature']
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'feature'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+ end
+ end
+
+ context 'unsetting a milestone' do
+ before do
+ issue1.milestone = milestone
+ issue2.milestone = milestone
+ issue1.save
+ issue2.save
+ issue1.labels << bug
+ issue2.labels << feature
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'labels are kept' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+
+ check 'check_all_issues'
+ open_milestone_dropdown(['No Milestone'])
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'First Release'
+ end
+ end
+ end
+
+ context 'toggling checked issues' do
+ before do
+ issue1.labels << bug
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+
+ check_issue issue1
+ open_labels_dropdown ['feature']
+ uncheck_issue issue1
+ check_issue issue1
+ update_issues
+ sleep 1 # needed
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'feature'
+ end
+ end
end
context 'as a guest' do
@@ -181,6 +308,16 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
end
+ def open_milestone_dropdown(items = [])
+ page.within('.issues_bulk_update') do
+ click_button 'Milestone'
+ wait_for_ajax
+ items.map do |item|
+ click_link item
+ end
+ end
+ end
+
def open_labels_dropdown(items = [], unmark = false)
page.within('.issues_bulk_update') do
click_button 'Label'
@@ -201,12 +338,20 @@ feature 'Issues > Labels bulk assignment', feature: true do
open_labels_dropdown(items, true)
end
- def check_issue(issue)
+ def check_issue(issue, uncheck = false)
page.within('.issues-list') do
- check "selected_issue_#{issue.id}"
+ if uncheck
+ uncheck "selected_issue_#{issue.id}"
+ else
+ check "selected_issue_#{issue.id}"
+ end
end
end
+ def uncheck_issue(issue)
+ check_issue(issue, true)
+ end
+
def update_issues
click_button 'Update issues'
wait_for_ajax
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 4bcb105b17..4b9b5394b6 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -7,6 +7,7 @@ describe 'Filter issues', feature: true do
let!(:user) { create(:user)}
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
+ let!(:issue1) { create(:issue, project: project) }
before do
project.team << [user, :master]
@@ -14,7 +15,6 @@ describe 'Filter issues', feature: true do
end
describe 'Filter issues for assignee from issues#index' do
-
before do
visit namespace_project_issues_path(project.namespace, project)
@@ -36,7 +36,6 @@ describe 'Filter issues', feature: true do
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
-
it 'should not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
@@ -46,7 +45,6 @@ describe 'Filter issues', feature: true do
end
describe 'Filter issues for milestone from issues#index' do
-
before do
visit namespace_project_issues_path(project.namespace, project)
@@ -68,7 +66,6 @@ describe 'Filter issues', feature: true do
expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
end
-
it 'should not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
@@ -113,7 +110,6 @@ describe 'Filter issues', feature: true do
end
describe 'Filter issues for assignee and label from issues#index' do
-
before do
visit namespace_project_issues_path(project.namespace, project)
@@ -144,7 +140,6 @@ describe 'Filter issues', feature: true do
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end
-
it 'should not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
@@ -202,6 +197,7 @@ describe 'Filter issues', feature: true do
page.within '.labels-filter' do
click_link 'bug'
end
+ find('.dropdown-menu-close-icon').click
page.within '.issues-list' do
expect(page).to have_selector('.issue', count: 1)
@@ -293,7 +289,7 @@ describe 'Filter issues', feature: true do
wait_for_ajax
page.within '.issues-list' do
- expect(first('.issue')).to have_content('Frontend')
+ expect(page).to have_content('Frontend')
end
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index c3cb337944..d51c9abea1 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -22,7 +22,7 @@ describe 'Issues', feature: true do
before do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
- click_button "Go full screen"
+ find('.js-zen-enter').click
end
it 'should open new issue popup' do
@@ -92,7 +92,7 @@ describe 'Issues', feature: true do
end
context 'on edit form' do
- let(:issue) { create(:issue, author: @user,project: project, due_date: Date.today.at_beginning_of_month.to_s) }
+ let(:issue) { create(:issue, author: @user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
before do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
@@ -361,7 +361,6 @@ describe 'Issues', feature: true do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
context 'by authorized user' do
-
it 'allows user to select unassigned', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
@@ -420,7 +419,6 @@ describe 'Issues', feature: true do
end
context 'by unauthorized user' do
-
let(:guest) { create(:user) }
before do
@@ -442,8 +440,6 @@ describe 'Issues', feature: true do
let!(:milestone) { create(:milestone, project: project) }
context 'by authorized user' do
-
-
it 'allows user to select unassigned', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 72b5ff231f..58753ff21f 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -28,6 +28,11 @@ feature 'Login', feature: true do
end
describe 'with two-factor authentication' do
+ def enter_code(code)
+ fill_in 'Two-Factor Authentication code', with: code
+ click_button 'Verify code'
+ end
+
context 'with valid username/password' do
let(:user) { create(:user, :two_factor) }
@@ -36,11 +41,6 @@ feature 'Login', feature: true do
expect(page).to have_content('Two-Factor Authentication')
end
- def enter_code(code)
- fill_in 'Two-Factor Authentication code', with: code
- click_button 'Verify code'
- end
-
it 'does not show a "You are already signed in." error message' do
enter_code(user.current_otp)
expect(page).not_to have_content('You are already signed in.')
@@ -108,6 +108,39 @@ feature 'Login', feature: true do
end
end
end
+
+ context 'logging in via OAuth' do
+ def saml_config
+ OpenStruct.new(name: 'saml', label: 'saml', args: {
+ assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
+ idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
+ idp_sso_target_url: 'https://idp.example.com/sso/saml',
+ issuer: 'https://localhost:3443/',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ })
+ end
+
+ def stub_omniauth_config(messages)
+ Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+ Rails.application.routes.disable_clear_and_finalize = true
+ Rails.application.routes.draw do
+ post '/users/auth/saml' => 'omniauth_callbacks#saml'
+ end
+ allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ allow_any_instance_of(Object).to receive(:user_omniauth_authorize_path).with('saml').and_return('/users/auth/saml')
+ end
+
+ it 'should show 2FA prompt after OAuth login' do
+ stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config])
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
+ login_via('saml', user, 'my-uid')
+
+ expect(page).to have_content('Two-Factor Authentication')
+ enter_code(user.current_otp)
+ expect(current_path).to eq root_path
+ end
+ end
end
describe 'without two-factor authentication' do
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 09ccc77c10..32159559c3 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -236,6 +236,14 @@ describe 'GitLab Markdown', feature: true do
it 'includes TaskListFilter' do
expect(doc).to parse_task_lists
end
+
+ it 'includes InlineDiffFilter' do
+ expect(doc).to parse_inline_diffs
+ end
+
+ it 'includes VideoLinkFilter' do
+ expect(doc).to parse_video_links
+ end
end
context 'wiki pipeline' do
@@ -293,6 +301,10 @@ describe 'GitLab Markdown', feature: true do
it 'includes InlineDiffFilter' do
expect(doc).to parse_inline_diffs
end
+
+ it 'includes VideoLinkFilter' do
+ expect(doc).to parse_video_links
+ end
end
# Fake a `current_user` helper
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index b4d2201c72..f676200ecf 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -30,7 +30,7 @@ feature 'Merge request created from fork' do
given(:pipeline) do
create(:ci_pipeline_with_two_job, project: fork_project,
- sha: merge_request.last_commit.id,
+ sha: merge_request.diff_head_sha,
ref: merge_request.source_branch)
end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
new file mode 100644
index 0000000000..c9a0059645
--- /dev/null
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+feature 'Diffs URL', js: true, feature: true do
+ before do
+ login_as :admin
+ @merge_request = create(:merge_request)
+ @project = @merge_request.source_project
+ end
+
+ context 'when visit with */* as accept header' do
+ before(:each) do
+ page.driver.add_header('Accept', '*/*')
+ end
+
+ it 'renders the notes' do
+ create :note_on_merge_request, project: @project, noteable: @merge_request, note: 'Rebasing with master'
+
+ visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+
+ # Load notes and diff through AJAX
+ expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master')
+ expect(page).to have_css('.diffs.tab-pane.active')
+ end
+ end
+end
diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
index c5e6412d7b..96f7b8c993 100644
--- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -12,7 +12,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
end
context "Active build for Merge Request" do
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) }
let!(:ci_build) { create(:ci_build, pipeline: pipeline) }
before do
@@ -47,7 +47,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
merge_user: user, title: "MepMep", merge_when_build_succeeds: true)
end
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) }
let!(:ci_build) { create(:ci_build, pipeline: pipeline) }
before do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
index 65e9185ec2..80e8b8fc64 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
@@ -19,7 +19,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature:
end
context 'when project has CI enabled' do
- let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) }
context 'when merge requests can only be merged if the build succeeds' do
before do
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 737efcef45..0b38c413f4 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -135,6 +135,28 @@ describe 'Comments', feature: true do
end
end
+ describe 'Handles cross-project system notes', js: true, feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:project2) { create(:project, :private) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') }
+ let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") }
+
+ it 'shows the system note' do
+ login_as :admin
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+
+ expect(page).to have_css('.system-note')
+ end
+
+ it 'hides redacted system note' do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+
+ expect(page).not_to have_css('.system-note')
+ end
+ end
+
describe 'On a merge request diff', js: true, feature: true do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
@@ -166,12 +188,14 @@ describe 'Comments', feature: true do
click_diff_line
is_expected.
- to have_css("tr[id='#{line_code}'] + .js-temp-notes-holder form",
+ to have_css("form[data-line-code='#{line_code}']",
count: 1)
end
it 'should be removed when canceled' do
- page.within(".diff-file form[id$='#{line_code}-true']") do
+ is_expected.to have_css('.js-temp-notes-holder')
+
+ page.within("form[data-line-code='#{line_code}']") do
find('.js-close-discussion-note-form').trigger('click')
end
@@ -229,6 +253,7 @@ describe 'Comments', feature: true do
end
def click_diff_line(data = line_code)
- execute_script("$('button[data-line-code=\"#{data}\"]').click()")
+ find(".line_holder[id='#{data}'] td.line_content").hover
+ find(".line_holder[id='#{data}'] button").trigger('click')
end
end
diff --git a/spec/features/pipelines_settings_spec.rb b/spec/features/pipelines_settings_spec.rb
new file mode 100644
index 0000000000..dcc364a3d0
--- /dev/null
+++ b/spec/features/pipelines_settings_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+feature "Pipelines settings", feature: true do
+ include GitlabRoutingHelper
+
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:role) { :developer }
+
+ background do
+ login_as(user)
+ project.team << [user, role]
+ visit namespace_project_pipelines_settings_path(project.namespace, project)
+ end
+
+ context 'for developer' do
+ given(:role) { :developer }
+
+ scenario 'to be disallowed to view' do
+ expect(page.status_code).to eq(404)
+ end
+ end
+
+ context 'for master' do
+ given(:role) { :master }
+
+ scenario 'be allowed to change' do
+ fill_in('Test coverage parsing', with: 'coverage_regex')
+ click_on 'Save changes'
+
+ expect(page.status_code).to eq(200)
+ expect(page).to have_field('Test coverage parsing', with: 'coverage_regex')
+ end
+ end
+end
diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb
index 98703ef3ac..7f861db196 100644
--- a/spec/features/pipelines_spec.rb
+++ b/spec/features/pipelines_spec.rb
@@ -62,6 +62,20 @@ describe "Pipelines" do
end
end
+ context 'with manual actions' do
+ let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it { expect(page).to have_link('Manual build') }
+
+ context 'when playing' do
+ before { click_link('Manual build') }
+
+ it { expect(manual.reload).to be_pending }
+ end
+ end
+
context 'for generic statuses' do
context 'when running' do
let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
@@ -117,13 +131,14 @@ describe "Pipelines" do
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+ @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
@external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
end
before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
it 'showing a list of builds' do
- expect(page).to have_content('Tests')
+ expect(page).to have_content('Test')
expect(page).to have_content(@success.id)
expect(page).to have_content('Deploy')
expect(page).to have_content(@failed.id)
@@ -131,6 +146,7 @@ describe "Pipelines" do
expect(page).to have_content(@external.id)
expect(page).to have_content('Retry failed')
expect(page).to have_content('Cancel running')
+ expect(page).to have_link('Play')
end
context 'retrying builds' do
@@ -154,6 +170,12 @@ describe "Pipelines" do
it { expect(page).to have_selector('.ci-canceled') }
end
end
+
+ context 'playing manual build' do
+ before { click_link('Play') }
+
+ it { expect(@manual.reload).to be_pending }
+ end
end
describe 'POST /:project/pipelines' do
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 01e90618a9..75166bca11 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -6,7 +6,7 @@ feature 'list of badges' do
project = create(:project)
project.team << [user, :master]
login_as(user)
- visit namespace_project_badges_path(project.namespace, project)
+ visit namespace_project_pipelines_settings_path(project.namespace, project)
end
scenario 'user displays list of badges' do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
new file mode 100644
index 0000000000..79abba2185
--- /dev/null
+++ b/spec/features/projects/branches_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'Branches', feature: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+
+ before do
+ login_as :user
+ project.team << [@user, :developer]
+ end
+
+ describe 'Initial branches page' do
+ it 'shows all the branches' do
+ visit namespace_project_branches_path(project.namespace, project)
+
+ repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
+ expect(page).to have_content("Protected branches can be managed in project settings")
+ end
+ end
+
+ describe 'Find branches' do
+ it 'shows filtered branches', js: true do
+ visit namespace_project_branches_path(project.namespace, project, project.id)
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('fix')
+ expect(find('.all-branches')).to have_selector('li', count: 1)
+ end
+ end
+end
diff --git a/spec/features/projects/branches_spec.rb~HEAD b/spec/features/projects/branches_spec.rb~HEAD
new file mode 100644
index 0000000000..79abba2185
--- /dev/null
+++ b/spec/features/projects/branches_spec.rb~HEAD
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'Branches', feature: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+
+ before do
+ login_as :user
+ project.team << [@user, :developer]
+ end
+
+ describe 'Initial branches page' do
+ it 'shows all the branches' do
+ visit namespace_project_branches_path(project.namespace, project)
+
+ repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
+ expect(page).to have_content("Protected branches can be managed in project settings")
+ end
+ end
+
+ describe 'Find branches' do
+ it 'shows filtered branches', js: true do
+ visit namespace_project_branches_path(project.namespace, project, project.id)
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('fix')
+ expect(find('.all-branches')).to have_selector('li', count: 1)
+ end
+ end
+end
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index 15c381c0f5..fcdf7870f3 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -20,7 +20,6 @@ feature 'project commit builds' do
visit builds_namespace_project_commit_path(project.namespace,
project, project.commit.sha)
-
expect(page).to have_content('Builds')
end
end
diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb
index f88c0616b5..1b4ff6b6f1 100644
--- a/spec/features/projects/commits/cherry_pick_spec.rb
+++ b/spec/features/projects/commits/cherry_pick_spec.rb
@@ -5,7 +5,6 @@ describe 'Cherry-pick Commits' do
let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') }
-
before do
login_as :user
project.team << [@user, :master]
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 073a83b689..9ebef505b9 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -24,6 +24,7 @@ feature 'User wants to add a .gitignore file', feature: true do
end
wait_for_ajax
+ expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Rails')
expect(page).to have_content('/.bundle')
expect(page).to have_content('# Gemfile.lock, .ruby-version, .ruby-gemset')
end
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
index d516e8ce55..fca40f68b0 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -19,11 +19,12 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do
find('.js-gitlab-ci-yml-selector').click
wait_for_ajax
within '.gitlab-ci-yml-selector' do
- find('.dropdown-input-field').set('jekyll')
- find('.dropdown-content li', text: 'jekyll').click
+ find('.dropdown-input-field').set('Jekyll')
+ find('.dropdown-content li', text: 'Jekyll').click
end
wait_for_ajax
+ expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Jekyll')
expect(page).to have_content('This file is a template, and might need editing before it works on your project')
expect(page).to have_content('jekyll build -d test')
end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index c5fb0fc783..7835e1678a 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -8,6 +8,7 @@ feature 'project import', feature: true, js: true do
let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
let(:project) { Project.last }
+ let(:project_hook) { Gitlab::Git::Hook.new('post-receive', project.repository.path) }
background do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
@@ -24,7 +25,7 @@ feature 'project import', feature: true, js: true do
visit new_project_path
select2('2', from: '#project_namespace_id')
- fill_in :project_path, with:'test-project-path', visible: true
+ fill_in :project_path, with: 'test-project-path', visible: true
click_link 'GitLab export'
expect(page).to have_content('GitLab project export')
@@ -37,11 +38,43 @@ feature 'project import', feature: true, js: true do
expect(project).not_to be_nil
expect(project.issues).not_to be_empty
expect(project.merge_requests).not_to be_empty
- expect(project.repo_exists?).to be true
+ expect(project_hook).to exist
expect(wiki_exists?).to be true
expect(project.import_status).to eq('finished')
end
+ scenario 'invalid project' do
+ project = create(:project, namespace_id: 2)
+
+ visit new_project_path
+
+ select2('2', from: '#project_namespace_id')
+ fill_in :project_path, with: project.name, visible: true
+ click_link 'GitLab export'
+
+ attach_file('file', file)
+ click_on 'Import project'
+
+ page.within('.flash-container') do
+ expect(page).to have_content('Project could not be imported')
+ end
+ end
+
+ scenario 'project with no name' do
+ create(:project, namespace_id: 2)
+
+ visit new_project_path
+
+ select2('2', from: '#project_namespace_id')
+
+ # click on disabled element
+ find(:link, 'GitLab export').trigger('click')
+
+ page.within('.flash-container') do
+ expect(page).to have_content('Please enter path and name')
+ end
+ end
+
def wiki_exists?
wiki = ProjectWiki.new(project)
File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty?
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 1fd04416d9..7bb0d26b21 100644
Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 461f173792..81b0c991d4 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -1,7 +1,6 @@
require 'spec_helper'
feature 'Issue prioritization', feature: true do
-
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
@@ -15,7 +14,6 @@ feature 'Issue prioritization', feature: true do
# According to https://gitlab.com/gitlab-org/gitlab-ce/issues/14189#note_4360653
context 'when issues have one label' do
scenario 'Are sorted properly' do
-
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
issue_2 = create(:issue, title: 'issue_2', project: project)
@@ -46,7 +44,6 @@ feature 'Issue prioritization', feature: true do
context 'when issues have multiple labels' do
scenario 'Are sorted properly' do
-
# Issues
issue_1 = create(:issue, title: 'issue_1', project: project)
issue_2 = create(:issue, title: 'issue_2', project: project)
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 6a39c302f5..98ba93b403 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -76,7 +76,7 @@ feature 'Prioritize labels', feature: true do
expect(page.all('li').last).to have_content('bug')
end
- visit current_url
+ refresh
wait_for_ajax
page.within('.prioritized-labels') do
diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
index 4d5d656f00..ff9b600780 100644
--- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
@@ -5,9 +5,6 @@ feature 'Projects > Members > Group member cannot request access to his group pr
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
- background do
- end
-
scenario 'owner does not see the request access button' do
group.add_owner(user)
login_and_visit_project_page(user)
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index 5fe4caa12f..f7fcd9b673 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -40,8 +40,8 @@ feature 'Projects > Members > Master manages access requests', feature: true do
end
def expect_visible_access_request(project, user)
- expect(project.members.request.exists?(user_id: user)).to be_truthy
- expect(page).to have_content "#{project.name} access requests (1)"
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
+ expect(page).to have_content "#{project.name} access requests 1"
expect(page).to have_content user.name
end
end
diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
new file mode 100644
index 0000000000..9564347e73
--- /dev/null
+++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Member cannot request access to his project', feature: true do
+ let(:member) { create(:user) }
+ let(:project) { create(:project) }
+
+ background do
+ project.team << [member, :developer]
+ login_as(member)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'member does not see the request access button' do
+ expect(page).not_to have_content 'Request Access'
+ end
+end
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
new file mode 100644
index 0000000000..79dec44281
--- /dev/null
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Member leaves project', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ background do
+ project.team << [user, :developer]
+ login_as(user)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'user leaves project' do
+ click_link 'Leave Project'
+
+ expect(current_path).to eq(dashboard_projects_path)
+ expect(project.users.exists?(user.id)).to be_falsey
+ end
+end
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
new file mode 100644
index 0000000000..67811b1048
--- /dev/null
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Owner cannot leave project', feature: true do
+ let(:owner) { create(:user) }
+ let(:project) { create(:project) }
+
+ background do
+ project.team << [owner, :owner]
+ login_as(owner)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'user does not see a "Leave Project" link' do
+ expect(page).not_to have_content 'Leave Project'
+ end
+end
diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
new file mode 100644
index 0000000000..0e54c4fdf2
--- /dev/null
+++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Owner cannot request access to his project', feature: true do
+ let(:owner) { create(:user) }
+ let(:project) { create(:project) }
+
+ background do
+ project.team << [owner, :owner]
+ login_as(owner)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'owner does not see the request access button' do
+ expect(page).not_to have_content 'Request Access'
+ end
+end
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index fd92a3a2f0..56ede8eb5b 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -11,22 +11,30 @@ feature 'Projects > Members > User requests access', feature: true do
visit namespace_project_path(project.namespace, project)
end
+ scenario 'request access feature is disabled' do
+ project.update_attributes(request_access_enabled: false)
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).not_to have_content 'Request Access'
+ end
+
scenario 'user can request access to a project' do
perform_enqueued_jobs { click_link 'Request Access' }
expect(ActionMailer::Base.deliveries.last.to).to eq [master.notification_email]
expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.name_with_namespace} project"
- expect(project.members.request.exists?(user_id: user)).to be_truthy
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
expect(page).to have_content 'Your request for access has been queued for review.'
expect(page).to have_content 'Withdraw Access Request'
+ expect(page).not_to have_content 'Leave Project'
end
scenario 'user is not listed in the project members page' do
click_link 'Request Access'
- expect(project.members.request.exists?(user_id: user)).to be_truthy
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
open_project_settings_menu
click_link 'Members'
@@ -40,11 +48,11 @@ feature 'Projects > Members > User requests access', feature: true do
scenario 'user can withdraw its request for access' do
click_link 'Request Access'
- expect(project.members.request.exists?(user_id: user)).to be_truthy
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
click_link 'Withdraw Access Request'
- expect(project.members.request.exists?(user_id: user)).to be_falsey
+ expect(project.requesters.exists?(user_id: user)).to be_falsey
expect(page).to have_content 'Your access request to the project has been withdrawn.'
end
diff --git a/spec/features/projects/slack_service/slack_service_spec.rb b/spec/features/projects/slack_service/slack_service_spec.rb
new file mode 100644
index 0000000000..16541f51d9
--- /dev/null
+++ b/spec/features/projects/slack_service/slack_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+feature 'Projects > Slack service > Setup events', feature: true do
+ let(:user) { create(:user) }
+ let(:service) { SlackService.new }
+ let(:project) { create(:project, slack_service: service) }
+
+ background do
+ service.fields
+ service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, build_channel: 6, wiki_page_channel: 7)
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ scenario 'user can filter events by channel' do
+ visit edit_namespace_project_service_path(project.namespace, project, service)
+
+ expect(page.find_field("service_push_channel").value).to have_content '1'
+ expect(page.find_field("service_issue_channel").value).to have_content '2'
+ expect(page.find_field("service_merge_request_channel").value).to have_content '3'
+ expect(page.find_field("service_note_channel").value).to have_content '4'
+ expect(page.find_field("service_tag_push_channel").value).to have_content '5'
+ expect(page.find_field("service_build_channel").value).to have_content '6'
+ expect(page.find_field("service_wiki_page_channel").value).to have_content '7'
+ end
+end
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
new file mode 100644
index 0000000000..a1c386ddc1
--- /dev/null
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -0,0 +1,140 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User previews markdown changes', feature: true, js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:wiki_content) do
+ <<-HEREDOC
+[regular link](regular)
+[relative link 1](../relative)
+[relative link 2](./relative)
+[relative link 3](./e/f/relative)
+ HEREDOC
+ end
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_path(project.namespace, project)
+ click_link 'Wiki'
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ context "while creating a new wiki page" do
+ context "when there are no spaces or hyphens in the page name" do
+ it "rewrites relative links as expected" do
+ click_link 'New Page'
+ fill_in :new_wiki_path, with: 'a/b/c/d'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: wiki_content
+ click_on "Preview"
+
+ expect(page).to have_content("regular link")
+
+ expect(page.html).to include("regular link")
+ expect(page.html).to include("relative link 1")
+ expect(page.html).to include("relative link 2")
+ expect(page.html).to include("relative link 3")
+ end
+ end
+
+ context "when there are spaces in the page name" do
+ it "rewrites relative links as expected" do
+ click_link 'New Page'
+ fill_in :new_wiki_path, with: 'a page/b page/c page/d page'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: wiki_content
+ click_on "Preview"
+
+ expect(page).to have_content("regular link")
+
+ expect(page.html).to include("regular link")
+ expect(page.html).to include("relative link 1")
+ expect(page.html).to include("relative link 2")
+ expect(page.html).to include("relative link 3")
+ end
+ end
+
+ context "when there are hyphens in the page name" do
+ it "rewrites relative links as expected" do
+ click_link 'New Page'
+ fill_in :new_wiki_path, with: 'a-page/b-page/c-page/d-page'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: wiki_content
+ click_on "Preview"
+
+ expect(page).to have_content("regular link")
+
+ expect(page.html).to include("regular link")
+ expect(page.html).to include("relative link 1")
+ expect(page.html).to include("relative link 2")
+ expect(page.html).to include("relative link 3")
+ end
+ end
+ end
+
+ context "while editing a wiki page" do
+ def create_wiki_page(path)
+ click_link 'New Page'
+ fill_in :new_wiki_path, with: path
+ click_button 'Create Page'
+ fill_in :wiki_content, with: 'content'
+ click_on "Create page"
+ end
+
+ context "when there are no spaces or hyphens in the page name" do
+ it "rewrites relative links as expected" do
+ create_wiki_page 'a/b/c/d'
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: wiki_content
+ click_on "Preview"
+
+ expect(page).to have_content("regular link")
+
+ expect(page.html).to include("regular link")
+ expect(page.html).to include("relative link 1")
+ expect(page.html).to include("relative link 2")
+ expect(page.html).to include("relative link 3")
+ end
+ end
+
+ context "when there are spaces in the page name" do
+ it "rewrites relative links as expected" do
+ create_wiki_page 'a page/b page/c page/d page'
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: wiki_content
+ click_on "Preview"
+
+ expect(page).to have_content("regular link")
+
+ expect(page.html).to include("regular link")
+ expect(page.html).to include("relative link 1")
+ expect(page.html).to include("relative link 2")
+ expect(page.html).to include("relative link 3")
+ end
+ end
+
+ context "when there are hyphens in the page name" do
+ it "rewrites relative links as expected" do
+ create_wiki_page 'a-page/b-page/c-page/d-page'
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: wiki_content
+ click_on "Preview"
+
+ expect(page).to have_content("regular link")
+
+ expect(page.html).to include("regular link")
+ expect(page.html).to include("relative link 1")
+ expect(page.html).to include("relative link 2")
+ expect(page.html).to include("relative link 3")
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 7e6eef6587..7afd83b725 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -30,18 +30,48 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
end
- scenario 'via the "new wiki page" page', js: true do
- click_link 'New Page'
+ context 'via the "new wiki page" page' do
+ scenario 'when the wiki page has a single word name', js: true do
+ click_link 'New Page'
- fill_in :new_wiki_path, with: 'foo'
- click_button 'Create Page'
+ fill_in :new_wiki_path, with: 'foo'
+ click_button 'Create Page'
- fill_in :wiki_content, with: 'My awesome wiki!'
- click_button 'Create page'
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
- expect(page).to have_content('Foo')
- expect(page).to have_content("last edited by #{user.name}")
- expect(page).to have_content('My awesome wiki!')
+ expect(page).to have_content('Foo')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+
+ scenario 'when the wiki page has spaces in the name', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'Spaces in the name'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Spaces in the name')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+
+ scenario 'when the wiki page has hyphens in the name', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'hyphens-in-the-name'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Hyphens in the name')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
end
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 9dd0378d16..6fa8298d48 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -70,22 +70,6 @@ feature 'Project', feature: true do
end
end
- describe 'leave project link' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
-
- before do
- login_with(user)
- project.team.add_user(user, Gitlab::Access::MASTER)
- visit namespace_project_path(project.namespace, project)
- end
-
- it 'click project-settings and find leave project' do
- find('#project-settings-button').click
- expect(page).to have_link('Leave Project')
- end
- end
-
describe 'project title' do
include WaitForAjax
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
new file mode 100644
index 0000000000..d94dee0c79
--- /dev/null
+++ b/spec/features/protected_branches_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+feature 'Projected Branches', feature: true, js: true do
+ let(:user) { create(:user, :admin) }
+ let(:project) { create(:project) }
+
+ before { login_as(user) }
+
+ def set_protected_branch_name(branch_name)
+ find(".js-protected-branch-select").click
+ find(".dropdown-input-field").set(branch_name)
+ click_on "Create Protected Branch: #{branch_name}"
+ end
+
+ describe "explicit protected branches" do
+ it "allows creating explicit protected branches" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('some-branch') }
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.name).to eq('some-branch')
+ end
+
+ it "displays the last commit on the matching branch if it exists" do
+ commit = create(:commit, project: project)
+ project.repository.add_branch(user, 'some-branch', commit.id)
+
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content(commit.id[0..7]) }
+ end
+
+ it "displays an error message if the named branch does not exist" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('branch was removed') }
+ end
+ end
+
+ describe "wildcard protected branches" do
+ it "allows creating protected branches with a wildcard" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('*-stable')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('*-stable') }
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.name).to eq('*-stable')
+ end
+
+ it "displays the number of matching branches" do
+ project.repository.add_branch(user, 'production-stable', 'master')
+ project.repository.add_branch(user, 'staging-stable', 'master')
+
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('*-stable')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content("2 matching branches") }
+ end
+
+ it "displays all the branches matching the wildcard" do
+ project.repository.add_branch(user, 'production-stable', 'master')
+ project.repository.add_branch(user, 'staging-stable', 'master')
+ project.repository.add_branch(user, 'development', 'master')
+ create(:protected_branch, project: project, name: "*-stable")
+
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ click_on "2 matching branches"
+
+ within(".protected-branches-list") do
+ expect(page).to have_content("production-stable")
+ expect(page).to have_content("staging-stable")
+ expect(page).not_to have_content("development")
+ end
+ end
+ end
+end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index b9e63a7152..83e8a62793 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
describe "Search", feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
+ let!(:issue) { create(:issue, project: project, assignee: user) }
+ let!(:issue2) { create(:issue, project: project, author: user) }
before do
login_with(user)
@@ -48,8 +50,16 @@ describe "Search", feature: true do
end
end
-
describe 'Right header search field', feature: true do
+ it 'allows enter key to search', js: true do
+ visit namespace_project_path(project.namespace, project)
+ fill_in 'search', with: 'gitlab'
+ find('#search').native.send_keys(:enter)
+
+ page.within '.title' do
+ expect(page).to have_content 'Search'
+ end
+ end
describe 'Search in project page' do
before do
@@ -73,7 +83,6 @@ describe "Search", feature: true do
end
context 'click the links in the category search dropdown', js: true do
-
before do
page.find('#search').click
end
@@ -124,6 +133,4 @@ describe "Search", feature: true do
end
end
end
-
-
end
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
index 71b783b727..35fcef7a71 100644
--- a/spec/features/security/group/internal_access_spec.rb
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -76,7 +76,6 @@ describe 'Internal Group access', feature: true do
it { is_expected.to be_denied_for :visitor }
end
-
describe 'GET /groups/:path/group_members' do
subject { group_group_members_path(group) }
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index cc9aee802f..75a9334262 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -76,7 +76,6 @@ describe 'Private Group access', feature: true do
it { is_expected.to be_denied_for :visitor }
end
-
describe 'GET /groups/:path/group_members' do
subject { group_group_members_path(group) }
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
index db986683db..6c5ee93970 100644
--- a/spec/features/security/group/public_access_spec.rb
+++ b/spec/features/security/group/public_access_spec.rb
@@ -76,7 +76,6 @@ describe 'Public Group access', feature: true do
it { is_expected.to be_allowed_for :visitor }
end
-
describe 'GET /groups/:path/group_members' do
subject { group_group_members_path(group) }
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 8625ea6bc1..b6acc50934 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -288,4 +288,161 @@ 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/pipelines" do
+ subject { namespace_project_pipelines_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
+
+ describe "GET /:project_path/pipelines/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ 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
+
+ describe "GET /:project_path/builds" do
+ subject { namespace_project_builds_path(project.namespace, project) }
+
+ context "when allowed for public and internal" do
+ before { project.update(public_builds: true) }
+
+ 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
+
+ context "when disallowed for public and internal" do
+ before { project.update(public_builds: false) }
+
+ 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
+
+ describe "GET /:project_path/builds/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ subject { namespace_project_build_path(project.namespace, project, build.id) }
+
+ context "when allowed for public and internal" do
+ before { project.update(public_builds: true) }
+
+ 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
+
+ context "when disallowed for public and internal" do
+ before { project.update(public_builds: false) }
+
+ 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
+
+ describe "GET /:project_path/environments" do
+ subject { namespace_project_environments_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
+
+ describe "GET /:project_path/environments/:id" do
+ let(:environment) { create(:environment, project: project) }
+ subject { namespace_project_environment_path(project.namespace, project, environment) }
+
+ 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
+
+ describe "GET /:project_path/environments/new" do
+ subject { new_namespace_project_environment_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_denied_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
+
+ 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 544270b403..ccb5c06dab 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -260,4 +260,125 @@ 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/pipelines" do
+ subject { namespace_project_pipelines_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
+
+ describe "GET /:project_path/pipelines/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ 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
+
+ describe "GET /:project_path/builds" do
+ subject { namespace_project_builds_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
+
+ describe "GET /:project_path/builds/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ subject { namespace_project_build_path(project.namespace, project, build.id) }
+
+ 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
+
+ describe "GET /:project_path/environments" do
+ subject { namespace_project_environments_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
+
+ describe "GET /:project_path/environments/:id" do
+ let(:environment) { create(:environment, project: project) }
+ subject { namespace_project_environment_path(project.namespace, project, environment) }
+
+ 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
+
+ describe "GET /:project_path/environments/new" do
+ subject { new_namespace_project_environment_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_denied_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
+
+ 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 f6c6687e16..985663e7c9 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -109,6 +109,35 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :external }
end
+ describe "GET /:project_path/pipelines" do
+ subject { namespace_project_pipelines_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
+
+ describe "GET /:project_path/pipelines/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ 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
+
describe "GET /:project_path/builds" do
subject { namespace_project_builds_path(project.namespace, project) }
@@ -191,7 +220,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environments_path(project.namespace, project, environment) }
+ subject { namespace_project_environment_path(project.namespace, project, environment) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
@@ -397,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/signup_spec.rb b/spec/features/signup_spec.rb
index 4229e82b44..a752c1d723 100644
--- a/spec/features/signup_spec.rb
+++ b/spec/features/signup_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
feature 'Signup', feature: true do
describe 'signup with no errors' do
-
context "when sending confirmation email" do
before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) }
@@ -40,7 +39,6 @@ feature 'Signup', feature: true do
expect(page).to have_content("Welcome! You have signed up successfully.")
end
end
-
end
describe 'signup with errors' do
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index f0990118e3..0f30f56253 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -22,7 +22,6 @@ feature 'Master deletes tag', feature: true do
namespace_project_tags_path(project.namespace, project))
expect(page).not_to have_content 'v1.1.0'
end
-
end
context 'from a specific tag page' do
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index 14613754f7..9335f5bf12 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
- 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
+ # 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"
+
+ 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/features/users_spec.rb b/spec/features/users_spec.rb
index cf11604039..b5a94fe038 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -47,5 +47,4 @@ feature 'Users', feature: true do
def number_of_errors_on_page(page)
page.find('#error_explanation').find('ul').all('li').count
end
-
end
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
new file mode 100644
index 0000000000..269c76d004
--- /dev/null
+++ b/spec/finders/branches_finder_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+describe BranchesFinder do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+
+ describe '#execute' do
+ context 'sort only' do
+ it 'sorts by name' do
+ branches_finder = described_class.new(repository, {})
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq("'test'")
+ end
+
+ it 'sorts by recently_updated' do
+ branches_finder = described_class.new(repository, { sort: 'recently_updated' })
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('crlf-diff')
+ end
+
+ it 'sorts by last_updated' do
+ branches_finder = described_class.new(repository, { sort: 'last_updated' })
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('feature')
+ end
+ end
+
+ context 'filter only' do
+ it 'filters branches by name' do
+ branches_finder = described_class.new(repository, { search: 'fix' })
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('fix')
+ expect(result.count).to eq(1)
+ end
+
+ it 'does not find any branch with that name' do
+ branches_finder = described_class.new(repository, { search: 'random' })
+
+ result = branches_finder.execute
+
+ expect(result.count).to eq(0)
+ end
+ end
+
+ context 'filter and sort' do
+ it 'filters branches by name and sorts by recently_updated' do
+ params = { sort: 'recently_updated', search: 'feature' }
+ branches_finder = described_class.new(repository, params)
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('feature_conflict')
+ expect(result.count).to eq(2)
+ end
+
+ it 'filters branches by name and sorts by last_updated' do
+ params = { sort: 'last_updated', search: 'feature' }
+ branches_finder = described_class.new(repository, params)
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('feature')
+ expect(result.count).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index fdd3849816..fbe09b28b3 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -12,14 +12,12 @@ describe GroupProjectsFinder do
let!(:shared_project_2) { create(:project, :private, path: '4') }
let!(:shared_project_3) { create(:project, :internal, path: '5') }
-
before do
shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
shared_project_2.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
shared_project_3.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
end
-
describe 'with a group member current user' do
before { group.add_user(current_user, Gitlab::Access::MASTER) }
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 639b28d49e..8db897b164 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -11,7 +11,7 @@ describe NotesFinder do
project.team << [user, :master]
end
- describe :execute do
+ describe '#execute' do
let(:params) { { target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
before do
@@ -49,6 +49,13 @@ describe NotesFinder do
user = create(:user)
expect { NotesFinder.new.execute(project, user, params) }.to raise_error(ActiveRecord::RecordNotFound)
end
+
+ it 'raises an error for project members with guest role' do
+ user = create(:user)
+ project.team << [user, :guest]
+
+ expect { NotesFinder.new.execute(project, user, params) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
end
end
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 810016c965..28bdc18e84 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -69,7 +69,6 @@ describe SnippetsFinder do
expect(snippets).to include(@snippet3)
expect(snippets).not_to include(@snippet2, @snippet1)
end
-
end
context 'by_project filter' do
diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md
new file mode 100644
index 0000000000..2652a842c0
--- /dev/null
+++ b/spec/fixtures/blockquote_fence_after.md
@@ -0,0 +1,115 @@
+Single `>>>` inside code block:
+
+```
+# Code
+>>>
+# Code
+```
+
+Double `>>>` inside code block:
+
+```txt
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Blockquote outside code block:
+
+> Quote
+
+Code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> ```
+>
+> Quote
+
+Single `>>>` inside code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> >>>
+> # Code
+> ```
+>
+> Quote
+
+Double `>>>` inside code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> >>>
+> # Code
+> >>>
+> # Code
+> ```
+>
+> Quote
+
+Single `>>>` inside HTML:
+
+
+# Code
+>>>
+# Code
+
+
+Double `>>>` inside HTML:
+
+
+# Code
+>>>
+# Code
+>>>
+# Code
+
+
+Blockquote outside HTML:
+
+> Quote
+
+HTML inside blockquote:
+
+> Quote
+>
+>
+> # Code
+>
+>
+> Quote
+
+Single `>>>` inside HTML inside blockquote:
+
+> Quote
+>
+>
+> # Code
+> >>>
+> # Code
+>
+>
+> Quote
+
+Double `>>>` inside HTML inside blockquote:
+
+> Quote
+>
+>
+> # Code
+> >>>
+> # Code
+> >>>
+> # Code
+>
+>
+> Quote
diff --git a/spec/fixtures/blockquote_fence_before.md b/spec/fixtures/blockquote_fence_before.md
new file mode 100644
index 0000000000..d52eec7289
--- /dev/null
+++ b/spec/fixtures/blockquote_fence_before.md
@@ -0,0 +1,131 @@
+Single `>>>` inside code block:
+
+```
+# Code
+>>>
+# Code
+```
+
+Double `>>>` inside code block:
+
+```txt
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Blockquote outside code block:
+
+>>>
+Quote
+>>>
+
+Code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+```
+
+Quote
+>>>
+
+Single `>>>` inside code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+>>>
+# Code
+```
+
+Quote
+>>>
+
+Double `>>>` inside code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Quote
+>>>
+
+Single `>>>` inside HTML:
+
+
+# Code
+>>>
+# Code
+
+
+Double `>>>` inside HTML:
+
+
+# Code
+>>>
+# Code
+>>>
+# Code
+
+
+Blockquote outside HTML:
+
+>>>
+Quote
+>>>
+
+HTML inside blockquote:
+
+>>>
+Quote
+
+
+# Code
+
+
+Quote
+>>>
+
+Single `>>>` inside HTML inside blockquote:
+
+>>>
+Quote
+
+
+# Code
+>>>
+# Code
+
+
+Quote
+>>>
+
+Double `>>>` inside HTML inside blockquote:
+
+>>>
+Quote
+
+
+# Code
+>>>
+# Code
+>>>
+# Code
+
+
+Quote
+>>>
diff --git a/spec/fixtures/dk.png b/spec/fixtures/dk.png
index 87ce25e877..1247f2fecd 100644
Binary files a/spec/fixtures/dk.png and b/spec/fixtures/dk.png differ
diff --git a/spec/fixtures/domain_blacklist.txt b/spec/fixtures/domain_blacklist.txt
new file mode 100644
index 0000000000..baeb11eda9
--- /dev/null
+++ b/spec/fixtures/domain_blacklist.txt
@@ -0,0 +1,3 @@
+example.com
+test.com
+foo.bar
\ No newline at end of file
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index c75d28d980..f3e7c2d1a9 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -256,3 +256,7 @@ However the wrapping tags can not be mixed as such -
- [+ additions +}
- {- delletions -]
- [- delletions -}
+
+### Videos
+
+![My Video](/assets/videos/gitlab-demo.mp4)
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
index a8b7907d4b..37066c8e93 100644
--- a/spec/fixtures/parallel_diff_result.yml
+++ b/spec/fixtures/parallel_diff_result.yml
@@ -3,322 +3,798 @@
:type: match
:number: 6
:text: "@@ -6,12 +6,18 @@ module Popen"
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :line_code:
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: match
:number: 6
:text: "@@ -6,12 +6,18 @@ module Popen"
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :line_code:
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 6
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 6
+ :new_line: 6
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 6
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 6
+ :new_line: 6
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 7
:text: |2
def popen(cmd, path=nil)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 7
+ :new_line: 7
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 7
:text: |2
def popen(cmd, path=nil)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 7
+ :new_line: 7
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 8
:text: |2
unless cmd.is_a?(Array)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 8
+ :new_line: 8
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 8
:text: |2
unless cmd.is_a?(Array)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 8
+ :new_line: 8
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type: old
:number: 9
:text: |
- - raise "System commands must be given as an array of strings"
+ - raise "System commands must be given as an array of strings"
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 9
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 9
:text: |
- + raise RuntimeError, "System commands must be given as an array of strings"
+ + raise RuntimeError, "System commands must be given as an array of strings"
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 9
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 10
:text: |2
end
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 10
+ :new_line: 10
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 10
:text: |2
end
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 10
+ :new_line: 10
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 11
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 11
+ :new_line: 11
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 11
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 11
+ :new_line: 11
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 12
:text: |2
path ||= Dir.pwd
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 12
+ :new_line: 12
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 12
:text: |2
path ||= Dir.pwd
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 12
+ :new_line: 12
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type: old
:number: 13
:text: |
- - vars = { "PWD" => path }
+ - vars = { "PWD" => path }
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13
- :right:
- :type: old
- :number:
- :text: ''
- :line_code:
-- :left:
- :type: old
- :number: 14
- :text: |
- - options = { chdir: path }
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 13
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 13
:text: |
+
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 13
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+ :type: old
+ :number: 14
+ :text: |
+ - options = { chdir: path }
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 14
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 14
:text: |
+ vars = {
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 14
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 15
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 15
:text: |
- + "PWD" => path
+ + "PWD" => path
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 15
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 16
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 16
:text: |
+ }
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 16
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 17
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 17
:text: |
+
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 17
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 18
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 18
:text: |
+ options = {
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 18
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 19
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 19
:text: |
+ chdir: path
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 19
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 20
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 20
:text: |
+ }
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 20
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 15
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 15
+ :new_line: 21
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 21
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 15
+ :new_line: 21
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 16
:text: |2
unless File.directory?(path)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 16
+ :new_line: 22
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 22
:text: |2
unless File.directory?(path)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 16
+ :new_line: 22
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 17
:text: |2
FileUtils.mkdir_p(path)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 17
+ :new_line: 23
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 23
:text: |2
FileUtils.mkdir_p(path)
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 17
+ :new_line: 23
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type: match
:number: 19
:text: "@@ -19,6 +25,7 @@ module Popen"
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+ :line_code:
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: match
:number: 25
:text: "@@ -19,6 +25,7 @@ module Popen"
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+ :line_code:
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 19
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 19
+ :new_line: 25
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 25
:text: |2
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 19
+ :new_line: 25
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 20
:text: |2
- @cmd_output = ""
+ @cmd_output = ""
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 20
+ :new_line: 26
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 26
:text: |2
- @cmd_output = ""
+ @cmd_output = ""
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 20
+ :new_line: 26
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 21
:text: |2
@cmd_status = 0
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 21
+ :new_line: 27
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 27
:text: |2
@cmd_status = 0
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 21
+ :new_line: 27
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 28
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 28
:text: |
+
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 28
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 22
:text: |2
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 22
+ :new_line: 29
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 29
:text: |2
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 22
+ :new_line: 29
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 23
:text: |2
@cmd_output << stdout.read
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 23
+ :new_line: 30
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 30
:text: |2
@cmd_output << stdout.read
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 23
+ :new_line: 30
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 24
:text: |2
@cmd_output << stderr.read
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 24
+ :new_line: 31
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 31
:text: |2
@cmd_output << stderr.read
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 24
+ :new_line: 31
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
diff --git a/spec/fixtures/video_sample.mp4 b/spec/fixtures/video_sample.mp4
new file mode 100644
index 0000000000..acd4519099
Binary files /dev/null and b/spec/fixtures/video_sample.mp4 differ
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 6d1c02db29..bd0108f993 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(':type "assem"))')
+ result = helper.highlight(blob_name, no_context_content)
+ expect(result).to eq(%[:type "assem"))
])
end
it 'should highlight single block' do
- expected = %Q[(make-pathname :defaults name
-:type "assem"))]
+ expected = %Q[(make-pathname :defaults name
+:type "assem"))
]
- 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(+aaa
+ %q(+aaa
+bbb
- ccc
- ddd)
+ ddd
)
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 = [
- '(make-pathname :defaults name',
- ':type "assem"))'
- ]
-
- 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/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 45199d0f09..637b02d938 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -7,7 +7,13 @@ describe CiStatusHelper do
let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
describe 'ci_icon_for_status' do
- it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
- it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') }
+ it 'renders to correct svg on success' do
+ expect(helper).to receive(:render).with('shared/icons/icon_status_success.svg', anything)
+ helper.ci_icon_for_status(success_commit.status)
+ end
+ it 'renders the correct svg on failure' do
+ expect(helper).to receive(:render).with('shared/icons/icon_status_failed.svg', anything)
+ helper.ci_icon_for_status(failed_commit.status)
+ end
end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 52764f41e0..c2fd2c8a53 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -9,7 +9,7 @@ describe DiffHelper do
let(:diffs) { commit.diffs }
let(:diff) { diffs.first }
let(:diff_refs) { [commit.parent, commit] }
- let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
describe 'diff_view' do
it 'returns a valid value when cookie is set' do
@@ -30,26 +30,30 @@ describe DiffHelper do
expect(helper.diff_view).to eq 'inline'
end
end
-
- describe 'diff_hard_limit_enabled?' do
- it 'should return true if param is provided' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(diff_hard_limit_enabled?).to be_truthy
- end
-
- it 'should return false if param is not provided' do
- expect(diff_hard_limit_enabled?).to be_falsey
- end
- end
-
+
describe 'diff_options' do
it 'should return hard limit for a diff if force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } }
expect(diff_options).to include(Commit.max_diff_options)
end
- it 'should return safe limit for a diff if force diff is false' do
- expect(diff_options).not_to include(:max_lines, :max_files)
+ it 'should return hard limit for a diff if expand_all_diffs is true' do
+ allow(controller).to receive(:params) { { expand_all_diffs: true } }
+ expect(diff_options).to include(Commit.max_diff_options)
+ end
+
+ it 'should return no collapse false' do
+ expect(diff_options).to include(no_collapse: false)
+ end
+
+ it 'should return no collapse true if expand_all_diffs' do
+ allow(controller).to receive(:params) { { expand_all_diffs: true } }
+ expect(diff_options).to include(no_collapse: true)
+ end
+
+ it 'should return no collapse true if action name diff_for_path' do
+ allow(controller).to receive(:action_name) { 'diff_for_path' }
+ expect(diff_options).to include(no_collapse: true)
end
end
@@ -59,7 +63,7 @@ describe DiffHelper do
end
it 'should return js class when bottom lines should be unfolded' do
- expect(unfold_bottom_class(true)).to eq('js-unfold-bottom')
+ expect(unfold_bottom_class(true)).to include('js-unfold-bottom')
end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index c0d2be98e8..6b5e3d93d4 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -57,7 +57,7 @@ describe EventsHelper do
expected = '' \
"def test\n" \
" \'hello world\'\n" \
- "end" \
+ "end\n" \
'
'
expect(helper.event_note(input)).to eq(expected)
end
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
new file mode 100644
index 0000000000..08a9350325
--- /dev/null
+++ b/spec/helpers/notes_helper_spec.rb
@@ -0,0 +1,46 @@
+require "spec_helper"
+
+describe NotesHelper do
+ describe "#notes_max_access_for_users" do
+ let(:owner) { create(:owner) }
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, namespace: group) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:owner_note) { create(:note, author: owner, project: project) }
+ let(:master_note) { create(:note, author: master, project: project) }
+ let(:reporter_note) { create(:note, author: reporter, project: project) }
+ let!(:notes) { [owner_note, master_note, reporter_note] }
+
+ before do
+ group.add_owner(owner)
+ project.team << [master, :master]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ it 'return human access levels' do
+ original_method = project.team.method(:human_max_access)
+ expect_any_instance_of(ProjectTeam).to receive(:human_max_access).exactly(3).times do |*args|
+ original_method.call(args[1])
+ end
+
+ expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
+ expect(helper.note_max_access_for_user(master_note)).to eq('Master')
+ expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter')
+ # Call it again to ensure value is cached
+ expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
+ end
+
+ it 'handles access in different projects' do
+ second_project = create(:empty_project)
+ second_project.team << [master, :reporter]
+ other_note = create(:note, author: master, project: second_project)
+
+ expect(helper.note_max_access_for_user(master_note)).to eq('Master')
+ expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
+ end
+ end
+end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 09e0bbfd00..604204cca0 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -123,11 +123,17 @@ describe ProjectsHelper do
end
describe '#sanitized_import_error' do
+ let(:project) { create(:project) }
+
+ before do
+ allow(project).to receive(:repository_storage_path).and_return('/base/repo/path')
+ end
+
it 'removes the repo path' do
- repo = File.join(Gitlab.config.gitlab_shell.repos_path, '/namespace/test.git')
+ repo = '/base/repo/path/namespace/test.git'
import_error = "Could not clone #{repo}\n"
- expect(sanitize_repo_path(import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git')
+ expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git')
end
end
end
diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index 3f62527c5b..bf3ed5c094 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -1,29 +1,11 @@
require 'spec_helper'
describe TimeHelper do
- describe "#duration_in_words" do
- it "returns minutes and seconds" do
- intervals_in_words = {
- 100 => "1 minute 40 seconds",
- 121 => "2 minutes 1 second",
- 3721 => "62 minutes 1 second",
- 0 => "0 seconds"
- }
-
- intervals_in_words.each do |interval, expectation|
- expect(duration_in_words(Time.now + interval, Time.now)).to eq(expectation)
- end
- end
-
- it "calculates interval from now if there is no finished_at" do
- expect(duration_in_words(nil, Time.now - 5)).to eq("5 seconds")
- end
- end
-
describe "#time_interval_in_words" do
it "returns minutes and seconds" do
intervals_in_words = {
100 => "1 minute 40 seconds",
+ 100.32 => "1 minute 40 seconds",
121 => "2 minutes 1 second",
3721 => "62 minutes 1 second",
0 => "0 seconds"
@@ -34,4 +16,20 @@ describe TimeHelper do
end
end
end
+
+ describe "#duration_in_numbers" do
+ it "returns minutes and seconds" do
+ duration_in_numbers = {
+ [100, 0] => "01:40",
+ [121, 0] => "02:01",
+ [3721, 0] => "01:02:01",
+ [0, 0] => "00:00",
+ [nil, Time.now.to_i - 42] => "00:42"
+ }
+
+ duration_in_numbers.each do |interval, expectation|
+ expect(duration_in_numbers(*interval)).to eq(expectation)
+ end
+ end
+ end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index ff98249570..db3ad1b99e 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -1,12 +1,6 @@
require 'spec_helper'
describe VisibilityLevelHelper do
- include Haml::Helpers
-
- before :all do
- init_haml_helpers
- end
-
let(:project) { build(:project) }
let(:group) { build(:group) }
let(:personal_snippet) { build(:personal_snippet) }
@@ -95,6 +89,5 @@ describe VisibilityLevelHelper do
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
-
end
end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
new file mode 100644
index 0000000000..baab30f482
--- /dev/null
+++ b/spec/initializers/6_validations_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+require_relative '../../config/initializers/6_validations.rb'
+
+describe '6_validations', lib: true do
+ before :all do
+ FileUtils.mkdir_p('tmp/tests/paths/a/b/c/d')
+ FileUtils.mkdir_p('tmp/tests/paths/a/b/c2')
+ FileUtils.mkdir_p('tmp/tests/paths/a/b/d')
+ end
+
+ after :all do
+ FileUtils.rm_rf('tmp/tests/paths')
+ end
+
+ context 'with correct settings' do
+ before do
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/d')
+ end
+
+ it 'passes through' do
+ expect { validate_storages }.not_to raise_error
+ end
+ end
+
+ context 'with invalid storage names' do
+ before do
+ mock_storages('name with spaces' => 'tmp/tests/paths/a/b/c')
+ end
+
+ it 'throws an error' do
+ expect { validate_storages }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.')
+ end
+ end
+
+ context 'with nested storage paths' do
+ before do
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c/d')
+ end
+
+ it 'throws an error' do
+ expect { validate_storages }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.')
+ end
+ end
+
+ context 'with similar but un-nested storage paths' do
+ before do
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c2')
+ end
+
+ it 'passes through' do
+ expect { validate_storages }.not_to raise_error
+ end
+ end
+
+ def mock_storages(storages)
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+ end
+end
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
index e58f2c80e9..47b4e43182 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -1,7 +1,7 @@
+require 'spec_helper'
require_relative '../../config/initializers/1_settings'
describe Settings, lib: true do
-
describe '#host_without_www' do
context 'URL with protocol' do
it 'returns the host' do
@@ -40,5 +40,4 @@ describe Settings, lib: true do
end
end
end
-
end
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index 4bb149f25f..290e47763e 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -6,14 +6,22 @@ describe 'trusted_proxies', lib: true do
set_trusted_proxies([])
end
- it 'preserves private IPs as remote_ip' do
+ it 'preserves private IPs' do
request = stub_request('HTTP_X_FORWARDED_FOR' => '10.1.5.89')
expect(request.remote_ip).to eq('10.1.5.89')
+ expect(request.ip).to eq('10.1.5.89')
end
- it 'filters out localhost from remote_ip' do
+ it 'filters out localhost' do
request = stub_request('HTTP_X_FORWARDED_FOR' => '1.1.1.1, 10.1.5.89, 127.0.0.1')
expect(request.remote_ip).to eq('10.1.5.89')
+ expect(request.ip).to eq('10.1.5.89')
+ end
+
+ it 'filters out bad values' do
+ request = stub_request('HTTP_X_FORWARDED_FOR' => '(null), 10.1.5.89')
+ expect(request.remote_ip).to eq('10.1.5.89')
+ expect(request.ip).to eq('10.1.5.89')
end
end
@@ -22,9 +30,10 @@ describe 'trusted_proxies', lib: true do
set_trusted_proxies([ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ])
end
- it 'filters out private and local IPs from remote_ip' do
+ it 'filters out private and local IPs' do
request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 10.1.5.89, 127.0.0.1')
expect(request.remote_ip).to eq('1.1.1.1')
+ expect(request.ip).to eq('1.1.1.1')
end
end
@@ -33,9 +42,16 @@ describe 'trusted_proxies', lib: true do
set_trusted_proxies([ "60.98.25.47" ])
end
- it 'filters out proxy IP from remote_ip' do
+ it 'filters out proxy IP' do
request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 60.98.25.47, 127.0.0.1')
expect(request.remote_ip).to eq('1.1.1.1')
+ expect(request.ip).to eq('1.1.1.1')
+ end
+
+ it 'handles invalid ip addresses' do
+ request = stub_request('HTTP_X_FORWARDED_FOR' => '(null), 1.1.1.1:12345, 1.1.1.1')
+ expect(request.remote_ip).to eq('1.1.1.1')
+ expect(request.ip).to eq('1.1.1.1')
end
end
diff --git a/spec/javascripts/application_spec.js.coffee b/spec/javascripts/application_spec.js.coffee
index 8af39c41f2..4b6a2bb544 100644
--- a/spec/javascripts/application_spec.js.coffee
+++ b/spec/javascripts/application_spec.js.coffee
@@ -1,4 +1,4 @@
-#= require lib/common_utils
+#= require lib/utils/common_utils
describe 'Application', ->
describe 'disable buttons', ->
diff --git a/spec/javascripts/awards_handler_spec.js.coffee b/spec/javascripts/awards_handler_spec.js.coffee
index ba191199dc..d7f9c6fc07 100644
--- a/spec/javascripts/awards_handler_spec.js.coffee
+++ b/spec/javascripts/awards_handler_spec.js.coffee
@@ -160,7 +160,6 @@ describe 'AwardsHandler', ->
expect($('[data-emoji=angel]').is(':visible')).toBe no
expect($('[data-emoji=anger]').is(':visible')).toBe no
expect($('[data-emoji=alien]').is(':visible')).toBe yes
- expect($('h5.emoji-search').is(':visible')).toBe yes
describe 'emoji menu', ->
diff --git a/spec/javascripts/fixtures/emoji_menu.coffee b/spec/javascripts/fixtures/emoji_menu.coffee
index e529dd5f1c..ce1a41390d 100644
--- a/spec/javascripts/fixtures/emoji_menu.coffee
+++ b/spec/javascripts/fixtures/emoji_menu.coffee
@@ -1,7 +1,7 @@
window.emojiMenu = """