diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-07-18 09:37:51 +0200 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-07-18 09:37:51 +0200 |
commit | bbda05863fa754c8f7302a577e91692ebd411a95 (patch) | |
tree | 3e55fd0cfb32325c0acca6919c114e5071ed16c8 | |
parent | 17084d42aa4f2a9d58d6b6d30656d5b7cfffe007 (diff) | |
parent | 65352b5baaf269a609b024fd13efc81e8bbdcefa (diff) | |
download | gitlab-ce-bbda05863fa754c8f7302a577e91692ebd411a95.tar.gz |
Merge branch 'master' into refactor/ci-config-move-job-entries
* master: (522 commits)
Fix CI yaml example
Align cancel and retry buttons
Remove deploy to production button
Fix a bug where the project's repository path was returned instead of the wiki path
Don't fail to highlight when Rouge doesn't have a lexer
Revert "Merge branch 'gl-dropdown-issuable-form' into 'master'"
Update tests
Don't fail when Ci::Pipeline doesn't have a project
Don't fail when a LegacyDiffNote didn't store the right diff
Update CHANGELOG
Use cattr_accessor instead duplicating code on NoteOnDiff concern
Fix mentioned users list on diff notes
Don't ask Heather to review documentation MR's
add project name and namespace to filename on project export
navbar_icon was renamed to custom_icon in:
use %(...) and %[...] in favor of %<...>
Fix spec Don't attempt to disable statement timeout on a MySQL DB
Disable statement timeout outside of transaction and during adding concurrent index
Disable PostgreSQL statement timeout during migrations
Add visibility icon
...
606 files changed, 15078 insertions, 6485 deletions
diff --git a/.hound.yml b/.hound.yml deleted file mode 100644 index 3bde29fb2bf..00000000000 --- 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 cd13f581517..db0bcfadcf4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,8 @@ require: - rubocop-rspec - ./rubocop/rubocop +inherit_from: .rubocop_todo.yml + AllCops: TargetRubyVersion: 2.1 # Cop names are not displayed in offense messages by default. Change behavior @@ -52,14 +54,6 @@ Style/AlignArray: Style/AlignHash: Enabled: true -# Align the parameters of a method call if they span more than one line. -Style/AlignParameters: - Enabled: false - -# Use &&/|| instead of and/or. -Style/AndOr: - Enabled: false - # Use `Array#join` instead of `Array#*`. Style/ArrayJoin: Enabled: true @@ -80,10 +74,6 @@ Style/Attr: Style/BeginBlock: Enabled: true -# Checks if usage of %() or %Q() matches configuration. -Style/BarePercentLiterals: - Enabled: false - # Do not use block comments. Style/BlockComments: Enabled: true @@ -97,14 +87,6 @@ Style/BlockEndNewline: Style/BlockDelimiters: Enabled: true -# Enforce braces style around hash parameters. -Style/BracesAroundHashParameters: - Enabled: false - -# Avoid explicit use of the case equality operator(===). -Style/CaseEquality: - Enabled: false - # Indentation of when in a case/when/[else/]end. Style/CaseIndentation: Enabled: true @@ -133,24 +115,10 @@ Style/ClassMethods: Style/ClassVars: Enabled: true -# Do not use :: for method call. -Style/ColonMethodCall: - Enabled: false - -# Checks formatting of special comments (TODO, FIXME, OPTIMIZE, HACK, REVIEW). -Style/CommentAnnotation: - Enabled: false - # Indentation of comments. Style/CommentIndentation: Enabled: true -# Use the return value of `if` and `case` statements for assignment to a -# variable and variable comparison instead of assigning that variable -# inside of each branch. -Style/ConditionalAssignment: - Enabled: false - # Constants should use SCREAMING_SNAKE_CASE. Style/ConstantName: Enabled: true @@ -159,34 +127,14 @@ Style/ConstantName: Style/DefWithParentheses: Enabled: true -# Checks for use of deprecated Hash methods. -Style/DeprecatedHashMethods: - Enabled: false - # Document classes and non-namespace modules. Style/Documentation: Enabled: false -# Checks the position of the dot in multi-line method calls. -Style/DotPosition: - Enabled: false - -# Checks for uses of double negation (!!). -Style/DoubleNegation: - Enabled: false - -# Prefer `each_with_object` over `inject` or `reduce`. -Style/EachWithObject: - Enabled: false - # Align elses and elsifs correctly. Style/ElseAlignment: Enabled: true -# Avoid empty else-clauses. -Style/EmptyElse: - Enabled: false - # Use empty lines between defs. Style/EmptyLineBetweenDefs: Enabled: false @@ -215,10 +163,6 @@ Style/EmptyLinesAroundModuleBody: Style/EmptyLinesAroundMethodBody: Enabled: false -# Prefer literals to Array.new/Hash.new/String.new. -Style/EmptyLiteral: - Enabled: false - # Avoid the use of END blocks. Style/EndBlock: Enabled: true @@ -231,10 +175,6 @@ Style/EndOfLine: Style/EvenOdd: Enabled: true -# Do not use unnecessary spacing. -Style/ExtraSpacing: - Enabled: false - # Use snake_case for source file names. Style/FileName: Enabled: true @@ -252,31 +192,15 @@ Style/FlipFlop: Style/For: Enabled: true -# Enforce the use of Kernel#sprintf, Kernel#format or String#%. -Style/FormatString: - Enabled: false - # Do not introduce global variables. Style/GlobalVars: Enabled: true -# Check for conditionals that can be replaced with guard clauses. -Style/GuardClause: - Enabled: false - # Prefer Ruby 1.9 hash syntax `{ a: 1, b: 2 }` # over 1.8 syntax `{ :a => 1, :b => 2 }`. Style/HashSyntax: Enabled: true -# Finds if nodes inside else, which can be converted to elsif. -Style/IfInsideElse: - Enabled: false - -# Favor modifier if/unless usage when you have a single-line body. -Style/IfUnlessModifier: - Enabled: false - # Do not use if x; .... Use the ternary operator instead. Style/IfWithSemicolon: Enabled: true @@ -284,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. @@ -299,22 +223,10 @@ Style/IndentationConsistency: Style/IndentationWidth: Enabled: true -# Checks the indentation of the first element in an array literal. -Style/IndentArray: - Enabled: false - -# Checks the indentation of the first key in a hash literal. -Style/IndentHash: - Enabled: false - # Use Kernel#loop for infinite loops. Style/InfiniteLoop: Enabled: true -# Use the new lambda literal syntax for single-line blocks. -Style/Lambda: - Enabled: false - # Use lambda.call(...) instead of lambda.(...). Style/LambdaCall: Enabled: true @@ -323,14 +235,6 @@ Style/LambdaCall: Style/LeadingCommentSpace: Enabled: true -# Use \ instead of + or << to concatenate two string literals at line end. -Style/LineEndConcatenation: - Enabled: false - -# Do not use parentheses for method calls with no arguments. -Style/MethodCallParentheses: - Enabled: false - # Checks if the method definitions have or don't have parentheses. Style/MethodDefParentheses: Enabled: true @@ -387,39 +291,18 @@ Style/MultilineMethodDefinitionBraceLayout: Style/MultilineOperationIndentation: Enabled: false -# Avoid multi-line `? :` (the ternary operator), use if/unless instead. -Style/MultilineTernaryOperator: - Enabled: false - -# Do not assign mutable objects to constants. -Style/MutableConstant: - Enabled: false - # Favor unless over if for negative conditions (or control flow or). Style/NegatedIf: Enabled: true -# Favor until over while for negative conditions. -Style/NegatedWhile: - Enabled: false - # Avoid using nested modifiers. Style/NestedModifier: Enabled: true -# Parenthesize method calls which are nested inside the argument list of -# another parenthesized method call. -Style/NestedParenthesizedCalls: - Enabled: false - # Use one expression per branch in a ternary operator. Style/NestedTernaryOperator: Enabled: true -# Use `next` to skip iteration instead of a condition at the end. -Style/Next: - Enabled: false - # Prefer x.nil? to x == nil. Style/NilComparison: Enabled: true @@ -444,51 +327,10 @@ Style/OneLineConditional: Style/OpMethod: Enabled: true -# Check for simple usages of parallel assignment. It will only warn when -# the number of variables matches on both sides of the assignment. -Style/ParallelAssignment: - Enabled: false - # Don't use parentheses around the condition of an if/unless/while. Style/ParenthesesAroundCondition: Enabled: true -# Use `%`-literal delimiters consistently. -Style/PercentLiteralDelimiters: - Enabled: false - -# Checks if uses of %Q/%q match the configured preference. -Style/PercentQLiterals: - Enabled: false - -# Avoid Perl-style regex back references. -Style/PerlBackrefs: - Enabled: false - -# Check the names of predicate methods. -Style/PredicateName: - Enabled: false - -# Use proc instead of Proc.new. -Style/Proc: - Enabled: false - -# Checks the arguments passed to raise/fail. -Style/RaiseArgs: - Enabled: false - -# Don't use begin blocks when they are not needed. -Style/RedundantBegin: - Enabled: false - -# Checks for an obsolete RuntimeException argument in raise/fail. -Style/RedundantException: - Enabled: false - -# Checks usages of Object#freeze on immutable objects. -Style/RedundantFreeze: - Enabled: false - # Checks for parentheses that seem not to serve any purpose. Style/RedundantParentheses: Enabled: true @@ -497,24 +339,6 @@ Style/RedundantParentheses: Style/RedundantReturn: Enabled: true -# Don't use self where it's not needed. -Style/RedundantSelf: - Enabled: false - -# Use %r for regular expressions matching more than `MaxSlashes` '/' -# characters. Use %r only for regular expressions matching more -# than `MaxSlashes` '/' character. -Style/RegexpLiteral: - Enabled: false - -# Avoid using rescue in its modifier form. -Style/RescueModifier: - Enabled: false - -# Checks for places where self-assignment shorthand should have been used. -Style/SelfAssignment: - Enabled: false - # Don't use semicolons to terminate expressions. Style/Semicolon: Enabled: true @@ -524,14 +348,6 @@ Style/SignalException: EnforcedStyle: only_raise Enabled: true -# Enforces the names of some block params. -Style/SingleLineBlockParams: - Enabled: false - -# Avoid single-line methods. -Style/SingleLineMethods: - Enabled: false - # Use spaces after colons. Style/SpaceAfterColon: Enabled: true @@ -553,11 +369,6 @@ Style/SpaceAfterNot: Style/SpaceAfterSemicolon: Enabled: true -# Checks that the equals signs in parameter default assignments have or don't -# have surrounding space depending on configuration. -Style/SpaceAroundEqualsInParameterDefault: - Enabled: false - # Use a space around keywords if appropriate. Style/SpaceAroundKeyword: Enabled: true @@ -566,10 +377,6 @@ Style/SpaceAroundKeyword: Style/SpaceAroundOperators: Enabled: true -# Checks that the left block brace has or doesn't have space before it. -Style/SpaceBeforeBlockBraces: - Enabled: false - # No spaces before commas. Style/SpaceBeforeComma: Enabled: true @@ -578,33 +385,14 @@ Style/SpaceBeforeComma: Style/SpaceBeforeComment: Enabled: true -# Checks that exactly one space is used between a method name and the first -# argument for method calls without parentheses. -Style/SpaceBeforeFirstArg: - Enabled: false - # No spaces before semicolons. Style/SpaceBeforeSemicolon: Enabled: true -# Checks that block braces have or don't have surrounding space. -# For blocks taking parameters, checks that the left brace has or doesn't -# have trailing space. -Style/SpaceInsideBlockBraces: - Enabled: false - -# No spaces after [ or before ]. -Style/SpaceInsideBrackets: - Enabled: false - # Use spaces inside hash literal braces - or don't. Style/SpaceInsideHashLiteralBraces: Enabled: true -# No spaces after ( or before ). -Style/SpaceInsideParens: - Enabled: false - # No spaces inside range literals. Style/SpaceInsideRangeLiteral: Enabled: true @@ -614,10 +402,6 @@ Style/SpaceInsideStringInterpolation: EnforcedStyle: no_space Enabled: true -# Avoid Perl-style global variables. -Style/SpecialGlobalVars: - Enabled: false - # Check for the usage of parentheses around stabby lambda arguments. Style/StabbyLambdaParentheses: EnforcedStyle: require_parentheses @@ -627,25 +411,12 @@ Style/StabbyLambdaParentheses: Style/StringLiterals: Enabled: false -# Checks if uses of quotes inside expressions in interpolated strings match the -# configured preference. -Style/StringLiteralsInInterpolation: - Enabled: false - # Checks if configured preferred methods are used over non-preferred. Style/StringMethods: PreferredMethods: intern: to_sym Enabled: true -# Use %i or %I for arrays of symbols. -Style/SymbolArray: - Enabled: false - -# Use symbols as procs instead of blocks when possible. -Style/SymbolProc: - Enabled: false - # No hard tabs. Style/Tab: Enabled: true @@ -654,40 +425,10 @@ Style/Tab: Style/TrailingBlankLines: Enabled: true -# Checks for trailing comma in array and hash literals. -Style/TrailingCommaInLiteral: - Enabled: false - -# Checks for trailing comma in argument lists. -Style/TrailingCommaInArguments: - Enabled: false - -# Avoid trailing whitespace. -Style/TrailingWhitespace: - Enabled: false - -# Checks for the usage of unneeded trailing underscores at the end of -# parallel variable assignment. -Style/TrailingUnderscoreVariable: - Enabled: false - -# Prefer attr_* methods to trivial readers/writers. -Style/TrivialAccessors: - Enabled: false - -# Do not use unless with else. Rewrite these with the positive case first. -Style/UnlessElse: - Enabled: false - # Checks for %W when interpolation is not needed. Style/UnneededCapitalW: Enabled: true -# TODO: Enable UnneededInterpolation Cop. -# Checks for strings that are just an interpolated expression. -Style/UnneededInterpolation: - Enabled: false - # Checks for %q/%Q when single quotes or double quotes would do. Style/UnneededPercentQ: Enabled: false @@ -717,12 +458,6 @@ Style/WhileUntilModifier: Style/WordArray: Enabled: false -# TODO: Enable ZeroLengthPredicate Cop. -# Use #empty? when testing for objects of length 0. -Style/ZeroLengthPredicate: - Enabled: false - - #################### Metrics ################################ # A calculated magnitude based on number of assignments, @@ -776,15 +511,6 @@ Metrics/PerceivedComplexity: Lint/AmbiguousOperator: Enabled: true -# Checks for ambiguous regexp literals in the first argument of a method -# invocation without parentheses. -Lint/AmbiguousRegexpLiteral: - Enabled: false - -# Don't use assignment in conditions. -Lint/AssignmentInCondition: - Enabled: false - # Align block ends correctly. Lint/BlockAlignment: Enabled: true @@ -810,14 +536,6 @@ Lint/DefEndAlignment: Lint/DeprecatedClassMethods: Enabled: true -# Check for duplicate method definitions. -Lint/DuplicateMethods: - Enabled: false - -# Check for duplicate keys in hash literals. -Lint/DuplicatedKey: - Enabled: false - # Check for immutable argument given to each_with_object. Lint/EachWithObjectArgument: Enabled: true @@ -830,10 +548,6 @@ Lint/ElseLayout: Lint/EmptyEnsure: Enabled: true -# Checks for empty string interpolation. -Lint/EmptyInterpolation: - Enabled: false - # Align ends correctly. Lint/EndAlignment: Enabled: true @@ -858,21 +572,11 @@ Lint/FloatOutOfRange: Lint/FormatParameterMismatch: Enabled: true -# Don't suppress exception. -Lint/HandleExceptions: - Enabled: false - # Checks for adjacent string literals on the same line, which could better be # represented as a single string literal. Lint/ImplicitStringConcatenation: Enabled: true -# TODO: Enable IneffectiveAccessModifier Cop. -# Checks for attempts to use `private` or `protected` to set the visibility -# of a class method, which does not work. -Lint/IneffectiveAccessModifier: - Enabled: false - # Checks for invalid character literals with a non-escaped whitespace # character. Lint/InvalidCharacterLiteral: @@ -886,11 +590,6 @@ Lint/LiteralInCondition: Lint/LiteralInInterpolation: Enabled: true -# Use Kernel#loop with break rather than begin/end/until or begin/end/while -# for post-loop tests. -Lint/Loop: - Enabled: false - # Do not use nested method definitions. Lint/NestedMethodDefinition: Enabled: true @@ -916,13 +615,8 @@ Lint/RequireParentheses: Lint/RescueException: Enabled: true -# Do not use the same name as outer local variable for block arguments -# or block local variables. -Lint/ShadowingOuterLocalVariable: - Enabled: false - -# 'Checks for Object#to_s usage in string interpolation. -Lint/StringConversionInInterpolation: +# Checks for the order which exceptions are rescued to avoid rescueing a less specific exception before a more specific exception. +Lint/ShadowedException: Enabled: false # Do not use prefix `_` for a variable that is used. @@ -935,22 +629,10 @@ Lint/UnderscorePrefixedVariableName: Lint/UnneededDisable: Enabled: false -# Checks for unused block arguments. -Lint/UnusedBlockArgument: - Enabled: false - -# Checks for unused method arguments. -Lint/UnusedMethodArgument: - Enabled: false - # Unreachable code. Lint/UnreachableCode: Enabled: true -# Checks for useless access modifiers. -Lint/UselessAccessModifier: - Enabled: false - # Checks for useless assignment to a local variable. Lint/UselessAssignment: Enabled: true @@ -983,11 +665,6 @@ Performance/Casecmp: Performance/DoubleStartEndWith: Enabled: true -# TODO: Enable EndWith Cop. -# Use `end_with?` instead of a regex match anchored to the end of a string. -Performance/EndWith: - Enabled: false - # Use `strip` instead of `lstrip.rstrip`. Performance/LstripRstrip: Enabled: true @@ -996,24 +673,6 @@ Performance/LstripRstrip: Performance/RangeInclude: Enabled: true -# TODO: Enable RedundantBlockCall Cop. -# Use `yield` instead of `block.call`. -Performance/RedundantBlockCall: - Enabled: false - -# TODO: Enable RedundantMatch Cop. -# Use `=~` instead of `String#match` or `Regexp#match` in a context where the -# returned `MatchData` is not needed. -Performance/RedundantMatch: - Enabled: false - -# TODO: Enable RedundantMerge Cop. -# Use `Hash#[]=`, rather than `Hash#merge!` with a single key-value pair. -Performance/RedundantMerge: - # Max number of key-value pairs to consider an offense - MaxKeyValuePairs: 2 - Enabled: false - # Use `sort` instead of `sort_by { |x| x }`. Performance/RedundantSortBy: Enabled: true @@ -1082,18 +741,6 @@ Rails/ReadWriteAttribute: Rails/ScopeArgs: Enabled: true -# Checks the correct usage of time zone aware methods. -# http://danilenko.org/2012/7/6/rails_timezones -Rails/TimeZone: - Enabled: false - -# Use validates :attribute, hash of validations. -Rails/Validation: - Enabled: false - -Rails/UniqBeforePluck: - Enabled: false - ##################### RSpec ################################## # Check that instances are not being stubbed globally. diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000000..9310e711889 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,462 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --exclude-limit 0` +# on 2016-07-13 12:36:08 -0600 using RuboCop version 0.41.2. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 154 +Lint/AmbiguousRegexpLiteral: + Enabled: false + +# Offense count: 43 +# Configuration parameters: AllowSafeAssignment. +Lint/AssignmentInCondition: + Enabled: false + +# Offense count: 14 +Lint/HandleExceptions: + Enabled: false + +# Offense count: 21 +Lint/IneffectiveAccessModifier: + Enabled: false + +# Offense count: 2 +Lint/Loop: + Enabled: false + +# Offense count: 15 +Lint/ShadowingOuterLocalVariable: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Lint/StringConversionInInterpolation: + Enabled: false + +# Offense count: 44 +# Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Enabled: false + +# Offense count: 129 +# Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +Lint/UnusedMethodArgument: + Enabled: false + +# Offense count: 11 +Lint/UselessAccessModifier: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +Performance/PushSplat: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Performance/RedundantBlockCall: + Enabled: false + +# Offense count: 4 +# Cop supports --auto-correct. +Performance/RedundantMatch: + Enabled: false + +# Offense count: 24 +# Cop supports --auto-correct. +# Configuration parameters: MaxKeyValuePairs. +Performance/RedundantMerge: + Enabled: false + +# Offense count: 60 +Rails/OutputSafety: + Enabled: false + +# Offense count: 128 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: strict, flexible +Rails/TimeZone: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/Validation: + Enabled: false + +# Offense count: 217 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: with_first_parameter, with_fixed_indentation +Style/AlignParameters: + Enabled: false + +# Offense count: 32 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: always, conditionals +Style/AndOr: + Enabled: false + +# Offense count: 47 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: percent_q, bare_percent +Style/BarePercentLiterals: + Enabled: false + +# Offense count: 258 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: braces, no_braces, context_dependent +Style/BracesAroundHashParameters: + Enabled: false + +# Offense count: 5 +Style/CaseEquality: + Enabled: false + +# Offense count: 19 +# Cop supports --auto-correct. +Style/ColonMethodCall: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW +Style/CommentAnnotation: + Enabled: false + +# Offense count: 34 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Enabled: false + +# Offense count: 789 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: leading, trailing +Style/DotPosition: + Enabled: false + +# Offense count: 13 +Style/DoubleNegation: + Enabled: false + +# Offense count: 3 +Style/EachWithObject: + Enabled: false + +# Offense count: 30 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: empty, nil, both +Style/EmptyElse: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Style/EmptyLiteral: + Enabled: false + +# Offense count: 123 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. +Style/ExtraSpacing: + Enabled: false + +# Offense count: 7 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: format, sprintf, percent +Style/FormatString: + Enabled: false + +# Offense count: 48 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Enabled: false + +# Offense count: 11 +Style/IfInsideElse: + Enabled: false + +# Offense count: 177 +# Cop supports --auto-correct. +# Configuration parameters: MaxLineLength. +Style/IfUnlessModifier: + Enabled: false + +# Offense count: 52 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Style/IndentArray: + Enabled: false + +# Offense count: 89 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Style/IndentHash: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: line_count_dependent, lambda, literal +Style/Lambda: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/LineEndConcatenation: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Style/MethodCallParentheses: + Enabled: false + +# Offense count: 3 +Style/MultilineTernaryOperator: + Enabled: false + +# Offense count: 62 +# Cop supports --auto-correct. +Style/MutableConstant: + Enabled: false + +# Offense count: 10 +# Cop supports --auto-correct. +Style/NestedParenthesizedCalls: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. +# SupportedStyles: skip_modifier_ifs, always +Style/Next: + Enabled: false + +# Offense count: 8 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles. +# SupportedOctalStyles: zero_with_o, zero_only +Style/NumericLiteralPrefix: + Enabled: false + +# Offense count: 29 +# Cop supports --auto-correct. +Style/ParallelAssignment: + Enabled: false + +# Offense count: 208 +# Cop supports --auto-correct. +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Enabled: false + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: lower_case_q, upper_case_q +Style/PercentQLiterals: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Style/PerlBackrefs: + Enabled: false + +# Offense count: 32 +# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. +# NamePrefix: is_, has_, have_ +# NamePrefixBlacklist: is_, has_, have_ +# NameWhitelist: is_a? +Style/PredicateName: + Enabled: false + +# Offense count: 28 +# Cop supports --auto-correct. +Style/PreferredHashMethods: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/Proc: + Enabled: false + +# Offense count: 20 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: compact, exploded +Style/RaiseArgs: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Style/RedundantBegin: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/RedundantException: + Enabled: false + +# Offense count: 23 +# Cop supports --auto-correct. +Style/RedundantFreeze: + Enabled: false + +# Offense count: 377 +# Cop supports --auto-correct. +Style/RedundantSelf: + Enabled: false + +# Offense count: 94 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed +Style/RegexpLiteral: + Enabled: false + +# Offense count: 17 +# Cop supports --auto-correct. +Style/RescueModifier: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SelfAssignment: + Enabled: false + +# Offense count: 2 +# Configuration parameters: Methods. +# Methods: {"reduce"=>["a", "e"]}, {"inject"=>["a", "e"]} +Style/SingleLineBlockParams: + Enabled: false + +# Offense count: 50 +# Cop supports --auto-correct. +# Configuration parameters: AllowIfMethodIsEmpty. +Style/SingleLineMethods: + Enabled: false + +# Offense count: 14 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceAroundEqualsInParameterDefault: + Enabled: false + +# Offense count: 119 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceBeforeBlockBraces: + Enabled: false + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Style/SpaceBeforeFirstArg: + Enabled: false + +# Offense count: 130 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +Style/SpaceInsideBlockBraces: + Enabled: false + +# Offense count: 98 +# Cop supports --auto-correct. +Style/SpaceInsideBrackets: + Enabled: false + +# Offense count: 60 +# Cop supports --auto-correct. +Style/SpaceInsideParens: + Enabled: false + +# Offense count: 5 +# Cop supports --auto-correct. +Style/SpaceInsidePercentLiteralDelimiters: + Enabled: false + +# Offense count: 36 +# Cop supports --auto-correct. +# Configuration parameters: SupportedStyles. +# SupportedStyles: use_perl_names, use_english_names +Style/SpecialGlobalVars: + EnforcedStyle: use_perl_names + +# Offense count: 30 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiteralsInInterpolation: + Enabled: false + +# Offense count: 24 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: respond_to, define_method +Style/SymbolProc: + Enabled: false + +# Offense count: 23 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. +# SupportedStyles: comma, consistent_comma, no_comma +Style/TrailingCommaInArguments: + Enabled: false + +# Offense count: 113 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. +# SupportedStyles: comma, consistent_comma, no_comma +Style/TrailingCommaInLiteral: + Enabled: false + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: AllowNamedUnderscoreVariables. +Style/TrailingUnderscoreVariable: + Enabled: false + +# Offense count: 90 +# Cop supports --auto-correct. +Style/TrailingWhitespace: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist. +# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym +Style/TrivialAccessors: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +Style/UnlessElse: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Style/UnneededInterpolation: + Enabled: false + +# Offense count: 8 +# Cop supports --auto-correct. +Style/ZeroLengthPredicate: + Enabled: false diff --git a/.teatro.yml b/.teatro.yml deleted file mode 100644 index 30054361981..00000000000 --- 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 4fac555e12a..57ee5361281 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,51 +1,138 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.10.0 (unreleased) + - Expose {should,force}_remove_source_branch (Ben Boeckel) + - Disable PostgreSQL statement timeout during migrations + - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho) - Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Replace Haml with Hamlit to make view rendering faster. !3666 + - Refresh the branch cache after `git gc` runs - 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) - 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) - Display last commit of deleted branch in push events !4699 (winniehell) + - Escape file extension when parsing search results !5141 (winniehell) - Apply the trusted_proxies config to the rack request object for use with rack_attack + - Upgrade to Rails 4.2.7. !5236 - Add Sidekiq queue duration to transaction metrics. - Add a new column `artifacts_size` to table `ci_builds` !4964 - Let Workhorse serve format-patch diffs + - Display tooltip for mentioned users and groups !5261 (winniehell) + - Added day name to contribution calendar tooltips - Make images fit to the size of the viewport !4810 - Fix check for New Branch button on Issue page !4630 (winniehell) - Fix MR-auto-close text added to description. !4836 + - Support U2F devices in Firefox. !5177 + - Fix issue, preventing users w/o push access to sort tags !5105 (redetection) + - Add Spring EmojiOne updates. + - 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 + - Eager load award emoji on notes - 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: Todos !3188 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups !5050 (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 - 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 + - 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 - Check for conflicts with existing Project's wiki path when creating a new project. + - Show last push widget in upstream after push to fork + - 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. - Add API endpoint for a group issues !4520 (mahcsig) - Add Bugzilla integration !4930 (iamtjg) + - 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 - 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 - -v 8.9.4 (unreleased) - - Ensure references to private repos aren't shown to logged-out users -v 8.9.5 (unreleased) + - Allow '?', or '&' for label names + - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests + - Add date when user joined the team on the member page + - 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) + - 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 markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.` + - Project export filename now includes the project and namespace path + - Fix last update timestamp on issues not preserved on gitlab.com and project imports + - Fix issues importing projects from EE to CE + - Fix creating group with space in group path + - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) + +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 + +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 - - Fix assigning shared runners as admins. !4961 - - Show "locked" label for locked runners on runners admin. !4961 - - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq - - Fixes issues importing events in Import/Export. Import/Export version bumped to 0.1.1 - - Fix import button disabled when import process fail due to the namespace already been taken. - - Security: Update RedCloth to 4.3.2 (Takuya Noguchi) v 8.9.4 - Fix privilege escalation issue with OAuth external users. @@ -75,7 +162,7 @@ v 8.9.3 - 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 by_pass all the dirty code on active_record. !4985 + - 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 @@ -191,6 +278,7 @@ v 8.9.0 - 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 @@ -2152,8 +2240,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 @@ -2579,13 +2665,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 f4472214778..14ff05c9aa3 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 fd2a01863fd..944880fa15e 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.1.0 +3.2.0 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 879be8a98fc..e7c7d3cc3c8 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.7 +0.7.8 @@ -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,7 +28,7 @@ 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.6.0' gem 'omniauth-shibboleth', '~> 1.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.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 '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' @@ -94,18 +94,18 @@ 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.3.2' gem 'rdoc', '~>3.6' @@ -113,7 +113,7 @@ gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' -gem 'rouge', '~> 1.11' +gem 'rouge', '~> 2.0' # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM @@ -124,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' @@ -156,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' @@ -169,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' @@ -187,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' @@ -214,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', '~> 2.6' gem 'gon', '~> 6.0.1' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' @@ -247,13 +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 '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,15 +261,15 @@ 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.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' @@ -277,7 +277,7 @@ group :development, :test do gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails', '~> 4.6.0' gem 'rspec-rails', '~> 3.5.0' - gem 'rspec-retry' + gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' @@ -299,18 +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 '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 @@ -318,33 +318,33 @@ 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.0' diff --git a/Gemfile.lock b/Gemfile.lock index 9b07c97b078..0987fd5665a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,34 +3,34 @@ GEM specs: 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) @@ -171,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) @@ -255,7 +255,7 @@ GEM ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) - gemojione (2.2.1) + gemojione (2.6.1) json get_process_mem (0.2.0) gherkin-ruby (0.3.2) @@ -264,7 +264,7 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) - github-markup (1.3.3) + github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -274,8 +274,6 @@ 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.3) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) @@ -289,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) @@ -324,8 +322,8 @@ GEM thor tilt 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 @@ -355,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) @@ -395,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) @@ -408,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) @@ -441,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) @@ -475,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) @@ -517,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) @@ -536,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) @@ -578,9 +576,9 @@ 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.3) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) @@ -608,8 +606,8 @@ GEM rspec-retry (0.4.5) rspec-core rspec-support (3.5.0) - rubocop (0.40.0) - parser (>= 2.3.1.0, < 3.0) + rubocop (0.41.2) + parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) @@ -699,7 +697,7 @@ GEM spring (>= 0.9.1) spring-commands-teaspoon (0.0.2) spring (>= 0.9.1) - sprockets (3.6.2) + sprockets (3.6.3) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.1.1) @@ -760,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 @@ -808,7 +806,7 @@ DEPENDENCIES 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) @@ -817,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) @@ -840,14 +838,14 @@ 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) @@ -856,23 +854,23 @@ DEPENDENCIES 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 (~> 2.6) 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_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) hamlit (~> 2.5) - health_check (~> 1.5.1) + health_check (~> 2.1.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty (~> 0.13.3) @@ -883,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) @@ -897,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) @@ -907,7 +905,7 @@ 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.6.0) omniauth-shibboleth (~> 1.2.0) @@ -918,28 +916,28 @@ DEPENDENCIES pg (~> 0.18.2) poltergeist (~> 1.9.0) premailer-rails (~> 1.9.0) - pry-rails + 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.5.0) - rspec-retry - rubocop (~> 0.40.0) + rspec-retry (~> 0.4.5) + rubocop (~> 0.41.2) rubocop-rspec (~> 1.5.0) ruby-fogbugz (~> 0.2.1) sanitize (~> 2.0) @@ -950,7 +948,7 @@ DEPENDENCIES select2-rails (~> 3.5.9) 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) diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png Binary files differindex 99093d4725a..6bacb0e92b6 100644 --- a/app/assets/images/emoji.png +++ b/app/assets/images/emoji.png diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png Binary files differindex 48989942a9f..99588b56616 100644 --- a/app/assets/images/emoji@2x.png +++ b/app/assets/images/emoji@2x.png diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index cf46f15a156..89b0ac697ed 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 20fe5a5cc27..eceff6d91d5 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/utils #= require_directory ./u2f #= require_directory . #= require fuzzaldrin-plus -#= require cropper -#= require u2f window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/build.coffee index 2d515d7efa2..cf203ea43a0 100644 --- a/app/assets/javascripts/ci/build.coffee +++ b/app/assets/javascripts/build.coffee @@ -1,9 +1,9 @@ -class @CiBuild +class @Build @interval: null @state: null - constructor: (@build_url, @build_status, @state) -> - clearInterval(CiBuild.interval) + constructor: (@page_url, @build_url, @build_status, @state) -> + clearInterval(Build.interval) # Init breakpoint checker @bp = Breakpoints.get() @@ -40,8 +40,8 @@ class @CiBuild # Check for new build output if user still watching build page # Only valid for runnig build when output changes during time # - CiBuild.interval = setInterval => - if window.location.href.split("#").first() is @build_url + Build.interval = setInterval => + if window.location.href.split("#").first() is @page_url @getBuildTrace() , 4000 @@ -57,7 +57,7 @@ class @CiBuild getBuildTrace: -> $.ajax - url: "#{@build_url}/trace.json?state=#{encodeURIComponent(@state)}" + url: "#{@page_url}/trace.json?state=#{encodeURIComponent(@state)}" dataType: "json" success: (log) => if log.state @@ -70,7 +70,7 @@ class @CiBuild $('.js-build-output').html log.html @checkAutoscroll() else if log.status isnt @build_status - Turbolinks.visit @build_url + Turbolinks.visit @page_url checkAutoscroll: -> $("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state") diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee deleted file mode 100644 index ca24c1d759f..00000000000 --- a/app/assets/javascripts/ci/application.js.coffee +++ /dev/null @@ -1,12 +0,0 @@ -#= require pager -#= require jquery_nested_form -#= require_tree . - -$(document).on 'click', '.assign-all-runner', -> - $(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..') - -window.unbindEvents = -> - $(document).unbind('scroll') - $(document).off('scroll') - -document.addEventListener("page:fetch", unbindEvents) diff --git a/app/assets/javascripts/ci/projects.js.coffee b/app/assets/javascripts/ci/projects.js.coffee deleted file mode 100644 index e6406011d11..00000000000 --- a/app/assets/javascripts/ci/projects.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -$(document).on 'click', '.badge-codes-toggle', -> - $('.badge-codes-block').toggleClass("hide") - return false diff --git a/app/assets/javascripts/compare_autocomplete.js.coffee b/app/assets/javascripts/compare_autocomplete.js.coffee new file mode 100644 index 00000000000..7ad9fd97637 --- /dev/null +++ b/app/assets/javascripts/compare_autocomplete.js.coffee @@ -0,0 +1,41 @@ +class @CompareAutocomplete + constructor: -> + @initDropdown() + + initDropdown: -> + $('.js-compare-dropdown').each -> + $dropdown = $(@) + selected = $dropdown.data('selected') + + $dropdown.glDropdown( + data: (term, callback) -> + $.ajax( + url: $dropdown.data('refs-url') + data: + ref: $dropdown.data('ref') + ).done (refs) -> + callback(refs) + selectable: true + filterable: true + filterByText: true + fieldName: $dropdown.attr('name') + filterInput: 'input[type="text"]' + renderRow: (ref) -> + if ref.header? + $('<li />') + .addClass('dropdown-header') + .text(ref.header) + else + link = $('<a />') + .attr('href', '#') + .addClass(if ref is selected then 'is-active' else '') + .text(ref) + .attr('data-ref', escape(ref)) + + $('<li />') + .append(link) + id: (obj, $el) -> + $el.attr('data-ref') + toggleLabel: (obj, $el) -> + $el.text().trim() + ) diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee index 6d9b364cb8d..c132cc8c542 100644 --- a/app/assets/javascripts/diff.js.coffee +++ b/app/assets/javascripts/diff.js.coffee @@ -1,6 +1,9 @@ class @Diff UNFOLD_COUNT = 20 constructor: -> + $('.files .diff-file').singleFileDiff() + @filesCommentButton = $('.files .diff-file').filesCommentButton() + $(document).off('click', '.js-unfold') $(document).on('click', '.js-unfold', (event) => target = $(event.target) @@ -36,7 +39,7 @@ class @Diff # see https://gitlab.com/gitlab-org/gitlab-ce/issues/707 indent: 1 - $.get(link, params, (response) => + $.get(link, params, (response) -> target.parent().replaceWith(response) ) ) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 9493a575801..afaa6407b05 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -127,17 +127,18 @@ class Dispatcher when 'groups' new UsersSelect() when 'projects' - new NamespaceSelect() + new NamespaceSelects() when 'dashboard', 'root' shortcut_handler = new ShortcutsDashboardNavigation() when 'profiles' - new Profile() new NotificationsForm() new NotificationsDropdown() when 'projects' new Project() new ProjectAvatar() switch path[1] + when 'compare' + new CompareAutocomplete() when 'edit' shortcut_handler = new ShortcutsNavigation() new ProjectNew() diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index e2194589b38..665246e2a7d 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -70,12 +70,12 @@ class @DropzoneInput pasteText response.link.markdown return - error: (temp, errorMessage) -> + error: (temp) -> errorAlert = $(form).find('.error-alert') checkIfMsgExists = errorAlert.children().length if checkIfMsgExists is 0 errorAlert.append divAlert - $(".div-dropzone-alert").append btnAlert + errorMessage + $(".div-dropzone-alert").append "#{btnAlert}Attaching the file failed." return totaluploadprogress: (totalUploadProgress) -> diff --git a/app/assets/javascripts/files_comment_button.js.coffee b/app/assets/javascripts/files_comment_button.js.coffee new file mode 100644 index 00000000000..db0bf7082a9 --- /dev/null +++ b/app/assets/javascripts/files_comment_button.js.coffee @@ -0,0 +1,97 @@ +class @FilesCommentButton + COMMENT_BUTTON_CLASS = '.add-diff-note' + COMMENT_BUTTON_TEMPLATE = _.template '<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>' + LINE_HOLDER_CLASS = '.line_holder' + LINE_NUMBER_CLASS = 'diff-line-num' + LINE_CONTENT_CLASS = 'line_content' + UNFOLDABLE_LINE_CLASS = 'js-unfold' + EMPTY_CELL_CLASS = 'empty-cell' + OLD_LINE_CLASS = 'old_line' + NEW_CLASS = 'new' + LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content" + TEXT_FILE_SELECTOR = '.text-file' + DEBOUNCE_TIMEOUT_DURATION = 100 + + constructor: (@filesContainerElement) -> + @VIEW_TYPE = $('input#view[type=hidden]').val() + + debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION + + $(document) + .on 'mouseover', LINE_COLUMN_CLASSES, debounce + .on 'mouseleave', LINE_COLUMN_CLASSES, @destroy + + render: (e) => + $currentTarget = $(e.currentTarget) + buttonParentElement = @getButtonParent $currentTarget + return unless @shouldRender e, buttonParentElement + + textFileElement = @getTextFileElement $currentTarget + lineContentElement = @getLineContent $currentTarget + + buttonParentElement.append @buildButton + noteableType: textFileElement.attr 'data-noteable-type' + noteableID: textFileElement.attr 'data-noteable-id' + commitID: textFileElement.attr 'data-commit-id' + noteType: lineContentElement.attr 'data-note-type' + position: lineContentElement.attr 'data-position' + lineType: lineContentElement.attr 'data-line-type' + discussionID: lineContentElement.attr 'data-discussion-id' + lineCode: lineContentElement.attr 'data-line-code' + return + + destroy: (e) => + return if @isMovingToSameType e + $(COMMENT_BUTTON_CLASS, @getButtonParent $(e.currentTarget)).remove() + return + + buildButton: (buttonAttributes) -> + initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE + COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr 1 + $(initializedButtonTemplate).attr + 'data-noteable-type': buttonAttributes.noteableType + 'data-noteable-id': buttonAttributes.noteableID + 'data-commit-id': buttonAttributes.commitID + 'data-note-type': buttonAttributes.noteType + 'data-line-code': buttonAttributes.lineCode + 'data-position': buttonAttributes.position + 'data-discussion-id': buttonAttributes.discussionID + 'data-line-type': buttonAttributes.lineType + + getTextFileElement: (hoveredElement) -> + $(hoveredElement.closest TEXT_FILE_SELECTOR) + + getLineContent: (hoveredElement) -> + return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS + + $(".#{LINE_CONTENT_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) + + getButtonParent: (hoveredElement) -> + if @VIEW_TYPE is 'inline' + return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS + + $(".#{OLD_LINE_CLASS}", hoveredElement.parent()) + else + return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS + + $(".#{LINE_NUMBER_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent()) + + diffTypeClass: (hoveredElement) -> + if hoveredElement.hasClass(NEW_CLASS) then '.new' else '.old' + + isMovingToSameType: (e) -> + newButtonParent = @getButtonParent $(e.toElement) + return false unless newButtonParent + newButtonParent.is @getButtonParent $(e.currentTarget) + + shouldRender: (e, buttonParentElement) -> + (not buttonParentElement.hasClass(EMPTY_CELL_CLASS) and \ + not buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) and \ + $(COMMENT_BUTTON_CLASS, buttonParentElement).length is 0) + +$.fn.filesCommentButton = -> + return unless this and @parent().data('can-create-note')? + + @each -> + unless $.data this, 'filesCommentButton' + $.data this, 'filesCommentButton', new FilesCommentButton $(this) diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index b76d214790a..5a493041538 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -1,24 +1,28 @@ class @Flash - constructor: (message, type = 'alert')-> - @flash = $(".flash-container") - @flash.html("") + hideFlash = -> $(@).fadeOut() - innerDiv = $('<div/>', + constructor: (message, type = 'alert', parent = null)-> + if parent + @flashContainer = parent.find('.flash-container') + else + @flashContainer = $('.flash-container-page') + + @flashContainer.html('') + + flash = $('<div/>', class: "flash-#{type}" ) - innerDiv.appendTo(".flash-container") + flash.on 'click', hideFlash - textDiv = $("<div/>", - class: "flash-text", + textDiv = $('<div/>', + class: 'flash-text', text: message ) - textDiv.appendTo(innerDiv) + textDiv.appendTo(flash) - if @flash.parent().hasClass('content-wrapper') + if @flashContainer.parent().hasClass('content-wrapper') textDiv.addClass('container-fluid container-limited') - @flash.click -> $(@).fadeOut() - @flash.show() + flash.appendTo(@flashContainer) + @flashContainer.show() - pinTo: (selector) -> - @flash.detach().appendTo(selector) diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index b7d040bae85..4a851d9c9fb 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -190,7 +190,7 @@ GitLab.GfmAutoComplete = callbacks: beforeSave: (merges) -> sanitizeLabelTitle = (title)-> - if /\w+\s+\w+/g.test(title) + if /[\w\?&]+\s+[\w\?&]+/g.test(title) "\"#{sanitize(title)}\"" else sanitize(title) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index ed9dfcc917e..1b0d0db8954 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -56,6 +56,7 @@ class GitLabDropdownFilter return BLUR_KEYCODES.indexOf(keyCode) >= 0 filter: (search_text) -> + @options.onFilter(search_text) if @options.onFilter data = @options.data() if data? and not @options.filterByText @@ -195,6 +196,7 @@ class GitLabDropdown @filter = new GitLabDropdownFilter @filterInput, filterInputBlur: @filterInputBlur filterByText: @options.filterByText + onFilter: @options.onFilter remote: @options.filterRemote query: @options.data keys: searchFields @@ -454,6 +456,8 @@ class GitLabDropdown rowClicked: (el) -> fieldName = @options.fieldName + isInput = $(@el).is('input') + if @renderedData groupName = el.data('group') if groupName @@ -464,10 +468,19 @@ class GitLabDropdown selectedObject = @renderedData[selectedIndex] value = if @options.id then @options.id(selectedObject, el) else selectedObject.id - field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") + + if isInput + field = $(@el) + else + field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") + if el.hasClass(ACTIVE_CLASS) el.removeClass(ACTIVE_CLASS) - field.remove() + + if isInput + field.val('') + else + field.remove() # Toggle the dropdown label if @options.toggleLabel @@ -488,7 +501,9 @@ class GitLabDropdown else if not @options.multiSelect or el.hasClass('dropdown-clear-active') @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS - @dropdown.parent().find("input[name='#{fieldName}']").remove() + + unless isInput + @dropdown.parent().find("input[name='#{fieldName}']").remove() if !value? field.remove() @@ -503,7 +518,9 @@ class GitLabDropdown if !field.length and fieldName @addInput(fieldName, value) else - field.val value + field + .val value + .trigger 'change' return selectedObject @@ -530,7 +547,7 @@ class GitLabDropdown if $el.length e.preventDefault() e.stopImmediatePropagation() - $(selector, @dropdown)[0].click() + $el.first().trigger('click') addArrowKeyEvent: -> ARROW_KEY_CODES = [38, 40] diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee index 0527c66461c..7f795f8096b 100644 --- a/app/assets/javascripts/issuable.js.coffee +++ b/app/assets/javascripts/issuable.js.coffee @@ -11,11 +11,11 @@ issuable_created = false initTemplates: -> Issuable.labelRow = _.template( '<% _.each(labels, function(label){ %> - <span class="label-row btn-group" role="group" aria-label="<%= _.escape(label.title) %>" style="color: <%= label.text_color %>;"> - <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%= label.color %>;" title="<%= _.escape(label.description) %>" data-container="body"> - <%= _.escape(label.title) %> + <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> + <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> + <%- label.title %> </a> - <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%= label.color %>;" data-label="<%= _.escape(label.title) %>"> + <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> @@ -32,13 +32,11 @@ issuable_created = false $search = $('#issue_search') $form = $('.js-filter-form') $input = $("input[name='#{$search.attr('name')}']", $form) - if $input.length is 0 $form.append "<input type='hidden' name='#{$search.attr('name')}' value='#{_.escape($search.val())}'/>" else $input.val $search.val() - - Issuable.filterResults $form + Issuable.filterResults $form if $search.val() isnt '' , 500) initLabelFilterRemove: -> diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index e95fd96a83f..7688609b301 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -32,9 +32,9 @@ class @LabelsSelect if issueUpdateURL labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> - <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%= _.escape(label.title) %>"> - <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;"> - <%= _.escape(label.title) %> + <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> + <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> + <%- label.title %> </span> </a> <% }); %>' @@ -261,7 +261,7 @@ class @LabelsSelect $a.attr('data-label-id', label.id) $a.addClass(selectedClass.join(' ')) - .html("#{colorEl} #{_.escape(label.title)}") + .html("#{colorEl} #{label.title}") # Return generated html $li.html($a).prop('outerHTML') @@ -288,7 +288,7 @@ class @LabelsSelect fieldName: $dropdown.data('field-name') id: (label) -> if $dropdown.hasClass("js-filter-submit") and not label.isAny? - _.escape label.title + label.title else label.id diff --git a/app/assets/javascripts/lib/cropper.js.coffee b/app/assets/javascripts/lib/cropper.js.coffee new file mode 100644 index 00000000000..32536d23fe3 --- /dev/null +++ b/app/assets/javascripts/lib/cropper.js.coffee @@ -0,0 +1 @@ +#= require cropper diff --git a/app/assets/javascripts/lib/utils/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee index e39dcb2daa9..d4dd3dc329a 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.coffee +++ b/app/assets/javascripts/lib/utils/common_utils.js.coffee @@ -5,12 +5,12 @@ w.gl.utils.isInGroupsPage = -> - return $('body').data('page').split(':')[0] is 'groups' + return gl.utils.getPagePath() is 'groups' w.gl.utils.isInProjectPage = -> - return $('body').data('page').split(':')[0] is 'projects' + return gl.utils.getPagePath() is 'projects' w.gl.utils.getProjectSlug = -> @@ -40,6 +40,9 @@ e.stopImmediatePropagation() return false + gl.utils.getPagePath = -> + return $('body').data('page').split(':')[0] + jQuery.timefor = (time, suffix, expiredLabel) -> diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee index 948d6dbf07e..178963fe0aa 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee +++ b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee @@ -2,10 +2,14 @@ w.gl ?= {} w.gl.utils ?= {} + w.gl.utils.days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] w.gl.utils.formatDate = (datetime) -> dateFormat(datetime, 'mmm d, yyyy h:MMtt Z') + w.gl.utils.getDayName = (date) -> + this.days[date.getDay()] + w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) -> $timeagoEls.each( -> $el = $(@) diff --git a/app/assets/javascripts/lib/utils/text_utility.js.coffee b/app/assets/javascripts/lib/utils/text_utility.js.coffee index 7bcb876d056..2e1407f8738 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js.coffee +++ b/app/assets/javascripts/lib/utils/text_utility.js.coffee @@ -49,8 +49,9 @@ insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}" if document.queryCommandSupported('insertText') - document.execCommand 'insertText', false, insertText - else + inserted = document.execCommand 'insertText', false, insertText + + unless inserted try document.execCommand("ms-beginUndoUnit") diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 894f80586f1..86539e0d725 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -153,17 +153,18 @@ class @MergeRequestTabs loadDiff: (source) -> return if @diffsLoaded - @_get url: "#{source}.json" + @_location.search success: (data) => $('#diffs').html data.html gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')) $('#diffs .js-syntax-highlight').syntaxHighlight() + $('#diffs .diff-file').singleFileDiff() @expandViewContainer() if @diffViewType() is 'parallel' @diffsLoaded = true @scrollToElement("#diffs") @highlighSelectedLine() + @filesCommentButton = $('.files .diff-file').filesCommentButton() $(document) .off 'click', '.diff-line-num a' diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 02480f3a025..8ab03ed93ee 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -24,14 +24,14 @@ class @MilestoneSelect if issueUpdateURL milestoneLinkTemplate = _.template( - '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>" class="bold has-tooltip" data-container="body" title="<%= remaining %>"><%= _.escape(title) %></a>' + '<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>' ) milestoneLinkNoneTemplate = '<span class="no-value">None</span>' collapsedSidebarLabelTemplate = _.template( - '<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left"> - <%= _.escape(title) %> + '<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> + <%- title %> </span>' ) diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index a02c4515ccc..3b419dff105 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -1,25 +1,56 @@ class @NamespaceSelect - constructor: -> - namespaceFormatResult = (namespace) -> - markup = "<div class='namespace-result'>" - markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" - markup += "<span class='namespace-path'>" + namespace.path + "</span>" - markup += "</div>" - markup - - formatSelection = (namespace) -> - namespace.kind + ": " + namespace.path - - $('.ajax-namespace-select').each (i, select) -> - $(select).select2 - placeholder: "Search for namespace" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.namespaces query.term, (namespaces) -> - data = { results: namespaces } - query.callback(data) - - dropdownCssClass: "ajax-namespace-dropdown" - formatResult: namespaceFormatResult - formatSelection: formatSelection + constructor: (opts) -> + { + @dropdown + } = opts + + showAny = true + fieldName = 'namespace_id' + + if @dropdown.attr 'data-field-name' + fieldName = @dropdown.data 'fieldName' + + if @dropdown.attr 'data-show-any' + showAny = @dropdown.data 'showAny' + + @dropdown.glDropdown( + filterable: true + selectable: true + filterRemote: true + search: + fields: ['path'] + fieldName: fieldName + toggleLabel: (selected) -> + return if not selected.id? then selected.text else "#{selected.kind}: #{selected.path}" + data: (term, dataCallback) -> + Api.namespaces term, (namespaces) -> + if showAny + anyNamespace = + text: 'Any namespace' + id: null + + namespaces.unshift(anyNamespace) + namespaces.splice 1, 0, 'divider' + + dataCallback(namespaces) + text: (namespace) -> + return if not namespace.id? then namespace.text else "#{namespace.kind}: #{namespace.path}" + renderRow: @renderRow + clicked: @onSelectItem + ) + + onSelectItem: (item, el, e) => + e.preventDefault() + +class @NamespaceSelects + constructor: (opts = {}) -> + { + @$dropdowns = $('.js-namespace-select') + } = opts + + @$dropdowns.each (i, dropdown) -> + $dropdown = $(dropdown) + + new NamespaceSelect( + dropdown: $dropdown + ) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 17f7e180127..0ea54faae1a 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -100,13 +100,40 @@ class @Notes $('.note .js-task-list-container').taskList('disable') $(document).off 'tasklist:changed', '.note .js-task-list-container' - keydownNoteText: (e) -> - $this = $(this) - if $this.val() is '' and e.which is 38 and not isMetaKey e - myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") - if myLastNote.length - myLastNoteEditBtn = myLastNote.find('.js-note-edit') - myLastNoteEditBtn.trigger('click', [true, myLastNote]) + keydownNoteText: (e) => + return if isMetaKey e + + $textarea = $(e.target) + + # Edit previous note when UP arrow is hit + switch e.which + when 38 + return unless $textarea.val() is '' + + myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") + if myLastNote.length + myLastNoteEditBtn = myLastNote.find('.js-note-edit') + myLastNoteEditBtn.trigger('click', [true, myLastNote]) + + # Cancel creating diff note or editing any note when ESCAPE is hit + when 27 + discussionNoteForm = $textarea.closest('.js-discussion-note-form') + if discussionNoteForm.length + if $textarea.val() isnt '' + return unless confirm('Are you sure you want to cancel creating this comment?') + + @removeDiscussionNoteForm(discussionNoteForm) + return + + editNote = $textarea.closest('.note') + if editNote.length + originalText = $textarea.closest('form').data('original-note') + newText = $textarea.val() + if originalText isnt newText + return unless confirm('Are you sure you want to cancel editing this comment?') + + @removeNoteEditForm(editNote) + isMetaKey = (e) -> (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey) @@ -167,8 +194,7 @@ class @Notes renderNote: (note) -> unless note.valid if note.award - flash = new Flash('You have already awarded this emoji!', 'alert') - flash.pinTo('.header-content') + new Flash('You have already awarded this emoji!', 'alert') return if note.award @@ -213,12 +239,16 @@ class @Notes @note_ids.push(note.id) form = $("#new-discussion-note-form-#{note.discussion_id}") + if note.original_discussion_id? and form.length is 0 + form = $("#new-discussion-note-form-#{note.original_discussion_id}") row = form.closest("tr") note_html = $(note.html) note_html.syntaxHighlight() # is this the first note of discussion? discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']") + if note.original_discussion_id? and discussionContainer.length is 0 + discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']") if discussionContainer.length is 0 # insert the note and the reply button after the temp row row.after note.discussion_html @@ -291,8 +321,11 @@ class @Notes form.addClass "js-main-target-form" form.find("#note_line_code").remove() + form.find("#note_position").remove() form.find("#note_type").remove() + @parentTimeline = form.parents('.timeline') + ### General note form setup. @@ -308,10 +341,12 @@ class @Notes new Autosave textarea, [ "Note" - form.find("#note_commit_id").val() - form.find("#note_line_code").val() form.find("#note_noteable_type").val() form.find("#note_noteable_id").val() + form.find("#note_commit_id").val() + form.find("#note_type").val() + form.find("#note_line_code").val() + form.find("#note_position").val() ] ### @@ -323,8 +358,7 @@ class @Notes @renderNote(note) addNoteError: (xhr, note, status) => - flash = new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert') - flash.pinTo('.md-area') + new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', @parentTimeline) ### Called in response to the new note form being submitted @@ -401,9 +435,12 @@ class @Notes Hides edit form and restores the original note text to the editor textarea. ### - cancelEdit: (e) -> + cancelEdit: (e) => e.preventDefault() - note = $(this).closest(".note") + note = $(e.target).closest('.note') + @removeNoteEditForm(note) + + removeNoteEditForm: (note) -> form = note.find(".current-note-edit-form") note.removeClass "is-editting" form.removeClass("current-note-edit-form") @@ -482,10 +519,12 @@ class @Notes setupDiscussionNoteForm: (dataHolder, form) => # setup note target form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}" + form.attr "data-line-code", dataHolder.data("lineCode") form.find("#note_type").val dataHolder.data("noteType") form.find("#line_type").val dataHolder.data("lineType") form.find("#note_commit_id").val dataHolder.data("commitId") form.find("#note_line_code").val dataHolder.data("lineCode") + form.find("#note_position").val dataHolder.attr("data-position") form.find("#note_noteable_type").val dataHolder.data("noteableType") form.find("#note_noteable_id").val dataHolder.data("noteableId") form.find('.js-note-discard') diff --git a/app/assets/javascripts/profile/application.js.coffee b/app/assets/javascripts/profile/application.js.coffee new file mode 100644 index 00000000000..91cacfece46 --- /dev/null +++ b/app/assets/javascripts/profile/application.js.coffee @@ -0,0 +1,2 @@ +# +#= require_tree . diff --git a/app/assets/javascripts/gl_crop.js.coffee b/app/assets/javascripts/profile/gl_crop.js.coffee index df9bfdfa6cc..df9bfdfa6cc 100644 --- a/app/assets/javascripts/gl_crop.js.coffee +++ b/app/assets/javascripts/profile/gl_crop.js.coffee diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee index 1583d1ba6f9..f3b05f2c646 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile/profile.js.coffee @@ -78,3 +78,6 @@ $ -> if comment && comment.length > 1 && $title.val() == '' $title.val(comment[1]).change() + + if gl.utils.getPagePath() == 'profiles' + new Profile() diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee index e4c4bf3b273..a7d78d9e461 100644 --- a/app/assets/javascripts/projects_list.js.coffee +++ b/app/assets/javascripts/projects_list.js.coffee @@ -5,13 +5,12 @@ this.initPagination() initSearch: -> - @timer = null - $(".projects-list-filter").on('keyup', -> - clearTimeout(@timer) - @timer = setTimeout(ProjectsList.filterResults, 500) - ) + projectsListFilter = $('.projects-list-filter') + debounceFilter = _.debounce ProjectsList.filterResults, 500 + projectsListFilter.on 'keyup', (e) -> + debounceFilter() if projectsListFilter.val() isnt '' - filterResults: => + filterResults: -> $('.projects-list-holder').fadeTo(250, 0.5) form = null diff --git a/app/assets/javascripts/protected_branch_select.js.coffee b/app/assets/javascripts/protected_branch_select.js.coffee new file mode 100644 index 00000000000..6d45770ace9 --- /dev/null +++ b/app/assets/javascripts/protected_branch_select.js.coffee @@ -0,0 +1,40 @@ +class @ProtectedBranchSelect + constructor: (currentProject) -> + $('.dropdown-footer').hide(); + @dropdown = $('.js-protected-branch-select').glDropdown( + data: @getProtectedBranches + filterable: true + remote: false + search: + fields: ['title'] + selectable: true + toggleLabel: (selected) -> if (selected and 'id' of selected) then selected.title else 'Protected Branch' + fieldName: 'protected_branch[name]' + text: (protected_branch) -> _.escape(protected_branch.title) + id: (protected_branch) -> _.escape(protected_branch.id) + onFilter: @toggleCreateNewButton + clicked: () -> $('.protect-branch-btn').attr('disabled', false) + ) + + $('.create-new-protected-branch').on 'click', (event) => + # Refresh the dropdown's data, which ends up calling `getProtectedBranches` + @dropdown.data('glDropdown').remote.execute() + @dropdown.data('glDropdown').selectRowAtIndex(event, 0) + + getProtectedBranches: (term, callback) => + if @selectedBranch + callback(gon.open_branches.concat(@selectedBranch)) + else + callback(gon.open_branches) + + toggleCreateNewButton: (branchName) => + @selectedBranch = { title: branchName, id: branchName, text: branchName } + + if branchName is '' + $('.protected-branch-select-footer-list').addClass('hidden') + $('.dropdown-footer').hide(); + else + $('.create-new-protected-branch').text("Create Protected Branch: #{branchName}") + $('.protected-branch-select-footer-list').removeClass('hidden') + $('.dropdown-footer').show(); + diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee index 5753c9d4e72..79c2306e4d2 100644 --- a/app/assets/javascripts/protected_branches.js.coffee +++ b/app/assets/javascripts/protected_branches.js.coffee @@ -11,7 +11,8 @@ $ -> dataType: "json" data: id: id - developers_can_push: checked + protected_branch: + developers_can_push: checked success: -> row = $(e.target) diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee index 3319a67a79d..8c8689bacee 100644 --- a/app/assets/javascripts/shortcuts.js.coffee +++ b/app/assets/javascripts/shortcuts.js.coffee @@ -2,9 +2,10 @@ class @Shortcuts constructor: (skipResetBindings) -> @enabledHelp = [] Mousetrap.reset() if not skipResetBindings - Mousetrap.bind('?', @onToggleHelp) - Mousetrap.bind('s', Shortcuts.focusSearch) - Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview) + Mousetrap.bind '?', @onToggleHelp + Mousetrap.bind 's', Shortcuts.focusSearch + Mousetrap.bind 'f', (e) => @focusFilter e + Mousetrap.bind ['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL? onToggleHelp: (e) => @@ -32,10 +33,16 @@ class @Shortcuts $('.js-more-help-button').remove() ) + focusFilter: (e) -> + @filterInput ?= $('input[type=search]', '.nav-controls') + @filterInput.focus() + e.preventDefault() + @focusSearch: (e) -> $('#search').focus() e.preventDefault() + $(document).on 'click.more_help', '.js-more-help-button', (e) -> $(@).remove() $('.hidden-shortcut').show() diff --git a/app/assets/javascripts/single_file_diff.js.coffee b/app/assets/javascripts/single_file_diff.js.coffee new file mode 100644 index 00000000000..f3e225c3728 --- /dev/null +++ b/app/assets/javascripts/single_file_diff.js.coffee @@ -0,0 +1,54 @@ +class @SingleFileDiff + + WRAPPER = '<div class="diff-content diff-wrap-lines"></div>' + LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>' + ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>' + COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>' + + constructor: (@file) -> + @content = $('.diff-content', @file) + @diffForPath = @content.find('[data-diff-for-path]').data 'diff-for-path' + @isOpen = !@diffForPath + + if @diffForPath + @collapsedContent = @content + @loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide() + @content = null + @collapsedContent.after(@loadingContent) + else + @collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide() + @content.after(@collapsedContent) + + @collapsedContent.on 'click', @toggleDiff + + $('.file-title > a', @file).on 'click', @toggleDiff + + toggleDiff: (e) => + @isOpen = !@isOpen + if not @isOpen and not @hasError + @content.hide() + @collapsedContent.show() + else if @content + @collapsedContent.hide() + @content.show() + else + @getContentHTML() + + getContentHTML: -> + @collapsedContent.hide() + @loadingContent.show() + $.get @diffForPath, (data) => + @loadingContent.hide() + if data.html + @content = $(data.html) + @content.syntaxHighlight() + else + @hasError = true + @content = $(ERROR_HTML) + @collapsedContent.after(@content) + return + +$.fn.singleFileDiff = -> + return @each -> + if not $.data this, 'singleFileDiff' + $.data this, 'singleFileDiff', new SingleFileDiff this diff --git a/app/assets/javascripts/u2f/authenticate.js.coffee b/app/assets/javascripts/u2f/authenticate.js.coffee index 6deb902c8de..918c0a560fd 100644 --- a/app/assets/javascripts/u2f/authenticate.js.coffee +++ b/app/assets/javascripts/u2f/authenticate.js.coffee @@ -6,8 +6,20 @@ class @U2FAuthenticate constructor: (@container, u2fParams) -> @appId = u2fParams.app_id - @challenges = u2fParams.challenges - @signRequests = u2fParams.sign_requests + @challenge = u2fParams.challenge + + # The U2F Javascript API v1.1 requires a single challenge, with + # _no challenges per-request_. The U2F Javascript API v1.0 requires a + # challenge per-request, which is done by copying the single challenge + # into every request. + # + # In either case, we don't need the per-request challenges that the server + # has generated, so we can remove them. + # + # Note: The server library fixes this behaviour in (unreleased) version 1.0.0. + # This can be removed once we upgrade. + # https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4 + @signRequests = u2fParams.sign_requests.map (request) -> _(request).omit('challenge') start: () => if U2FUtil.isU2FSupported() @@ -16,7 +28,7 @@ class @U2FAuthenticate @renderNotSupported() authenticate: () => - u2f.sign(@appId, @challenges, @signRequests, (response) => + u2f.sign(@appId, @challenge, @signRequests, (response) => if response.errorCode error = new U2FError(response.errorCode) @renderError(error); diff --git a/app/assets/javascripts/u2f/util.js.coffee b/app/assets/javascripts/u2f/util.js.coffee new file mode 100644 index 00000000000..5ef324f609d --- /dev/null +++ b/app/assets/javascripts/u2f/util.js.coffee @@ -0,0 +1,3 @@ +class @U2FUtil + @isU2FSupported: -> + window.u2f diff --git a/app/assets/javascripts/u2f/util.js.coffee.erb b/app/assets/javascripts/u2f/util.js.coffee.erb deleted file mode 100644 index d59341c38b9..00000000000 --- a/app/assets/javascripts/u2f/util.js.coffee.erb +++ /dev/null @@ -1,15 +0,0 @@ -# Helper class for U2F (universal 2nd factor) device registration and authentication. - -class @U2FUtil - @isU2FSupported: -> - if @testMode - true - else - gon.u2f.browser_supports_u2f - - @enableTestMode: -> - @testMode = true - -<% if Rails.env.test? %> -U2FUtil.enableTestMode(); -<% end %> diff --git a/app/assets/javascripts/users/calendar.js.coffee b/app/assets/javascripts/users/calendar.js.coffee index c081f023b04..c49ba5186f2 100644 --- a/app/assets/javascripts/users/calendar.js.coffee +++ b/app/assets/javascripts/users/calendar.js.coffee @@ -87,14 +87,15 @@ class @Calendar .attr 'width', @daySize .attr 'height', @daySize .attr 'title', (stamp) => + date = new Date(stamp.date) contribText = 'No contributions' if stamp.count > 0 contribText = "#{stamp.count} contribution#{if stamp.count > 1 then 's' else ''}" - date = dateFormat(stamp.date, 'mmm d, yyyy') + dateText = dateFormat(date, 'mmm d, yyyy') - "#{contribText}<br />#{date}" + "#{contribText}<br />#{gl.utils.getDayName(date)} #{dateText}" .attr 'class', 'user-contrib-cell js-tooltip' .attr 'fill', (stamp) => if stamp.count isnt 0 diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 2548efb2186..344be811e0d 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -56,14 +56,18 @@ class @UsersSelect username: '' avatar: '' $value.html(assigneeTemplate(user)) + + $collapsedSidebar + .attr('title', user.name) + .tooltip('fixTitle') + $collapsedSidebar.html(collapsedAssigneeTemplate(user)) collapsedAssigneeTemplate = _.template( '<% if( avatar ) { %> - <a class="author_link" href="/u/<%= username %>"> - <img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>"> - <span class="author">Toni Boehm</span> + <a class="author_link" href="/u/<%- username %>"> + <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> @@ -72,13 +76,13 @@ class @UsersSelect assigneeTemplate = _.template( '<% if (username) { %> - <a class="author_link bold" href="/u/<%= username %>"> + <a class="author_link bold" href="/u/<%- username %>"> <% if( avatar ) { %> - <img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>"> + <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> - <span class="author"><%= name %></span> + <span class="author"><%- name %></span> <span class="username"> - @<%= username %> + @<%- username %> </span> </a> <% } else { %> diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index bb8d71fbae8..8b6ddf8ba18 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -20,6 +20,7 @@ } &.s16 { width: 16px; height: 16px; margin-right: 6px; } + &.s20 { width: 20px; height: 20px; margin-right: 7px; } &.s24 { width: 24px; height: 24px; margin-right: 8px; } &.s26 { width: 26px; height: 26px; margin-right: 8px; } &.s32 { width: 32px; height: 32px; margin-right: 10px; } diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index 40b5171a8c6..540718197e0 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -1,3 +1,12 @@ +.blank-state-welcome { + text-align: center; + border-bottom: 1px solid $border-color; + + .blank-state-text { + margin-bottom: 0; + } +} + .blank-state { padding-top: 20px; padding-bottom: 20px; @@ -6,7 +15,18 @@ .blank-state-no-icon { padding-top: 40px; - padding-bottom: 40px; + padding-bottom: 40px; +} + +.blank-state-icon { + padding-bottom: 20px; + color: $gray-darkest; + font-size: 56px; + + path, + polygon { + fill: currentColor; + } } .blank-state-title { @@ -20,4 +40,12 @@ margin-top: 0; margin-bottom: $gl-padding; font-size: 15px; + + > strong { + font-weight: 600; + } +} + +.blank-state-welcome-title { + font-size: 24px; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 41e77a4ac68..ad94e457cfd 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -16,6 +16,15 @@ font-weight: normal; font-size: 16px; line-height: 36px; + + &.diff-collapsed { + padding: 5px; + cursor: pointer; + + &:hover { + background-color: $row-hover; + } + } } .row-content-block { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 1e3083cce55..590b8f54363 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -281,3 +281,21 @@ color: $gl-icon-color; } } + +.clone-dropdown-btn a { + color: $dropdown-link-color; + &:hover { + text-decoration: none; + } +} + +.btn-static { + background-color: $background-color !important; + border: 1px solid lightgrey; + cursor: default; + &:active { + -moz-box-shadow: inset 0 0 0 white; + -webkit-box-shadow: inset 0 0 0 white; + box-shadow: inset 0 0 0 white; + } +} diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index f8aecd0558d..c1e5305644b 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -270,21 +270,6 @@ table { } } -.dashboard-intro-icon { - float: left; - text-align: center; - font-size: 32px; - color: #aaa; - width: 60px; -} - -.dashboard-intro-text { - display: inline-block; - margin-left: -60px; - padding-left: 60px; - width: 100%; -} - .btn-sign-in { text-shadow: none; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 2a90a1fef37..d4e900f80ef 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -23,6 +23,9 @@ .dropdown-menu, .dropdown-menu-nav { display: block; + @media (max-width: $screen-xs-max) { + width: 100%; + } } .dropdown-menu-toggle { @@ -65,6 +68,10 @@ color: $dropdown-toggle-hover-icon-color; } } + + &.large { + width: 200px; + } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 71a9f79be3e..71e4b50f2af 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -50,7 +50,7 @@ } a:not(.btn) { - color: $gl-dark-link-color; + color: $gl-text-color; } .left-options { diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index a951a2b97fe..0c21d0240b3 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -1,8 +1,8 @@ .flash-container { cursor: pointer; margin: 0; + margin-bottom: $gl-padding; font-size: 14px; - width: 100%; z-index: 100; .flash-notice { @@ -18,9 +18,27 @@ } .flash-notice, .flash-alert { - .container-fluid.flash-text { + border-radius: $border-radius-default; + + .container-fluid.container-limited.flash-text { background: transparent; } } + + &.flash-container-page { + margin-bottom: 0; + + .flash-notice, .flash-alert { + border-radius: 0; + } + } +} + +@media (max-width: $screen-md-min) { + ul.notes { + .flash-container.timeline-content { + margin-left: 0; + } + } } diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 0a8603b6702..3673b81f183 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -11,7 +11,6 @@ .toggle-nav-collapse, .pin-nav-btn { color: $color-light; - background: $color; &:hover { color: $white-light; @@ -20,17 +19,6 @@ .sidebar-wrapper { background: $color-darker; - - .sidebar-user { - background: $color-darker; - color: $color-light; - - &:hover { - background-color: $color-dark; - color: $white-light; - text-decoration: none; - } - } } .nav-sidebar li { diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index a12c0bba44a..2c40ec430ca 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -137,6 +137,15 @@ ul.content-list { padding-top: 1px; float: right; + > .control-text { + margin-right: $gl-padding-top; + line-height: 40px; + + &:last-child { + margin-right: 0; + } + } + > .btn, > .btn-group { margin-right: $gl-padding-top; @@ -166,6 +175,12 @@ ul.content-list { .panel > .content-list > li { padding: $gl-padding-top $gl-padding; + + &.commit { + @media (min-width: $screen-sm-min) { + padding-left: 46px + $gl-padding; + } + } } ul.controls { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 6e5f216c894..364952d3b4a 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -77,10 +77,10 @@ &.sub-nav { text-align: center; - background-color: $background-color; + background-color: $dark-background-color; .container-fluid { - background-color: $background-color; + background-color: $dark-background-color; margin-bottom: 0; } @@ -134,6 +134,11 @@ margin-bottom: 0; border-bottom: none; + &.wide { + width: 100%; + display: block; + } + li a { padding: 16px 10px 11px; } @@ -164,6 +169,7 @@ > .btn { margin-right: $gl-padding-top; display: inline-block; + vertical-align: top; &:last-child { margin-right: 0; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index e8d6a7f2775..d52e8f00172 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -3,6 +3,12 @@ padding-bottom: 25px; transition: padding $sidebar-transition-duration; + &.page-sidebar-pinned { + .sidebar-wrapper { + @include box-shadow(none); + } + } + .sidebar-wrapper { position: fixed; top: 0; @@ -11,6 +17,7 @@ height: 100%; overflow: hidden; transition: width $sidebar-transition-duration; + @include box-shadow(2px 0 16px 0 $black-transparent); } } @@ -40,34 +47,14 @@ } } -.sidebar-user { - padding: 15px; - position: absolute; - left: 0; - bottom: 0; - width: $sidebar_width; - overflow: hidden; - font-size: 16px; - line-height: 36px; - transition: width $sidebar-transition-duration, padding $sidebar-transition-duration; - - @media (min-width: $sidebar-breakpoint) { - bottom: 50px; - } -} - .nav-sidebar { position: absolute; top: 50px; - bottom: 65px; + bottom: 0; width: $sidebar_width; overflow-y: auto; overflow-x: hidden; - @media (min-width: $sidebar-breakpoint) { - bottom: 115px; - } - &.navbar-collapse { padding: 0 !important; } @@ -85,7 +72,7 @@ } a { - padding: 7px 15px 7px 12px; + padding: 7px $gl-sidebar-padding; font-size: $gl-font-size; line-height: 24px; display: block; @@ -117,7 +104,7 @@ } } -.toggle-nav-collapse { +.sidebar-action-buttons { width: $sidebar_width; position: absolute; top: 0; @@ -126,12 +113,37 @@ padding: 5px 0; font-size: 18px; line-height: 30px; + + .toggle-nav-collapse { + left: 0; + } + + .pin-nav-btn { + right: 0; + display: none; + + @media (min-width: $sidebar-breakpoint) { + display: block; + } + + .fa { + transition: transform .15s; + } + + &.is-active { + .fa { + transform: rotate(90deg); + } + } + } } .nav-header-btn { - padding: 10px 5px; + padding: 10px $gl-sidebar-padding; color: inherit; transition-duration: .3s; + position: absolute; + top: 0; &:hover, &:focus { @@ -140,30 +152,6 @@ } } -.pin-nav-btn { - display: none; - position: absolute; - left: 0; - bottom: 0; - height: 50px; - width: $sidebar_width; - line-height: 30px; - - @media (min-width: $sidebar-breakpoint) { - display: block; - } - - .fa { - transition: transform .15s; - } - - &.is-active { - .fa { - transform: rotate(90deg); - } - } -} - .page-sidebar-collapsed { padding-left: 0; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 211a9af2348..f0e7002e4cd 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,10 +12,12 @@ $sidebar-breakpoint: 1024px; /* * UI elements */ -$border-color: #e5e5e5; -$focus-border-color: #3aabf0; -$table-border-color: #f0f0f0; -$background-color: #fafafa; +$border-color: #e5e5e5; +$focus-border-color: #3aabf0; +$table-border-color: #f0f0f0; +$background-color: #fafafa; +$dark-background-color: #f7f7f7; +$table-text-gray: #8f8f8f; /* * Text @@ -62,6 +64,7 @@ $gl-btn-padding: 10px; $gl-input-padding: 10px; $gl-vert-padding: 6px; $gl-padding-top: 10px; +$gl-sidebar-padding: 22px; /* * Misc @@ -153,9 +156,6 @@ $warning-message-bg: #fbf2d9; $warning-message-color: #9e8e60; $warning-message-border: #f0e2bb; -/* header */ -$light-grey-header: #faf9f9; - /* tanuki logo colors */ $tanuki-red: #e24329; $tanuki-orange: #fc6d26; diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index e05f14e7496..5607239d92d 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -71,3 +71,30 @@ @extend .broadcast-message; margin-bottom: 20px; } + + +// Users List + +.users-list { + .user-row { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + } + + .user-details { + flex: 1 1 auto; + } + + .user-name { + display: inline-block; + font-weight: 600; + } + + .dropdown { + .btn-block { + margin-bottom: 0; + line-height: inherit; + } + } +} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index e8f1935d239..99a2cd306cf 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -83,14 +83,6 @@ } } -table.builds { - .build-link { - a { - color: $gl-dark-link-color; - } - } -} - .build-trace { background: $ci-output-bg; color: $ci-text-color; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 85bbf70e188..0298577c494 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -61,7 +61,7 @@ font-size: 0; } - .btn-transparent { + .btn-clipboard, .btn-transparent { padding-left: 0; padding-right: 0; } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 5286b73cc50..21b1c223c88 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -434,13 +434,3 @@ } } } - -.discussion { - .diff-content { - .diff-line-num { - &:before { - content: attr(data-linenumber); - } - } - } -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 3d79f4400e2..2a3acc3eb4c 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -40,6 +40,12 @@ } } +.ldap-group-links { + .form-actions { + margin-bottom: $gl-padding; + } +} + .groups-cover-block { .container-fluid { position: relative; diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 4e35ca329e4..0e4d8c140aa 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -63,8 +63,8 @@ form.edit-issue { .merge-request, .issue { &.today { - background: #efe; - border-color: #cec; + background: #f8feef; + border-color: #e1e8d5; } &.closed { diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 47bfd144930..3b1e38fc07d 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -162,9 +162,15 @@ } .filtered-labels { + font-size: 0; + padding: 12px 16px; + .label-row { + margin-top: 4px; + margin-bottom: 4px; + &:not(:last-child) { - margin-right: 5px; + margin-right: 8px; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 124f4afaa0d..15c6c9f231a 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -73,11 +73,14 @@ color: #888; } - &.ci-pending, - &.ci-running { + &.ci-pending { color: $gl-warning; } + &.ci-running { + color: $blue-normal; + } + &.ci-failed, &.ci-error { color: $gl-danger; @@ -167,7 +170,8 @@ .commit { margin: 0; - padding: 2px 0; + padding-top: 2px; + padding-bottom: 2px; list-style: none; &:hover { background: none; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6128868b670..a0334207c68 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -1,15 +1,12 @@ .pipelines { .stage { - max-width: 100px; + max-width: 80px; + width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .duration, .finished_at { - margin: 4px 0; - } - .commit-title { margin: 0; } @@ -22,3 +19,156 @@ margin: 4px; } } + +.content-list { + + &.pipelines, + &.builds-content-list { + width: 100%; + overflow: auto; + } +} + +.table.builds { + min-width: 1100px; + + tr { + th { + padding: 16px; + border: none; + } + } + + tbody { + border-top-width: 1px; + } + + .commit-link { + + a:hover { + text-decoration: none; + } + } + + .branch-commit { + + .branch-name { + margin-left: 8px; + font-weight: bold; + max-width: 180px; + overflow: hidden; + display: inline-block; + white-space: nowrap; + vertical-align: top; + text-overflow: ellipsis; + } + + svg { + margin: 0 6px; + height: 14px; + width: auto; + vertical-align: middle; + } + + .commit-id { + color: $gl-link-color; + margin-right: 8px; + } + + .commit-title { + margin-top: 4px; + max-width: 320px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .avatar { + margin-left: 0; + } + + .label { + margin-right: 4px; + } + + .label-container { + font-size: 0; + + .label { + margin-top: 5px; + } + } + } + + .duration, + .finished-at { + color: $table-text-gray; + margin: 4px 0; + + .fa { + font-size: 12px; + } + + svg { + height: 12px; + width: auto; + vertical-align: middle; + } + + .fa, + svg { + margin-right: 5px; + } + } + + .pipeline-actions { + + .btn { + margin: 0; + color: $table-text-gray; + } + + .cancel-retry-btns { + vertical-align: middle; + + .btn:not(:first-child) { + margin-left: 8px; + } + } + + .dropdown-toggle, + .dropdown-menu { + color: $table-text-gray; + + .fa { + color: $table-text-gray; + margin-right: 6px; + font-size: 14px; + } + } + + .btn-remove { + color: $white-light; + } + + .btn-group { + &.open { + .btn-default { + background-color: $white-normal; + border-color: $border-white-normal; + } + } + } + } + + .build-link { + + a { + color: $gl-dark-link-color; + } + } + + .btn-group.open .dropdown-toggle { + box-shadow: none; + } +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 817c2982923..ea9f7cf0540 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -64,86 +64,49 @@ } .project-home-panel { - background: $white-light; - text-align: left; - padding: 24px 0; + padding-top: 24px; + padding-bottom: 24px; - .container-fluid { - position: relative; - - @media (min-width: $screen-lg-min) { - .row { - display: flex; - -ms-flex-align: center; - -webkit-align-items: center; - -webkit-box-align: center; - } - } + @media (min-width: $screen-sm-min) { + border-bottom: 1px solid $border-color; } - .cover-controls { - .project-settings-dropdown { - margin-left: 10px; - display: inline-block; + .project-avatar { + float: none; + margin-left: auto; + margin-right: auto; - .dropdown-menu { - left: auto; - width: auto; - right: 0; - max-width: 240px; - } + &.identicon { + border-radius: 50%; } } - .cover-title { - margin-bottom: 0; - } - - .project-image-container { - @include make-sm-column(1); - max-width: 86px; - min-width: 86px; - padding-right: 0; - - @media (max-width: $screen-md-max) { - padding-left: 0; - margin: 0 0 10px; - max-width: none; - min-width: none; + .project-title { + margin-top: 10px; + margin-bottom: 10px; + font-size: 24px; + font-weight: 400; + line-height: 1; - .avatar.s70 { - margin: auto; - } + .fa { + margin-left: 2px; + font-size: 12px; + vertical-align: middle; } } - .project-info { - @include make-sm-column(10); + .project-home-desc { + margin-left: auto; + margin-right: auto; + margin-bottom: 15px; + max-width: 480px; - h1 { - font-size: 24px; - font-weight: normal; - margin: 0; + > p { + margin-bottom: 0; } - - .project-home-desc { - p { - margin: 0; - } - } - } - - .identicon { - float: left; - @include border-radius(50%); - } - - .avatar { - float: none; } .notifications-btn { - .fa-bell, .fa-spinner { margin-right: 6px; @@ -153,127 +116,106 @@ margin-left: 6px; } } +} - .project-repo-buttons { - font-size: 0; - - .btn { - @include btn-gray; - padding: 3px 10px; - text-transform: none; - background-color: $background-color; +.project-repo-buttons { + font-size: 0; - .fa { - color: $layout-link-gray; - } + .btn { + @include btn-gray; + padding: 3px 10px; - .fa-caret-down { - margin-left: 3px; - } + .fa { + color: $layout-link-gray; } - form { - margin-left: 10px; + .fa-caret-down { + margin-left: 3px; } + } - .count-buttons { - display: inline-block; - vertical-align: top; - margin-top: 16px; - } + .project-repo-btn-group, + .notification-dropdown { + margin-left: 10px; + } - .project-clone-holder { - display: inline-block; - margin-top: 16px; + .count-buttons { + display: inline-block; + vertical-align: top; + } - input { - height: 29px; - } + .project-clone-holder { + display: inline-block; + + input { + height: 29px; } + } - .count-with-arrow { - display: inline-block; - position: relative; - margin-left: 4px; - - .arrow { - &:before { - content: ''; - display: inline-block; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: 50%; - left: 0; - margin-top: -6px; - border-width: 7px 5px 7px 0; - border-right-color: #dce0e5; - } + .count-with-arrow { + display: inline-block; + position: relative; + margin-left: 4px; - &:after { - content: ''; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: 50%; - left: 1px; - margin-top: -9px; - border-width: 10px 7px 10px 0; - border-right-color: #fff; - } - } - .count { - @include btn-gray; + .arrow { + &:before { + content: ''; display: inline-block; - background: white; - border-radius: 2px; - border-width: 1px; + position: absolute; + width: 0; + height: 0; + border-color: transparent; border-style: solid; - font-size: 13px; - font-weight: 600; - line-height: 13px; - padding: $gl-vert-padding $gl-padding; - letter-spacing: .4px; - padding: 7px 14px; - text-align: center; - vertical-align: middle; - touch-action: manipulation; - cursor: pointer; - background-image: none; - white-space: nowrap; - margin: 0 10px 0 4px; - - a { - color: inherit; - } - - &:hover { - background: #fff; - } + top: 50%; + left: 0; + margin-top: -6px; + border-width: 7px 5px 7px 0; + border-right-color: #dce0e5; + pointer-events: none; } - } - } - - .project-right-buttons { - position: absolute; - right: 16px; - bottom: 0; - @media (max-width: $screen-md-max) { - top: 0; + &:after { + content: ''; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 50%; + left: 1px; + margin-top: -9px; + border-width: 10px 7px 10px 0; + border-right-color: #fff; + pointer-events: none; + } } - } - - @media (max-width: $screen-md-max) { - text-align: center; + .count { + @include btn-gray; + display: inline-block; + background: white; + border-radius: 2px; + border-width: 1px; + border-style: solid; + font-size: 13px; + font-weight: 600; + line-height: 13px; + padding: $gl-vert-padding $gl-padding; + letter-spacing: .4px; + padding: 7px 14px; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + background-image: none; + white-space: nowrap; + margin: 0 10px 0 4px; + + a { + color: inherit; + } - .project-info, - .project-image-container { - width: 100%; + &:hover { + background: #fff; + } } } } @@ -398,59 +340,72 @@ a.deploy-project-label { .project-import { .form-group { - margin-bottom: 0; + margin-bottom: 5px; } + .import-buttons { padding-left: 0; display: -webkit-flex; display: flex; -webkit-flex-wrap: wrap; flex-wrap: wrap; + .btn { - margin-right: 10px; - padding: 8px 12px; + margin: 0 10px 10px 0; + padding: 8px; } - &> div { - margin-bottom: 14px; + + > div { padding-left: 0; + &:last-child { margin-bottom: 0; + + .btn { + margin-right: 0; + } } } } } .project-stats { - margin-top: $gl-padding; - margin-bottom: 0; - padding: 0; - background-color: $white-light; font-size: 0; + border-bottom: 1px solid $border-color; - ul.nav { - display: inline-block; + .nav { + padding-top: 12px; + padding-bottom: 12px; } - .nav li { + .nav > li { display: inline-block; - margin: 16px 0; - margin-right: 16px; + + &:not(:last-child) { + margin-right: $gl-padding; + } + + &.project-repo-buttons-right { + margin-top: 10px; + + @media (min-width: $screen-md-min) { + float: right; + margin-top: 0; + } + } } .nav > li > a { + padding: 0; background-color: transparent; - padding: 5px 10px; font-size: 15px; + line-height: 29px; color: $notes-light-color; - } - li { - display: inline; - } - - a { - float: left; - font-size: 17px; + &:hover, + &:focus { + color: darken($notes-light-color, 15%); + } } li.missing { @@ -458,6 +413,8 @@ a.deploy-project-label { border-radius: $border-radius-default; a { + padding-left: 10px; + padding-right: 10px; color: $notes-light-color; display: block; } @@ -466,10 +423,6 @@ a.deploy-project-label { background-color: $gray-normal; } } - - &.row-content-block.second-block { - margin-top: 0; - } } pre.light-well { @@ -557,8 +510,32 @@ pre.light-well { } .project-last-commit { + @media (min-width: $screen-sm-min) { + margin-top: $gl-padding; + } + + &.container-fluid { + padding-top: 12px; + padding-bottom: 12px; + background-color: $background-color; + border: 1px solid $border-color; + border-right-width: 0; + border-left-width: 0; + + @media (min-width: $screen-sm-min) { + border-right-width: 1px; + border-left-width: 1px; + } + } + + &.container-limited { + @media (min-width: 1281px) { + border-radius: $border-radius-base; + } + } + .ci-status { - margin-right: 16px; + margin-right: $gl-padding; } .commit-row-message { @@ -566,19 +543,12 @@ pre.light-well { } .commit_short_id { - margin: 0 5px; + margin-right: 5px; color: $gl-link-color; font-weight: 600; } .commit-author-link { - margin-left: 7px; - text-decoration: none; - .avatar { - float: none; - margin-right: 4px; - } - .commit-author-name { font-weight: 600; } @@ -601,15 +571,10 @@ pre.light-well { } .git-clone-holder { - width: 498px; + width: 380px; .btn-clipboard { border: 1px solid $border-color; - padding: 6px $gl-padding; - } - - .project-home-dropdown + & { - margin-right: 45px; } .clone-options { @@ -678,3 +643,9 @@ pre.light-well { width: 300px; } } + +.compare-form-group { + .dropdown-menu { + width: 300px; + } +} diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index ae524cd6bae..c9d436d72ba 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -185,7 +185,7 @@ padding-right: $gl-padding + 15px; } - .btn-search { + .btn-search, .btn-new { width: 100%; margin-top: 5px; @@ -208,7 +208,7 @@ margin-top: 5px; @media (min-width: $screen-sm-min) { - width: 160px; + width: 180px; margin-top: 0; } } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 2370d35924e..c6b053150be 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -32,11 +32,15 @@ border-color: $gl-gray; } - &.ci-pending, - &.ci-running { + &.ci-pending { color: $gl-warning; border-color: $gl-warning; } + + &.ci-running { + color: $blue-normal; + border-color: $blue-normal; + } } .ci-status-icon-success { @@ -45,10 +49,12 @@ .ci-status-icon-failed { color: $gl-danger; } - .ci-status-icon-running, .ci-status-icon-pending { color: $gl-warning; } + .ci-status-icon-running { + color: $blue-normal; + } .ci-status-icon-canceled, .ci-status-icon-disabled, .ci-status-icon-not-found, diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 5b61270daa8..42a20e9775f 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -23,12 +23,11 @@ } &:hover { - cursor: pointer; - td { background-color: $row-hover; border-top: 1px solid $row-hover-border; border-bottom: 1px solid $row-hover-border; + cursor: pointer; } } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 5f65dd3aff0..23ba83aba0e 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -87,6 +87,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :version_check_enabled, :admin_notification_email, :user_oauth_applications, + :user_default_external, :shared_runners_enabled, :shared_runners_text, :max_artifacts_size, @@ -110,6 +111,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :send_user_confirmation_email, :container_registry_token_expire_delay, :repository_storage, + :enabled_git_access_protocol, restricted_visibility_levels: [], import_sources: [], disabled_oauth_sign_in_sources: [] diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 4c9c6362ffc..0d2f4f6eb38 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -5,11 +5,12 @@ class Admin::ProjectsController < Admin::ApplicationController def index @projects = Project.all @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? - @projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? + @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.with_push if params[:with_push].present? @projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present? - @projects = @projects.non_archived unless params[:with_archived].present? + @projects = @projects.non_archived unless params[:archived].present? + @projects = @projects.personal(current_user) if params[:personal].present? @projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9cc31620d9f..a1004d9bcea 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -344,10 +344,6 @@ class ApplicationController < ActionController::Base session[:skip_tfa] && session[:skip_tfa] > Time.current end - def browser_supports_u2f? - browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile? - end - def redirect_to_home_page_url? # If user is not signed-in and tries to access root_path - redirect him to landing page # Don't redirect to the default URL to prevent endless redirections diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index 998b8adc411..ba07cea569c 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -57,7 +57,7 @@ module AuthenticatesWithTwoFactor # Authenticate using the response from a U2F (universal 2nd factor) device def authenticate_with_two_factor_via_u2f(user) - if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenges]) + if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge]) # Remove any lingering user data from login session.delete(:otp_user_id) session.delete(:challenges) @@ -77,11 +77,9 @@ module AuthenticatesWithTwoFactor if key_handles.present? sign_requests = u2f.authentication_requests(key_handles) - challenges = sign_requests.map(&:challenge) - session[:challenges] = challenges - gon.push(u2f: { challenges: challenges, app_id: u2f_app_id, - sign_requests: sign_requests, - browser_supports_u2f: browser_supports_u2f? }) + session[:challenge] ||= u2f.challenge + gon.push(u2f: { challenge: session[:challenge], app_id: u2f_app_id, + sign_requests: sign_requests }) end end end diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb new file mode 100644 index 00000000000..e09b8789eb2 --- /dev/null +++ b/app/controllers/concerns/diff_for_path.rb @@ -0,0 +1,25 @@ +module DiffForPath + extend ActiveSupport::Concern + + def render_diff_for_path(diffs, diff_refs, project) + diff_file = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository).find do |diff| + diff.old_path == params[:old_path] && diff.new_path == params[:new_path] + end + + return render_404 unless diff_file + + diff_commit = commit_for_diff(diff_file) + blob = diff_file.blob(diff_commit) + @expand_all_diffs = true + + locals = { + diff_file: diff_file, + diff_commit: diff_commit, + diff_refs: diff_refs, + blob: blob, + project: project + } + + render json: { html: view_to_html_string('projects/diffs/_content', locals) } + end +end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 3a2db3e6eeb..19a76a5b5d8 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -1,6 +1,4 @@ class Dashboard::TodosController < Dashboard::ApplicationController - include TodosHelper - before_action :find_todos, only: [:index, :destroy_all] def index @@ -13,7 +11,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController respond_to do |format| format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' } format.js { head :ok } - format.json { render json: { count: todos_pending_count, done_count: todos_done_count } } + format.json { render json: todos_counts } end end @@ -23,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController respond_to do |format| format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } format.js { head :ok } - format.json { render json: { count: todos_pending_count, done_count: todos_done_count } } + format.json { render json: todos_counts } end end @@ -36,4 +34,11 @@ class Dashboard::TodosController < Dashboard::ApplicationController def find_todos @todos ||= TodosFinder.new(current_user, params).execute end + + def todos_counts + { + count: TodosFinder.new(current_user, state: :pending).execute.count, + done_count: TodosFinder.new(current_user, state: :done).execute.count + } + end end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 9b5c43b17e2..d3dd98c8a4e 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -12,13 +12,12 @@ class HelpController < ApplicationController end def show - @category = clean_path_info(path_params[:category]) - @file = path_params[:file] + @path = clean_path_info(path_params[:path]) respond_to do |format| format.any(:markdown, :md, :html) do # Note: We are purposefully NOT using `Rails.root.join` - path = File.join(Rails.root, 'doc', @category, "#{@file}.md") + path = File.join(Rails.root, 'doc', "#{@path}.md") if File.exist?(path) @markdown = File.read(path) @@ -33,7 +32,7 @@ class HelpController < ApplicationController # Allow access to images in the doc folder format.any(:png, :gif, :jpeg) do # Note: We are purposefully NOT using `Rails.root.join` - path = File.join(Rails.root, 'doc', @category, "#{@file}.#{params[:format]}") + path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}") if File.exist?(path) send_file(path, disposition: 'inline') @@ -57,8 +56,7 @@ class HelpController < ApplicationController private def path_params - params.require(:category) - params.require(:file) + params.require(:path) params end diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 513348c39af..30df1fb2fec 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -27,10 +27,7 @@ class Import::GitlabProjectsController < Import::BaseController notice: "Project '#{@project.name}' is being imported." ) else - redirect_to( - new_import_gitlab_project_path, - alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" - ) + redirect_back_or_default(options: { alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" }) end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index f35d631df0c..f54c79c2e37 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -107,7 +107,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Only allow properly saved users to login. if @user.persisted? && @user.valid? log_audit_event(@user, with: oauth['provider']) - sign_in_and_redirect(@user) + if @user.two_factor_enabled? + prompt_for_two_factor(@user) + else + sign_in_and_redirect(@user) + end else error_message = @user.errors.full_messages.to_sentence diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 6a358fdcc05..e37e9e136db 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -100,7 +100,6 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController gon.push(u2f: { challenges: session[:challenges], app_id: u2f_app_id, register_requests: registration_requests, - sign_requests: sign_requests, - browser_supports_u2f: browser_supports_u2f? }) + sign_requests: sign_requests }) end end diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index f11c8321464..7241949393b 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -23,10 +23,9 @@ class Projects::ArtifactsController < Projects::ApplicationController entry = build.artifacts_metadata_entry(params[:path]) if entry.exists? - render json: { archive: build.artifacts_file.path, - entry: Base64.encode64(entry.path) } + send_artifacts_entry(build, entry) else - render json: {}, status: 404 + render_404 end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7599fec3cdf..5356fdf010d 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -57,7 +57,7 @@ class Projects::BlobController < Projects::ApplicationController diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) diff_lines = diffy.diff.scan(/.*\n/)[2..-1] diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines) - @diff_lines = Gitlab::Diff::Highlight.new(diff_lines).highlight + @diff_lines = Gitlab::Diff::Highlight.new(diff_lines, repository: @repository).highlight render layout: false end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index d162a5a3165..727e84b40a1 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -3,6 +3,7 @@ # Not to be confused with CommitsController, plural. class Projects::CommitController < Projects::ApplicationController include CreatesCommit + include DiffForPath include DiffHelper # Authorize @@ -11,29 +12,14 @@ class Projects::CommitController < Projects::ApplicationController before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds] before_action :authorize_read_commit_status!, only: [:builds] before_action :commit - before_action :define_show_vars, only: [:show, :builds] + before_action :define_commit_vars, only: [:show, :diff_for_path, :builds] + before_action :define_status_vars, only: [:show, :builds] + before_action :define_note_vars, only: [:show, :diff_for_path] before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] def show apply_diff_view_cookie! - @grouped_diff_notes = commit.notes.grouped_diff_notes - @notes = commit.notes.non_diff_notes.fresh - - Banzai::NoteRenderer.render( - @grouped_diff_notes.values.flatten + @notes, - @project, - current_user, - ) - - @note = @project.build_commit_note(commit) - - @noteable = @commit - @comments_target = { - noteable_type: 'Commit', - commit_id: @commit.id - } - respond_to do |format| format.html format.diff { render text: @commit.to_diff } @@ -41,6 +27,10 @@ class Projects::CommitController < Projects::ApplicationController end end + def diff_for_path + render_diff_for_path(@diffs, @commit.diff_refs, @project) + end + def builds end @@ -114,16 +104,36 @@ class Projects::CommitController < Projects::ApplicationController @ci_builds ||= Ci::Build.where(pipeline: pipelines) end - def define_show_vars + def define_commit_vars return git_not_found! unless commit opts = diff_options opts[:ignore_whitespace_change] = true if params[:format] == 'diff' @diffs = commit.diffs(opts) - @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count + end + + def define_note_vars + @grouped_diff_notes = commit.notes.grouped_diff_notes + @notes = commit.notes.non_diff_notes.fresh + + Banzai::NoteRenderer.render( + @grouped_diff_notes.values.flatten + @notes, + @project, + current_user, + ) + + @note = @project.build_commit_note(commit) + + @noteable = @commit + @comments_target = { + noteable_type: 'Commit', + commit_id: @commit.id + } + end + def define_status_vars @statuses = CommitStatus.where(pipeline: pipelines) @builds = Ci::Build.where(pipeline: pipelines) end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index af0b69a2442..5f3ee71444d 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,30 +1,26 @@ require 'addressable/uri' class Projects::CompareController < Projects::ApplicationController + include DiffForPath include DiffHelper # Authorize before_action :require_non_empty_project before_action :authorize_download_code! - before_action :assign_ref_vars, only: [:index, :show] + before_action :define_ref_vars, only: [:index, :show, :diff_for_path] + before_action :define_diff_vars, only: [:show, :diff_for_path] before_action :merge_request, only: [:index, :show] def index end def show - compare = CompareService.new. - execute(@project, @head_ref, @project, @base_ref, diff_options) + end - if compare - @commits = Commit.decorate(compare.commits, @project) - @commit = @project.commit(@head_ref) - @base_commit = @project.merge_base_commit(@base_ref, @head_ref) - @diffs = compare.diffs(diff_options) - @diff_refs = [@base_commit, @commit] - @diff_notes_disabled = true - @grouped_diff_notes = {} - end + def diff_for_path + return render_404 unless @compare + + render_diff_for_path(@diffs, @diff_refs, @project) end def create @@ -34,13 +30,35 @@ class Projects::CompareController < Projects::ApplicationController private - def assign_ref_vars - @base_ref = Addressable::URI.unescape(params[:from]) + def define_ref_vars + @start_ref = Addressable::URI.unescape(params[:from]) @ref = @head_ref = Addressable::URI.unescape(params[:to]) end + def define_diff_vars + @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref) + + if @compare + @commits = Commit.decorate(@compare.commits, @project) + + @start_commit = @project.commit(@start_ref) + @commit = @project.commit(@head_ref) + @base_commit = @project.merge_base_commit(@start_ref, @head_ref) + + @diffs = @compare.diffs(diff_options) + @diff_refs = Gitlab::Diff::DiffRefs.new( + base_sha: @base_commit.try(:sha), + start_sha: @start_commit.try(:sha), + head_sha: @commit.try(:sha) + ) + + @diff_notes_disabled = true + @grouped_diff_notes = {} + end + end + def merge_request @merge_request ||= @project.merge_requests.opened. - find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref) + find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref) end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 62c3fa8de53..40a8b7940d9 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -19,6 +19,8 @@ class Projects::GitHttpController < Projects::ApplicationController render_ok elsif receive_pack? && receive_pack_allowed? render_ok + elsif http_blocked? + render_not_allowed else render_not_found end @@ -154,6 +156,10 @@ class Projects::GitHttpController < Projects::ApplicationController render plain: 'Not Found', status: :not_found end + def render_not_allowed + render plain: download_access.message, status: :forbidden + end + def ci? @ci.present? end @@ -162,12 +168,28 @@ class Projects::GitHttpController < Projects::ApplicationController return false unless Gitlab.config.gitlab_shell.upload_pack if user - Gitlab::GitAccess.new(user, project).download_access_check.allowed? + download_access.allowed? else ci? || project.public? end end + def access + return @access if defined?(@access) + + @access = Gitlab::GitAccess.new(user, project, 'http') + end + + def download_access + return @download_access if defined?(@download_access) + + @download_access = access.check('git-upload-pack') + end + + def http_blocked? + !access.protocol_allowed? + end + def receive_pack_allowed? return false unless Gitlab.config.gitlab_shell.receive_pack diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index dd86b940a08..df659bb8c3b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -1,5 +1,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController include ToggleSubscriptionAction + include DiffForPath include DiffHelper include IssuableActions include ToggleAwardEmoji @@ -9,10 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip ] - before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds] before_action :define_show_vars, only: [:show, :diffs, :commits, :builds] before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check] + before_action :define_commit_vars, only: [:diffs] + before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds] # Allow read any merge_request @@ -53,19 +55,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def show - @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)). - group(:commit_id).count - respond_to do |format| - format.html - format.json { render json: @merge_request } + format.html { define_discussion_vars } + + format.json do + render json: @merge_request + end + format.patch do - headers.store(*Gitlab::Workhorse.send_git_patch(@project.repository, - @merge_request.diff_base_commit.id, - @merge_request.last_commit.id)) - headers['Content-Disposition'] = 'inline' - head :ok + return render_404 unless @merge_request.diff_refs + + send_git_patch @project.repository, @merge_request.diff_refs end + format.diff do return render_404 unless @merge_request.diff_refs @@ -77,52 +79,65 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs apply_diff_view_cookie! - @commit = @merge_request.last_commit - @base_commit = @merge_request.diff_base_commit - - # MRs created before 8.4 don't have a diff_base_commit, - # but we need it for the "View file @ ..." link by deleted files - @base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit - - @comments_target = { - noteable_type: 'MergeRequest', - noteable_id: @merge_request.id - } - - @grouped_diff_notes = @merge_request.notes.grouped_diff_notes - - Banzai::NoteRenderer.render( - @grouped_diff_notes.values.flatten, - @project, - current_user, - @path, - @project_wiki, - @ref - ) + @merge_request_diff = @merge_request.merge_request_diff respond_to do |format| - format.html + format.html { define_discussion_vars } format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } end end + # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new + # and uses that (unsaved) MR. + # + def diff_for_path + if params[:id] + merge_request + define_diff_comment_vars + else + build_merge_request + @diff_notes_disabled = true + @grouped_diff_notes = {} + end + + define_commit_vars + diffs = @merge_request.diffs(diff_options) + + render_diff_for_path(diffs, @merge_request.diff_refs, @merge_request.project) + end + def commits respond_to do |format| - format.html { render 'show' } - format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } } + format.html do + define_discussion_vars + + render 'show' + end + format.json do + # Get commits from repository + # or from cache if already merged + @commits = @merge_request.commits + @note_counts = Note.where(commit_id: @commits.map(&:id)). + group(:commit_id).count + + render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } + end end end def builds respond_to do |format| - format.html { render 'show' } + format.html do + define_discussion_vars + + render 'show' + end format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } } end end def new - params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) - @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute + build_merge_request @noteable = @merge_request @target_branches = if @merge_request.target_project @@ -134,7 +149,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @target_project = merge_request.target_project @source_project = merge_request.source_project @commits = @merge_request.compare_commits.reverse - @commit = @merge_request.last_commit + @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @diff_notes_disabled = true @@ -212,7 +227,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController return end - if params[:sha] != @merge_request.source_sha + if params[:sha] != @merge_request.diff_head_sha @status = :sha_mismatch return end @@ -274,16 +289,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController status ||= "preparing" else ci_service = @merge_request.source_project.ci_service - status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service + status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service if ci_service.respond_to?(:commit_coverage) - coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch) + coverage = ci_service.commit_coverage(merge_request.diff_head_sha, merge_request.source_branch) end end response = { title: merge_request.title, - sha: merge_request.last_commit_short_sha, + sha: merge_request.diff_head_commit.short_id, status: status, coverage: coverage } @@ -308,10 +323,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController alias_method :issuable, :merge_request alias_method :awardable, :merge_request - def closes_issues - @closes_issues ||= @merge_request.closes_issues - end - def authorize_update_merge_request! return render_404 unless can?(current_user, :update_merge_request, @merge_request) end @@ -340,14 +351,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_show_vars + @noteable = @merge_request + @commits_count = @merge_request.commits.count + + @pipeline = @merge_request.pipeline + @statuses = @pipeline.statuses if @pipeline + + if @merge_request.locked_long_ago? + @merge_request.unlock_mr + @merge_request.close + end + end + + # Discussion tab data is rendered on html responses of actions + # :show, :diff, :commits, :builds. but not when request the data through AJAX + def define_discussion_vars # Build a note object for comment form - @note = @project.notes.new(noteable: @merge_request) + @note = @project.notes.new(noteable: @noteable) - @discussions = @merge_request.mr_and_commit_notes. + @discussions = @noteable.mr_and_commit_notes. inc_author_project_award_emoji. fresh. discussions + # This is not executed lazily @notes = Banzai::NoteRenderer.render( @discussions.flatten, @project, @@ -356,28 +383,35 @@ class Projects::MergeRequestsController < Projects::ApplicationController @project_wiki, @ref ) - - @noteable = @merge_request - - # Get commits from repository - # or from cache if already merged - @commits = @merge_request.commits - - @merge_request_diff = @merge_request.merge_request_diff - - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses if @pipeline - - if @merge_request.locked_long_ago? - @merge_request.unlock_mr - @merge_request.close - end end def define_widget_vars @pipeline = @merge_request.pipeline @pipelines = [@pipeline].compact - closes_issues + end + + def define_commit_vars + @commit = @merge_request.diff_head_commit + @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit + end + + def define_diff_comment_vars + @comments_target = { + noteable_type: 'MergeRequest', + noteable_id: @merge_request.id + } + + @use_legacy_diff_notes = !@merge_request.support_new_diff_notes? + @grouped_diff_notes = @merge_request.notes.grouped_diff_notes + + Banzai::NoteRenderer.render( + @grouped_diff_notes.values.flatten, + @project, + current_user, + @path, + @project_wiki, + @ref + ) end def invalid_mr @@ -408,4 +442,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active? end + + def build_merge_request + params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) + @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute + end end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index e14fe26dde7..3eacdbbd067 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -128,7 +128,7 @@ class Projects::NotesController < Projects::ApplicationController elsif note.valid? Banzai::NoteRenderer.render([note], @project, current_user) - { + attrs = { valid: true, id: note.id, discussion_id: note.discussion_id, @@ -138,6 +138,23 @@ class Projects::NotesController < Projects::ApplicationController discussion_html: note_to_discussion_html(note), discussion_with_diff_html: note_to_discussion_with_diff_html(note) } + + # The discussion_id is used to add the comment to the correct discussion + # element on the merge request page. Among other things, the discussion_id + # contains the sha of head commit of the merge request. + # When new commits are pushed into the merge request after the initial + # load of the merge request page, the discussion elements will still have + # the old discussion_ids, with the old head commit sha. The new comment, + # however, will have the new discussion_id with the new commit sha. + # To ensure that these new comments will still end up in the correct + # discussion element, we also send the original discussion_id, with the + # old commit sha, along, and fall back on this value when no discussion + # element with the new discussion_id could be found. + if note.new_diff_note? && note.position != note.original_position + attrs[:original_discussion_id] = note.original_discussion_id + end + + attrs else { valid: false, @@ -154,7 +171,7 @@ class Projects::NotesController < Projects::ApplicationController def note_params params.require(:note).permit( :note, :noteable, :noteable_id, :noteable_type, :project_id, - :attachment, :line_code, :commit_id, :type + :attachment, :line_code, :commit_id, :type, :position ) end diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index efa7bf14d0f..80dad758afa 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -2,12 +2,14 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController # Authorize before_action :require_non_empty_project before_action :authorize_admin_project! + before_action :load_protected_branch, only: [:show, :update, :destroy] layout "project_settings" def index - @branches = @project.protected_branches.to_a + @protected_branches = @project.protected_branches.order(:name).page(params[:page]) @protected_branch = @project.protected_branches.new + gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }) end def create @@ -16,26 +18,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController @project) end - def update - protected_branch = @project.protected_branches.find(params[:id]) - - if protected_branch && - protected_branch.update_attributes( - developers_can_push: params[:developers_can_push] - ) + def show + @matching_branches = @protected_branch.matching(@project.repository.branches) + end + def update + if @protected_branch && @protected_branch.update_attributes(protected_branch_params) respond_to do |format| - format.json { render json: protected_branch, status: :ok } + format.json { render json: @protected_branch, status: :ok } end else respond_to do |format| - format.json { render json: protected_branch.errors, status: :unprocessable_entity } + format.json { render json: @protected_branch.errors, status: :unprocessable_entity } end end end def destroy - @project.protected_branches.find(params[:id]).destroy + @protected_branch.destroy respond_to do |format| format.html { redirect_to namespace_project_protected_branches_path } @@ -45,6 +45,10 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController private + def load_protected_branch + @protected_branch = @project.protected_branches.find(params[:id]) + end + def protected_branch_params params.require(:protected_branch).permit(:name, :developers_can_push) end diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 23868d986e9..5685d0f4e7c 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -5,7 +5,7 @@ class Projects::TodosController < Projects::ApplicationController todo = TodoService.new.mark_todo(issuable, current_user) render json: { - count: current_user.todos_pending_count, + count: TodosFinder.new(current_user, state: :pending).execute.count, delete_path: dashboard_todo_path(todo) } end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 12e0d5a8413..1803aa8eab4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -53,11 +53,11 @@ class ProjectsController < Projects::ApplicationController notice: "Project '#{@project.name}' was successfully updated." ) end - format.js else format.html { render 'edit' } - format.js end + + format.js end end diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 7806d9e4cc5..ff866c2faa5 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -8,7 +8,7 @@ # action_id: integer # author_id: integer # project_id; integer -# state: 'pending' or 'done' +# state: 'pending' (default) or 'done' # type: 'Issue' or 'MergeRequest' # @@ -37,7 +37,7 @@ class TodosFinder private def action_id? - action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED, Todo::MARKED].include?(action_id.to_i) + action_id.present? && Todo::ACTION_NAMES.has_key?(action_id.to_i) end def action_id diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index f240584ccbf..e12a1052988 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -31,7 +31,7 @@ module AppearancesHelper end end - def navbar_icon(icon_name) - render "shared/icons/#{icon_name}.svg" + def custom_icon(icon_name, size: 16) + render "shared/icons/#{icon_name}.svg", size: size end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 62d13a4b4f3..03495cf5ec4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -306,4 +306,15 @@ module ApplicationHelper def truncate_first_line(message, length = 50) truncate(message.each_line.first.chomp, length: length) if message end + + # While similarly named to Rails's `link_to_if`, this method behaves quite differently. + # If `condition` is truthy, a link will be returned with the result of the block + # as its body. If `condition` is falsy, only the result of the block will be returned. + def conditional_link_to(condition, options, html_options = {}, &block) + if condition + link_to options, html_options, &block + else + capture(&block) + end + end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 6e580c62ccd..78c0b79d2bd 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -31,6 +31,28 @@ module ApplicationSettingsHelper current_application_settings.akismet_enabled? end + def allowed_protocols_present? + current_application_settings.enabled_git_access_protocol.present? + end + + def enabled_protocol + case current_application_settings.enabled_git_access_protocol + when 'http' + gitlab_config.protocol + when 'ssh' + 'ssh' + end + end + + def enabled_project_button(project, protocol) + case protocol + when 'ssh' + ssh_clone_button(project, 'bottom', append_link: false) + else + http_clone_button(project, 'bottom', append_link: false) + end + end + # Return a group of checkboxes that use Bootstrap's button plugin for a # toggle button effect. def restricted_level_checkboxes(help_block_id) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 428a42266d0..abe115d8c68 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,10 +1,7 @@ module BlobHelper - def highlighter(blob_name, blob_content, repository: nil, nowrap: false) - Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository) - end - - def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false) - Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository) + def highlight(blob_name, blob_content, repository: nil, plain: false) + highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository) + raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>) end def no_highlight_files diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index c533659b600..601df5c18df 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -12,7 +12,7 @@ module BranchesHelper 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::GitAccess.new(current_user, project, 'web').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 9051a493b9b..b478580978b 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") - # # => "<button class='btn btn-clipboard' data-clipboard-target='div#foo'>...</button>" - def clipboard_button_with_class(data = {}, css_class: 'btn-clipboard') - content_tag :button, - icon('clipboard'), - class: "btn #{css_class}", - data: data, - type: :button - end - - def http_clone_button(project) + def http_clone_button(project, placement = 'right', append_link: true) klass = 'http-selector' klass << ' has-tooltip' if current_user.try(:require_password?) protocol = gitlab_config.protocol.upcase - content_tag :a, protocol, + content_tag (append_link ? :a : :span), protocol, class: klass, - href: project.http_url_to_repo, + href: (project.http_url_to_repo if append_link), data: { html: true, - placement: 'right', + placement: placement, container: 'body', title: "Set a password on your account<br>to pull or push via #{protocol}" } end - def ssh_clone_button(project) + def ssh_clone_button(project, placement = 'right', append_link: true) klass = 'ssh-selector' klass << ' has-tooltip' if current_user.try(:require_ssh_key?) - content_tag :a, 'SSH', + content_tag (append_link ? :a : :span), 'SSH', class: klass, - href: project.ssh_url_to_repo, + href: (project.ssh_url_to_repo if append_link), data: { html: true, - placement: 'right', + placement: placement, container: 'body', title: 'Add an SSH key to your profile<br>to pull or push via SSH.' } diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8e4ae1e6aec..e6c99c9959e 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -29,8 +29,10 @@ module CiStatusHelper 'check' when 'failed' 'close' - when 'running', 'pending' + when 'pending' 'clock-o' + when 'running' + 'spinner' else 'circle' end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index e22dce59d0f..adab901700c 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -8,6 +8,10 @@ module DiffHelper [marked_old_line, marked_new_line] end + def expand_all_diffs? + @expand_all_diffs || params[:expand_all_diffs].present? + end + def diff_view diff_views = %w(inline parallel) @@ -18,28 +22,22 @@ module DiffHelper end end - def diff_hard_limit_enabled? - params[:force_show_diff].present? - end - def diff_options - options = { ignore_whitespace_change: hide_whitespace? } - if diff_hard_limit_enabled? - options.merge!(Commit.max_diff_options) + default_options = Commit.max_diff_options + + if action_name == 'diff_for_path' + default_options[:paths] = params.values_at(:old_path, :new_path) end - options - end - def safe_diff_files(diffs, diff_refs) - diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) } + default_options.merge(ignore_whitespace_change: hide_whitespace?) end - def generate_line_code(file_path, line) - Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) + def safe_diff_files(diffs, diff_refs: nil, repository: nil) + diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } end def unfold_bottom_class(bottom) - bottom ? 'js-unfold-bottom' : '' + bottom ? 'js-unfold js-unfold-bottom' : '' end def unfold_class(unfold) @@ -93,6 +91,8 @@ module DiffHelper end def commit_for_diff(diff_file) + return diff_file.content_commit if diff_file.content_commit + if diff_file.deleted_file @base_commit || @commit.parent || @commit else @@ -100,10 +100,11 @@ module DiffHelper end end - def diff_file_html_data(project, diff_commit, diff_file) + def diff_file_html_data(project, diff_file) + commit = commit_for_diff(diff_file) { blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, - tree_join(diff_commit.id, diff_file.file_path)) + tree_join(commit.id, diff_file.file_path)) } end diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 7c140538012..4566f3782cc 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -39,7 +39,7 @@ module DropdownsHelper end end - def dropdown_toggle(toggle_text, data_attr, options) + def dropdown_toggle(toggle_text, data_attr, options = {}) content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text") output << icon('chevron-down') diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 294b7e92b8d..47d174361db 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -61,7 +61,7 @@ module IssuablesHelper output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier" output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe output << content_tag(:strong) do - author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs") + author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true) author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 72bd1fbbd81..2b0defd1dda 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -118,7 +118,7 @@ module IssuesHelper end def emoji_icon(name, unicode = nil, aliases = [], sprite: true) - unicode ||= Emoji.emoji_filename(name) rescue "" + unicode ||= Gitlab::Emoji.emoji_filename(name) rescue "" data = { aliases: aliases.join(" "), diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index c70cd19b587..ec106418f2d 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -12,17 +12,6 @@ module MembersHelper can?(current_user, action_member_permission(:admin, member), member.source) end - def can_see_request_access_button?(source) - source_parent = source.respond_to?(:group) && source.group - - return false if source_parent && source.group.members.exists?(user_id: current_user.id) - return false if source_parent && source.group.requesters.exists?(user_id: current_user.id) - return false if source.members.exists?(user_id: current_user.id) - return true if source.requesters.exists?(user_id: current_user.id) - - true - end - def remove_member_message(member, user: nil) user = current_user if defined?(current_user) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 1dd07a2a220..db6e731c744 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -27,7 +27,7 @@ module MergeRequestsHelper end def ci_build_details_path(merge_request) - build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) + build_url = merge_request.source_project.ci_service.build_page(merge_request.diff_head_sha, merge_request.source_branch) return nil unless build_url parsed_url = URI.parse(build_url) @@ -55,6 +55,10 @@ module MergeRequestsHelper end.sort.to_sentence end + def mr_closes_issues + @mr_closes_issues ||= @merge_request.closes_issues + end + def mr_change_branches_path(merge_request) new_namespace_project_merge_request_path( @project.namespace, @project, @@ -92,4 +96,8 @@ module MergeRequestsHelper ["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"] end end + + def merge_request_button_visibility(merge_request, closed) + return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) + end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index e85ba76887d..98143dcee9b 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -24,28 +24,61 @@ module NotesHelper }.to_json end - def link_to_new_diff_note(line_code, line_type = nil) - discussion_id = LegacyDiffNote.build_discussion_id( - @comments_target[:noteable_type], - @comments_target[:noteable_id] || @comments_target[:commit_id], - line_code - ) + def diff_view_data + return {} unless @comments_target + + @comments_target.slice(:noteable_id, :noteable_type, :commit_id) + end + + def diff_view_line_data(line_code, position, line_type) + return if @diff_notes_disabled + + use_legacy_diff_note = @use_legacy_diff_notes + # If the controller doesn't force the use of legacy diff notes, we + # determine this on a line-by-line basis by seeing if there already exist + # active legacy diff notes at this line, in which case newly created notes + # will use the legacy technology as well. + # We do this because the discussion_id values of legacy and "new" diff + # notes, which are used to group notes on the merge request discussion tab, + # are incompatible. + # If we didn't, diff notes that would show for the same line on the changes + # tab, would show in different discussions on the discussion tab. + use_legacy_diff_note ||= begin + line_diff_notes = @grouped_diff_notes[line_code] + line_diff_notes && line_diff_notes.any?(&:legacy_diff_note?) + end data = { - noteable_type: @comments_target[:noteable_type], - noteable_id: @comments_target[:noteable_id], - commit_id: @comments_target[:commit_id], - line_type: line_type, - line_code: line_code, - note_type: LegacyDiffNote.name, - discussion_id: discussion_id + line_code: line_code, + line_type: line_type, } - button_tag(class: 'btn add-diff-note js-add-diff-note-button', - data: data, - title: 'Add a comment to this line') do - icon('comment-o') + if use_legacy_diff_note + discussion_id = LegacyDiffNote.build_discussion_id( + @comments_target[:noteable_type], + @comments_target[:noteable_id] || @comments_target[:commit_id], + line_code + ) + + data.merge!( + note_type: LegacyDiffNote.name, + discussion_id: discussion_id + ) + else + discussion_id = DiffNote.build_discussion_id( + @comments_target[:noteable_type], + @comments_target[:noteable_id] || @comments_target[:commit_id], + position + ) + + data.merge!( + position: position.to_json, + note_type: DiffNote.name, + discussion_id: discussion_id + ) end + + data end def link_to_reply_discussion(note, line_type = nil) @@ -60,14 +93,15 @@ module NotesHelper } if note.diff_note? - data.merge!( - line_code: note.line_code, - note_type: LegacyDiffNote.name - ) + data[:note_type] = note.type + + data.merge!(note.diff_attributes) end - button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', - data: data, title: 'Add a reply' + content_tag(:div, class: "discussion-reply-holder") do + button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', + data: data, title: 'Add a reply' + end end def note_max_access_for_user(note) @@ -79,4 +113,14 @@ module NotesHelper full_key = { project: note.project, user_id: note.author_id } @max_access_by_user_id[full_key] end + + def diff_note_path(note) + return unless note.diff_note? + + if note.for_merge_request? && note.active? + diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) + elsif note.for_commit? + namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) + end + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f312a7ccca3..a733dff1579 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -19,7 +19,7 @@ module ProjectsHelper end def link_to_member(project, author, opts = {}, &block) - default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } + default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false } opts = default_opts.merge(opts) return "(deleted)" unless author @@ -33,7 +33,8 @@ module ProjectsHelper if opts[:by_username] author_html << content_tag(:span, sanitize("@#{author.username}"), class: opts[:author_class]) if opts[:name] else - author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] + tooltip_data = { placement: 'top' } + author_html << content_tag(:span, sanitize(author.name), class: [opts[:author_class], ('has-tooltip' if opts[:tooltip])], title: (author.to_reference if opts[:tooltip]), data: (tooltip_data if opts[:tooltip])) if opts[:name] end author_html << capture(&block) if block @@ -60,7 +61,7 @@ module ProjectsHelper project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } if current_user - project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) + project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) end full_title = "#{namespace_link} / #{project_link}".html_safe @@ -206,10 +207,14 @@ module ProjectsHelper end def default_clone_protocol - if !current_user || current_user.require_ssh_key? - gitlab_config.protocol + if allowed_protocols_present? + enabled_protocol else - "ssh" + if !current_user || current_user.require_ssh_key? + gitlab_config.protocol + else + 'ssh' + end end end @@ -289,7 +294,11 @@ module ProjectsHelper end def last_push_event - if current_user + return unless current_user + + if fork = current_user.fork_of(@project) + current_user.recent_push(fork.id) + else current_user.recent_push(@project.id) end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index f9fc525df6f..fcb2703e837 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -43,15 +43,15 @@ module SearchHelper # Autocomplete results for internal help pages def help_autocomplete [ - { category: "Help", label: "API Help", url: help_page_path("api", "README") }, - { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") }, - { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") }, - { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") }, - { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") }, - { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") }, - { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") }, - { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") }, - { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") }, + { category: "Help", label: "API Help", url: help_page_path("api/README") }, + { category: "Help", label: "Markdown Help", url: help_page_path("markdown/markdown") }, + { category: "Help", label: "Permissions Help", url: help_page_path("user/permissions") }, + { category: "Help", label: "Public Access Help", url: help_page_path("public_access/public_access") }, + { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks/README") }, + { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh/README") }, + { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks/system_hooks") }, + { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks/web_hooks") }, + { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") }, ] end diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index b04b0a5114c..8cb82c2d5cc 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -23,4 +23,11 @@ module TimeHelper def date_from_to(from, to) "#{from.to_s(:short)} - #{to.to_s(:short)}" end + + def duration_in_numbers(finished_at, started_at) + diff_in_seconds = finished_at.to_i - started_at.to_i + time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S" + + Time.at(diff_in_seconds).utc.strftime(time_format) + end end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index a832a6c8df7..e3a208f826a 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -1,10 +1,10 @@ module TodosHelper def todos_pending_count - TodosFinder.new(current_user, state: :pending).execute.count + @todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count end def todos_done_count - TodosFinder.new(current_user, state: :done).execute.count + @todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count end def todo_action_name(todo) @@ -13,6 +13,7 @@ module TodosHelper when Todo::MENTIONED then 'mentioned you on' when Todo::BUILD_FAILED then 'The build failed for your' when Todo::MARKED then 'added a todo for' + when Todo::APPROVAL_REQUIRED then 'set you as an approver for' end end diff --git a/app/helpers/u2f_helper.rb b/app/helpers/u2f_helper.rb new file mode 100644 index 00000000000..143b4ca6b51 --- /dev/null +++ b/app/helpers/u2f_helper.rb @@ -0,0 +1,5 @@ +module U2fHelper + def inject_u2f_api? + browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile? + end +end diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index 2bd0dbfd095..d887cdadc34 100644 --- a/app/helpers/workhorse_helper.rb +++ b/app/helpers/workhorse_helper.rb @@ -1,4 +1,4 @@ -# Helpers to send Git blobs, diffs or archives through Workhorse. +# Helpers to send Git blobs, diffs, patches or archives through Workhorse. # Workhorse will also serve files when using `send_file`. module WorkhorseHelper # Send a Git blob through Workhorse @@ -16,9 +16,22 @@ module WorkhorseHelper head :ok end + # Send a Git patch through Workhorse + def send_git_patch(repository, diff_refs) + headers.store(*Gitlab::Workhorse.send_git_patch(repository, diff_refs)) + headers['Content-Disposition'] = 'inline' + head :ok + end + # Archive a Git repository and send it through Workhorse def send_git_archive(repository, ref:, format:) headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format)) head :ok end + + # Send an entry from artifacts through Workhorse + def send_artifacts_entry(build, entry) + headers.store(*Gitlab::Workhorse.send_artifacts_entry(build, entry)) + head :ok + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index ba1f2ae4075..eeb0ceba081 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -157,10 +157,11 @@ class Ability # Push abilities on the users team role rules.push(*project_team_rules(project.team, user)) - if project.owner == user || - (project.group && project.group.has_owner?(user)) || - user.admin? + owner = user.admin? || + project.owner == user || + (project.group && project.group.has_owner?(user)) + if owner rules.push(*project_owner_rules) end @@ -169,6 +170,10 @@ class Ability # Allow to read builds for internal projects rules << :read_build if project.public_builds? + + unless owner || project.team.member?(user) || project_group_member?(project, user) + rules << :request_access + end end if project.archived? @@ -345,8 +350,11 @@ class Ability rules = [] rules << :read_group if can_read_group?(user, group) + owner = user.admin? || group.has_owner?(user) + master = owner || group.has_master?(user) + # Only group masters and group owners can create new projects - if group.has_master?(user) || group.has_owner?(user) || user.admin? + if master rules += [ :create_projects, :admin_milestones @@ -354,7 +362,7 @@ class Ability end # Only group owner and administrators can admin group - if group.has_owner?(user) || user.admin? + if owner rules += [ :admin_group, :admin_namespace, @@ -363,6 +371,10 @@ class Ability ] end + if group.public? || (group.internal? && !user.external?) + rules << :request_access unless group.users.include?(user) + end + rules.flatten end @@ -564,5 +576,13 @@ class Ability rules end + + def project_group_member?(project, user) + project.group && + ( + project.group.members.exists?(user_id: user.id) || + project.group.requesters.exists?(user_id: user.id) + ) + end end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 5fa6eacd234..c6f77cc055f 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -59,6 +59,9 @@ class ApplicationSetting < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } + validates :enabled_git_access_protocol, + inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true } + validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? value.each do |level| @@ -139,6 +142,7 @@ class ApplicationSetting < ActiveRecord::Base send_user_confirmation_email: false, container_registry_token_expire_delay: 5, repository_storage: 'default', + user_default_external: false, ) end diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb index 59c7d87f5df..46b17479d6d 100644 --- a/app/models/award_emoji.rb +++ b/app/models/award_emoji.rb @@ -8,7 +8,7 @@ class AwardEmoji < ActiveRecord::Base belongs_to :user validates :awardable, :user, presence: true - validates :name, presence: true, inclusion: { in: Emoji.emojis_names } + validates :name, presence: true, inclusion: { in: Gitlab::Emoji.emojis_names } validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] } participant :user diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5c973749975..e189dbac285 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -13,6 +13,7 @@ module Ci scope :ignore_failures, ->() { where(allow_failure: false) } scope :with_artifacts, ->() { where.not(artifacts_file: nil) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } + scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader @@ -25,10 +26,6 @@ module Ci after_create :execute_hooks class << self - def last_month - where('created_at > ?', Date.today - 1.month) - end - def first_pending pending.unstarted.order('created_at ASC').first end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index fa4071e2482..7d743ce99f0 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -215,6 +215,8 @@ module Ci end def keep_around_commits + return unless project + project.repository.keep_around(self.sha) project.repository.keep_around(self.before_sha) end diff --git a/app/models/commit.rb b/app/models/commit.rb index 174ccbaea6c..2ef3973c160 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -214,6 +214,13 @@ class Commit @raw.short_id(7) end + def diff_refs + Gitlab::Diff::DiffRefs.new( + base_sha: self.parent_id || self.sha, + head_sha: self.sha + ) + end + def pipelines @pipeline ||= project.pipelines.where(sha: sha) end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 4066958f67c..630ee9601e0 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -23,7 +23,7 @@ class CommitRange attr_reader :commit_from, :notation, :commit_to attr_reader :ref_from, :ref_to - # Optional Project model + # The Project model attr_accessor :project # The beginning and ending refs can be named or SHAs, and @@ -56,7 +56,7 @@ class CommitRange # Initialize a CommitRange # # range_string - The String commit range. - # project - An optional Project model. + # project - The Project model. # # Raises ArgumentError if `range_string` does not match `PATTERN`. def initialize(range_string, project) diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index 06beff177b1..800a16ab246 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -65,8 +65,7 @@ module Awardable def create_award_emoji(name, current_user) return unless emoji_awardable? - - award_emoji.create(name: name, user: current_user) + award_emoji.create(name: normalize_name(name), user: current_user) end def remove_award_emoji(name, current_user) @@ -80,4 +79,10 @@ module Awardable create_award_emoji(emoji_name, current_user) end end + + private + + def normalize_name(name) + Gitlab::AwardEmoji.normalize_emoji_name(name) + end end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 8cac47246db..ec9e0f1b1d0 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -14,14 +14,14 @@ module Mentionable attr = attr.to_s mentionable_attrs << [attr, options] end + end + included do # Accessor for attributes marked mentionable. - def mentionable_attrs - @mentionable_attrs ||= [] + cattr_accessor :mentionable_attrs, instance_accessor: false do + [] end - end - included do if self < Participable participant -> (user, ext) { all_references(user, extractor: ext) } end diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb new file mode 100644 index 00000000000..2785fbb21c9 --- /dev/null +++ b/app/models/concerns/note_on_diff.rb @@ -0,0 +1,52 @@ +module NoteOnDiff + extend ActiveSupport::Concern + + NUMBER_OF_TRUNCATED_DIFF_LINES = 16 + + included do + delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true + end + + def diff_note? + true + end + + def diff_file + raise NotImplementedError + end + + def diff_line + raise NotImplementedError + end + + def for_line?(line) + raise NotImplementedError + end + + def diff_attributes + raise NotImplementedError + end + + def can_be_award_emoji? + false + end + + # Returns an array of at most 16 highlighted lines above a diff note + def truncated_diff_lines + prev_lines = [] + + highlighted_diff_lines.each do |line| + if line.meta? + prev_lines.clear + else + prev_lines << line + + break if for_line?(line) + + prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES + end + end + + prev_lines + end +end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 9822844357d..70740c76e43 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -41,9 +41,12 @@ module Participable def participant(attr) participant_attrs << attr end + end - def participant_attrs - @participant_attrs ||= [] + included do + # Accessor for participant attributes. + cattr_accessor :participant_attrs, instance_accessor: false do + [] end end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index e498ca96e3c..520026c18dd 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,6 +11,8 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true + after_save :keep_around_commit + def commit project.commit(sha) end @@ -26,4 +28,8 @@ class Deployment < ActiveRecord::Base def last? self == environment.last_deployment end + + def keep_around_commit + project.repository.keep_around(self.sha) + end end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb new file mode 100644 index 00000000000..9671955db36 --- /dev/null +++ b/app/models/diff_note.rb @@ -0,0 +1,127 @@ +class DiffNote < Note + include NoteOnDiff + + serialize :original_position, Gitlab::Diff::Position + serialize :position, Gitlab::Diff::Position + + validates :original_position, presence: true + validates :position, presence: true + validates :diff_line, presence: true + validates :line_code, presence: true, line_code: true + validates :noteable_type, inclusion: { in: ['Commit', 'MergeRequest'] } + validate :positions_complete + validate :verify_supported + + before_validation :set_original_position, :update_position, on: :create + before_validation :set_line_code + after_save :keep_around_commits + + class << self + def build_discussion_id(noteable_type, noteable_id, position) + [super(noteable_type, noteable_id), *position.key].join("-") + end + end + + def new_diff_note? + true + end + + def diff_attributes + { position: position.to_json } + end + + def discussion_id + @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, position) + end + + def original_discussion_id + @original_discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, original_position) + end + + def position=(new_position) + if new_position.is_a?(String) + new_position = JSON.parse(new_position) rescue nil + end + + if new_position.is_a?(Hash) + new_position = new_position.with_indifferent_access + new_position = Gitlab::Diff::Position.new(new_position) + end + + super(new_position) + end + + def diff_file + @diff_file ||= self.original_position.diff_file(self.project.repository) + end + + def diff_line + @diff_line ||= diff_file.line_for_position(self.original_position) if diff_file + end + + def for_line?(line) + diff_file.position(line) == self.original_position + end + + def active?(diff_refs = nil) + return false unless supported? + return true if for_commit? + + diff_refs ||= self.noteable.diff_refs + + self.position.diff_refs == diff_refs + end + + private + + def supported? + !self.for_merge_request? || self.noteable.support_new_diff_notes? + end + + def set_original_position + self.original_position = self.position.dup + end + + def set_line_code + self.line_code = self.position.line_code(self.project.repository) + end + + def update_position + return unless supported? + return if for_commit? + + return if active? + + Notes::DiffPositionUpdateService.new( + self.project, + nil, + old_diff_refs: self.position.diff_refs, + new_diff_refs: self.noteable.diff_refs, + paths: self.position.paths + ).execute(self) + end + + def verify_supported + return if supported? + + errors.add(:noteable, "doesn't support new-style diff notes") + end + + def positions_complete + return if self.original_position.complete? && self.position.complete? + + errors.add(:position, "is invalid") + end + + def keep_around_commits + project.repository.keep_around(self.original_position.base_sha) + project.repository.keep_around(self.original_position.start_sha) + project.repository.keep_around(self.original_position.head_sha) + + if self.position != self.original_position + project.repository.keep_around(self.position.base_sha) + project.repository.keep_around(self.position.start_sha) + project.repository.keep_around(self.position.head_sha) + end + end +end diff --git a/app/models/event.rb b/app/models/event.rb index d7d23c7ae6d..fd736d12359 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -67,7 +67,7 @@ class Event < ActiveRecord::Base elsif issue? || issue_note? Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target) else - ((merge_request? || note?) && target) || milestone? + ((merge_request? || note?) && target.present?) || milestone? end end @@ -136,7 +136,7 @@ class Event < ActiveRecord::Base end def note? - target_type == "Note" + target.is_a?(Note) end def issue? diff --git a/app/models/group.rb b/app/models/group.rb index a8be7004ee8..37631b99701 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -90,7 +90,7 @@ class Group < Namespace end def avatar_url(size = nil) - if avatar.present? + if self[:avatar].present? [gitlab_config.url, avatar.url].join end end diff --git a/app/models/label.rb b/app/models/label.rb index 49c352cc239..35e678001dc 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -20,10 +20,10 @@ class Label < ActiveRecord::Base validates :color, color: true, allow_blank: false validates :project, presence: true, unless: Proc.new { |service| service.template? } - # Don't allow '?', '&', and ',' for label titles + # Don't allow ',' for label titles validates :title, presence: true, - format: { with: /\A[^&\?,]+\z/ }, + format: { with: /\A[^,]+\z/ }, uniqueness: { scope: :project_id } before_save :nullify_priority @@ -52,14 +52,17 @@ class Label < ActiveRecord::Base # This pattern supports cross-project references. # def self.reference_pattern + # NOTE: The id pattern only matches when all characters on the expression + # are digits, so it will match ~2 but not ~2fa because that's probably a + # label name and we want it to be matched as such. @reference_pattern ||= %r{ (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)} (?: - (?<label_id>\d+) | # Integer-based label ID, or + (?<label_id>\d+(?!\S\w)\b) | # Integer-based label ID, or (?<label_name> - [A-Za-z0-9_-]+ | # String-based single-word label title, or - "[^&\?,]+" # String-based multi-word label surrounded in quotes + [A-Za-z0-9_\-\?\.&]+ | # String-based single-word label title, or + ".+?" # String-based multi-word label surrounded in quotes ) ) }x @@ -114,7 +117,7 @@ class Label < ActiveRecord::Base end def title=(value) - write_attribute(:title, Sanitize.clean(value.to_s)) if value.present? + write_attribute(:title, sanitize_title(value)) if value.present? end private @@ -132,4 +135,8 @@ class Label < ActiveRecord::Base def nullify_priority self.priority = nil if priority.blank? end + + def sanitize_title(value) + CGI.unescapeHTML(Sanitize.clean(value.to_s)) + end end diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 33d2a69ebaf..04a651d50ab 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -1,4 +1,6 @@ class LegacyDiffNote < Note + include NoteOnDiff + serialize :st_diff validates :line_code, presence: true, line_code: true @@ -11,12 +13,12 @@ class LegacyDiffNote < Note end end - def diff_note? + def legacy_diff_note? true end - def legacy_diff_note? - true + def diff_attributes + { line_code: line_code } end def discussion_id @@ -27,61 +29,20 @@ class LegacyDiffNote < Note line_code.split('_')[0] if line_code end - def diff_old_line - line_code.split('_')[1].to_i if line_code - end - - def diff_new_line - line_code.split('_')[2].to_i if line_code - end - def diff @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map) end - def diff_file_path - diff.new_path.presence || diff.old_path - end - - def diff_lines - @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line) + def diff_file + @diff_file ||= Gitlab::Diff::File.new(diff, repository: self.project.repository) if diff end def diff_line - @diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code } + @diff_line ||= diff_file.line_for_line_code(self.line_code) if diff_file end - def diff_line_text - diff_line.try(:text) - end - - def diff_line_type - diff_line.try(:type) - end - - def highlighted_diff_lines - Gitlab::Diff::Highlight.new(diff_lines).highlight - end - - def truncated_diff_lines - max_number_of_lines = 16 - prev_match_line = nil - prev_lines = [] - - highlighted_diff_lines.each do |line| - if line.type == "match" - prev_lines.clear - prev_match_line = line - else - prev_lines << line - - break if generate_line_code(line) == self.line_code - - prev_lines.shift if prev_lines.length >= max_number_of_lines - end - end - - prev_lines + def for_line?(line) + !line.meta? && diff_file.line_code(line) == self.line_code end # Check if this note is part of an "active" discussion @@ -94,7 +55,7 @@ class LegacyDiffNote < Note def active? return @active if defined?(@active) return true if for_commit? - return true unless self.diff + return true unless diff_line return false unless noteable noteable_diff = find_noteable_diff @@ -102,7 +63,7 @@ class LegacyDiffNote < Note if noteable_diff parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line) - @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text } + @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line.text } else @active = false end @@ -110,10 +71,6 @@ class LegacyDiffNote < Note @active end - def award_emoji_supported? - false - end - private def find_diff @@ -149,10 +106,6 @@ class LegacyDiffNote < Note self.class.where(attributes).last.try(:diff) end - def generate_line_code(line) - Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos) - end - # Find the diff on noteable that matches our own def find_noteable_diff diffs = noteable.diffs(Commit.max_diff_options) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index cb0f871897a..157901378d3 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -16,10 +16,10 @@ class MergeRequest < ActiveRecord::Base serialize :merge_params, Hash - after_create :create_merge_request_diff, unless: :importing + after_create :create_merge_request_diff, unless: :importing? after_update :update_merge_request_diff - delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil + delegate :commits, :real_size, to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -29,10 +29,6 @@ class MergeRequest < ActiveRecord::Base # when creating new merge request attr_accessor :can_be_created, :compare_commits, :compare - # Temporary fields to store target_sha, and base_sha to - # compare when importing pull requests from GitHub - attr_accessor :base_target_sha, :head_source_sha - state_machine :state, initial: :opened do event :close do transition [:reopened, :opened] => :closed @@ -89,12 +85,7 @@ class MergeRequest < ActiveRecord::Base state :cannot_be_merged around_transition do |merge_request, transition, block| - merge_request.record_timestamps = false - begin - block.call - ensure - merge_request.record_timestamps = true - end + Gitlab::Timeless.timeless(merge_request, &block) end end @@ -169,28 +160,99 @@ class MergeRequest < ActiveRecord::Base reference end - def last_commit - merge_request_diff ? merge_request_diff.last_commit : compare_commits.last - end - def first_commit merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end + def diffs(*args) + merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args) + end + def diff_size merge_request_diff.size end def diff_base_commit - if merge_request_diff + if persisted? merge_request_diff.base_commit - elsif source_sha - self.target_project.merge_base_commit(self.source_sha, self.target_branch) + elsif diff_start_commit && diff_head_commit + self.target_project.merge_base_commit(diff_start_sha, diff_head_sha) + end + end + + # MRs created before 8.4 don't store a MergeRequestDiff#base_commit_sha, + # but we need to get a commit for the "View file @ ..." link by deleted files, + # so we find the likely one if we can't get the actual one. + # This will not be the actual base commit if the target branch was merged into + # the source branch after the merge request was created, but it is good enough + # for the specific purpose of linking to a commit. + # It is not good enough for use in `Gitlab::Git::DiffRefs`, which needs the + # true base commit, so we can't simply have `#diff_base_commit` fall back on + # this method. + def likely_diff_base_commit + first_commit.parent || first_commit + end + + def diff_start_commit + if persisted? + merge_request_diff.start_commit + else + target_branch_head + end + end + + def diff_head_commit + if persisted? + merge_request_diff.head_commit + else + source_branch_head end end - def last_commit_short_sha - last_commit.short_id + def diff_start_sha + diff_start_commit.try(:sha) + end + + def diff_base_sha + diff_base_commit.try(:sha) + end + + def diff_head_sha + diff_head_commit.try(:sha) + end + + # When importing a pull request from GitHub, the old and new branches may no + # longer actually exist by those names, but we need to recreate the merge + # request diff with the right source and target shas. + # We use these attributes to force these to the intended values. + attr_writer :target_branch_sha, :source_branch_sha + + def source_branch_head + source_branch_ref = @source_branch_sha || source_branch + source_project.repository.commit(source_branch) if source_branch_ref + end + + def target_branch_head + target_branch_ref = @target_branch_sha || target_branch + target_project.repository.commit(target_branch) if target_branch_ref + end + + def target_branch_sha + target_branch_head.try(:sha) + end + + def source_branch_sha + source_branch_head.try(:sha) + end + + def diff_refs + return unless diff_start_commit || diff_base_commit + + Gitlab::Diff::DiffRefs.new( + base_sha: diff_base_sha, + start_sha: diff_start_sha, + head_sha: diff_head_sha + ) end def validate_branches @@ -227,21 +289,30 @@ class MergeRequest < ActiveRecord::Base def update_merge_request_diff if source_branch_changed? || target_branch_changed? - reload_code + reload_diff end end - def reload_code - if merge_request_diff && open? - merge_request_diff.reload_content - end + def reload_diff + return unless merge_request_diff && open? + + old_diff_refs = self.diff_refs + + merge_request_diff.reload_content + + new_diff_refs = self.diff_refs + + update_diff_notes_positions( + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs + ) end def check_if_can_be_merged return unless unchecked? can_be_merged = - !broken? && project.repository.can_be_merged?(source_sha, target_branch) + !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch) if can_be_merged mark_as_mergeable @@ -251,11 +322,11 @@ class MergeRequest < ActiveRecord::Base end def merge_event - self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last + @merge_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last end def closed_event - self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last + @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze @@ -293,7 +364,7 @@ class MergeRequest < ActiveRecord::Base !source_project.protected_branch?(source_branch) && !source_project.root_ref?(source_branch) && Ability.abilities.allowed?(current_user, :push_code, source_project) && - last_commit == source_project.commit(source_branch) + diff_head_commit == source_branch_head end def should_remove_source_branch? @@ -331,8 +402,8 @@ class MergeRequest < ActiveRecord::Base work_in_progress: work_in_progress? } - if last_commit - attrs.merge!(last_commit: last_commit.hook_attrs) + if diff_head_commit + attrs.merge!(last_commit: diff_head_commit.hook_attrs) end attributes.merge!(attrs) @@ -481,7 +552,7 @@ class MergeRequest < ActiveRecord::Base end def can_be_merged_by?(user) - ::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch) + ::Gitlab::GitAccess.new(user, project, 'web').can_push_to_branch?(target_branch) end def mergeable_ci_state? @@ -510,22 +581,6 @@ class MergeRequest < ActiveRecord::Base end end - def target_sha - return @base_target_sha if defined?(@base_target_sha) - - target_project.repository.commit(target_branch).try(:sha) - end - - def source_sha - return @head_source_sha if defined?(@head_source_sha) - - last_commit.try(:sha) || source_tip.try(:sha) - end - - def source_tip - source_branch && source_project.repository.commit(source_branch) - end - def fetch_ref target_project.repository.fetch_ref( source_project.repository.path_to_repo, @@ -558,10 +613,10 @@ class MergeRequest < ActiveRecord::Base def diverged_commits_count cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits") - if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha + if cache.blank? || cache[:source_sha] != source_branch_sha || cache[:target_sha] != target_branch_sha cache = { - source_sha: source_sha, - target_sha: target_sha, + source_sha: source_branch_sha, + target_sha: target_branch_sha, diverged_commits_count: compute_diverged_commits_count } Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache) @@ -571,9 +626,9 @@ class MergeRequest < ActiveRecord::Base end def compute_diverged_commits_count - return 0 unless source_sha && target_sha + return 0 unless source_branch_sha && target_branch_sha - Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size + Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_branch_sha, target_branch_sha).size end private :compute_diverged_commits_count @@ -582,13 +637,7 @@ class MergeRequest < ActiveRecord::Base end def pipeline - @pipeline ||= source_project.pipeline(last_commit.id, source_branch) if last_commit && source_project - end - - def diff_refs - return nil unless diff_base_commit - - [diff_base_commit, last_commit] + @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project end def merge_commit @@ -603,6 +652,36 @@ class MergeRequest < ActiveRecord::Base merge_commit end + def support_new_diff_notes? + diff_refs && diff_refs.complete? + end + + def update_diff_notes_positions(old_diff_refs:, new_diff_refs:) + return unless support_new_diff_notes? + return if new_diff_refs == old_diff_refs + + active_diff_notes = self.notes.diff_notes.select do |note| + note.new_diff_note? && note.active?(old_diff_refs) + end + + return if active_diff_notes.empty? + + paths = active_diff_notes.flat_map { |n| n.diff_file.paths }.uniq + + service = Notes::DiffPositionUpdateService.new( + self.project, + nil, + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs, + paths: paths + ) + + active_diff_notes.each do |note| + service.execute(note) + Gitlab::Timeless.timeless(note, &:save) + end + end + def keep_around_commit project.repository.keep_around(self.merge_commit_sha) end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0fcde6fc8f1..feaba925bad 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request - delegate :head_source_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil + delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil state_machine :state, initial: :empty do state :collected @@ -24,7 +24,7 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_diffs after_create :reload_content, unless: :importing? - after_save :keep_around_commit + after_save :keep_around_commits, unless: :importing? def reload_content reload_commits @@ -39,14 +39,15 @@ class MergeRequestDiff < ActiveRecord::Base if options[:ignore_whitespace_change] @diffs_no_whitespace ||= begin compare = Gitlab::Git::Compare.new( - self.repository.raw_repository, - self.base, - self.head, + repository.raw_repository, + self.start_commit_sha || self.target_branch_sha, + self.head_commit_sha || self.source_branch_sha, ) compare.diffs(options) end else - @diffs ||= load_diffs(st_diffs, options) + @diffs ||= {} + @diffs[options] ||= load_diffs(st_diffs, options) end end @@ -63,37 +64,39 @@ class MergeRequestDiff < ActiveRecord::Base end def base_commit - return nil unless self.base_commit_sha + return unless self.base_commit_sha - merge_request.target_project.commit(self.base_commit_sha) + project.commit(self.base_commit_sha) end - def last_commit_short_sha - @last_commit_short_sha ||= last_commit.short_id - end + def start_commit + return unless self.start_commit_sha - def dump_commits(commits) - commits.map(&:to_hash) + project.commit(self.start_commit_sha) end - def load_commits(array) - array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } - end + def head_commit + return last_commit unless self.head_commit_sha - def dump_diffs(diffs) - if diffs.respond_to?(:map) - diffs.map(&:to_hash) - end + project.commit(self.head_commit_sha) end - def load_diffs(raw, options) - if raw.respond_to?(:each) - Gitlab::Git::DiffCollection.new(raw, options) - else - Gitlab::Git::DiffCollection.new([]) - end + def compare + @compare ||= + begin + # Update ref for merge request + merge_request.fetch_ref + + Gitlab::Git::Compare.new( + repository.raw_repository, + self.target_branch_sha, + self.source_branch_sha + ) + end end + private + # Collect array of Git::Commit objects # between target and source branches def unmerged_commits @@ -106,6 +109,14 @@ class MergeRequestDiff < ActiveRecord::Base commits end + def dump_commits(commits) + commits.map(&:to_hash) + end + + def load_commits(array) + array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } + end + # Reload all commits related to current merge request from repo # and save it as array of hashes in st_commits db field def reload_commits @@ -120,6 +131,32 @@ class MergeRequestDiff < ActiveRecord::Base update_columns_serialized(new_attributes) end + # Collect array of Git::Diff objects + # between target and source branches + def unmerged_diffs + compare.diffs(Commit.max_diff_options) + end + + def dump_diffs(diffs) + if diffs.respond_to?(:map) + diffs.map(&:to_hash) + end + end + + def load_diffs(raw, options) + if raw.respond_to?(:each) + if paths = options[:paths] + raw = raw.select do |diff| + paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) + end + end + + Gitlab::Git::DiffCollection.new(raw, options) + else + Gitlab::Git::DiffCollection.new([]) + end + end + # Reload diffs between branches related to current merge request from repo # and save it as array of hashes in st_diffs db field def reload_diffs @@ -147,59 +184,33 @@ class MergeRequestDiff < ActiveRecord::Base new_attributes[:st_diffs] = new_diffs - base_commit_sha = self.repository.merge_base(self.head, self.base) - new_attributes[:base_commit_sha] = base_commit_sha - - self.repository.keep_around(base_commit_sha) + new_attributes[:start_commit_sha] = self.target_branch_sha + new_attributes[:head_commit_sha] = self.source_branch_sha + new_attributes[:base_commit_sha] = branch_base_sha update_columns_serialized(new_attributes) - end - - # Collect array of Git::Diff objects - # between target and source branches - def unmerged_diffs - compare.diffs(Commit.max_diff_options) - end - def repository - merge_request.target_project.repository + keep_around_commits end - def source_sha - return head_source_sha if head_source_sha.present? - - source_commit = merge_request.source_project.commit(source_branch) - source_commit.try(:sha) + def project + merge_request.target_project end - def target_sha - merge_request.target_sha + def repository + project.repository end - def base - self.target_sha || self.target_branch - end + def branch_base_commit + return unless self.source_branch_sha && self.target_branch_sha - def head - self.source_sha + project.merge_base_commit(self.source_branch_sha, self.target_branch_sha) end - def compare - @compare ||= - begin - # Update ref for merge request - merge_request.fetch_ref - - Gitlab::Git::Compare.new( - self.repository.raw_repository, - self.base, - self.head - ) - end + def branch_base_sha + branch_base_commit.try(:sha) end - private - # # #save or #update_attributes providing changes on serialized attributes do a lot of # serialization and deserialization calls resulting in bad performance. @@ -223,7 +234,9 @@ class MergeRequestDiff < ActiveRecord::Base reload end - def keep_around_commit - self.repository.keep_around(self.base_commit_sha) + def keep_around_commits + repository.keep_around(target_branch_sha) + repository.keep_around(source_branch_sha) + repository.keep_around(branch_base_sha) end end diff --git a/app/models/note.rb b/app/models/note.rb index 81b5c47b738..0ce10c77de9 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -10,6 +10,10 @@ class Note < ActiveRecord::Base # Banzai::ObjectRenderer. attr_accessor :note_html + # An Array containing the number of visible references as generated by + # Banzai::ObjectRenderer + attr_accessor :user_visible_reference_count + default_value_for :system, false attr_mentionable :note, pipeline: :note @@ -56,7 +60,7 @@ class Note < ActiveRecord::Base scope :inc_author, ->{ includes(:author) } scope :inc_author_project_award_emoji, ->{ includes(:project, :author, :award_emoji) } - scope :legacy_diff_notes, ->{ where(type: 'LegacyDiffNote') } + scope :diff_notes, ->{ where(type: ['LegacyDiffNote', 'DiffNote']) } scope :non_diff_notes, ->{ where(type: ['Note', nil]) } scope :with_associations, -> do @@ -82,7 +86,7 @@ class Note < ActiveRecord::Base end def grouped_diff_notes - legacy_diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code) + diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code) end # Searches for notes matching the given query. @@ -115,6 +119,10 @@ class Note < ActiveRecord::Base false end + def new_diff_note? + false + end + def active? true end @@ -189,11 +197,19 @@ class Note < ActiveRecord::Base end def cross_reference_not_visible_for?(user) - cross_reference? && referenced_mentionables(user).empty? + cross_reference? && !has_referenced_mentionables?(user) + end + + def has_referenced_mentionables?(user) + if user_visible_reference_count.present? + user_visible_reference_count > 0 + else + referenced_mentionables(user).any? + end end def award_emoji? - award_emoji_supported? && contains_emoji_only? + can_be_award_emoji? && contains_emoji_only? end def emoji_awardable? @@ -204,7 +220,7 @@ class Note < ActiveRecord::Base self.line_code = nil if self.line_code.blank? end - def award_emoji_supported? + def can_be_award_emoji? noteable.is_a?(Awardable) end @@ -213,8 +229,7 @@ class Note < ActiveRecord::Base end def award_emoji_name - original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] - Gitlab::AwardEmoji.normalize_emoji_name(original_name) + note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] end private diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index d41fc7073c6..121b598b8f3 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -5,6 +5,7 @@ class NotificationSetting < ActiveRecord::Base belongs_to :user belongs_to :source, polymorphic: true + belongs_to :project, foreign_key: 'source_id' validates :user, presence: true validates :level, presence: true @@ -13,7 +14,13 @@ class NotificationSetting < ActiveRecord::Base allow_nil: true } scope :for_groups, -> { where(source_type: 'Namespace') } - scope :for_projects, -> { where(source_type: 'Project') } + + # Exclude projects not included by the Project model's default scope (those that are + # pending delete). + # + scope :for_projects, -> do + includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil }) + end EMAIL_EVENTS = [ :new_note, diff --git a/app/models/project.rb b/app/models/project.rb index ae96f00a705..e7b9835692d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -162,9 +162,7 @@ class Project < ActiveRecord::Base validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id - validates :import_url, - url: { protocols: %w(ssh git http https) }, - if: :external_import? + validates :import_url, addressable_url: true, if: :import_url validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, @@ -427,8 +425,8 @@ class Project < ActiveRecord::Base container_registry_repository.tags.any? end - def commit(id = 'HEAD') - repository.commit(id) + def commit(ref = 'HEAD') + repository.commit(ref) end def merge_base_commit(first_commit_id, second_commit_id) @@ -463,9 +461,11 @@ class Project < ActiveRecord::Base end def import_url=(value) + return super(value) unless Gitlab::UrlSanitizer.valid?(value) + import_url = Gitlab::UrlSanitizer.new(value) - create_or_update_import_data(credentials: import_url.credentials) super(import_url.sanitized_url) + create_or_update_import_data(credentials: import_url.credentials) end def import_url @@ -477,7 +477,13 @@ class Project < ActiveRecord::Base end end + def valid_import_url? + valid? || errors.messages[:import_url].nil? + end + def create_or_update_import_data(data: nil, credentials: nil) + return unless valid_import_url? + project_import_data = import_data || build_import_data if data project_import_data.data ||= {} @@ -701,7 +707,7 @@ class Project < ActiveRecord::Base end def avatar_url - if avatar.present? + if self[:avatar].present? [gitlab_config.url, avatar.url].join elsif avatar_in_git Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self) @@ -802,18 +808,12 @@ class Project < ActiveRecord::Base @repo_exists = false end + # Branches that are not _exactly_ matched by a protected branch. def open_branches - # We're using a Set here as checking values in a large Set is faster than - # checking values in a large Array. - protected_set = Set.new(protected_branch_names) - - repository.branches.reject do |branch| - protected_set.include?(branch.name) - end - end - - def protected_branch_names - @protected_branch_names ||= protected_branches.pluck(:name) + exact_protected_branch_names = protected_branches.reject(&:wildcard?).map(&:name) + branch_names = repository.branches.map(&:name) + non_open_branch_names = Set.new(exact_protected_branch_names).intersection(Set.new(branch_names)) + repository.branches.reject { |branch| non_open_branch_names.include? branch.name } end def root_ref?(branch) @@ -830,11 +830,12 @@ class Project < ActiveRecord::Base # Check if current branch name is marked as protected in the system def protected_branch?(branch_name) - protected_branch_names.include?(branch_name) + @protected_branches ||= self.protected_branches.to_a + ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present? end def developers_can_push_to_protected_branch?(branch_name) - protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push } + protected_branches.matching(branch_name).any?(&:developers_can_push) end def forked? diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 58cb720c3c1..ce7d1c5d5b1 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -112,15 +112,7 @@ class IrkerService < Service # Authorize both irc://domain.com/#chan and irc://domain.com/chan if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? - # Do not authorize irc://domain.com/ - if uri.fragment.nil? && uri.path.length > 1 - uri.to_s - else - # Authorize irc://domain.com/smthg#chan - # The irker daemon will deal with it by concatenating smthg and - # chan, thus sending messages on #smthgchan - uri.to_s - end + uri.to_s end end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 27bf08bf7d9..97bcbacf2b9 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -144,7 +144,7 @@ class JiraService < IssueTrackerService commit_id = if entity.is_a?(Commit) entity.id elsif entity.is_a?(MergeRequest) - entity.last_commit.id + entity.diff_head_sha end commit_url = build_entity_url(:commit, commit_id) diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 33cf046fa75..b7011d7afdf 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -8,4 +8,51 @@ class ProtectedBranch < ActiveRecord::Base def commit project.commit(self.name) end + + # Returns all protected branches that match the given branch name. + # This realizes all records from the scope built up so far, and does + # _not_ return a relation. + # + # This method optionally takes in a list of `protected_branches` to search + # through, to avoid calling out to the database. + def self.matching(branch_name, protected_branches: nil) + (protected_branches || all).select { |protected_branch| protected_branch.matches?(branch_name) } + end + + # Returns all branches (among the given list of branches [`Gitlab::Git::Branch`]) + # that match the current protected branch. + def matching(branches) + branches.select { |branch| self.matches?(branch.name) } + end + + # Checks if the protected branch matches the given branch name. + def matches?(branch_name) + return false if self.name.blank? + + exact_match?(branch_name) || wildcard_match?(branch_name) + end + + # Checks if this protected branch contains a wildcard + def wildcard? + self.name && self.name.include?('*') + end + + protected + + def exact_match?(branch_name) + self.name == branch_name + end + + def wildcard_match?(branch_name) + wildcard_regex === branch_name + end + + def wildcard_regex + @wildcard_regex ||= begin + name = self.name.gsub('*', 'STAR_DONT_ESCAPE') + quoted_name = Regexp.quote(name) + regex_string = quoted_name.gsub('STAR_DONT_ESCAPE', '.*?') + /\A#{regex_string}\z/ + end + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 078ca8f4e13..5b670cb4b8f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -78,9 +78,9 @@ class Repository end end - def commit(id = 'HEAD') + def commit(ref = 'HEAD') return nil unless exists? - commit = Gitlab::Git::Commit.find(raw_repository, id) + commit = Gitlab::Git::Commit.find(raw_repository, ref) commit = ::Commit.new(commit, @project) if commit commit rescue Rugged::OdbError @@ -653,16 +653,6 @@ class Repository end end - def blob_for_diff(commit, diff) - blob_at(commit.id, diff.file_path) - end - - def prev_blob_for_diff(commit, diff) - if commit.parent_id - blob_at(commit.parent_id, diff.old_path) - end - end - def refs_contains_sha(ref_type, sha) args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) names = Gitlab::Popen.popen(args, path_to_repo).first @@ -911,7 +901,7 @@ class Repository if line =~ /^.*:.*:\d+:/ ref, filename, startline = line.split(':') startline = startline.to_i - index - extname = File.extname(filename) + extname = Regexp.escape(File.extname(filename)) basename = filename.sub(/#{extname}$/, '') break end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index a2df899d012..f4bcb49b34d 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -1,4 +1,6 @@ class SentNotification < ActiveRecord::Base + serialize :position, Gitlab::Diff::Position + belongs_to :project belongs_to :noteable, polymorphic: true belongs_to :recipient, class_name: "User" @@ -7,7 +9,7 @@ class SentNotification < ActiveRecord::Base validates :reply_key, uniqueness: true validates :noteable_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? - validates :line_code, line_code: true, allow_blank: true + validate :note_valid after_save :keep_around_commit @@ -20,7 +22,7 @@ class SentNotification < ActiveRecord::Base find_by(reply_key: reply_key) end - def record(noteable, recipient_id, reply_key, params = {}) + def record(noteable, recipient_id, reply_key, attrs = {}) return unless reply_key noteable_id = nil @@ -31,7 +33,7 @@ class SentNotification < ActiveRecord::Base noteable_id = noteable.id end - params.reverse_merge!( + attrs.reverse_merge!( project: noteable.project, noteable_type: noteable.class.name, noteable_id: noteable_id, @@ -40,13 +42,17 @@ class SentNotification < ActiveRecord::Base reply_key: reply_key ) - create(params) + create(attrs) end - def record_note(note, recipient_id, reply_key, params = {}) - params[:line_code] = note.line_code + def record_note(note, recipient_id, reply_key, attrs = {}) + if note.diff_note? + attrs[:note_type] = note.type + + attrs.merge!(note.diff_attributes) + end - record(note.noteable, recipient_id, reply_key, params) + record(note.noteable, recipient_id, reply_key, attrs) end end @@ -66,12 +72,50 @@ class SentNotification < ActiveRecord::Base end end + def position=(new_position) + if new_position.is_a?(String) + new_position = JSON.parse(new_position) rescue nil + end + + if new_position.is_a?(Hash) + new_position = new_position.with_indifferent_access + new_position = Gitlab::Diff::Position.new(new_position) + end + + super(new_position) + end + def to_param self.reply_key end + def note_attributes + { + project: self.project, + author: self.recipient, + type: self.note_type, + noteable_type: self.noteable_type, + noteable_id: self.noteable_id, + commit_id: self.commit_id, + line_code: self.line_code, + position: self.position.to_json + } + end + + def create_note(note) + Notes::CreateService.new( + self.project, + self.recipient, + self.note_attributes.merge(note: note) + ).execute + end + private + def note_valid + Note.new(note_attributes.merge(note: "Test")).valid? + end + def keep_around_commit project.repository.keep_around(self.commit_id) end diff --git a/app/models/todo.rb b/app/models/todo.rb index ac3fdbc7f3b..8d7a5965aa1 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -1,14 +1,16 @@ class Todo < ActiveRecord::Base - ASSIGNED = 1 - MENTIONED = 2 - BUILD_FAILED = 3 - MARKED = 4 + ASSIGNED = 1 + MENTIONED = 2 + BUILD_FAILED = 3 + MARKED = 4 + APPROVAL_REQUIRED = 5 # This is an EE-only feature ACTION_NAMES = { ASSIGNED => :assigned, MENTIONED => :mentioned, BUILD_FAILED => :build_failed, - MARKED => :marked + MARKED => :marked, + APPROVAL_REQUIRED => :approval_required } belongs_to :author, class_name: "User" diff --git a/app/models/user.rb b/app/models/user.rb index 5036a3e300c..7a72c202150 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,7 +15,7 @@ class User < ActiveRecord::Base add_authentication_token_field :authentication_token default_value_for :admin, false - default_value_for :external, false + default_value_for(:external) { current_application_settings.user_default_external } default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_team, false default_value_for :hide_no_ssh_key, false @@ -87,7 +87,7 @@ class User < ActiveRecord::Base has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :todos, dependent: :destroy has_many :notification_settings, dependent: :destroy - has_many :award_emoji, as: :awardable, dependent: :destroy + has_many :award_emoji, dependent: :destroy # # Validations @@ -653,7 +653,7 @@ class User < ActiveRecord::Base end def avatar_url(size = nil, scale = 2) - if avatar.present? + if self[:avatar].present? [gitlab_config.url, avatar.url].join else GravatarService.new.execute(email, size, scale) diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index a7f090655e1..8a000585e89 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -7,7 +7,7 @@ class AuditEventService @details = { with: @details[:with], target_id: @author.id, - target_type: "User", + target_type: 'User', target_details: @author.name, } diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index e57b95f21ec..e294a962352 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -20,9 +20,11 @@ module Auth token.issuer = registry.issuer token.audience = AUDIENCE token.expire_time = token_expire_at + token[:access] = names.map do |name| { type: 'repository', name: name, actions: %w(*) } end + token.encoded end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 6b69cb53b2c..c578097376a 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -23,7 +23,7 @@ module Commits private def check_push_permissions - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch) unless allowed raise ValidationError.new('You are not allowed to push into this branch') diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index e2bccbdbcc3..149822aa647 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch, diff_options = {}) + def execute(source_project, source_branch, target_project, target_branch) source_commit = source_project.commit(source_branch) return unless source_commit diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index cc128563437..d874582d54f 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -3,17 +3,20 @@ require_relative 'base_service' class CreateBranchService < BaseService def execute(branch_name, ref, source_project: @project) valid_branch = Gitlab::GitRefValidator.validate(branch_name) - if valid_branch == false + + unless valid_branch return error('Branch name is invalid') end repository = project.repository existing_branch = repository.find_branch(branch_name) + if existing_branch return error('Branch already exists') end new_branch = nil + if source_project != @project repository.with_tmp_ref do |tmp_ref| repository.fetch_ref( @@ -29,7 +32,6 @@ class CreateBranchService < BaseService end if new_branch - # GitPushService handles execution of services and hooks for branch pushes success(new_branch) else error('Invalid reference name') @@ -39,8 +41,6 @@ class CreateBranchService < BaseService end def success(branch) - out = super() - out[:branch] = branch - out + super().merge(branch: branch) end end diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb index f029db72d40..d6d4afcf29a 100644 --- a/app/services/create_release_service.rb +++ b/app/services/create_release_service.rb @@ -23,8 +23,6 @@ class CreateReleaseService < BaseService end def success(release) - out = super() - out[:release] = release - out + super().merge(release: release) end end diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb index 9884cb96661..95cc9baf406 100644 --- a/app/services/create_snippet_service.rb +++ b/app/services/create_snippet_service.rb @@ -1,10 +1,10 @@ class CreateSnippetService < BaseService def execute - if project.nil? - snippet = PersonalSnippet.new(params) - else - snippet = project.snippets.build(params) - end + snippet = if project + project.snippets.build(params) + else + PersonalSnippet.new(params) + end unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) deny_visibility_level(snippet) diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index bd8d982e1fb..c0e7ecf6a96 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -9,6 +9,7 @@ class CreateTagService < BaseService message.strip! if message new_tag = nil + begin new_tag = repository.add_tag(current_user, tag_name, target, message) rescue Rugged::TagError @@ -22,6 +23,7 @@ class CreateTagService < BaseService CreateReleaseService.new(@project, @current_user). execute(tag_name, release_description) end + success.merge(tag: new_tag) else error("Target #{target} is invalid") diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index 752a7029952..332c55581a1 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -5,7 +5,6 @@ class DeleteBranchService < BaseService repository = project.repository branch = repository.find_branch(branch_name) - # No such branch unless branch return error('No such branch', 404) end @@ -14,18 +13,15 @@ class DeleteBranchService < BaseService return error('Cannot remove HEAD branch', 405) end - # Dont allow remove of protected branch if project.protected_branch?(branch_name) return error('Protected branch cant be removed', 405) end - # Dont allow user to remove branch if he is not allowed to push unless current_user.can?(:push_code, project) return error('You dont have push access to repo', 405) end if repository.rm_branch(current_user, branch_name) - # GitPushService handles execution of services and hooks for branch pushes success('Branch was removed') else error('Failed to remove branch') @@ -35,15 +31,11 @@ class DeleteBranchService < BaseService end def error(message, return_code = 400) - out = super(message) - out[:return_code] = return_code - out + super(message).merge(return_code: return_code) end def success(message) - out = super() - out[:message] = message - out + super().merge(message: message) end def build_push_data(branch) diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index de3352a6756..1e41fbe34b6 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -5,7 +5,6 @@ class DeleteTagService < BaseService repository = project.repository tag = repository.find_tag(tag_name) - # No such tag unless tag return error('No such tag', 404) end @@ -26,15 +25,11 @@ class DeleteTagService < BaseService end def error(message, return_code = 400) - out = super(message) - out[:return_code] = return_code - out + super(message).merge(return_code: return_code) end def success(message) - out = super() - out[:message] = message - out + super().merge(message: message) end def build_push_data(tag) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 0326a8823e9..37c5e321b39 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -15,7 +15,6 @@ module Files params[:file_content] end - # Validate parameters validate # Create new branch if it different from source_branch @@ -26,7 +25,7 @@ module Files if commit success else - error("Something went wrong. Your changes were not committed") + error('Something went wrong. Your changes were not committed') end rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex error(ex.message) @@ -43,7 +42,7 @@ module Files end def validate - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch) unless allowed raise_error("You are not allowed to push into this branch") @@ -51,12 +50,12 @@ module Files unless project.empty_repo? unless @source_project.repository.branch_names.include?(@source_branch) - raise_error("You can only create or edit files when you are on a branch") + raise_error('You can only create or edit files when you are on a branch') end if different_branch? if repository.branch_names.include?(@target_branch) - raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes") + raise_error('Branch with such name already exists. You need to switch to this branch in order to make changes') end end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index e4cde4a2fd8..8eaf6db8012 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -29,7 +29,7 @@ module Files blob = repository.blob_at_branch(@source_branch, @file_path) if blob - raise_error("Your changes could not be committed because a file with the same name already exists") + raise_error('Your changes could not be committed because a file with the same name already exists') end end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 299a0a967b0..58573078048 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -26,6 +26,7 @@ class GitTagPushService < BaseService unless Gitlab::Git.blank_ref?(params[:newrev]) tag_name = Gitlab::Git.ref_name(params[:ref]) tag = project.repository.find_tag(tag_name) + if tag && tag.target == params[:newrev] commit = project.commit(tag.target) commits = [commit].compact diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb index 15825b81685..cd08c3a0cb9 100644 --- a/app/services/issues/bulk_update_service.rb +++ b/app/services/issues/bulk_update_service.rb @@ -9,6 +9,7 @@ module Issues end issues = Issue.where(id: issues_ids) + issues.each do |issue| next unless can?(current_user, :update_issue, issue) diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 3bec66cea88..f1b1d90c457 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -34,7 +34,7 @@ module MergeRequests committer: committer } - commit_id = repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options) + commit_id = repository.merge(current_user, merge_request.diff_head_sha, merge_request.target_branch, options) merge_request.update(merge_commit_sha: commit_id) rescue GitHooksService::PreReceiveError => e merge_request.update(merge_error: e.message) diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 870f5705184..4ad5fb08311 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -12,7 +12,7 @@ module MergeRequests merge_request.merge_when_build_succeeds = true merge_request.merge_user = @current_user - SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.last_commit) + SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit) end merge_request.save diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index 064910f81f7..8437d9b8b43 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -20,6 +20,7 @@ module MergeRequests return unless merge_request.target_branch == project.default_branch closed_issues = merge_request.closes_issues(current_user) + closed_issues.each do |issue| if can?(current_user, :update_issue, issue) Issues::CloseService.new(project, current_user, {}).execute(issue, commit: merge_request) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index fe0579744b4..b11ecd97a57 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -34,10 +34,10 @@ module MergeRequests def close_merge_requests commit_ids = @commits.map(&:id) merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a - merge_requests = merge_requests.select(&:last_commit) + merge_requests = merge_requests.select(&:diff_head_commit) merge_requests = merge_requests.select do |merge_request| - commit_ids.include?(merge_request.last_commit.id) + commit_ids.include?(merge_request.diff_head_sha) end merge_requests.uniq.select(&:source_project).each do |merge_request| @@ -60,20 +60,15 @@ module MergeRequests merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? - merge_request.reload_code - merge_request.mark_as_unchecked + merge_request.reload_diff else mr_commit_ids = merge_request.commits.map(&:id) push_commit_ids = @commits.map(&:id) matches = mr_commit_ids & push_commit_ids - - if matches.any? - merge_request.reload_code - merge_request.mark_as_unchecked - else - merge_request.mark_as_unchecked - end + merge_request.reload_diff if matches.any? end + + merge_request.mark_as_unchecked end end @@ -94,12 +89,10 @@ module MergeRequests merge_request = merge_requests_for_source_branch.first return unless merge_request - last_commit = merge_request.last_commit - begin # Since any number of commits could have been made to the restored branch, # find the common root to see what has been added. - common_ref = @project.repository.merge_base(last_commit.id, @newrev) + common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @newrev) # If the a commit no longer exists in this repo, gitlab_git throws # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index 8279ad2001b..eb88ae9d11c 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -6,7 +6,7 @@ module MergeRequests create_note(merge_request) notification_service.reopen_mr(merge_request, current_user) execute_hooks(merge_request, 'reopen') - merge_request.reload_code + merge_request.reload_diff merge_request.mark_as_unchecked end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 02fca5c0ea3..18971bd0be3 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -8,7 +8,6 @@ module Notes if note.award_emoji? noteable = note.noteable todo_service.new_award_emoji(noteable, current_user) - return noteable.create_award_emoji(note.award_emoji_name, current_user) end diff --git a/app/services/notes/diff_position_update_service.rb b/app/services/notes/diff_position_update_service.rb new file mode 100644 index 00000000000..0cb731f5bc3 --- /dev/null +++ b/app/services/notes/diff_position_update_service.rb @@ -0,0 +1,30 @@ +module Notes + class DiffPositionUpdateService < BaseService + def execute(note) + new_position = tracer.trace(note.position) + + # Don't update the position if the type doesn't match, since that means + # the diff line commented on was changed, and the comment is now outdated + old_position = note.position + if new_position && + new_position != old_position && + new_position.type == old_position.type + + note.position = new_position + end + + note + end + + private + + def tracer + @tracer ||= Gitlab::Diff::PositionTracer.new( + repository: project.repository, + old_diff_refs: params[:old_diff_refs], + new_diff_refs: params[:new_diff_refs], + paths: params[:paths] + ) + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 590350a11e5..ab6e51209ee 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -153,6 +153,7 @@ class NotificationService else mentioned_users end + recipients = recipients.concat(participants) # Merge project watchers @@ -176,6 +177,7 @@ class NotificationService # build notify method like 'note_commit_email' notify_method = "note_#{note.noteable_type.underscore}_email".to_sym + recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later end diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index a47df22f1ba..29b3981f49f 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -7,8 +7,6 @@ # module Projects class HousekeepingService < BaseService - include Gitlab::ShellAdapter - LEASE_TIMEOUT = 3600 class LeaseTaken < StandardError @@ -24,11 +22,7 @@ module Projects def execute raise LeaseTaken unless try_obtain_lease - GitlabShellOneShotWorker.perform_async(:gc, @project.repository_storage_path, @project.path_with_namespace) - ensure - Gitlab::Metrics.measure(:reset_pushes_since_gc) do - @project.update_column(:pushes_since_gc, 0) - end + execute_gitlab_shell_gc end def needed? @@ -36,13 +30,27 @@ module Projects end def increment! - Gitlab::Metrics.measure(:increment_pushes_since_gc) do - @project.increment!(:pushes_since_gc) + if Gitlab::ExclusiveLease.new("project_housekeeping:increment!:#{@project.id}", timeout: 60).try_obtain + Gitlab::Metrics.measure(:increment_pushes_since_gc) do + update_pushes_since_gc(@project.pushes_since_gc + 1) + end end end private + def execute_gitlab_shell_gc + GitGarbageCollectWorker.perform_async(@project.id) + ensure + Gitlab::Metrics.measure(:reset_pushes_since_gc) do + update_pushes_since_gc(0) + end + end + + def update_pushes_since_gc(new_value) + @project.update_column(:pushes_since_gc, new_value) + end + def try_obtain_lease Gitlab::Metrics.measure(:obtain_housekeeping_lease) do lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 3f507d5c400..998789d64d2 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -10,7 +10,7 @@ module Projects def save_all if [version_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) - Gitlab::ImportExport::Saver.save(shared: @shared) + Gitlab::ImportExport::Saver.save(project: project, shared: @shared) notify_success else cleanup_and_notify @@ -38,6 +38,8 @@ module Projects end def cleanup_and_notify + Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}") + FileUtils.rm_rf(@shared.export_path) notify_error @@ -45,6 +47,8 @@ module Projects end def notify_success + Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported") + notification_service.project_exported(@project, @current_user) end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 163ebf26c84..cdad0426b02 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -43,7 +43,7 @@ module Projects def import_repository begin gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) - rescue Gitlab::Shell::Error => e + rescue => e raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 941df08995c..f06311511cc 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -3,10 +3,11 @@ module Projects def execute # check that user is allowed to set specified visibility_level new_visibility = params[:visibility_level] + if new_visibility && new_visibility.to_i != project.visibility_level unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) - + deny_visibility_level(project, new_visibility) return project end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index b868d2e7e83..1ab3b5789bc 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -82,7 +82,7 @@ class SystemNoteService end body << ' ' << 'label'.pluralize(labels_count) - body = "#{body.capitalize}" + body = body.capitalize create_note(noteable: noteable, project: project, author: author, note: body) end @@ -125,7 +125,7 @@ class SystemNoteService # Returns the created Note object def self.change_status(noteable, project, author, status, source) body = "Status changed to #{status}" - body += " by #{source.gfm_reference(project)}" if source + body << " by #{source.gfm_reference(project)}" if source create_note(noteable: noteable, project: project, author: author, note: body) end @@ -139,7 +139,7 @@ class SystemNoteService # Called when 'merge when build succeeds' is canceled def self.cancel_merge_when_build_succeeds(noteable, project, author) - body = "Canceled the automatic merge" + body = 'Canceled the automatic merge' create_note(noteable: noteable, project: project, author: author, note: body) end @@ -236,6 +236,7 @@ class SystemNoteService else 'deleted' end + body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 6bb0a72d30e..6b48d68cccb 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -194,7 +194,7 @@ class TodoService end def create_assignment_todo(issuable, author) - if issuable.assignee && issuable.assignee != author + if issuable.assignee attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED) create_todos(issuable.assignee, attributes) end @@ -239,7 +239,6 @@ class TodoService def filter_mentioned_users(project, target, author) mentioned_users = target.mentioned_users(author) mentioned_users = reject_users_without_access(mentioned_users, project, target) - mentioned_users.delete(author) mentioned_users.uniq end diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb index 0c0f68d169b..0ee1ff2d7d9 100644 --- a/app/services/update_release_service.rb +++ b/app/services/update_release_service.rb @@ -21,8 +21,6 @@ class UpdateReleaseService < BaseService end def success(release) - out = super() - out[:release] = release - out + super().merge(release: release) end end diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb index 93af8f21972..a6bb36821c3 100644 --- a/app/services/update_snippet_service.rb +++ b/app/services/update_snippet_service.rb @@ -9,6 +9,7 @@ class UpdateSnippetService < BaseService def execute # check that user is allowed to set specified visibility_level new_visibility = params[:visibility_level] + if new_visibility && new_visibility.to_i != snippet.visibility_level unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) deny_visibility_level(snippet, new_visibility) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb new file mode 100644 index 00000000000..09bfa613cbe --- /dev/null +++ b/app/validators/addressable_url_validator.rb @@ -0,0 +1,45 @@ +# AddressableUrlValidator +# +# Custom validator for URLs. This is a stricter version of UrlValidator - it also checks +# for using the right protocol, but it actually parses the URL checking for any syntax errors. +# The regex is also different from `URI` as we use `Addressable::URI` here. +# +# By default, only URLs for http, https, ssh, and git protocols will be considered valid. +# Provide a `:protocols` option to configure accepted protocols. +# +# Example: +# +# class User < ActiveRecord::Base +# validates :personal_url, addressable_url: true +# +# validates :ftp_url, addressable_url: { protocols: %w(ftp) } +# +# validates :git_url, addressable_url: { protocols: %w(http https ssh git) } +# end +# +class AddressableUrlValidator < ActiveModel::EachValidator + DEFAULT_OPTIONS = { protocols: %w(http https ssh git) } + + def validate_each(record, attribute, value) + unless valid_url?(value) + record.errors.add(attribute, "must be a valid URL") + end + end + + private + + def valid_url?(value) + return false unless value + + valid_protocol?(value) && valid_uri?(value) + end + + def valid_uri?(value) + Gitlab::UrlSanitizer.valid?(value) + end + + def valid_protocol?(value) + options = DEFAULT_OPTIONS.merge(self.options) + value =~ /\A#{URI.regexp(options[:protocols])}\z/ + end +end diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index 862b86d9d4a..dd2e7ebd030 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -3,14 +3,14 @@ %tr %td - if user - = link_to user.name, [:admin, user] + = link_to user.name, user .light.small Joined #{time_ago_with_tooltip(user.created_at)} - else (removed) %td - if reporter - = link_to reporter.name, [:admin, reporter] + = link_to reporter.name, reporter - else (removed) .light.small diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index dc083e50178..92e2dae4842 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -13,7 +13,7 @@ .col-sm-10 = f.text_area :description, class: "form-control", rows: 10 .hint - Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown', 'markdown'), target: '_blank'}. + Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown/markdown'), target: '_blank'}. .form-group = f.label :logo, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index c1f70bc1866..538d8176ce7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -38,11 +38,17 @@ = source %span.help-block#import-sources-help Enabled sources for code import during project creation. OmniAuth must be configured for GitHub - = link_to "(?)", help_page_path("integration", "github") + = link_to "(?)", help_page_path("integration/github") , Bitbucket - = link_to "(?)", help_page_path("integration", "bitbucket") + = link_to "(?)", help_page_path("integration/bitbucket") and GitLab.com - = link_to "(?)", help_page_path("integration", "gitlab") + = link_to "(?)", help_page_path("integration/gitlab") + .form-group + %label.control-label.col-sm-2 Enabled Git access protocols + .col-sm-10 + = select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control') + %span.help-block#clone-protocol-help + Allow only the selected protocols to be used for Git access. .form-group .col-sm-offset-2.col-sm-10 .checkbox @@ -94,6 +100,13 @@ = f.label :user_oauth_applications do = f.check_box :user_oauth_applications Allow users to register any application to use GitLab as an OAuth provider + .form-group + = f.label :user_default_external, 'New users set to external', class: 'control-label col-sm-2' + .col-sm-10 + .checkbox + = f.label :user_default_external do + = f.check_box :user_default_external + Newly registered users will by default be external %fieldset %legend Sign-in Restrictions diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 967151bc33b..ce818c30c30 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -1,30 +1,40 @@ - project = build.project -%tr.build +%tr.build.commit %td.status = ci_status_with_icon(build.status) - %td.build-link - - if can?(current_user, :read_build, build.project) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %strong Build ##{build.id} - - else - %strong Build ##{build.id} + %td + .branch-commit + - if can?(current_user, :read_build, build.project) + = link_to namespace_project_build_url(build.project.namespace, build.project, build) do + %span.build-link ##{build.id} + - else + %span.build-link ##{build.id} - - if build.stuck? - %i.fa.fa-warning.text-warning + - if build.stuck? + %i.fa.fa-warning.text-warning - %td - - if project - = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) + - if build.ref + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" + - else + .light none + = custom_icon("icon_commit") - %td - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" + = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id" + + .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 %td - - if build.ref - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) - - else - .light none + - if project + = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) %td - if build.try(:runner) @@ -36,22 +46,15 @@ #{build.stage} / #{build.name} %td - - 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 - - %td.duration - if build.duration - #{duration_in_words(build.finished_at, build.started_at)} + %p.duration + = custom_icon("icon_timer") + = duration_in_numbers(build.finished_at, build.started_at) - %td.timestamp - 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 diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 1e60205f91a..9ea3cca0ecb 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -27,7 +27,7 @@ .row-content-block.second-block #{(@scope || 'all').capitalize} builds - %ul.content-list + %ul.content-list.builds-content-list - if @builds.blank? %li .nothing-here-block No builds to show @@ -37,15 +37,11 @@ %thead %tr %th Status - %th Build ID - %th Project %th Commit - %th Ref + %th Project %th Runner %th Name - %th Tags - %th Duration - %th Finished at + %th %th - @builds.each do |build| diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index 15aa059c93d..5c410a695bf 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -14,7 +14,7 @@ .col-sm-10 %p.light 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.text_area :key, class: "form-control thin_area", rows: 5 .form-actions diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 9025aaac097..77a11e49e20 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -1,11 +1,9 @@ -- css_class = '' unless local_assigns[:css_class] -- css_class += ' no-description' if group.description.blank? +- css_class = 'no-description' if group.description.blank? %li.group-row{ class: css_class } - .controls.hidden-xs - = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm' - = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove' - + .controls + = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn' + = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove' .stats %span = icon('bookmark') @@ -18,7 +16,7 @@ %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} = visibility_level_icon(group.visibility_level, fw: false) - = image_tag group_icon(group), class: 'avatar s40 hidden-xs' + = image_tag group_icon(group), class: "avatar s40 hidden-xs" .title = link_to [:admin, group], class: 'group-name' do = group.name diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 94aa5f5a942..794f910a61f 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -3,41 +3,32 @@ = render "admin/dashboard/head" %div{ class: container_class } - %h3.page-title - Groups (#{number_with_delimiter(@groups.total_count)}) - - %p.light - Group allows you to keep projects organized. - Use groups for uniting related projects. - .top-area - .nav-search - = form_tag admin_groups_path, method: :get, class: 'form-inline' do + .prepend-top-default.append-bottom-default + = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f| = hidden_field_tag :sort, @sort - = text_field_tag :name, params[:name], class: "form-control" - = button_tag "Search", class: "btn submit btn-primary" - - .nav-controls - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_created - %b.caret - %ul.dropdown-menu - %li - = link_to admin_groups_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to admin_groups_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to admin_groups_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to admin_groups_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated - = link_to 'New Group', new_admin_group_path, class: "btn btn-new" - + .search-holder + - project_name = params[:name].present? ? params[:name] : nil + .search-field-holder + = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name' + = icon("search", class: "search-icon") + .dropdown + - toggle_text = @sort.present? ? sort_options_hash[@sort] : sort_title_recently_created + = dropdown_toggle(toggle_text, { toggle: 'dropdown' }) + %ul.dropdown-menu.dropdown-menu-align-right + %li.dropdown-header + Sort by + %li + = link_to admin_groups_path(sort: sort_value_recently_created, name: project_name) do + = sort_title_recently_created + = link_to admin_groups_path(sort: sort_value_oldest_created, name: project_name) do + = sort_title_oldest_created + = link_to admin_groups_path(sort: sort_value_recently_updated, name: project_name) do + = sort_title_recently_updated + = link_to admin_groups_path(sort: sort_value_oldest_updated, name: project_name) do + = sort_title_oldest_updated + = link_to new_admin_group_path, class: "btn btn-new" do + New Group %ul.content-list = render @groups diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 522153b37e3..bb374694400 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -79,7 +79,7 @@ .panel-body.form-holder %p.light Read more about project permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 7b388cf7862..c217490963f 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -3,7 +3,7 @@ System hooks %p.light - #{link_to "System hooks ", help_page_path("system_hooks", "system_hooks"), class: "vlink"} can be + #{link_to "System hooks ", help_page_path("system_hooks/system_hooks"), class: "vlink"} can be used for binding events when GitLab creates a User or Project. %hr diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 7d2eb423223..1e755785d90 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,97 +1,94 @@ - @no_container = true - page_title "Projects" -= render 'shared/show_aside' +- params[:visibility_level] ||= [] + = render "admin/dashboard/head" %div{ class: container_class } - .row.prepend-top-default - %aside.col-md-3 - .panel.admin-filter - = form_tag admin_namespaces_projects_path, method: :get, class: '' do - .form-group - = label_tag :name, 'Name:' - = text_field_tag :name, params[:name], class: "form-control" + .top-area + .prepend-top-default + = form_tag admin_namespaces_projects_path, method: :get do |f| + .search-holder + .search-field-holder + = search_field_tag :name, params[:name], class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false, placeholder: 'Search by name' + + - if params[:visibility_level].present? + = hidden_field_tag 'visibility_level', params[:visibility_level] + + - if params[:sort].present? + = hidden_field_tag 'sort', params[:sort] + + - if params[:personal].present? + = hidden_field_tag 'visibility_level', 'true' + + - if params[:archived].present? + = hidden_field_tag 'archived', 'true' + + = icon("search", class: "search-icon") + + .dropdown + - toggle_text = 'Search for Namespace' + - if params[:namespace_id].present? + - namespace = Namespace.find(params[:namespace_id]) + - toggle_text = "#{namespace.kind}: #{namespace.path}" + = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' }) + .dropdown-menu.dropdown-select.dropdown-menu-align-right + = dropdown_title('Namespaces') + = dropdown_filter("Search for Namespace") + = dropdown_content + = dropdown_loading + + = button_tag "Search", class: "btn btn-primary btn-search" + + %ul.nav-links + - opts = params[:visibility_level].present? ? {} : { page: admin_namespaces_projects_path } + = nav_link(opts) do + = link_to admin_namespaces_projects_path do + All - .form-group - = label_tag :namespace_id, "Namespace" - = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' + = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s ? 'active' : '' }) do + = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do + Private + = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s ? 'active' : '' }) do + = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do + Internal + = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s ? 'active' : '' }) do + = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do + Public - .form-group - %strong Activity - .checkbox - = label_tag :with_push do - = check_box_tag :with_push, 1, params[:with_push] - %span Projects with push events - .checkbox - = label_tag :abandoned do - = check_box_tag :abandoned, 1, params[:abandoned] - %span No activity over 6 month - .checkbox - = label_tag :with_archived do - = check_box_tag :with_archived, 1, params[:with_archived] - %span Show archived projects + .nav-controls + = render 'shared/projects/dropdown' + = link_to new_project_path, class: 'btn btn-new' do + New Project - %fieldset - %strong Visibility level: - .visibility-levels - - Project.visibility_levels.each do |label, level| - .checkbox - %label - = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) - %span.descr - = visibility_level_icon(level) - = label - %fieldset - %strong Problems - .checkbox - = label_tag :last_repository_check_failed do - = check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed] - %span Last repository check failed + .projects-list-holder + - if @projects.any? + %ul.projects-list.content-list + - @projects.each_with_index do |project| + %li.project-row + .controls + - if project.archived + %span.label.label-warning archived + %span.label.label-gray + = repository_size(project) + = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn" + = link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove" + .title + = link_to [:admin, project.namespace.becomes(Namespace), project] do + .dash-project-avatar + = project_icon(project, alt: '', class: 'avatar project-avatar s40') + %span.project-full-name + %span.namespace-name + - if project.namespace + = project.namespace.human_name + \/ + %span.project-name.filter-title + = project.name - = hidden_field_tag :sort, params[:sort] - = button_tag "Search", class: "btn submit btn-primary" - = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" + - if project.description.present? + .description + = markdown(project.description, pipeline: :description) - %section.col-md-9 - .panel.panel-default - .panel-heading - Projects (#{@projects.total_count}) - .controls - .dropdown.inline - %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_recently_created - %b.caret - %ul.dropdown-menu - %li - = link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do - = sort_title_recently_created - = link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do - = sort_title_oldest_created - = link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated - = link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do - = sort_title_largest_repo - = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success" - %ul.well-list - - @projects.each do |project| - %li - .list-item-name - %span{ class: visibility_level_color(project.visibility_level) } - = visibility_level_icon(project.visibility_level) - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] - .pull-right - - if project.archived - %span.label.label-warning archived - %span.label.label-gray - = repository_size(project) - = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" - = link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove" - - if @projects.blank? - .nothing-here-block 0 projects matches - = paginate @projects, theme: "gitlab" + = paginate @projects, theme: 'gitlab' + - else + .nothing-here-block No projects found diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 82d3169c6f9..b2c607361b3 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -99,7 +99,13 @@ .form-group = f.label :new_namespace_id, "Namespace", class: 'control-label' .col-sm-10 - = namespace_select_tag :new_namespace_id, selected: params[:namespace_id], class: 'input-large' + .dropdown + = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id', show_any: 'false' }, { toggle_class: 'js-namespace-select large' }) + .dropdown-menu.dropdown-select + = dropdown_title('Namespaces') + = dropdown_filter("Search for Namespace") + = dropdown_content + = dropdown_loading .form-group .col-sm-offset-2.col-sm-10 @@ -126,7 +132,7 @@ - else passed. - = link_to icon('question-circle'), help_page_path('administration', 'repository_checks') + = link_to icon('question-circle'), help_page_path('administration/repository_checks') .form-group = f.submit 'Trigger repository check', class: 'btn btn-primary' diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index fe0b9d3a491..3145212728f 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -44,7 +44,7 @@ %legend Access .form-group = f.label :projects_limit, class: 'control-label' - .col-sm-10= f.number_field :projects_limit, class: 'form-control' + .col-sm-10= f.number_field :projects_limit, min: 0, class: 'form-control' .form-group = f.label :can_create_group, class: 'control-label' diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml new file mode 100644 index 00000000000..4bf1c9cde3c --- /dev/null +++ b/app/views/admin/users/_user.html.haml @@ -0,0 +1,42 @@ +%li.user-row + .user-avatar + = image_tag avatar_icon(user), class: "avatar", alt: '' + .user-details + .user-name + = link_to user.name, [:admin, user] + - if user.blocked? + %span.label.label-danger blocked + - if user.admin? + %span.label.label-success Admin + - if user.external? + %span.label.label-default External + - if user == current_user + %span It's you! + .user-email + = mail_to user.email, user.email + .controls + = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn' + - unless user == current_user + .dropdown.inline + %a.dropdown-new.btn.btn-default#project-settings-button{href: '#', data: { toggle: 'dropdown' } } + = icon('cog') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + %li.dropdown-header + Settings + %li + - if user.ldap_blocked? + %span.small Cannot unblock LDAP blocked users + - elsif user.blocked? + = link_to 'Unblock', unblock_admin_user_path(user), method: :put + - else + = link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put + - if user.access_locked? + %li + = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' } + - if user.can_be_removed? + %li.divider + %li + = link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" }, + class: 'btn btn-remove btn-block', + method: :delete diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 21bb99a792c..357123c2c13 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,110 +1,78 @@ - @no_container = true - page_title "Users" -= render 'shared/show_aside' = render "admin/dashboard/head" %div{ class: container_class } - .admin-filter - %ul.nav-links - %li{class: "#{'active' unless params[:filter]}"} - = link_to admin_users_path do - Active - %small.badge= number_with_delimiter(User.active.count) - %li{class: "#{'active' if params[:filter] == "admins"}"} - = link_to admin_users_path(filter: "admins") do - Admins - %small.badge= number_with_delimiter(User.admins.count) - %li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"} - = link_to admin_users_path(filter: 'two_factor_enabled') do - 2FA Enabled - %small.badge= number_with_delimiter(User.with_two_factor.count) - %li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"} - = link_to admin_users_path(filter: 'two_factor_disabled') do - 2FA Disabled - %small.badge= number_with_delimiter(User.without_two_factor.count) - %li.filter-external{class: "#{'active' if params[:filter] == 'external'}"} - = link_to admin_users_path(filter: 'external') do - External - %small.badge= number_with_delimiter(User.external.count) - %li{class: "#{'active' if params[:filter] == "blocked"}"} - = link_to admin_users_path(filter: "blocked") do - Blocked - %small.badge= number_with_delimiter(User.blocked.count) - %li{class: "#{'active' if params[:filter] == "wop"}"} - = link_to admin_users_path(filter: "wop") do - Without projects - %small.badge= number_with_delimiter(User.without_projects.count) + .top-area + .prepend-top-default + = form_tag admin_users_path, method: :get do + - if params[:filter].present? + = hidden_field_tag "filter", h(params[:filter]) + .search-holder + .search-field-holder + = search_field_tag :name, params[:name], placeholder: 'Search by name, email or username', class: 'form-control search-text-input js-search-input', spellcheck: false + = icon("search", class: "search-icon") + .dropdown + - toggle_text = if @sort.present? then sort_options_hash[@sort] else sort_title_name end + = dropdown_toggle(toggle_text, { toggle: 'dropdown' }) + %ul.dropdown-menu.dropdown-menu-align-right + %li.dropdown-header + Sort by + %li + = link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do + = sort_title_name + = link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do + = sort_title_recently_signin + = link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do + = sort_title_oldest_signin + = link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do + = sort_title_recently_created + = link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do + = sort_title_oldest_created + = link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do + = sort_title_recently_updated + = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do + = sort_title_oldest_updated + = link_to 'New User', new_admin_user_path, class: 'btn btn-new btn-search' - .row-content-block.second-block - .pull-right - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %span.light - - if @sort.present? - = sort_options_hash[@sort] - - else - = sort_title_name - %b.caret - %ul.dropdown-menu - %li - = link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do - = sort_title_name - = link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do - = sort_title_recently_signin - = link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do - = sort_title_oldest_signin - = link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do - = sort_title_recently_created - = link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do - = sort_title_oldest_created - = link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do - = sort_title_recently_updated - = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do - = sort_title_oldest_updated + .nav-block + %ul.nav-links.wide.scrolling-tabs.white.scrolling-tabs + .fade-left + = nav_link(html_options: { class: ('active' unless params[:filter]) }) do + = link_to admin_users_path do + Active + %small.badge= number_with_delimiter(User.active.count) + = nav_link(html_options: { class: ('active' if params[:filter] == 'admins') }) do + = link_to admin_users_path(filter: "admins") do + Admins + %small.badge= number_with_delimiter(User.admins.count) + = nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_enabled'} filter-two-factor-enabled" }) do + = link_to admin_users_path(filter: 'two_factor_enabled') do + 2FA Enabled + %small.badge= number_with_delimiter(User.with_two_factor.count) + = nav_link(html_options: { class: "#{'active' if params[:filter] == 'two_factor_disabled'} filter-two-factor-disabled" }) do + = link_to admin_users_path(filter: 'two_factor_disabled') do + 2FA Disabled + %small.badge= number_with_delimiter(User.without_two_factor.count) + = nav_link(html_options: { class: ('active' if params[:filter] == 'external') }) do + = link_to admin_users_path(filter: 'external') do + External + %small.badge= number_with_delimiter(User.external.count) + = nav_link(html_options: { class: ('active' if params[:filter] == 'blocked') }) do + = link_to admin_users_path(filter: "blocked") do + Blocked + %small.badge= number_with_delimiter(User.blocked.count) + = nav_link(html_options: { class: ('active' if params[:filter] == 'wop') }) do + = link_to admin_users_path(filter: "wop") do + Without projects + %small.badge= number_with_delimiter(User.without_projects.count) + .fade-right - = link_to 'New User', new_admin_user_path, class: "btn btn-new" - = form_tag admin_users_path, method: :get, class: 'form-inline' do - .form-group - = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false - = hidden_field_tag "filter", params[:filter] - = button_tag class: 'btn btn-primary' do - %i.fa.fa-search + %ul.users-list.content-list + - if @users.empty? + %li + .nothing-here-block No users found. + - else + = render partial: 'admin/users/user', collection: @users - - .panel.panel-default - %ul.well-list - - @users.each do |user| - %li - .list-item-name - - if user.blocked? - = icon("lock", class: "cred") - - else - = icon("user", class: "cgreen") - = link_to user.name, [:admin, user] - - if user.admin? - %strong.cred (Admin) - - if user.external? - %strong.cred (External) - - if user == current_user - %span.cred It's you! - .pull-right - %span.light - %i.fa.fa-envelope - = mail_to user.email, user.email, class: 'light' - - .pull-right - = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn-grouped btn btn-xs' - - unless user == current_user - - if user.ldap_blocked? - = link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn-grouped btn btn-xs btn-success disabled' do - %i.fa.fa-lock - Unblock - - elsif user.blocked? - = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success' - - else - = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: 'btn-grouped btn btn-xs btn-warning' - - if user.access_locked? - = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' } - - if user.can_be_removed? - = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: 'btn-grouped btn btn-xs btn-remove' = paginate @users, theme: "gitlab" diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index d54c7cad7be..fdea834ff45 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,53 +1,46 @@ - publicish_project_count = ProjectsFinder.new.execute(current_user).count -%h3.page-title Welcome to GitLab! -%p.light Self hosted Git management application. -%hr -%div - .dashboard-intro-icon - %i.fa.fa-bookmark-o - .dashboard-intro-text - %p.slead - You don't have access to any projects right now. - %br - - if current_user.can_create_project? - You can create up to - %strong= pluralize(number_with_delimiter(current_user.projects_limit), "project") + "." - - else - If you are added to a project, it will be displayed here. - +.blank-state.blank-state-welcome + %h2.blank-state-welcome-title + Welcome to GitLab + %p.blank-state-text + Code, test, and deploy together +.blank-state + .blank-state-icon + = custom_icon("project", size: 50) + %h3.blank-state-title + You don't have access to any projects right now + %p.blank-state-text - if current_user.can_create_project? - .link_holder - = link_to new_project_path, class: "btn btn-new" do - = icon('plus') - New Project + You can create up to + %strong= number_with_delimiter(current_user.projects_limit) + = succeed "." do + = "project".pluralize(current_user.projects_limit) + - else + If you are added to a project, it will be displayed here. + - if current_user.can_create_project? + = link_to new_project_path, class: "btn btn-new" do + New project - if current_user.can_create_group? - %hr - %div - .dashboard-intro-icon - %i.fa.fa-users - .dashboard-intro-text - %p.slead - You can create a group for several dependent projects. - %br - Groups are the best way to manage projects and members. - .link_holder - = link_to new_group_path, class: "btn btn-new" do - %i.fa.fa-plus - New Group + .blank-state + .blank-state-icon + = custom_icon("group", size: 50) + %h3.blank-state-title + You can create a group for several dependent projects. + %p.blank-state-text + Groups are the best way to manage projects and members. + = link_to new_group_path, class: "btn btn-new" do + New group -if publicish_project_count > 0 - %hr - %div - .dashboard-intro-icon - %i.fa.fa-globe - .dashboard-intro-text - %p.slead - There are - %strong= number_with_delimiter(publicish_project_count) - public projects on this server. - %br - Public projects are an easy way to allow everyone to have read-only access. - .link_holder - = link_to trending_explore_projects_path, class: "btn btn-new" do - Browse public projects + .blank-state + .blank-state-icon + = icon("globe") + %h3.blank-state-title + There are + = number_with_delimiter(publicish_project_count) + public projects on this server. + %p.blank-state-text + Public projects are an easy way to allow everyone to have read-only access. + = link_to trending_explore_projects_path, class: "btn btn-new" do + Browse projects diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 4565e752c1f..4f36a4a1c73 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -5,7 +5,8 @@ - page_title "Projects" - header_title "Projects", dashboard_projects_path -= render 'dashboard/projects_head' +- if @projects.any? || params[:filter_projects] + = render 'dashboard/projects_head' - if @last_push = render "events/event_last_push", event: @last_push diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index fc42e5dcc66..4e340b6ec16 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -9,14 +9,14 @@ %span To do %span.badge - = todos_pending_count + = number_with_delimiter(todos_pending_count) - todo_done_active = ('active' if params[:state] == 'done') %li{class: "todos-done #{todo_done_active}"} = link_to todos_filter_path(state: 'done') do %span Done %span.badge - = todos_done_count + = number_with_delimiter(todos_done_count) .nav-controls - if @todos.any?(&:pending?) diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index a373f61bd3c..4debd3d608f 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -1,3 +1,7 @@ +- if inject_u2f_api? + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('u2f.js') + %div .login-box .login-heading diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index 012e9857642..c034bbe430e 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -3,4 +3,4 @@ %h3 Access Denied %hr %p You are not allowed to access this page. -%p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "vlink"} +%p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 9b838b9f3b7..6306fe6d0bf 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -10,7 +10,6 @@ - if current_user .pull-right = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do - = icon('plus') New Snippet .oneline diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index e7ab4f2409b..9bb9f962177 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -12,7 +12,7 @@ = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_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 group', class: "btn btn-create" diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 121a7de3ad7..a8fdbd8c426 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -6,7 +6,6 @@ .nav-controls - if can?(current_user, :admin_milestones, @group) = link_to new_group_milestone_path(@group), class: "btn btn-new" do - = icon('plus') New Milestone .row-content-block diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index c2f2d9912f7..33fee334d93 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -7,7 +7,6 @@ - if can? current_user, :admin_group, @group .controls = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do - = icon('plus') New Project %ul.well-list - @projects.each do |project| diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index a83eb7e88bb..eddeae98bc4 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -6,8 +6,7 @@ .cover-block.groups-cover-block %div{ class: container_class } - = link_to group_icon(@group), target: '_blank' do - = image_tag group_icon(@group), class: "avatar group-avatar s70" + = image_tag group_icon(@group), class: "avatar group-avatar s70" .group-info .cover-title %h1 diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 8cc0b59edeb..ce4536ebdc6 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -20,6 +20,10 @@ %td Focus Search %tr %td.shortcut + .key f + %td Focus Filter + %tr + %td.shortcut .key ? %td Show/hide this dialog %tr diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index 0398afb4c1d..be257b51b9e 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,3 @@ -- page_title @file.humanize, *@category.split("/").reverse.map(&:humanize) +- page_title @path.split("/").reverse.map(&:humanize) .documentation.wiki = markdown @markdown.gsub('$your_email', current_user.try(:email) || "email@example.com") diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index d676bc28c89..431d312b4ca 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -549,4 +549,4 @@ %li wiki page %li help page - You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown", "markdown")}. + You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown/markdown")}. diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index 435ed7bd4cb..4c6af0b7908 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -38,6 +38,6 @@ 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") + = 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 8c140a5943e..00000000000 --- 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 cc8ea066cb9..3612f1ce5c6 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/_page.html.haml b/app/views/layouts/_page.html.haml index 2234bf79c87..a1a71c2fb33 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 245b9c3b4d4..f7580f00159 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -44,7 +44,7 @@ name: "#{j(@project.name)}" }; - - if @group and @group.path + - if @group && @group.persisted? && @group.path :javascript gl.groupOptions = gl.groupOptions || {}; gl.groupOptions["#{j(@group.path)}"] = { diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index d59a93a8fd7..94c53882623 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,7 +1,7 @@ %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'} @@ -13,25 +13,25 @@ %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 @@ -45,10 +45,12 @@ .dropdown-menu-nav.dropdown-menu-align-right %ul %li - = link_to "Profile", current_user + = 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", title: 'Sign out' + = 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/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 52e41b1a857..21668698814 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 = 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/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 96fe62c39c3..6d514f669db 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -18,9 +18,9 @@ %span Applications = nav_link(controller: :personal_access_tokens) do - = link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do + = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do %span - Personal Access Tokens + Access Tokens = nav_link(controller: :emails) do = link_to profile_emails_path, title: 'Emails' do %span diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index a3643a00cfe..ea7e3d199fd 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/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index f1532371b2e..c161ecc3463 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 00000000000..003884a5bd9 --- /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/application.js') diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 8efe486e01b..57d16d29158 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 9c404b6935f..9fe86e6b291 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 6f7fefdb46d..dc499be885b 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 b3ed59a1a4a..6ea358d9f63 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 6a067a03535..a42b3b8eb38 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 89f6f01581a..6283ceebf10 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 f77738f97f5..844fce59704 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 1b45548bd02..71ac367830d 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 1b1b16d656f..2afa026847a 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 eef50d887c7..d9fa74fad90 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 593be2617c1..366f1fed35b 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/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml index 2987f6b5b22..e74fd5b93ea 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 index 0568c2d305e..fff30f11d82 100644 --- a/app/views/projects/_builds_settings.html.haml +++ b/app/views/projects/_builds_settings.html.haml @@ -4,7 +4,7 @@ - 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' + = 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 diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml index 377cf0187b8..e9f39b16aa7 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 540efa4780f..cf11723dc8e 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)} +.project-home-panel.text-center{ 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_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 - .pull-left.append-right-10= 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 66c30283c7a..630ae7d6140 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 434d8644b83..3c6b931f41a 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -7,7 +7,9 @@ %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/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 771a2e0df7d..19b4249374b 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', anchor: '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/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index e4f04ca7764..b1c9895f43e 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/builds/index.html.haml b/app/views/projects/builds/index.html.haml index a131289ee97..85c31dfd918 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -31,12 +31,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 +46,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 d1c468c4692..4421f3b9562 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, :json)}", "#{@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 34ad9fe2c43..a9eaed4c5f6 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -14,6 +14,5 @@ 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 5bd6e3f0ebc..e1b42b2cfa5 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,32 +1,45 @@ -%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} + %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 + %span ##{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 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 + - if build.ref + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" + - else + .light none + = 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 defined?(ref) && ref - %td - - if build.ref - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) - - else - .light none - if defined?(runner) && runner %td @@ -43,25 +56,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 @@ -79,4 +81,4 @@ = 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') + = icon('repeat') diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index e38d1ff5ff0..631873fb0a3 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -1,17 +1,18 @@ - 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" - · + = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name" + = 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? @@ -25,6 +26,7 @@ %p.commit-title - if commit = pipeline.commit + = 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 @@ -45,27 +47,34 @@ %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 + .inline + .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 ae7bb01223e..9d925cacc0d 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/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 0411137b7c6..41fd5459429 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -42,9 +42,7 @@ %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 401cb4f7e30..d0da2606587 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 929496f81d8..c8c7b858baa 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -25,7 +25,7 @@ .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') + = 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/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index dd590a4b8ec..af09b3418ea 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 00000000000..c604c6d0135 --- /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/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index 894c36a96df..901605f7ca3 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/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml new file mode 100644 index 00000000000..0c0424edffd --- /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.diff_lines.length > 0 + - if diff_file.collapsed_by_default? && !expand_all_diffs? + - 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_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 f18bc8c41b3..20aaab5accf 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 + - unless expand_all_diffs? + = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), 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 2395ea3c275..c306909fb1a 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 00000000000..95a2772fd0b --- /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 2731219ccad..9ec6a7aa5cd 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 f1577e8a47b..5a8a131d10c 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 0cd888876e0..b9c0d9dcdfd 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 4ecc9528bd2..d208fcee10b 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 068593a7dd1..196f8122db3 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 15536c17f8e..10fa1ddf2e5 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 27a94fe02dc..57af167180b 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 diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 5242021243e..303d7c23d01 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -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 diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index da325efecd2..89e06567196 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 53c62ef234d..b17aba2431f 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -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/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 5bc5c71283e..542827b2f15 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 + = icon("clock-o") #{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)} %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/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 7ce4c1e5555..7612fe3719a 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -6,21 +6,37 @@ - if current_user = 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) +%div{ class: (container_class) } + - 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/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 2ec96308fd7..873ed9b59ee 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/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index b3bea900d42..b727efaa6a6 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 5bf5210aeab..b24bdf22ceb 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 08a38d283d2..489c632ae22 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -7,7 +7,7 @@ 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 0e0af57d76e..dc18f715f25 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 941513febbd..bf2e76f0083 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/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index ad898ff153b..2b6b5e05e86 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/new.html.haml b/app/views/projects/new.html.haml index 05f33b78a47..c72d0140bb9 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -107,7 +107,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") = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project) = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 @@ -154,4 +154,9 @@ $('.import_gitlab_project').attr('disabled',true); $('.import_gitlab_project').attr('title', 'Project path 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 8144c1ba49e..ec6c4938efc 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 45986b0d1e8..e50a4f86d03 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 03b3f6935d1..7c61ba750fe 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 7d1cbc62e86..25466e7562e 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -1,7 +1,7 @@ .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') diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 1c39ce897a3..56d302fab82 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) } 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 6401245bf73..4a69b8f8840 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 e598e3c7c63..a785149549d 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 28b475d5c2f..5f466bdbac2 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -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/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 82892a33358..978c4dfc5ec 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/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 565905cbe7b..97cb1a9052b 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 @@ -9,33 +9,18 @@ %table.table.protected-branches-list %colgroup %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 - 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 00000000000..b803d932e67 --- /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 00000000000..8a5332ca5bb --- /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 00000000000..474aec3a97c --- /dev/null +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -0,0 +1,21 @@ +- 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 }) + - 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 c7d317dbaee..883d3e3af1e 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -4,30 +4,38 @@ .col-lg-3 %h4.prepend-top-0 = page_title - %p Keep stable branches secure and force developers to use Merge Requests - .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 + %p Keep stable branches secure and force developers to use merge requests. + %p.prepend-top-20 + Protected branches are designed to: %ul - %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} + %li prevent pushes from everybody except #{link_to "masters", help_page_path("user/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"} + %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"} + .col-lg-9 + %h5.prepend-top-0 + Protect a branch - 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('workflow/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" + = 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 00000000000..4d8169815b3 --- /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/show.html.haml b/app/views/projects/show.html.haml index f6e81af2638..dd1cf680cfa 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 - %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)}) +%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 namespace_project_tags_path(@project.namespace, @project) do - #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) + = link_to 'Readme', readme_path(@project) - - if default_project_view != 'readme' && @repository.readme - %li - = link_to 'Readme', readme_path(@project) + - if @repository.changelog + %li + = link_to 'Changelog', changelog_path(@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.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 @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 + %li.project-repo-buttons-right + .project-repo-buttons.project-right-buttons + - if current_user + = render 'shared/members/access_request_buttons', source: @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 bf57beb9d07..bdbf3e5f4d6 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 96fee3b17b2..1646bcf4b8a 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 c375bb6dd1b..368231e73fe 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -7,22 +7,22 @@ .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 - if @tags.any? diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 84b3f44c0ad..3b82d8e686f 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/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 627814bcfae..65a3a6bddab 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 5507a05f6c1..dce492352ac 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 label_filter_path(@project, label, type: controller.controller_name), + %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/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index 1c6ec198d3d..107ad19177c 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 index 75cae0d16c8..53635016900 100644 --- a/app/views/shared/icons/_group.svg +++ b/app/views/shared/icons/_group.svg.erb @@ -1,9 +1,4 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> - <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch --> - <title>Group</title> - <desc>Created with Sketch.</desc> - <defs></defs> +<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16"> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Group" fill="#303030"> <path d="M15.6667,10.0105 L10.3337,10.0105 C10.1497,10.0105 9.9997,10.1775 9.9997,10.3845 L9.9997,15.6145 C9.9997,15.8215 10.1497,15.9885 10.3337,15.9885 L15.6667,15.9885 C15.8507,15.9885 15.9997,15.8215 15.9997,15.6145 L15.9997,10.3845 C15.9997,10.1775 15.8507,10.0105 15.6667,10.0105 L15.6667,10.0105 L15.6667,10.0105 Z M11.9997,14.0105 L13.9997,14.0105 L13.9997,12.0105 L11.9997,12.0105 L11.9997,14.0105 L11.9997,14.0105 Z" id="Fill-11"></path> @@ -15,4 +10,4 @@ <path d="M11.6667,6.21724894e-15 L4.3337,6.21724894e-15 C4.1497,6.21724894e-15 3.9997,0.167 3.9997,0.374 L3.9997,6.604 C3.9997,6.811 4.1497,6.978 4.3337,6.978 L11.6667,6.978 C11.8507,6.978 11.9997,6.811 11.9997,6.604 L11.9997,0.374 C11.9997,0.167 11.8507,6.21724894e-15 11.6667,6.21724894e-15 L11.6667,6.21724894e-15 L11.6667,6.21724894e-15 Z M5.9997,5 L9.9997,5 L9.9997,2 L5.9997,2 L5.9997,5 L5.9997,5 Z" id="Fill-14"></path> </g> </g> -</svg>
\ No newline at end of file +</svg> diff --git a/app/views/shared/icons/_icon_commit.svg b/app/views/shared/icons/_icon_commit.svg new file mode 100644 index 00000000000..0e96035b7b7 --- /dev/null +++ b/app/views/shared/icons/_icon_commit.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"> + <path fill="#8F8F8F" fill-rule="evenodd" d="M28.7769836,18 C27.8675252,13.9920226 24.2831748,11 20,11 C15.7168252,11 12.1324748,13.9920226 11.2230164,18 L4.0085302,18 C2.90195036,18 2,18.8954305 2,20 C2,21.1122704 2.8992496,22 4.0085302,22 L11.2230164,22 C12.1324748,26.0079774 15.7168252,29 20,29 C24.2831748,29 27.8675252,26.0079774 28.7769836,22 L35.9914698,22 C37.0980496,22 38,21.1045695 38,20 C38,18.8877296 37.1007504,18 35.9914698,18 L28.7769836,18 L28.7769836,18 Z M20,25 C22.7614237,25 25,22.7614237 25,20 C25,17.2385763 22.7614237,15 20,15 C17.2385763,15 15,17.2385763 15,20 C15,22.7614237 17.2385763,25 20,25 L20,25 Z"/> +</svg> diff --git a/app/views/shared/icons/_icon_timer.svg b/app/views/shared/icons/_icon_timer.svg new file mode 100644 index 00000000000..0b1e5804427 --- /dev/null +++ b/app/views/shared/icons/_icon_timer.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><g fill="#8F8F8F" fill-rule="evenodd"><path d="M29.513 10.134A15.922 15.922 0 0 0 23 7.28V6h2.993C26.55 6 27 5.552 27 5V2a1 1 0 0 0-1.007-1H14.007C13.45 1 13 1.448 13 2v3a1 1 0 0 0 1.007 1H17v1.28C9.597 8.686 4 15.19 4 23c0 8.837 7.163 16 16 16s16-7.163 16-16c0-3.461-1.099-6.665-2.967-9.283l1.327-1.58a2.498 2.498 0 0 0-.303-3.53 2.499 2.499 0 0 0-3.528.315l-1.016 1.212zM20 34c6.075 0 11-4.925 11-11s-4.925-11-11-11S9 16.925 9 23s4.925 11 11 11z"/><path d="M19 21h-4.002c-.552 0-.998.452-.998 1.01v1.98c0 .567.447 1.01.998 1.01h7.004c.274 0 .521-.111.701-.291a.979.979 0 0 0 .297-.704v-8.01c0-.54-.452-.995-1.01-.995h-1.98a.997.997 0 0 0-1.01.995V21z"/></g></svg>
\ No newline at end of file diff --git a/app/views/shared/icons/_issues.svg b/app/views/shared/icons/_issues.svg deleted file mode 100644 index 2682c27ade9..00000000000 --- a/app/views/shared/icons/_issues.svg +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> - <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch --> - <title>Group</title> - <desc>Created with Sketch.</desc> - <defs></defs> - <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> - <g id="Group" fill="#7E7C7C"> - <path d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2" id="Fill-1"></path> - <path d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z" id="Combined-Shape"></path> - </g> - </g> -</svg>
\ No newline at end of file diff --git a/app/views/shared/icons/_issues.svg.erb b/app/views/shared/icons/_issues.svg.erb new file mode 100644 index 00000000000..fa8655b5609 --- /dev/null +++ b/app/views/shared/icons/_issues.svg.erb @@ -0,0 +1,4 @@ +<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16" class="gitlab-icon"> + <path fill="#7E7C7C" d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2"></path> + <path fill="#7E7C7C" d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z"></path> +</svg> diff --git a/app/views/shared/icons/_project.svg b/app/views/shared/icons/_project.svg deleted file mode 100644 index 1e8b43f8c6b..00000000000 --- a/app/views/shared/icons/_project.svg +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> - <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch --> - <title>Page 1</title> - <desc>Created with Sketch.</desc> - <defs></defs> - <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> - <path d="M6,6 L12,6 L12,5 L6,5 L6,6 Z M6,8 L12,8 L12,7 L6,7 L6,8 Z M6,10 L12,10 L12,9 L6,9 L6,10 Z M6,12 L12,12 L12,11 L6,11 L6,12 Z M4,6 L5,6 L5,5 L4,5 L4,6 Z M4,8 L5,8 L5,7 L4,7 L4,8 Z M4,10 L5,10 L5,9 L4,9 L4,10 Z M4,12 L5,12 L5,11 L4,11 L4,12 Z M13,3 L10,3 L10,4 L6,4 L6,3 L3,3 L3,13 L13,13 L13,3 Z M2,14 L14,14 L14,2 L2,2 L2,14 Z M1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 L1,0 Z" fill="#7F7E7E"></path> - </g> -</svg>
\ No newline at end of file diff --git a/app/views/shared/icons/_project.svg.erb b/app/views/shared/icons/_project.svg.erb new file mode 100644 index 00000000000..2f60bb7245e --- /dev/null +++ b/app/views/shared/icons/_project.svg.erb @@ -0,0 +1,3 @@ +<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16"> + <path d="M6,6 L12,6 L12,5 L6,5 L6,6 Z M6,8 L12,8 L12,7 L6,7 L6,8 Z M6,10 L12,10 L12,9 L6,9 L6,10 Z M6,12 L12,12 L12,11 L6,11 L6,12 Z M4,6 L5,6 L5,5 L4,5 L4,6 Z M4,8 L5,8 L5,7 L4,7 L4,8 Z M4,10 L5,10 L5,9 L4,9 L4,10 Z M4,12 L5,12 L5,11 L4,11 L4,12 Z M13,3 L10,3 L10,4 L6,4 L6,3 L3,3 L3,13 L13,13 L13,3 Z M2,14 L14,14 L14,2 L2,2 L2,14 Z M1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 L1,0 Z" fill="#7F7E7E" fill-rule="evenodd"></path> +</svg> diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index adfab1af53e..e020a7d4d00 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -19,7 +19,7 @@ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| .block.assignee - .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)} + .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.name if issuable.assignee)} - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 24) - else diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index 35dcdccc921..eff914398bb 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,4 +1,4 @@ -- if can_see_request_access_button?(source) +- if can?(current_user, :request_access, source) - if requester = source.requesters.find_by(user_id: current_user.id) = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), method: :delete, diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index a884e78e6e7..5ae485f36ba 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -3,71 +3,74 @@ - user = member.user %li.js-toggle-container{ class: dom_class(member), id: dom_id(member) } - %span{ class: ("list-item-name" if show_controls) } - - if user - = image_tag avatar_icon(user, 24), class: "avatar s24", alt: '' - %strong - = link_to user.name, user_path(user) - %span.cgray= user.username - - - if user == current_user - %span.label.label-success It's you - - - if user.blocked? - %label.label.label-danger - %strong Blocked - - - if member.request? - %span.cgray - – Requested - = time_ago_with_tooltip(member.requested_at) - - else - = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' - %strong= member.invite_email - %span.cgray - – Invited - - if member.created_by - by - = link_to member.created_by.name, user_path(member.created_by) - = time_ago_with_tooltip(member.created_at) - - - if show_controls && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn-xs btn' - - if show_roles - %span.pull-right - %strong= member.human_access + .controls + %strong.control-text= member.human_access - if show_controls + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), + method: :post, + class: 'btn' + - if can?(current_user, action_member_permission(:update, member), member) = button_tag icon('pencil'), type: 'button', - class: 'btn-xs btn btn-grouped inline js-toggle-button', + class: 'btn inline js-toggle-button', title: 'Edit access level' - if member.request? - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, - class: 'btn-xs btn btn-success', + class: 'btn btn-success', title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) - - if current_user == user = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), method: :delete, data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn-xs btn btn-remove' + class: 'btn btn-remove' - else = link_to icon('trash'), member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, - class: 'btn-xs btn btn-remove', + class: 'btn btn-remove', title: remove_member_title(member) + + %span{ class: ("list-item-name" if show_controls) } + - if user + = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' + %strong + = link_to user.name, user_path(user) + %span.cgray= user.username + + - if user == current_user + %span.label.label-success It's you + + - if user.blocked? + %label.label.label-danger + %strong Blocked + + .cgray + - if member.request? + Requested + = time_ago_with_tooltip(member.requested_at) + - else + Joined #{time_ago_with_tooltip(member.created_at)} + + - else + = image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: '' + %strong= member.invite_email + .cgray + Invited + - if member.created_by + by + = link_to member.created_by.name, user_path(member.created_by) + = time_ago_with_tooltip(member.created_at) + + - if show_roles .edit-member.hide.js-toggle-content %br = form_for member, remote: true do |f| diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml index 975c74f4ea6..dee2472fa79 100644 --- a/app/views/shared/milestones/_summary.html.haml +++ b/app/views/shared/milestones/_summary.html.haml @@ -26,7 +26,6 @@ %span.pull-right.tab-issues-buttons - if project && can?(current_user, :create_issue, project) = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do - %i.fa.fa-plus New Issue = link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped" %span.pull-right.tab-merge-requests-buttons.hidden diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml index 1169bed0382..b7f8551153b 100644 --- a/app/views/shared/projects/_dropdown.html.haml +++ b/app/views/shared/projects/_dropdown.html.haml @@ -1,31 +1,30 @@ - @sort ||= sort_value_recently_updated - personal = params[:personal] - archived = params[:archived] +- namespace_id = params[:namespace_id] .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} - %span.light - = projects_sort_options_hash[@sort] - %b.caret + - toggle_text = projects_sort_options_hash[@sort] + = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable %li.dropdown-header Sort by - projects_sort_options_hash.each do |value, title| %li - = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do = title %li.divider %li - = link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do Hide archived projects %li - = link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do Show archived projects - if current_user %li.divider %li - = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: nil), class: ("is-active" unless personal.present?) do Owned by anyone %li - = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: true), class: ("is-active" if personal.present?) do Owned by me diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index d1e861ca80c..2585ed9360b 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -6,7 +6,7 @@ %h4.prepend-top-0 = page_title %p - #{link_to "Webhooks", help_page_path("web_hooks", "web_hooks")} can be + #{link_to "Webhooks", help_page_path("web_hooks/web_hooks")} can be used for binding events when something is happening within the project. .col-lg-9.append-bottom-default = form_for hook, as: :hook, url: polymorphic_path(url_components + [:hooks]) do |f| diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index a7769654b61..160c6cd84da 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,27 +1,28 @@ .hidden-xs - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do - = icon('plus') - New Snippet + - if current_user + = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do + New Snippet - if can?(current_user, :update_personal_snippet, @snippet) = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do Edit - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", 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_snippet_path, title: "New Snippet" do - New Snippet - - if can?(current_user, :update_personal_snippet, @snippet) +- if current_user + .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 edit_snippet_path(@snippet) do - Edit - - if can?(current_user, :admin_personal_snippet, @snippet) - %li - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do - Delete + = link_to new_snippet_path, title: "New Snippet" do + New Snippet + - if can?(current_user, :update_personal_snippet, @snippet) + %li + = link_to edit_snippet_path(@snippet) do + Edit + - if can?(current_user, :admin_personal_snippet, @snippet) + %li + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do + Delete diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 68665858c3e..db2b4885861 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -29,6 +29,11 @@ = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do = icon('rss') + - if current_user.admin? + + = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', + data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('users') .avatar-holder = link_to avatar_icon(@user, 400), target: '_blank' do diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 971f969e25e..8551288e2f2 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -28,18 +28,30 @@ class EmailsOnPushWorker :push end + merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha) + diff_refs = nil compare = nil reverse_compare = false if action == :push compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) - diff_refs = [project.merge_base_commit(before_sha, after_sha), project.commit(after_sha)] + + diff_refs = Gitlab::Diff::DiffRefs.new( + base_sha: merge_base_sha, + start_sha: before_sha, + head_sha: after_sha + ) return false if compare.same if compare.commits.empty? compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) - diff_refs = [project.merge_base_commit(after_sha, before_sha), project.commit(before_sha)] + + diff_refs = Gitlab::Diff::DiffRefs.new( + base_sha: merge_base_sha, + start_sha: after_sha, + head_sha: before_sha + ) reverse_compare = true diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb new file mode 100644 index 00000000000..a6cefd4d601 --- /dev/null +++ b/app/workers/git_garbage_collect_worker.rb @@ -0,0 +1,16 @@ +class GitGarbageCollectWorker + include Sidekiq::Worker + include Gitlab::ShellAdapter + + sidekiq_options queue: :gitlab_shell, retry: false + + def perform(project_id) + project = Project.find(project_id) + + gitlab_shell.gc(project.repository_storage_path, project.path_with_namespace) + # Refresh the branch cache in case garbage collection caused a ref lookup to fail + project.repository.after_create_branch + project.repository.branch_names + project.repository.has_visible_content? + end +end diff --git a/app/workers/gitlab_shell_one_shot_worker.rb b/app/workers/gitlab_shell_one_shot_worker.rb deleted file mode 100644 index 4ddbcf574d5..00000000000 --- a/app/workers/gitlab_shell_one_shot_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class GitlabShellOneShotWorker - include Sidekiq::Worker - include Gitlab::ShellAdapter - - sidekiq_options queue: :gitlab_shell, retry: false - - def perform(action, *arg) - gitlab_shell.send(action, *arg) - end -end diff --git a/config/application.rb b/config/application.rb index 2b0595ede2b..5f7b6a3c049 100644 --- a/config/application.rb +++ b/config/application.rb @@ -84,8 +84,10 @@ module Gitlab config.assets.precompile << "graphs/application.js" config.assets.precompile << "users/application.js" config.assets.precompile << "network/application.js" + config.assets.precompile << "profile/application.js" config.assets.precompile << "lib/utils/*.js" config.assets.precompile << "lib/*.js" + config.assets.precompile << "u2f.js" # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' diff --git a/config/gitlab.teatro.yml b/config/gitlab.teatro.yml deleted file mode 100644 index 75b79b837e0..00000000000 --- a/config/gitlab.teatro.yml +++ /dev/null @@ -1,87 +0,0 @@ - -production: &base - gitlab: - host: localhost - port: 80 - https: false - - user: root - - email_from: example@example.com - - support_email: support@example.com - - default_projects_features: - issues: true - merge_requests: true - wiki: true - snippets: false - visibility_level: "private" # can be "private" | "internal" | "public" - - issues_tracker: - - gravatar: - enabled: true # Use user avatar image from Gravatar.com (default: true) - - ldap: - enabled: false - host: '_your_ldap_server' - port: 636 - uid: 'sAMAccountName' - method: 'ssl' # "tls" or "ssl" or "plain" - bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' - password: '_the_password_of_the_bind_user' - allow_username_or_email_login: true - - base: '' - - user_filter: '' - - omniauth: - enabled: false - - satellites: - # Relative paths are relative to Rails.root (default: tmp/repo_satellites/) - path: /apps/gitlab-satellites/ - - backup: - path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/) - - repositories: - storages: # REPO PATHS MUST NOT BE A SYMLINK!!! - default: /apps/repositories/ - - gitlab_shell: - path: /apps/gitlab-shell/ - - hooks_path: /apps/gitlab-shell/hooks/ - - upload_pack: true - receive_pack: true - - git: - bin_path: /usr/bin/git - max_size: 5242880 # 5.megabytes - timeout: 10 - - extra: - -development: - <<: *base - -test: - <<: *base - gravatar: - enabled: true - gitlab: - host: localhost - port: 80 - issues_tracker: - redmine: - title: "Redmine" - project_url: "http://redmine/projects/:issues_tracker_id" - issues_url: "http://redmine/:project_id/:issues_tracker_id/:id" - new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new" - -staging: - <<: *base diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb index 6796407d4e6..4c91a61fb4a 100644 --- a/config/initializers/health_check.rb +++ b/config/initializers/health_check.rb @@ -1,16 +1,3 @@ -# Email forcibly included in the standard checks, but the email health check -# doesn't support the full range of SMTP options, which can result in failures -# for valid SMTP configurations. -# Overwrite the HealthCheck's detection of whether email is configured -# in order to avoid the email check during standard checks -module HealthCheck - class Utils - def self.mailer_configured? - false - end - end -end - HealthCheck.setup do |config| config.standard_checks = ['database', 'migrations', 'cache'] config.full_checks = ['database', 'migrations', 'cache'] diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 44601f2b2bd..c4266ab8ba5 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -135,6 +135,8 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(Rouge::Plugins::Redcarpet) config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab) + + config.instrument_methods(Rinku) end GC::Profiler.enable diff --git a/config/routes.rb b/config/routes.rb index 1572656b8c5..3160fd767b8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,8 +89,9 @@ Rails.application.routes.draw do mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put] # Help + get 'help' => 'help#index' - get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ } + get 'help/*path' => 'help#show', as: :help_page get 'help/shortcuts' get 'help/ui' => 'help#ui' @@ -615,10 +616,18 @@ Rails.application.routes.draw do post :retry_builds post :revert post :cherry_pick + get :diff_for_path end end - resources :compare, only: [:index, :create] + resources :compare, only: [:index, :create] do + collection do + get :diff_for_path + end + end + + get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } + resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do @@ -629,9 +638,6 @@ Rails.application.routes.draw do end end - get '/compare/:from...:to' => 'compare#show', :as => 'compare', - :constraints => { from: /.+/, to: /.+/ } - resources :snippets, constraints: { id: /\d+/ } do member do get 'raw' @@ -706,12 +712,14 @@ Rails.application.routes.draw do post :toggle_subscription post :toggle_award_emoji post :remove_wip + get :diff_for_path end collection do get :branch_from get :branch_to get :update_branches + get :diff_for_path end end @@ -720,7 +728,7 @@ Rails.application.routes.draw do resource :release, only: [:edit, :update] end - resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :variables, only: [:index, :show, :update, :create, :destroy] resources :triggers, only: [:index, :create, :destroy] diff --git a/db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb b/db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb new file mode 100644 index 00000000000..1c4d60e7234 --- /dev/null +++ b/db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb @@ -0,0 +1,5 @@ +class AddHeadCommitIdToMergeRequestDiffs < ActiveRecord::Migration + def change + add_column :merge_request_diffs, :head_commit_sha, :string + end +end diff --git a/db/migrate/20160508215920_add_positions_to_diff_notes.rb b/db/migrate/20160508215920_add_positions_to_diff_notes.rb new file mode 100644 index 00000000000..2952c25004e --- /dev/null +++ b/db/migrate/20160508215920_add_positions_to_diff_notes.rb @@ -0,0 +1,6 @@ +class AddPositionsToDiffNotes < ActiveRecord::Migration + def change + add_column :notes, :position, :text + add_column :notes, :original_position, :text + end +end diff --git a/db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb b/db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb new file mode 100644 index 00000000000..b7fd76ee84b --- /dev/null +++ b/db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb @@ -0,0 +1,5 @@ +class AddStartCommitIdToMergeRequestDiffs < ActiveRecord::Migration + def change + add_column :merge_request_diffs, :start_commit_sha, :string + end +end diff --git a/db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb b/db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb new file mode 100644 index 00000000000..4eef16c9408 --- /dev/null +++ b/db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb @@ -0,0 +1,22 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddNoteTypeAndPositionToSentNotification < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :sent_notifications, :note_type, :string + add_column :sent_notifications, :position, :text + end +end diff --git a/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb b/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb new file mode 100644 index 00000000000..34c702e3fa6 --- /dev/null +++ b/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb @@ -0,0 +1,13 @@ +class AddUserDefaultExternalToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def up + add_column_with_default(:application_settings, :user_default_external, :boolean, + default: false, allow_null: false) + end + + def down + remove_column(:application_settings, :user_default_external) + end +end diff --git a/db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb b/db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb new file mode 100644 index 00000000000..013904b3f4f --- /dev/null +++ b/db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb @@ -0,0 +1,11 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. +# rubocop:disable all + +class AddEnabledGitAccessProtocolsToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + add_column :application_settings, :enabled_git_access_protocol, :string + end +end diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb index 00a45d7fe73..180a75e0998 100644 --- a/db/migrate/20160616102642_remove_duplicated_keys.rb +++ b/db/migrate/20160616102642_remove_duplicated_keys.rb @@ -4,12 +4,12 @@ class RemoveDuplicatedKeys < ActiveRecord::Migration select_all("SELECT fingerprint FROM #{quote_table_name(:keys)} GROUP BY fingerprint HAVING COUNT(*) > 1").each do |row| fingerprint = connection.quote(row['fingerprint']) execute(%Q{ - DELETE FROM keys + DELETE FROM #{quote_table_name(:keys)} WHERE fingerprint = #{fingerprint} AND id != ( SELECT id FROM ( SELECT max(id) AS id - FROM keys + FROM #{quote_table_name(:keys)} WHERE fingerprint = #{fingerprint} ) max_ids ) diff --git a/db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb b/db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb new file mode 100644 index 00000000000..0c25f87dfb4 --- /dev/null +++ b/db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb @@ -0,0 +1,11 @@ +# rubocop:disable all +# Migration type: online without errors + +class AddIndexOnAwardEmojiUserAndName < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def change + add_concurrent_index(:award_emoji, [:user_id, :name]) + end +end diff --git a/db/migrate/20160705163108_remove_requesters_that_are_owners.rb b/db/migrate/20160705163108_remove_requesters_that_are_owners.rb new file mode 100644 index 00000000000..1fca230c019 --- /dev/null +++ b/db/migrate/20160705163108_remove_requesters_that_are_owners.rb @@ -0,0 +1,40 @@ +class RemoveRequestersThatAreOwners < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def up + # Delete requesters that are owner of their projects and actually requested + # access to it + execute <<-SQL + DELETE FROM members + WHERE members.source_type = 'Project' + AND members.type = 'ProjectMember' + AND members.requested_at IS NOT NULL + AND members.user_id = ( + SELECT namespaces.owner_id + FROM namespaces + JOIN projects ON namespaces.id = projects.namespace_id + WHERE namespaces.type IS NULL + AND projects.id = members.source_id + AND namespaces.owner_id = members.user_id); + SQL + + # Delete requesters that are owner of their project's group and actually requested + # access to it + execute <<-SQL + DELETE FROM members + WHERE members.source_type = 'Project' + AND members.type = 'ProjectMember' + AND members.requested_at IS NOT NULL + AND members.user_id = ( + SELECT namespaces.owner_id + FROM namespaces + JOIN projects ON namespaces.id = projects.namespace_id + WHERE namespaces.type = 'Group' + AND projects.id = members.source_id + AND namespaces.owner_id = members.user_id); + SQL + end + + def down + end +end diff --git a/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb b/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb new file mode 100644 index 00000000000..668c22bb51c --- /dev/null +++ b/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb @@ -0,0 +1,21 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveAwardEmojisWithNoUser < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def up + AwardEmoji.joins('LEFT JOIN users ON users.id = user_id').where('users.id IS NULL').destroy_all + end +end diff --git a/db/schema.rb b/db/schema.rb index 0f5f9f243fa..8c12898eec9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160628085157) do +ActiveRecord::Schema.define(version: 20160712171823) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -84,8 +84,10 @@ ActiveRecord::Schema.define(version: 20160628085157) do t.string "health_check_access_token" t.boolean "send_user_confirmation_email", default: false t.integer "container_registry_token_expire_delay", default: 5 + t.boolean "user_default_external", default: false, null: false t.text "after_sign_up_text" t.string "repository_storage", default: "default" + t.string "enabled_git_access_protocol" end create_table "audit_events", force: :cascade do |t| @@ -112,6 +114,7 @@ ActiveRecord::Schema.define(version: 20160628085157) do end add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree + add_index "award_emoji", ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree create_table "broadcast_messages", force: :cascade do |t| @@ -591,6 +594,8 @@ ActiveRecord::Schema.define(version: 20160628085157) do t.datetime "updated_at" t.string "base_commit_sha" t.string "real_size" + t.string "head_commit_sha" + t.string "start_commit_sha" end add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree @@ -687,10 +692,12 @@ ActiveRecord::Schema.define(version: 20160628085157) do t.string "line_code" t.string "commit_id" t.integer "noteable_id" - t.boolean "system", default: false, null: false + t.boolean "system", default: false, null: false t.text "st_diff" t.integer "updated_by_id" t.string "type" + t.text "position" + t.text "original_position" end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -879,6 +886,8 @@ ActiveRecord::Schema.define(version: 20160628085157) do t.string "commit_id" t.string "reply_key", null: false t.string "line_code" + t.string "note_type" + t.text "position" end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree diff --git a/doc/README.md b/doc/README.md index b98d6812a81..cc0b6e0c1e5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -11,7 +11,7 @@ - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab. -- [Permissions](permissions/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do. +- [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do. - [Profile Settings](profile/README.md) - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. @@ -21,9 +21,10 @@ ## Administrator documentation +- [Access restrictions](administration/access_restrictions.md) Define which Git access protocols can be used to talk to GitLab - [Authentication/Authorization](administration/auth/README.md) Configure external authentication with LDAP, SAML, CAS and additional Omniauth providers. -- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough. +- [Custom Git hooks](administration/custom_hooks.md) Custom Git hooks (on the filesystem) for when webhooks aren't enough. - [Install](install/README.md) Requirements, directory structures and installation from source. - [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter. diff --git a/doc/administration/access_restrictions.md b/doc/administration/access_restrictions.md new file mode 100644 index 00000000000..51d7996effd --- /dev/null +++ b/doc/administration/access_restrictions.md @@ -0,0 +1,38 @@ +# Access Restrictions + +> **Note:** This feature is only available on versions 8.10 and above. + +With GitLab's Access restrictions you can choose which Git access protocols you +want your users to use to communicate with GitLab. This feature can be enabled +via the `Application Settings` in the Admin interface. + +The setting is called `Enabled Git access protocols`, and it gives you the option +to choose between: + +- Both SSH and HTTP(S) +- Only SSH +- Only HTTP(s) + +![Settings Overview](img/access_restrictions.png) + +## Enabled Protocol + +When both SSH and HTTP(S) are enabled, GitLab will behave as usual, it will give +your users the option to choose which protocol they would like to use. + +When you choose to allow only one of the protocols, a couple of things will happen: + +- The project page will only show the allowed protocol's URL, with no option to + change it. +- A tooltip will be shown when you hover over the URL's protocol, if an action + on the user's part is required, e.g. adding an SSH key, or setting a password. + +![Project URL with SSH only access](img/restricted_url.png) + +On top of these UI restrictions, GitLab will deny all Git actions on the protocol +not selected. + +> **Note:** Please keep in mind that disabling an access protocol does not actually + block access to the server itself. The ports used for the protocol, be it SSH or + HTTP, will still be accessible. What GitLab does is restrict access on the + application level.
\ No newline at end of file diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 10096779844..7186f707ad6 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -130,27 +130,27 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server first_name: 'givenName' last_name: 'sn' - ## EE only - - # Base where we can search for groups - # - # Ex. ou=groups,dc=gitlab,dc=example - # - group_base: '' - - # The CN of a group containing GitLab administrators - # - # Ex. administrators - # - # Note: Not `cn=administrators` or the full DN - # - admin_group: '' - - # The LDAP attribute containing a user's public SSH key - # - # Ex. ssh_public_key - # - sync_ssh_keys: false + ## EE only + + # Base where we can search for groups + # + # Ex. ou=groups,dc=gitlab,dc=example + # + group_base: '' + + # The CN of a group containing GitLab administrators + # + # Ex. administrators + # + # Note: Not `cn=administrators` or the full DN + # + admin_group: '' + + # The LDAP attribute containing a user's public SSH key + # + # Ex. ssh_public_key + # + sync_ssh_keys: false # GitLab EE only: add more LDAP servers # Choose an ID made of a-z and 0-9 . This ID will be stored in the database diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md new file mode 100644 index 00000000000..e3306c22d3f --- /dev/null +++ b/doc/administration/custom_hooks.md @@ -0,0 +1,57 @@ +# Custom Git Hooks + +> +**Note:** Custom Git hooks must be configured on the filesystem of the GitLab +server. Only GitLab server administrators will be able to complete these tasks. +Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not +have filesystem access. For a user configurable Git hook interface, please see +[GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html). + +Git natively supports hooks that are executed on different actions. +Examples of server-side git hooks include pre-receive, post-receive, and update. +See [Git SCM Server-Side Hooks][hooks] for more information about each hook type. + +As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab +administrators can add custom git hooks to any GitLab project. + +## Setup + +Normally, Git hooks are placed in the repository or project's `hooks` directory. +GitLab creates a symlink from each project's `hooks` directory to the +gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell +upgrades. As such, custom hooks are implemented a little differently. Behavior +is exactly the same once the hook is created, though. + +Follow the steps below to set up a custom hook: + +1. Pick a project that needs a custom Git hook. +1. On the GitLab server, navigate to the project's repository directory. + For an installation from source the path is usually + `/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is + usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`. +1. Create a new directory in this location called `custom_hooks`. +1. Inside the new `custom_hooks` directory, create a file with a name matching + the hook type. For a pre-receive hook the file name should be `pre-receive` + with no extension. +1. Make the hook file executable and make sure it's owned by git. +1. Write the code to make the Git hook function as expected. Hooks can be + in any language. Ensure the 'shebang' at the top properly reflects the language + type. For example, if the script is in Ruby the shebang will probably be + `#!/usr/bin/env ruby`. + +That's it! Assuming the hook code is properly implemented the hook will fire +as appropriate. + +## Custom error messages + +>**Note:** +This feature was [introduced][5073] in GitLab 8.10. + +If the commit is declined or an error occurs during the Git hook check, +the STDERR or STDOUT message of the hook will be present in GitLab's UI. +STDERR takes precedence over STDOUT. + +![Custom message from custom Git hook](img/custom_hooks_error_msg.png) + +[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks +[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073 diff --git a/doc/administration/img/access_restrictions.png b/doc/administration/img/access_restrictions.png Binary files differnew file mode 100644 index 00000000000..66fd9491e85 --- /dev/null +++ b/doc/administration/img/access_restrictions.png diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png Binary files differnew file mode 100644 index 00000000000..92e87e15fb3 --- /dev/null +++ b/doc/administration/img/custom_hooks_error_msg.png diff --git a/doc/administration/img/restricted_url.png b/doc/administration/img/restricted_url.png Binary files differnew file mode 100644 index 00000000000..0a677433dcf --- /dev/null +++ b/doc/administration/img/restricted_url.png diff --git a/doc/api/groups.md b/doc/api/groups.md index 1ccb9715e96..87480bebfc4 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -42,46 +42,49 @@ Parameters: ```json
[
{
- "id": 4,
- "description": null,
+ "id": 9,
+ "description": "foo",
"default_branch": "master",
+ "tag_list": [],
"public": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
- "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
- "web_url": "http://example.com/diaspora/diaspora-client",
- "tag_list": [
- "example",
- "disapora client"
- ],
- "owner": {
- "id": 3,
- "name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
- },
- "name": "Diaspora Client",
- "name_with_namespace": "Diaspora / Diaspora Client",
- "path": "diaspora-client",
- "path_with_namespace": "diaspora/diaspora-client",
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
+ "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "Experimental / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
"issues_enabled": true,
"merge_requests_enabled": true,
- "builds_enabled": true,
"wiki_enabled": true,
- "snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
- "creator_id": 3,
+ "builds_enabled": true,
+ "snippets_enabled": true,
+ "created_at": "2016-04-05T21:40:50.169Z",
+ "last_activity_at": "2016-04-06T16:52:08.432Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
- "description": "",
- "id": 3,
- "name": "Diaspora",
- "owner_id": 1,
- "path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "id": 5,
+ "name": "Experimental",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-04-05T21:40:49.152Z",
+ "updated_at": "2016-04-07T08:07:48.466Z",
+ "description": "foo",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 10
},
- "archived": false,
- "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
+ "avatar_url": null,
+ "star_count": 1,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
}
]
```
@@ -96,7 +99,180 @@ GET /groups/:id Parameters:
-- `id` (required) - The ID or path of a group
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or path of a group |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4
+```
+
+Example response:
+
+```json
+{
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "visibility_level": 20,
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/twitter",
+ "projects": [
+ {
+ "id": 7,
+ "description": "Voluptas veniam qui et beatae voluptas doloremque explicabo facilis.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/typeahead-js.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/typeahead-js.git",
+ "web_url": "https://gitlab.example.com/twitter/typeahead-js",
+ "name": "Typeahead.Js",
+ "name_with_namespace": "Twitter / Typeahead.Js",
+ "path": "typeahead-js",
+ "path_with_namespace": "twitter/typeahead-js",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:25.578Z",
+ "last_activity_at": "2016-06-17T07:47:25.881Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
+ },
+ {
+ "id": 6,
+ "description": "Aspernatur omnis repudiandae qui voluptatibus eaque.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/flight.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/flight.git",
+ "web_url": "https://gitlab.example.com/twitter/flight",
+ "name": "Flight",
+ "name_with_namespace": "Twitter / Flight",
+ "path": "flight",
+ "path_with_namespace": "twitter/flight",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:24.661Z",
+ "last_activity_at": "2016-06-17T07:47:24.838Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 8,
+ "public_builds": true,
+ "shared_with_groups": []
+ }
+ ],
+ "shared_projects": [
+ {
+ "id": 8,
+ "description": "Velit eveniet provident fugiat saepe eligendi autem.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@gitlab.example.com:h5bp/html5-boilerplate.git",
+ "http_url_to_repo": "https://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "https://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "H5bp / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:27.089Z",
+ "last_activity_at": "2016-06-17T07:47:27.310Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "H5bp",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:26.621Z",
+ "updated_at": "2016-06-17T07:47:26.621Z",
+ "description": "Id consequatur rem vel qui doloremque saepe.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 4,
+ "public_builds": true,
+ "shared_with_groups": [
+ {
+ "group_id": 4,
+ "group_name": "Twitter",
+ "group_access_level": 30
+ },
+ {
+ "group_id": 3,
+ "group_name": "Gitlab Org",
+ "group_access_level": 10
+ }
+ ]
+ }
+ ]
+}
+```
## New group
@@ -201,7 +377,8 @@ Example response: "star_count": 1,
"forks_count": 0,
"open_issues_count": 3,
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
}
]
}
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index aee94b3fc36..a8c3b068d22 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -49,10 +49,10 @@ Parameters: "state": "active", "created_at": "2012-04-29T08:46:00Z" }, - "source_project_id": "2", - "target_project_id": "3", + "source_project_id": 2, + "target_project_id": 3, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -68,7 +68,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : false, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ] ``` @@ -113,10 +115,10 @@ Parameters: "state": "active", "created_at": "2012-04-29T08:46:00Z" }, - "source_project_id": "2", - "target_project_id": "3", + "source_project_id": 2, + "target_project_id": 3, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -132,7 +134,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -233,6 +237,8 @@ Parameters: "merge_status": "can_be_merged", "subscribed" : true, "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false, "changes": [ { "old_path": "VERSION", @@ -296,7 +302,7 @@ Parameters: "source_project_id": 4, "target_project_id": 4, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -312,7 +318,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 0 + "user_notes_count": 0, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -383,7 +391,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -465,7 +475,7 @@ Parameters: "source_project_id": 4, "target_project_id": 4, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -481,7 +491,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -531,7 +543,7 @@ Parameters: "source_project_id": 4, "target_project_id": 4, "labels": [ ], - "description":"fixed login page css paddings", + "description": "fixed login page css paddings", "work_in_progress": false, "milestone": { "id": 5, @@ -547,7 +559,9 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, - "user_notes_count": 1 + "user_notes_count": 1, + "should_remove_source_branch": true, + "force_remove_source_branch": false } ``` @@ -866,7 +880,9 @@ Example response: "merge_when_build_succeeds": false, "merge_status": "unchecked", "subscribed": true, - "user_notes_count": 7 + "user_notes_count": 7, + "should_remove_source_branch": true, + "force_remove_source_branch": false }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", diff --git a/doc/api/projects.md b/doc/api/projects.md index f5f195b97df..dceee7b4ea7 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -52,7 +52,7 @@ Parameters: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Client", "name_with_namespace": "Diaspora / Diaspora Client", @@ -64,17 +64,18 @@ Parameters: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "archived": false, "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png", @@ -82,7 +83,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] }, { "id": 6, @@ -112,6 +114,7 @@ Parameters: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, + "container_registry_enabled": false, "created_at": "2013-09-30T13:46:02Z", "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, @@ -140,7 +143,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] } ] ``` @@ -223,7 +227,7 @@ Parameters: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Project Site", "name_with_namespace": "Diaspora / Diaspora Project Site", @@ -235,17 +239,18 @@ Parameters: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "permissions": { "project_access": { @@ -262,7 +267,20 @@ Parameters: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [ + { + "group_id": 4, + "group_name": "Twitter", + "group_access_level": 30 + }, + { + "group_id": 3, + "group_name": "Gitlab Org", + "group_access_level": 10 + } + ] } ``` @@ -425,6 +443,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -449,6 +468,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -475,6 +495,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `public_builds` (optional) @@ -537,23 +558,26 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "archived": true, "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", "shared_runners_enabled": true, "forks_count": 0, - "star_count": 1 + "star_count": 1, + "public_builds": true, + "shared_with_groups": [] } ``` @@ -600,23 +624,26 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "archived": true, "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png", "shared_runners_enabled": true, "forks_count": 0, - "star_count": 0 + "star_count": 0, + "public_builds": true, + "shared_with_groups": [] } ``` @@ -660,7 +687,7 @@ Example response: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Project Site", "name_with_namespace": "Diaspora / Diaspora Project Site", @@ -672,17 +699,18 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "permissions": { "project_access": { @@ -699,7 +727,9 @@ Example response: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [] } ``` @@ -713,7 +743,7 @@ have the proper access rights, code 403 is returned. Status 404 is returned if t doesn't exist, or is hidden to the user. ``` -POST /projects/:id/archive +POST /projects/:id/unarchive ``` | Attribute | Type | Required | Description | @@ -743,7 +773,7 @@ Example response: "owner": { "id": 3, "name": "Diaspora", - "created_at": "2013-09-30T13: 46: 02Z" + "created_at": "2013-09-30T13:46:02Z" }, "name": "Diaspora Project Site", "name_with_namespace": "Diaspora / Diaspora Project Site", @@ -755,17 +785,18 @@ Example response: "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "created_at": "2013-09-30T13: 46: 02Z", - "last_activity_at": "2013-09-30T13: 46: 02Z", + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", "creator_id": 3, "namespace": { - "created_at": "2013-09-30T13: 46: 02Z", + "created_at": "2013-09-30T13:46:02Z", "description": "", "id": 3, "name": "Diaspora", "owner_id": 1, "path": "diaspora", - "updated_at": "2013-09-30T13: 46: 02Z" + "updated_at": "2013-09-30T13:46:02Z" }, "permissions": { "project_access": { @@ -782,7 +813,9 @@ Example response: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [] } ``` @@ -965,11 +998,11 @@ Parameters: "id": 1, "url": "http://example.com/hook", "project_id": 3, - "push_events": "true", - "issues_events": "true", - "merge_requests_events": "true", - "note_events": "true", - "enable_ssl_verification": "true", + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "note_events": true, + "enable_ssl_verification": true, "created_at": "2012-10-12T17:04:47Z" } ``` @@ -1089,8 +1122,8 @@ Parameters: "name": "Jeremy Ashkenas", "email": "jashkenas@example.com" }, - "authored_date": "2013-09-07T12: 58: 21+00: 00", - "committed_date": "2013-09-07T12: 58: 21+00: 00" + "authored_date": "2013-09-07T12:58:21+00:00", + "committed_date": "2013-09-07T12:58:21+00:00" }, "protected": false } diff --git a/doc/api/settings.md b/doc/api/settings.md index 741c5a29581..d9b68eaeadf 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -68,6 +68,7 @@ PUT /application/settings | `after_sign_out_path` | string | no | Where to redirect users after logout | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes | | `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml | +| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. ```bash curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1 diff --git a/doc/api/todos.md b/doc/api/todos.md index 29e73664410..23f6e35f2a4 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -15,7 +15,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, or `marked`. | +| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, or `approval_required`. | | `author_id` | integer | no | The ID of an author | | `project_id` | integer | no | The ID of a project | | `state` | string | no | The state of the todo. Can be either `pending` or `done` | diff --git a/doc/ci/README.md b/doc/ci/README.md index a9d407528e8..0833027f91d 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -15,6 +15,6 @@ - [Use SSH keys in your build environment](ssh_keys/README.md) - [Trigger builds through the API](triggers/README.md) - [Build artifacts](build_artifacts/README.md) -- [User permissions](permissions/README.md) +- [User permissions](../user/permissions.md#gitlab-ci) - [API](../api/ci/README.md) - [CI services (linked docker containers)](services/README.md) diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index 17e1c64bb8a..bfafcc44d66 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -49,7 +49,7 @@ apt-get update -yqq apt-get install git -yqq # Install phpunit, the tool that we will use for testing -curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar +curl -Lo /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar chmod +x /usr/local/bin/phpunit # Install mysql driver diff --git a/doc/ci/permissions/README.md b/doc/ci/permissions/README.md index d77061c14cd..42eb59f84c8 100644 --- a/doc/ci/permissions/README.md +++ b/doc/ci/permissions/README.md @@ -1,24 +1,3 @@ # Users Permissions -GitLab CI relies on user's role on the GitLab. There are three permissions levels on GitLab CI: admin, master, developer, other. - -Admin user can perform any actions on GitLab CI in scope of instance and project. Also user with admin permission can use admin interface. - - - - -| Action | Guest, Reporter | Developer | Master | Admin | -|---------------------------------------|-----------------|-------------|----------|--------| -| See commits and builds | ✓ | ✓ | ✓ | ✓ | -| Retry or cancel build | | ✓ | ✓ | ✓ | -| Remove project | | | ✓ | ✓ | -| Create project | | | ✓ | ✓ | -| Change project configuration | | | ✓ | ✓ | -| Add specific runners | | | ✓ | ✓ | -| Add shared runners | | | | ✓ | -| See events in the system | | | | ✓ | -| Admin interface | | | | ✓ | - - - - +This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-ci). diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index d2d1b04f893..50fa263f693 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -133,7 +133,7 @@ builds, including deploy builds. This can be an array or a multi-line string. ### after_script >**Note:** -Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 (not yet released) +Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 `after_script` is used to define the command that will be run after for all builds. This has to be an array or a multi-line string. @@ -811,7 +811,7 @@ deploy: It's possible to overwrite globally defined `before_script` and `after_script`: ```yaml -before_script +before_script: - global before script job: @@ -985,11 +985,11 @@ directive defined in `.postgres_services` and `.mysql_services` respectively: - ruby test:postgres: - << *job_definition + <<: *job_definition services: *postgres_definition test:mysql: - << *job_definition + <<: *job_definition services: *mysql_definition ``` diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 975bb82c37d..fac35ec964d 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -44,7 +44,7 @@ it organized and easy to find. - When introducing a new document, be careful for the headings to be grammatically and syntactically correct. It is advised to mention one or all of the following GitLab members for a review: `@axil`, `@rspeicher`, - `@dblessing`, `@ashleys`, `@nearlythere`. This is to ensure that no document + `@dblessing`, `@ashleys`. This is to ensure that no document with wrong heading is going live without an audit, thus preventing dead links and redirection issues when corrected - Leave exactly one newline after a heading diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md index 5893b7c219e..65252288019 100644 --- a/doc/development/ui_guide.md +++ b/doc/development/ui_guide.md @@ -1,43 +1,45 @@ -# UI Guide for building GitLab +# UI Guide for building GitLab ## GitLab UI development kit We created a page inside GitLab where you can check commonly used html and css elements. -When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples +When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples you can use during GitLab development. ## Design repository -All design files are stored in the [gitlab-design](https://gitlab.com/gitlab-org/gitlab-design) -repository and maintained by GitLab UX designers. +All design files are stored in the [gitlab-design](https://gitlab.com/gitlab-org/gitlab-design) +repository and maintained by GitLab UX designers. ## Navigation -GitLab's layout contains 2 sections: the left sidebar and the content. The left sidebar contains a static navigation menu. -This menu will be visible regardless of what page you visit. The left sidebar also contains the GitLab logo -and the current user's profile picture. The content section contains a header and the content itself. -The header describes the current GitLab page and what navigation is -available to user in this area. Depending on the area (project, group, profile setting) the header name and navigation may change. For example when user visits one of the +GitLab's layout contains 2 sections: the left sidebar and the content. The left sidebar contains a static navigation menu. +This menu will be visible regardless of what page you visit. The left sidebar also contains the GitLab logo +and the current user's profile picture. The content section contains a header and the content itself. +The header describes the current GitLab page and what navigation is +available to user in this area. Depending on the area (project, group, profile setting) the header name and navigation may change. For example when user visits one of the project pages the header will contain a project name and navigation for that project. When the user visits a group page it will contain a group name and navigation related to this group. ### Adding new tab to header navigation -We try to keep the amount of tabs in the header navigation between 5 and 10 so that it fits on a typical laptop screen. We also try not to confuse the user with too many options. Ideally each -tab should represent separate functionality. Everything related to the issue -tracker should be under the 'Issues' tab while everything related to the wiki should +We try to keep the amount of tabs in the header navigation between 5 and 10 so that it fits on a typical laptop screen. We also try not to confuse the user with too many options. Ideally each +tab should represent separate functionality. Everything related to the issue +tracker should be under the 'Issues' tab while everything related to the wiki should be under 'Wiki' tab and so on and so forth. +When adding a new tab to the header don't use more than 2 words for text in the link. +We want to keep links short and easy to remember and fit all of them in the small screen. -## Mobile screen size +## Mobile screen size -We want GitLab to work well on small mobile screens as well. Size limitations make it is impossible to fit everything on a mobile screen. In this case it is OK to hide -part of the UI for smaller resolutions in favor of a better user experience. +We want GitLab to work well on small mobile screens as well. Size limitations make it is impossible to fit everything on a mobile screen. In this case it is OK to hide +part of the UI for smaller resolutions in favor of a better user experience. However core functionality like browsing files, creating issues, writing comments, should be available on all resolutions. ## Icons -* `trash` icon for button or link that does destructive action like removing +* `trash` icon for button or link that does destructive action like removing information from database or file system * `x` icon for closing/hiding UI element. For example close modal window * `pencil` icon for edit button or link @@ -50,7 +52,14 @@ information from database or file system * Button should contain icon or text. Exceptions should be approved by UX designer. * Use red button for destructive actions (not revertable). For example removing issue. -* Use green or blue button for primary action. Primary button should be only one. -Do not use both green and blue button in one form. -* For all other cases use default white button +* Use green or blue button for primary action. Primary button should be only one. +Do not use both green and blue button in one form. +* For all other cases use default white button. +* Text button should have only first word capitalized. So should be "Create issue" instead of "Create Issue" +## Counts + +* Always use the [`number_with_delimiter`][number_with_delimiter] helper to + display counts in the UI. + +[number_with_delimiter]: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_delimiter diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index 820934f97f1..1d5e5dd6e15 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -1,41 +1,3 @@ # Custom Git Hooks -**Note: Custom git hooks must be configured on the filesystem of the GitLab -server. Only GitLab server administrators will be able to complete these tasks. -Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html).** - -Git natively supports hooks that are executed on different actions. -Examples of server-side git hooks include pre-receive, post-receive, and update. -See -[Git SCM Server-Side Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) -for more information about each hook type. - -As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab -administrators can add custom git hooks to any GitLab project. - -## Setup - -Normally, git hooks are placed in the repository or project's `hooks` directory. -GitLab creates a symlink from each project's `hooks` directory to the -gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell -upgrades. As such, custom hooks are implemented a little differently. Behavior -is exactly the same once the hook is created, though. Follow these steps to -set up a custom hook. - -1. Pick a project that needs a custom git hook. -1. On the GitLab server, navigate to the project's repository directory. -For an installation from source the path is usually -`/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is -usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`. -1. Create a new directory in this location called `custom_hooks`. -1. Inside the new `custom_hooks` directory, create a file with a name matching -the hook type. For a pre-receive hook the file name should be `pre-receive` with -no extension. -1. Make the hook file executable and make sure it's owned by git. -1. Write the code to make the git hook function as expected. Hooks can be -in any language. Ensure the 'shebang' at the top properly reflects the language -type. For example, if the script is in Ruby the shebang will probably be -`#!/usr/bin/env ruby`. - -That's it! Assuming the hook code is properly implemented the hook will fire -as appropriate. +This document was moved to [administration/custom_hooks.md](../administration/custom_hooks.md). diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index e51ff5a5de2..e8093f0b257 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -36,7 +36,7 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; # Grant the GitLab user necessary permissions on the database - mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; # Quit the database session mysql> \q diff --git a/doc/install/installation.md b/doc/install/installation.md index dc8d9c65535..19d083d580d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -269,9 +269,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-9-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-10-stable gitlab -**Note:** You can change `8-9-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It @@ -398,7 +398,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.5 + sudo -u git -H git checkout v0.7.7 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/integration/github.md b/doc/integration/github.md index e7497e475c9..340c8a55fb3 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -19,7 +19,7 @@ GitHub will generate an application ID and secret key for you to use. - Application name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive. - Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com' - Application description: Fill this in if you wish. - - Default authorization callback URL is '${YOUR_DOMAIN}/import/github/callback' + - Authorization callback URL is 'http(s)://${YOUR_DOMAIN}' 1. Select "Register application". 1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md index 5f8bb57365c..0c53584d201 100644 --- a/doc/integration/oauth_provider.md +++ b/doc/integration/oauth_provider.md @@ -28,7 +28,8 @@ GitLab supports two ways of adding a new OAuth2 application to an instance. You can either add an application as a regular user or add it in the admin area. What this means is that GitLab can actually have instance-wide and a user-wide applications. There is no difference between them except for the different -permission levels they are set (user/admin). +permission levels they are set (user/admin). The default callback URL is +`http://your-gitlab.example.com/users/auth/gitlab/callback` ## Adding an application through the profile diff --git a/doc/integration/saml.md b/doc/integration/saml.md index 8a7205caaa4..f3b2a288776 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -138,7 +138,7 @@ This setting is only available on GitLab 8.7 and above. SAML login includes support for external groups. You can define in the SAML settings which groups, to which your users belong in your IdP, you wish to be -marked as [external](../permissions/permissions.md). +marked as [external](../user/permissions.md). ### Requirements @@ -306,4 +306,4 @@ For this you need take the following into account: validators are optional Make sure that one of the above described scenarios is valid, or the requests will -fail with one of the mentioned errors.
\ No newline at end of file +fail with one of the mentioned errors. diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md index b6b2d4e5e88..5210ce0de9a 100644 --- a/doc/integration/shibboleth.md +++ b/doc/integration/shibboleth.md @@ -2,7 +2,7 @@ This documentation is for enabling shibboleth with omnibus-gitlab package. -In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however I did not found way to easily configure Nginx that is bundled in omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider. +In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled NIGNX provided in the omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider. To enable the Shibboleth OmniAuth provider you must: diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 236eb7b12c4..fb2dd582754 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -7,11 +7,12 @@ * [Newlines](#newlines) * [Multiple underscores in words](#multiple-underscores-in-words) * [URL auto-linking](#url-auto-linking) +* [Multiline Blockquote](#multiline-blockquote) * [Code and Syntax Highlighting](#code-and-syntax-highlighting) * [Inline Diff](#inline-diff) * [Emoji](#emoji) * [Special GitLab references](#special-gitlab-references) -* [Task lists](#task-lists) +* [Task Lists](#task-lists) **[Standard Markdown](#standard-markdown)** @@ -89,6 +90,37 @@ GFM will autolink almost any URL you copy and paste into your text. * irc://irc.freenode.net/gitlab * http://localhost:3000 +## Multiline Blockquote + +On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, +GFM supports multiline blockquotes fenced by <code>>>></code>. + +```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 _GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 963b35de3a0..78d67aeec78 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/public_access/public_access.md b/doc/public_access/public_access.md index 9a5c5a5c92a..a3921f1b89f 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.md) +**Any logged in user** will have [Guest](../user/permissions.md) permissions on the repository. ### Internal projects @@ -27,7 +27,7 @@ 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.md) permissions +Any logged in user will have [Guest](../user/permissions.md) permissions on the repository. ### How to change project visibility diff --git a/doc/update/8.8-to-8.9.md b/doc/update/8.8-to-8.9.md index 423140a92c7..f078a2bece5 100644 --- a/doc/update/8.8-to-8.9.md +++ b/doc/update/8.8-to-8.9.md @@ -62,7 +62,23 @@ sudo -u git -H git checkout v0.7.5 sudo -u git -H make ``` -### 6. Install libs, migrations, etc. +### 6. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +# Login to MySQL +mysql -u root -p + +# Grant the GitLab user the REFERENCES permission on the database +GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Quit the database session +mysql> \q +``` + +### 7. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -84,7 +100,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ``` -### 7. Update configuration files +### 8. Update configuration files #### New configuration options for `gitlab.yml` @@ -141,12 +157,12 @@ Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -### 8. Start application +### 9. Start application sudo service gitlab start sudo service nginx restart -### 9. Check application status +### 10. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md new file mode 100644 index 00000000000..84065a84e50 --- /dev/null +++ b/doc/update/8.9-to-8.10.md @@ -0,0 +1,191 @@ +# From 8.9 to 8.10 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-10-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-10-stable-ee +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v3.2.0 +``` + +### 5. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +cd /home/git/gitlab-workhorse +sudo -u git -H git fetch --all +sudo -u git -H git checkout v0.7.7 +sudo -u git -H make +``` + +### 6. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +# Login to MySQL +mysql -u root -p + +# Grant the GitLab user the REFERENCES permission on the database +GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Quit the database session +mysql> \q +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +``` + +### 8. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-9-stable:config/gitlab.yml.example origin/8-10-stable:config/gitlab.yml.example +``` + +#### Git configuration + +Disable `git gc --auto` because GitLab runs `git gc` for us already. + +```sh +sudo -u git -H git config --global gc.auto 0 +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-9-stable:lib/support/nginx/gitlab-ssl origin/8-10-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-9-stable:lib/support/nginx/gitlab origin/8-10-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-10-stable/lib/support/init.d/gitlab.default.example#L37 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/v8.9.0/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.9) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.8 to 8.9](8.8-to-8.9.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. diff --git a/doc/user/permissions.md b/doc/user/permissions.md new file mode 100644 index 00000000000..66542861781 --- /dev/null +++ b/doc/user/permissions.md @@ -0,0 +1,131 @@ +# 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 group's project and the +project itself, the highest permission level is used. + +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. + +GitLab administrators receive all permissions. + +To add or import a user, you can follow the [project users and members +documentation](../workflow/add-user/add-user.md). + +## Project + +The following table depicts the various user permission levels in a 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/Accept 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 protected 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 + +Any user can remove themselves from a group, unless they are the last Owner of +the group. The following table depicts the various user permission levels in a +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. + +By default new users are not set as external users. This behavior can be changed +by an administrator under **Admin > Application Settings**. + +## GitLab CI + +GitLab CI permissions rely on the role the user has in GitLab. There are four +permission levels it total: + +- admin +- master +- developer +- guest/reporter + +The admin user can perform any action on GitLab CI in scope of the GitLab +instance and project. In addition, all admins can use the admin interface under +`/admin/runners`. + +| Action | Guest, Reporter | Developer | Master | Admin | +|---------------------------------------|-----------------|-------------|----------|--------| +| See commits and builds | ✓ | ✓ | ✓ | ✓ | +| Retry or cancel build | | ✓ | ✓ | ✓ | +| Remove project | | | ✓ | ✓ | +| Create project | | | ✓ | ✓ | +| Change project configuration | | | ✓ | ✓ | +| Add specific runners | | | ✓ | ✓ | +| Add shared runners | | | | ✓ | +| See events in the system | | | | ✓ | +| Admin interface | | | | ✓ | diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md index 4b551130255..0537ce0bcd4 100644 --- a/doc/workflow/add-user/add-user.md +++ b/doc/workflow/add-user/add-user.md @@ -23,7 +23,7 @@ want to add. --- -Select the user and the [permission level](../../permissions/permissions.md) +Select the user and the [permission level](../../user/permissions.md) that you'd like to give the user. Note that you can select more than one user. ![Give user permissions](img/add_user_give_permissions.png) diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md index 217a4a4012f..733d079bd4a 100644 --- a/doc/workflow/forking_workflow.md +++ b/doc/workflow/forking_workflow.md @@ -38,7 +38,7 @@ Forking a project is in most cases a two-step process. --- After the forking is done, you can start working on the newly created -repository. There, you will have full [Owner](../permissions/permissions.md) +repository. There, you will have full [Owner](../user/permissions.md) access, so you can set it up as you please. ## Merging upstream diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md index d854ec1e025..5c1c7b47c8a 100644 --- a/doc/workflow/protected_branches.md +++ b/doc/workflow/protected_branches.md @@ -1,4 +1,4 @@ -# Protected branches +# Protected Branches Permissions in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches. @@ -12,7 +12,7 @@ A protected branch does three simple things: You can make any branch a protected branch. GitLab makes the master branch a protected branch by default. -To protect a branch, user needs to have at least a Master permission level, see [permissions document](../permissions/permissions.md). +To protect a branch, user needs to have at least a Master permission level, see [permissions document](../user/permissions.md). ![protected branches page](protected_branches/protected_branches1.png) @@ -28,4 +28,28 @@ For those workflows, you can allow everyone with write access to push to a prote On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box. -![Developers can push](protected_branches/protected_branches2.png)
\ No newline at end of file +![Developers can push](protected_branches/protected_branches2.png) + +## Wildcard Protected Branches + +>**Note:** +This feature was added in GitLab 8.10. + +1. You can specify a wildcard protected branch, which will protect all branches matching the wildcard. For example: + + | Wildcard Protected Branch | Matching Branches | + |---------------------------+--------------------------------------------------------| + | `*-stable` | `production-stable`, `staging-stable` | + | `production/*` | `production/app-server`, `production/load-balancer` | + | `*gitlab*` | `gitlab`, `gitlab/staging`, `master/gitlab/production` | + +1. Protected branch settings (like "Developers Can Push") apply to all matching branches. + +1. Two different wildcards can potentially match the same branch. For example, `*-stable` and `production-*` would both match a `production-stable` branch. + >**Note:** + If _any_ of these protected branches have "Developers Can Push" set to true, then `production-stable` has it set to true. + +1. If you click on a protected branch's name, you will be presented with a list of all matching branches: + + ![protected branch matches](protected_branches/protected_branches3.png) + diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png Binary files differindex bb3ab7d7913..c00443803de 100644 --- a/doc/workflow/protected_branches/protected_branches1.png +++ b/doc/workflow/protected_branches/protected_branches1.png diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png Binary files differindex 58ace31ac57..a4f664d3b21 100644 --- a/doc/workflow/protected_branches/protected_branches2.png +++ b/doc/workflow/protected_branches/protected_branches2.png diff --git a/doc/workflow/protected_branches/protected_branches3.png b/doc/workflow/protected_branches/protected_branches3.png Binary files differnew file mode 100644 index 00000000000..2a50cb174bb --- /dev/null +++ b/doc/workflow/protected_branches/protected_branches3.png diff --git a/features/admin/projects.feature b/features/admin/projects.feature index c5ee80136c8..8929bcf8d80 100644 --- a/features/admin/projects.feature +++ b/features/admin/projects.feature @@ -10,10 +10,11 @@ Feature: Admin Projects Then I should see all non-archived projects And I should not see project "Archive" + @javascript Scenario: I should see all projects in the list Given archived project "Archive" When I visit admin projects page - And I check "Show archived projects" + And I select "Show archived projects" Then I should see all projects And I should see "archived" label @@ -22,6 +23,7 @@ Feature: Admin Projects And I click on first project Then I should see project details + @javascript Scenario: Transfer project Given group 'Web' And I visit admin project page diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index db73309804c..1f4c9020731 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -7,6 +7,7 @@ Feature: Dashboard And project "Shop" has CI enabled And project "Shop" has CI build And project "Shop" has labels: "bug", "feature", "enhancement" + And project "Shop" has issue: "bug report" And I visit dashboard page Scenario: I should see projects list diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index a95df038357..8b0cb90765e 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -83,11 +83,6 @@ Feature: Project Commits #Given I visit my project's commits stats page #Then I see commits stats - Scenario: I browse big commit - Given I visit big commit page - Then I see big commit warning - And I see "Reload with full diff" link - Scenario: I browse a commit with an image Given I visit a commit with an image that changed Then The diff links to both the previous and current image diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index 2bde4c8a99b..35687aac9ea 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -6,10 +6,6 @@ Feature: Project Commits Diff Comments And I visit project commit page @javascript - Scenario: I can access add diff comment buttons - Then I should see add a diff comment button - - @javascript Scenario: I can comment on a commit diff Given I leave a diff comment like "Typo, please fix" Then I should see a diff comment saying "Typo, please fix" diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 0e97e4d5954..21768c15c17 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -89,13 +89,6 @@ Feature: Project Merge Requests Then The list should be sorted by "Oldest updated" @javascript - Scenario: Visiting Issues after being sorted the list - Given I visit project "Shop" merge requests page - And I sort the list by "Oldest updated" - And I visit project "Shop" issues page - Then The list should be sorted by "Oldest updated" - - @javascript Scenario: Visiting Merge Requests from a differente Project after sorting Given I visit project "Shop" merge requests page And I sort the list by "Oldest updated" diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 8613dc537cc..0c89a3db9ad 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -62,7 +62,8 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps step 'I should see "johndoe@gitlab.com" in team list in every project as "Reporter"' do page.within ".group-users-list" do - expect(page).to have_content "johndoe@gitlab.com – Invited by" + expect(page).to have_content "johndoe@gitlab.com" + expect(page).to have_content "Invited by" expect(page).to have_content "Reporter" end end diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb index a7a28755a6c..d77945a6b9c 100644 --- a/features/steps/admin/projects.rb +++ b/features/steps/admin/projects.rb @@ -18,9 +18,9 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps end end - step 'I check "Show archived projects"' do - page.check 'Show archived projects' - click_button "Search" + step 'I select "Show archived projects"' do + find(:css, '#sort-projects-dropdown').click + click_link 'Show archived projects' end step 'I should see "archived" label' do @@ -45,7 +45,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps step 'I transfer project to group \'Web\'' do allow_any_instance_of(Projects::TransferService). to receive(:move_uploads_to_new_namespace).and_return(true) - find(:xpath, "//input[@id='new_namespace_id']").set group.id + click_button 'Search for Namespace' + click_link 'group: web' click_button 'Transfer' end diff --git a/features/steps/dashboard/help.rb b/features/steps/dashboard/help.rb index 800e869533e..9c94dc70df0 100644 --- a/features/steps/dashboard/help.rb +++ b/features/steps/dashboard/help.rb @@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps end step 'I visit the "Rake Tasks" help page' do - visit help_page_path("raketasks", "maintenance") + visit help_page_path("raketasks/maintenance") end step 'I should see "Rake Tasks" page markdown rendered' do diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 9e5602dacf1..4ee6784a086 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -155,8 +155,11 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I click on my profile picture' do - find(:css, '.side-nav-toggle').click - find(:css, '.sidebar-user').click + find(:css, '.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_link "Profile" + end end step 'I should see my user page' do diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb index 2876e8812e9..b4a32ed2e38 100644 --- a/features/steps/project/builds/artifacts.rb +++ b/features/steps/project/builds/artifacts.rb @@ -68,10 +68,16 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps end step 'download of a file extracted from build artifacts should start' do - # this will be accelerated by Workhorse - response_json = JSON.parse(page.body, symbolize_names: true) - expect(response_json[:archive]).to end_with('build_artifacts.zip') - expect(response_json[:entry]).to eq Base64.encode64('ci_artifacts.txt') + send_data = response_headers[Gitlab::Workhorse::SEND_DATA_HEADER] + + expect(send_data).to start_with('artifacts-entry:') + + base64_params = send_data.sub(/\Aartifacts\-entry:/, '') + params = JSON.parse(Base64.urlsafe_decode64(base64_params)) + + expect(params.keys).to eq(['Archive', 'Entry']) + expect(params['Archive']).to end_with('build_artifacts.zip') + expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt')) end step 'I click a first row within build artifacts table' do diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index 239036e431d..bea9f9d198b 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -125,25 +125,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).to have_content 'Authors' end - step 'I visit big commit page' do - # Create a temporary scope to ensure that the stub_const is removed after user - RSpec::Mocks.with_temporary_scope do - stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_lines: 1, max_files: 1 }) - visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id) - end - end - - step 'I see big commit warning' do - expect(page).to have_content sample_big_commit.message - expect(page).to have_content "Too many changes" - end - - step 'I see "Reload with full diff" link' do - link = find_link('Reload with full diff') - expect(link[:href]).to end_with('?force_show_diff=true') - expect(link[:href]).not_to include('.html') - end - step 'I visit a commit with an image that changed' do visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id) end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 640f1720a6c..da848afd48e 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -272,10 +272,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do mr = MergeRequest.find_by(title: "Bug NS-05") - create(:note_on_merge_request_diff, project: project, + create(:diff_note_on_merge_request, project: project, noteable: mr, author: user_exists("John Doe"), - line_code: sample_commit.line_code, note: 'Line is wrong') end @@ -519,7 +518,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step '"Bug NS-05" has CI status' do project = merge_request.source_project project.enable_ci - pipeline = create :ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch + pipeline = create :ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch create :ci_build, pipeline: pipeline end diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index e8b1e4b4879..4df4e89f5b9 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -23,27 +23,26 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.line_code) - page.within("form[id$='#{sample_commit.line_code}-true']") do + page.within("form[data-line-code='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Typo, please fix" - find(".js-comment-button").trigger("click") - sleep 0.05 + find(".js-comment-button").click end end end step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do - click_parallel_diff_line(sample_commit.line_code, 'old') - page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do + click_parallel_diff_line(sample_commit.del_line_code, 'old') + page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.del_line_code}']") do fill_in "note[note]", with: "Old comment" - find(".js-comment-button").trigger("click") + find(".js-comment-button").click end end step 'I leave a diff comment in a parallel view on the right side like "New comment"' do click_parallel_diff_line(sample_commit.line_code, 'new') - page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do + page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.line_code}']") do fill_in "note[note]", with: "New comment" - find(".js-comment-button").trigger("click") + find(".js-comment-button").click end end @@ -51,7 +50,7 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.line_code) - page.within("form[id$='#{sample_commit.line_code}-true']") do + page.within("form[data-line-code='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Should fix it :smile:" find('.js-md-preview-button').click end @@ -62,7 +61,7 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.del_line_code) - page.within("form[id$='#{sample_commit.del_line_code}-true']") do + page.within("form[data-line-code='#{sample_commit.del_line_code}']") do fill_in "note[note]", with: "DRY this up" find('.js-md-preview-button').click end @@ -91,7 +90,7 @@ module SharedDiffNote page.within(diff_file_selector) do click_diff_line(sample_commit.line_code) - page.within("form[id$='#{sample_commit.line_code}-true']") do + page.within("form[data-line-code='#{sample_commit.line_code}']") do fill_in 'note[note]', with: ':smile:' click_button('Comment') end @@ -165,10 +164,6 @@ module SharedDiffNote end end - step 'I should see add a diff comment button' do - expect(page).to have_css('.js-add-diff-note-button') - end - step 'I should see an empty diff comment form' do page.within(diff_file_selector) do expect(page).to have_field("note[note]", with: "") @@ -215,7 +210,7 @@ module SharedDiffNote end step 'I click side-by-side diff button' do - find('#parallel-diff-btn').trigger('click') + find('#parallel-diff-btn').click end step 'I see side-by-side diff button' do @@ -227,10 +222,12 @@ module SharedDiffNote end def click_diff_line(code) - find("button[data-line-code='#{code}']").trigger('click') + find(".line_holder[id='#{code}'] td:nth-of-type(1)").trigger 'mouseover' + find(".line_holder[id='#{code}'] button").trigger 'click' end def click_parallel_diff_line(code, line_type) - find("button[data-line-code='#{code}'][data-line-type='#{line_type}']").trigger('click') + find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover' + find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click' end end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index b3411c03118..0b4920883b8 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -223,6 +223,11 @@ module SharedProject create(:label, project: project, title: 'enhancement') end + step 'project "Shop" has issue: "bug report"' do + project = Project.find_by(name: "Shop") + create(:issue, project: project, title: "bug report") + end + step 'project "Shop" has CI enabled' do project = Project.find_by(name: "Shop") project.enable_ci diff --git a/features/support/env.rb b/features/support/env.rb index ab3f0ca7aeb..f0a3dd8d2d0 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -13,7 +13,7 @@ require_relative 'rerun' if ENV['CI'] require 'knapsack' - Knapsack::Adapters::RSpecAdapter.bind + Knapsack::Adapters::SpinachAdapter.bind end %w(select2_helper test_env repo_helpers).each do |f| diff --git a/fixtures/emojis/digests.json b/fixtures/emojis/digests.json index 41ca617847e..50ee5089d8f 100644 --- a/fixtures/emojis/digests.json +++ b/fixtures/emojis/digests.json @@ -2,62 +2,62 @@ { "name": "100", "unicode": "1F4AF", - "digest": "6d57c7cc93335f853e1a5670233f121bc94730dbd82b2b3c5c5a509e092ef0fd" + "digest": "add3bd7d06b6dd445788b277f8c9e5dcf42a54d3ec8b7fb9e7a39695dd95d094" }, { "name": "1234", "unicode": "1F522", - "digest": "727763fd9f18fd5df59e9f78e678ea4ec753e674d70f15d4e77c7802067d660b" + "digest": "c5ac5c8147f5bfd644fad6b470432bba86ffc7bcee04a0e0d277cd1ca485207f" }, { "name": "8ball", "unicode": "1F3B1", - "digest": "1aecf21951452ba24e921ec71b3d313b7ddc2e185b0339c9e0eebc85be4f031d" + "digest": "a6e6855775b66c505adee65926a264103ebddf2e2d963db7c009b4fec3a24178" }, { "name": "a", "unicode": "1F170", - "digest": "2272113a5bcb7faf8db7c1bd35df576d32f2f7cbd881463934ad3382eb87c723" + "digest": "bddbb39e8a1d35d42b7c08e7d47f63988cb4d8614b79f74e70b9c67c221896cc" }, { "name": "ab", "unicode": "1F18E", - "digest": "6f8a237751fdc84db4121f408272d9a23258515449610e4c6c54f50f6e995627" + "digest": "67430fe5fce981160e2ea9052962e49f264322d3abfc2828fbc311b6cdf67ae8" }, { "name": "abc", "unicode": "1F524", - "digest": "652a2381a7b587d8a52d5178e2d7d6c8600b33d36160fa69677943da374105bc" + "digest": "282c817ee3414d77a74b815962c33dd9fe71fabaea8c7a9cec466100fbe32187" }, { "name": "abcd", "unicode": "1F521", - "digest": "35ade4fd3d75294ebb72c24490aa32745604edc6cabe095b90634cd3ce78c07b" + "digest": "686728c759f4683c64762ee4eda0a91bf2041f0ae4f358aacf6c09bf51892eff" }, { "name": "accept", "unicode": "1F251", - "digest": "8212ed158cc447c92813273fc915e84d3d5c4c48d1b38e498c088bad27ab8145" + "digest": "7208d34c761f10a7fd28f98e25535eba13ff91a64442fc282a98bb77722614f1" }, { "name": "aerial_tramway", "unicode": "1F6A1", - "digest": "8039d7f67e6e5b211066cab6cf2142afc3aca5c830a357369362c9b484029563" + "digest": "98df666f34370fc34ce280d84bba5a7e617f733fbbfe66caa424b2afa6ab6777" }, { "name": "airplane", "unicode": "2708", - "digest": "18f4dfac323555d8cdabb79148874c0185ce98e1a08e69414d236b23e502a854" + "digest": "cc12cf259ef88e57717620cd2bd5aa6a02a8631ee532a3bde24bee78edc5de33" }, { "name": "airplane_arriving", "unicode": "1F6EC", - "digest": "9a1c81d97512e5d0e3acec40290d00f616ec182140909859e366a734b9f840bb" + "digest": "80d5b4675f91c4cff06d146d795a065b0ce2a74557df4d9e3314e3d3b5c4ae82" }, { "name": "airplane_departure", "unicode": "1F6EB", - "digest": "e3c5ff4038db998c1897cb237d0b865da0bc60331c758f204e45a979d5fab445" + "digest": "5544eace06b8e1b6ea91940e893e013d33d6b166e14e6d128a87f2cd2de88332" }, { "name": "airplane_northeast", @@ -72,12 +72,12 @@ { "name": "airplane_small", "unicode": "1F6E9", - "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3" + "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d" }, { "name": "small_airplane", "unicode": "1F6E9", - "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3" + "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d" }, { "name": "airplane_small_up", @@ -102,67 +102,67 @@ { "name": "alarm_clock", "unicode": "23F0", - "digest": "84ddd7b3b857c165410b7b44863e5354ca0f3591c3bfe56231f12c9f7531a96f" + "digest": "fef05a3cd1cddbeca4de8091b94bddb93790b03fa213da86c0eec420f8c49599" }, { "name": "alembic", "unicode": "2697", - "digest": "45698914a21683f06931d807af171bcb6984e5ebce66012bba71b467565bd69d" + "digest": "c94b2a4bf24ccf4db27a22c9725cfe900f4a99ec49ef2411d67952bcb2ca1bfb" }, { "name": "alien", "unicode": "1F47D", - "digest": "94dbe4e90614c654145aba93610c43e3ab86df8ca07391bd4e56383f9329c008" + "digest": "856ba98202b244c13a5ee3014a6f7ad592d8c119a30d79e4fc790b74b0e321f7" }, { "name": "ambulance", "unicode": "1F691", - "digest": "82ef36bcd13c88a4b2397c918b8048adc6bf045ed2532ff568e0dfd1b1b29c3c" + "digest": "d9b3c1873de496a4554e715342c72290fb69a9c6766d7885f38bfe9491d052da" }, { "name": "amphora", "unicode": "1F3FA", - "digest": "d3758d88aa1fc3be01894102f57479d3a49790510d38ad3d06a2774962010608" + "digest": "4015f907b649b5e348502cc0e3685ed184e180dca5cc81c43ec516e14df127bf" }, { "name": "anchor", "unicode": "2693", - "digest": "27c6034f769d9f020362fc5b227b9279651cc940861e727d1f6ccd59af98f851" + "digest": "2b29b34ef896ebab70016301e3d1880209bbc3c5a5b8d832e43afff9b17ad792" }, { "name": "angel", "unicode": "1F47C", - "digest": "c1b8ad2adc7686e7fbbe4ec357071e7228a5e0762e001bb589e2f97ff258d5c7" + "digest": "db75c2460aaf9cd07cb41fe22c8a6079f3667ffe612a71611358720e2b5512a4" }, { "name": "angel_tone1", "unicode": "1F47C-1F3FB", - "digest": "90b701c43311b1096c4a012d9905a186f1a16829ea2707921a8418c28617d751" + "digest": "5871a622469b96296365adaf77d83167759692124c20e5a6e062a525af33472a" }, { "name": "angel_tone2", "unicode": "1F47C-1F3FC", - "digest": "d6bcaf1b76e25d486d4ab9b159cf727782d508543d1ae27c8d2c12d2f13d6eb0" + "digest": "f5993198a5d9daf39e761c783461f07bca237f4e9b739ac300bb8ca001a69a1a" }, { "name": "angel_tone3", "unicode": "1F47C-1F3FD", - "digest": "3069285e6218c8083cb0085aa10017bcdea033e321d97ba339a84892074b903a" + "digest": "f0c97a7c4354626267d6ab0f388e4297ad255ab9b061f9c68fbcaa0abfc52783" }, { "name": "angel_tone4", "unicode": "1F47C-1F3FE", - "digest": "dbb87019752d9caa94ce086858c1e3225b62e221ad599f5106548fda2456fc2b" + "digest": "6e5dc724c1939d1b0d1a91343662b5bd61ced7709c97802977145ffab6a1f7ac" }, { "name": "angel_tone5", "unicode": "1F47C-1F3FF", - "digest": "f77703df97720c27a128b5f3c0948b9e04a6b6b81ea5306468154f9bf56225db" + "digest": "52186e1de350c27d25d6010edf44f64a30338b65912ca178429fbcfbd88113c2" }, { "name": "anger", "unicode": "1F4A2", - "digest": "2253b7ff0894f247bc6f04d841a748c56d6c94684880c13df42387691ff20e75" + "digest": "332493913891aa0eda2743b4bb16c4682400f249998bf34eb292246c9009e17f" }, { "name": "anger_left", @@ -177,152 +177,152 @@ { "name": "anger_right", "unicode": "1F5EF", - "digest": "24b572d64c519251a3ae8844e8d66fd6955752aff99aebe7dc20179505a466c4" + "digest": "8b049511ef3b1b28325841e2f87c60773eaf2f65cabba58d8b0ec3de9b10c0ae" }, { "name": "right_anger_bubble", "unicode": "1F5EF", - "digest": "24b572d64c519251a3ae8844e8d66fd6955752aff99aebe7dc20179505a466c4" + "digest": "8b049511ef3b1b28325841e2f87c60773eaf2f65cabba58d8b0ec3de9b10c0ae" }, { "name": "angry", "unicode": "1F620", - "digest": "c4188ba70df99d8ccef5706d711176725d3dd50d62f065a177d68d85c7828107" + "digest": "7e09e7e821f511606341fb5ce4011a8ed9809766ab86b7983ffa6ea352b39ec1" }, { "name": "anguished", "unicode": "1F627", - "digest": "9c2347308133ae50dc04da62042fff847f4c477b2956b8aa976f0413899e38bc" + "digest": "a2b6f052996969a17150249d9ef5db742da3d6585bd38ca61eb14c4c13cda54f" }, { "name": "ant", "unicode": "1F41C", - "digest": "d2af2ed1cfe15d649aa329d965764a1e8726941d833841781a5b66d7dd0b0921" + "digest": "929abeaff7ba21ab71cd1ab798af7a6b611e3b3ce1af80cede09a116b223e442" }, { "name": "apple", "unicode": "1F34E", - "digest": "a9babee24f454934a5e1fb8d781cbce354dfd88e8a8e01f02e8b30071fd40460" + "digest": "2a1b85ce57e3d236ae7777dcf332ec37d03bfd7b19806521a353bc532083224d" }, { "name": "aquarius", "unicode": "2652", - "digest": "1a168c252678847d1f9ef450887489e3bdc207ecae4b6fb05e92295ff861ae2c" + "digest": "fdc42cd41b0dace5eae6baba3143f1e40295d48a29e7103a5bba1d84a056c39d" }, { "name": "aries", "unicode": "2648", - "digest": "bde262a8795e12f8b0ebb3f0f8c3a56104062fcee8d5d678cf4bb445a7daf698" + "digest": "deb135debcde0a98f40361a84ab64d57c18b5b445cd2f4199e8936f052899737" }, { "name": "arrow_backward", "unicode": "25C0", - "digest": "ddae36d1febf5c246e51d599e2898a8aa30cd47f88b5bcb469e3ca9d22538b97" + "digest": "e162ac82e90d1e925d479fa5c45b9340e0a53287be04e43cbbb2a89c7e7e45e4" }, { "name": "arrow_double_down", "unicode": "23EC", - "digest": "906f42b5f788128ed90d2d162cf03e6e595a50ad05e0aa5f64e925637379d0cd" + "digest": "03ca890b05338d40972c7a056d672df620a203c6ca52ff3ff530f1a710905507" }, { "name": "arrow_double_up", "unicode": "23EB", - "digest": "2129a57402980de6fc6f59ad8354525c2dbcd66d1b78f4de091181ddc81e0693" + "digest": "e753f05bce993d62d5dc79e33c441ced059381b6ce21fa3ea4200f1b3236e59d" }, { "name": "arrow_down", "unicode": "2B07", - "digest": "370e4f41565d5dab245c20e45c502505a56d26c2392283781b841eb3e905edb2" + "digest": "9bf1bd2ea652ca9321087de58c7a112ea04c35676a6ee0766154183f8b95af6c" }, { "name": "arrow_down_small", "unicode": "1F53D", - "digest": "98a2b183f2daec425160bbfce1d2b940b8baa0d5032fdacfa9453e39bed5651b" + "digest": "7766198bc60cf59d6cdaeeaa700c2282bfff2f0fdeb22cf4581ca284b87a3bb7" }, { "name": "arrow_forward", "unicode": "25B6", - "digest": "348627b8e0f55cf1e9ab19c9de1d170371b2c4cb4dda9a2aa8e0c558db08b18a" + "digest": "db77d9accd1e02224f5d612f79cd691e6befdf22063475204836be6572510fb7" }, { "name": "arrow_heading_down", "unicode": "2935", - "digest": "96c64953fc3134711247bef320f252c48993ebc90494925b7fee42ffce2a2ec2" + "digest": "f5396069c8f63c13e6c3e0ecd34267c932451309ade9c1171d410563153bf909" }, { "name": "arrow_heading_up", "unicode": "2934", - "digest": "94f94e74176cc050703b3584f3f700debf86e4e61b893a441825a21fa3f8ce74" + "digest": "1cad71923fa3df24cf543cae4ce775b0f74936f2edd685fd86a7525c41a14568" }, { "name": "arrow_left", "unicode": "2B05", - "digest": "4553be62a63d7550deac4f7dbeffce6006f769ae6cddfb8c795671672011ba0b" + "digest": "b629bb3dbe161ef89cfcfced0c7968a68e44a019ad509132987e4973bdc874e7" }, { "name": "arrow_lower_left", "unicode": "2199", - "digest": "10f83c252110d705cdcfebc35a70c341ad288730d0c0729479e3a96e263d5120" + "digest": "879136ba0e24e6bf3be70118abcb716d71bd74f7b62347bc052b6533c0ea534d" }, { "name": "arrow_lower_right", "unicode": "2198", - "digest": "ee33abd4c96c19e9b80a2fc1500ba8ecaa6668c49310cc816a496e8c61af3850" + "digest": "86d52ac9b961991e3aaa6a9f9b5ace4db6ffd1b5c171c09c23b516473b55066d" }, { "name": "arrow_right", "unicode": "27A1", - "digest": "2611e9138a2651916f414015d0287f5f0af266514d96a42915d32b04fb652a90" + "digest": "45f26a1cbb0f00ed3609b39da52e9d9e896a77e361c4c8036b1bf8038171bd49" }, { "name": "arrow_right_hook", "unicode": "21AA", - "digest": "628b06384a2963a4fe81e9fbf4e22511f697878d9b9db7d2fc98f8aadbe8f4f9" + "digest": "4f452679c71bcea4fc4a701c55156fef3ddc1ebbc70570bedfc9d3a029637ab1" }, { "name": "arrow_up", "unicode": "2B06", - "digest": "c09e5f41c01028b45707c525d30d3d6731ec57b7447f0d7ba4ad6c1404449e5c" + "digest": "982b988ef6651d8a71867ba7c87f640f62dd0eeb0b7c358f5a5c37e8fe507b8b" }, { "name": "arrow_up_down", "unicode": "2195", - "digest": "e7fd92d24a01702f76c7fcc0de998bc81fbfb93711d076984f6da91d1dccd84c" + "digest": "645ed8fb6646f49bfd95af1752336deacdadbe5cba13904023a704288f3b0e2c" }, { "name": "arrow_up_small", "unicode": "1F53C", - "digest": "bc48dad74bc1d0c5579cbf5e3d005314b0d21bc5b5ebbba2b05136e33f49296d" + "digest": "4a8c5789c13a852517e639e7a62c2d331464e6fb0358985aa97c1515e97b5e8b" }, { "name": "arrow_upper_left", "unicode": "2196", - "digest": "792a9709f03843024e53d201cb4769c59b656c3bf0dff2306e8e605493a66b93" + "digest": "79026f828d6ceb7c55a9542770962ba6dcd08203995f6ceeb70333a12307d376" }, { "name": "arrow_upper_right", "unicode": "2197", - "digest": "ee934b0c9cff270efd30a6cafc15253d405efd2c93b4785ac2ed4ea6420266a6" + "digest": "7e0f33dfbe65628991c170130d366a3e2cedaf8862ddfcaf3960f395d3da1926" }, { "name": "arrows_clockwise", "unicode": "1F503", - "digest": "914f4120513730d7a19c9f8c4e59223a90568de0b25a225b712b31fa9697ef4f" + "digest": "88669679977f7157f0acaa9d6a1b77ccf84d25eb78c5bc8afcde38d3635e7144" }, { "name": "arrows_counterclockwise", "unicode": "1F504", - "digest": "86d87597e4e3db6dbba9907ee82412db0cbab1ea875bd0be6505dd886dc19b90" + "digest": "a2c6a6d3643c128aee3304cd03bb3d7cfe4d35d3ba825bc9c1142d7832b4426e" }, { "name": "art", "unicode": "1F3A8", - "digest": "dfc6b0da780199df86507d65b0499ba1706c266ae7badcb0e7fb5b85af7c9578" + "digest": "b6bc6c4bfb594aadcbb641d006031867678504764bbe0ab84e7b08567a9498da" }, { "name": "articulated_lorry", "unicode": "1F69B", - "digest": "4c4de240ebd175f7b53453eda4e51f2e57d0db2a98d317f804116e14e47cff1d" + "digest": "c115e6613ebd718268aa31d265e017138b9fb58bbb8201eb3f40de2380e460aa" }, { "name": "ascending_notes", @@ -332,117 +332,117 @@ { "name": "asterisk", "unicode": "002A-20E3", - "digest": "0b7f27f545b616677c83d40ff957337477b2881459b4d3c839ae55e23797419f" + "digest": "33d92093f2914448d5a939cf62e8ee3e32931923abdef5f0210e8a8150fa312d" }, { "name": "keycap_asterisk", "unicode": "002A-20E3", - "digest": "0b7f27f545b616677c83d40ff957337477b2881459b4d3c839ae55e23797419f" + "digest": "33d92093f2914448d5a939cf62e8ee3e32931923abdef5f0210e8a8150fa312d" }, { "name": "astonished", "unicode": "1F632", - "digest": "58632b97e274ade5183752db2b3c5c4fe29effcd5a9720a8d01fa809b97023dc" + "digest": "f8531bdda5070d10492709085f4ff652b8be9be6458758940358b9fc594a1f14" }, { "name": "athletic_shoe", "unicode": "1F45F", - "digest": "1fc55d85a4d6751f9e60467801b051d2fb3341bdcc33b8d3695d5143359edb43" + "digest": "1f90dc390e0dea679085465b7f9e786dfd7dd56a3b219987144ed37ab1e9bf95" }, { "name": "atm", "unicode": "1F3E7", - "digest": "bf827ef6c349f5b6912d821457975a4720d1750529d907e94ece429b7a388d7e" + "digest": "7d3ce6a6afb4951546883404b8e36904179f88f1aa533706cf7bf0bbe0d6fd3c" }, { "name": "atom", "unicode": "269B", - "digest": "cbce1725602efbb77a935cfae5407e4d75489ee988910296c7f6140665afc669" + "digest": "6b6bb83b00707a314e46ff8eefbda40978a291ec7881caba1b1ee273f49c1368" }, { "name": "atom_symbol", "unicode": "269B", - "digest": "cbce1725602efbb77a935cfae5407e4d75489ee988910296c7f6140665afc669" + "digest": "6b6bb83b00707a314e46ff8eefbda40978a291ec7881caba1b1ee273f49c1368" }, { "name": "b", "unicode": "1F171", - "digest": "9116256b3189977e37f6da7ddedf82bb29b0358829a4e8718fd59e51d9b86b3c" + "digest": "722f9db9442e7c0fc0d0ac0f5291fbf47c6a0ac4d8abd42e97957da705fb82bf" }, { "name": "baby", "unicode": "1F476", - "digest": "66596bea11015154e0b1752b85f349f4286c6643ee6f51ee5e60e0d625c4ae9a" + "digest": "219ae5a571aaf90c060956cd1c56dcc27708c827cecdca3ba1122058a3c4847b" }, { "name": "baby_bottle", "unicode": "1F37C", - "digest": "ed42994b4a539b8bfeccde0f3c7e9c7f54d6696ff48ce7e48171bbab51002348" + "digest": "4fb71689e9d634e8d1699cf454a71e43f2b5b1a5dbab0bf186626934fdf5b782" }, { "name": "baby_chick", "unicode": "1F424", - "digest": "ea2cfa0e5c2cbff5fffdb52cc04dfe7872834bd7cfeaa45e0541b8faffcbd0e9" + "digest": "14119874e9b5548028dfb9cc593a541efc1d075ac839a565b92e0c3253cffe7e" }, { "name": "baby_symbol", "unicode": "1F6BC", - "digest": "65df04dff8739b86f7663ae9c0648927341f360a986655e109721b0e16013b75" + "digest": "fb4db66868cda45ea3879ffc2ff4f763c56d2d889ae0ab17fe171129ede02f98" }, { "name": "baby_tone1", "unicode": "1F476-1F3FB", - "digest": "bc747527a2d723cf99ef3fc2539c19d29634c92ff417736982d3bf87d65d06eb" + "digest": "cd3faf223a298c34e05d469d9d0db08438d97df7fd82c0973f8a9e07d553f5b1" }, { "name": "baby_tone2", "unicode": "1F476-1F3FC", - "digest": "b82bba7a666b7d070751726e54acc7fb8f96e2dfc09e9610d61cfd20947aef9c" + "digest": "5b4539e22e0dd726c27eb8af2357f9240a52aed3f710f3234571cff029cc6198" }, { "name": "baby_tone3", "unicode": "1F476-1F3FD", - "digest": "7f45dfd4ea2ae8515d419ffa13e7ee5c625b024b4e521ace5344c414bb929da0" + "digest": "720e740e1ac63c6372269132b1fb6e07a6b91f5c808cc3adef59f0b4500e5e72" }, { "name": "baby_tone4", "unicode": "1F476-1F3FE", - "digest": "80b1854626616f15426649cc6415e4911a55c8f761422fe48a08af9e8ac6a7cb" + "digest": "5e43b69c509bd526ad6f081764578c30b6f3285fb7442222e05ccf62e53bfb64" }, { "name": "baby_tone5", "unicode": "1F476-1F3FF", - "digest": "9f890804d19a61bee76a29644c818045dd96cf69d67cfbca2d11f4ad376b27da" + "digest": "85bba6e0940ccfb99999fe124e815f9dd340d00a5568e13967b02245a62dbf54" }, { "name": "back", "unicode": "1F519", - "digest": "1dc73947b8f56e033777ca3f747407923bd16b07e53a6c78b09950ca474b7e7a" + "digest": "083e4e48b51092c28efb4532e840e1091b5d4b685c6e0f221aa0228f061cd91e" }, { "name": "badminton", "unicode": "1F3F8", - "digest": "3f95180c1175d0248ebf4b8650cf86566c39e0486d828078244080194c14d4fe" + "digest": "353eb7ee93decd9fe0072e4d78a5618d5e2d9e77a6e4de9fe171870d75e02a66" }, { "name": "baggage_claim", "unicode": "1F6C4", - "digest": "7c1a69511aa2a93984d601da4d1cef1cb4cefbbf127b1486278da8c01345bbf3" + "digest": "7d6bceca92c266da6d2b91dfcf244546fc11022e039e7da8e6888c1696bb2186" }, { "name": "balloon", "unicode": "1F388", - "digest": "a10c2b0865179cdbdef339494ec9b2a109451a356e53738d6a9dd43232500956" + "digest": "65760aedc1503b426927cff78c24449d563843a274961d962718fa9638375d54" }, { "name": "ballot_box", "unicode": "1F5F3", - "digest": "0455ea75612efe78354315b4c345953d2d559bb471d5b01c1adc1d6b74ed693a" + "digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892" }, { "name": "ballot_box_with_ballot", "unicode": "1F5F3", - "digest": "0455ea75612efe78354315b4c345953d2d559bb471d5b01c1adc1d6b74ed693a" + "digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892" }, { "name": "ballot_box_check", @@ -457,7 +457,7 @@ { "name": "ballot_box_with_check", "unicode": "2611", - "digest": "5f5cec7fe462557d31e8d2b836534c1e76d546cc0061236fa2af3667972b84aa" + "digest": "c98d6f3588dd87e2f318bbfe6c646399a905450edfd814edae4e5b1bddef2134" }, { "name": "ballot_box_x", @@ -482,277 +482,277 @@ { "name": "bamboo", "unicode": "1F38D", - "digest": "feb0cf2f1012a1c0649b8c66f7e96e2d8bcdefe879c5a52dab3e25c51009e3b2" + "digest": "e4ee65088df43d7081b1ce6fd996f66f3e0accd88840855c47a98a22997823dd" }, { "name": "banana", "unicode": "1F34C", - "digest": "aa9a1e6db00efa94a7f414c570eff7fc29011be64031a24d03b7f37b617cfd2d" + "digest": "f9e8ff910c282c20a8907ff64926b5de4ee250529a1ed718fb33302e6fff8dd9" }, { "name": "bangbang", "unicode": "203C", - "digest": "bdd350766ccd1c0138f6294f7ebfa3e9867b02bda40a743f7062e52c68358765" + "digest": "76536fee63fe964a3f3839d309b1f45028fb0c43f4d1eeee495f17e1532b4def" }, { "name": "bank", "unicode": "1F3E6", - "digest": "c9648c93049cf8e7884242e58ae3145383d2e5034c9090e0d34c53f5bbce397f" + "digest": "f5d2976bf6d521638ccacc74be06bd4abfeab06c5d898a9d245edad45a5b6306" }, { "name": "bar_chart", "unicode": "1F4CA", - "digest": "942277f72a5b754b13454dab62c85b1ff3447544f38ec76a285f3be32f6f5d12" + "digest": "65a328a1b2d7a5332dd4d93f4dbca13d976f0a505b00835c3fc458e394804240" }, { "name": "barber", "unicode": "1F488", - "digest": "e1526eea685aafc56fb83d07f8ff63c9967600e447b0e5f831a17d6153f2062d" + "digest": "5e8053d3bb3765a8632fd1cbfe21163f74ed79f6be377eb9603eaaf883d8dc46" }, { "name": "baseball", "unicode": "26BE", - "digest": "3d028b16a898f3a15874bc9d3891f9fbf59ea1c226c5c774eddb58a712c489ae" + "digest": "46ac16f8b5455b942f6dbff9483a6fd277721e6719d2731573baabd21c44b34f" }, { "name": "basketball", "unicode": "1F3C0", - "digest": "b2f5a3904d505db066337a24fc840ef75b49ef4c5f152227d8e632ff82285b12" + "digest": "cc83e2aea8fcd2e9a5789e1932ee3766c40843c142fd3565c4e77dafb21ec7d7" }, { "name": "basketball_player", "unicode": "26F9", - "digest": "e94beb69f631667479a80095bf313ceb3aa109d6ebb80f182722360a6d2a214e" + "digest": "793ba53c95e8def769383b612037bc9b9bceecaf1e0430c50a4cc128ad18d9b9" }, { "name": "person_with_ball", "unicode": "26F9", - "digest": "e94beb69f631667479a80095bf313ceb3aa109d6ebb80f182722360a6d2a214e" + "digest": "793ba53c95e8def769383b612037bc9b9bceecaf1e0430c50a4cc128ad18d9b9" }, { "name": "basketball_player_tone1", "unicode": "26F9-1F3FB", - "digest": "6fc77cf2f26ee18e9a3faea500d4277839f77633f31ee618a68c301f1ad32d90" + "digest": "2a06522b971e68ee5b8777a58253009b548f4da2fb723c638acb3d7b04edba8f" }, { "name": "person_with_ball_tone1", "unicode": "26F9-1F3FB", - "digest": "6fc77cf2f26ee18e9a3faea500d4277839f77633f31ee618a68c301f1ad32d90" + "digest": "2a06522b971e68ee5b8777a58253009b548f4da2fb723c638acb3d7b04edba8f" }, { "name": "basketball_player_tone2", "unicode": "26F9-1F3FC", - "digest": "6ee9060c24d92708e12a854fb0bdf5c717c90b8c0350d8aa40c278b41bfa12fc" + "digest": "ecc0e44ab9bc478ba45a055fd69a3a38377b917aac5047963fe80ff8ae5fd8e3" }, { "name": "person_with_ball_tone2", "unicode": "26F9-1F3FC", - "digest": "6ee9060c24d92708e12a854fb0bdf5c717c90b8c0350d8aa40c278b41bfa12fc" + "digest": "ecc0e44ab9bc478ba45a055fd69a3a38377b917aac5047963fe80ff8ae5fd8e3" }, { "name": "basketball_player_tone3", "unicode": "26F9-1F3FD", - "digest": "752e90dbfa7c7a9ae3f37de924e22f3c3d5a7e54dd41c8e8eb99cabb0dad73cf" + "digest": "2d38f1851c685d29532c042461d7b5b996e5f04f0ed54857c66073c62a99ceac" }, { "name": "person_with_ball_tone3", "unicode": "26F9-1F3FD", - "digest": "752e90dbfa7c7a9ae3f37de924e22f3c3d5a7e54dd41c8e8eb99cabb0dad73cf" + "digest": "2d38f1851c685d29532c042461d7b5b996e5f04f0ed54857c66073c62a99ceac" }, { "name": "basketball_player_tone4", "unicode": "26F9-1F3FE", - "digest": "38bedc3074e6243454d568d9b665f5764f1a3d983875651ce7a1cdb53da9f6c8" + "digest": "09e957c6e9ffc196415f28073aa261feba8efba0bdc694dc08f8f7cd1f88f720" }, { "name": "person_with_ball_tone4", "unicode": "26F9-1F3FE", - "digest": "38bedc3074e6243454d568d9b665f5764f1a3d983875651ce7a1cdb53da9f6c8" + "digest": "09e957c6e9ffc196415f28073aa261feba8efba0bdc694dc08f8f7cd1f88f720" }, { "name": "basketball_player_tone5", "unicode": "26F9-1F3FF", - "digest": "25ee1e84670d3db96d3ad098c859abd6b3448f55f668ce0c195ee2337a215de7" + "digest": "c631cefc5d2a0a31bdb9f0a0d97ea68b1c6928e565468998403034644572a0b0" }, { "name": "person_with_ball_tone5", "unicode": "26F9-1F3FF", - "digest": "25ee1e84670d3db96d3ad098c859abd6b3448f55f668ce0c195ee2337a215de7" + "digest": "c631cefc5d2a0a31bdb9f0a0d97ea68b1c6928e565468998403034644572a0b0" }, { "name": "bath", "unicode": "1F6C0", - "digest": "ae6301a6354630cd9dc06a5137f23f826d019c8298b2b012b6ff31b773a910b6" + "digest": "33b371832f90aad50baf5296f3ad4cc081c319b279f989c74409903d8568e917" }, { "name": "bath_tone1", "unicode": "1F6C0-1F3FB", - "digest": "fce7ae2e7ef3f7f44f36c2ad49348b4cf7fce0b0c17e1a90a1e85734cee95b2a" + "digest": "7ae2989e47788ba71359d52da68feec95aaff68a77d5a6556957df1617af8536" }, { "name": "bath_tone2", "unicode": "1F6C0-1F3FC", - "digest": "4d1c9444f16467488fe939fdad279d6855d28be564e5dcc1990451c4b9ae8c95" + "digest": "2e86f8edad54d15a7094cd52160cbe51d10aa1750cfb0b3b58e93533f070e327" }, { "name": "bath_tone3", "unicode": "1F6C0-1F3FD", - "digest": "9a59a4360effb48af4cbb1a953655ef61e69375407038b4d0bd8068fbaf3cc16" + "digest": "654c0cd083a67ff330a38d07352876d265390e5399e5352598d64a6c7e5eeba7" }, { "name": "bath_tone4", "unicode": "1F6C0-1F3FE", - "digest": "01aafa8a53a08018b9fbf28ec6b3b918d6bd0dee7a891196f32f81f60d114f0e" + "digest": "adad88c6830f31c4b5be194d1987d6aadf4adf45e4cb7f2e4657f0d20c0d663a" }, { "name": "bath_tone5", "unicode": "1F6C0-1F3FF", - "digest": "2733e81ccaee21231c2e47e3310b431e9bd784bf34f0db609f8eadcee359500d" + "digest": "952c4c9bf24e001e23a33ebf97bd92969cd9143e28ce93f9aafc708a8f966903" }, { "name": "bathtub", "unicode": "1F6C1", - "digest": "9515e3bb9ab41350305e64fc6877aae82d51e1ba8ce8b2b4b8ffaeda960820cd" + "digest": "844dffb87ef872594195069b0d0df27c3fe51f3967ccbc8b2df811a086dd483a" }, { "name": "battery", "unicode": "1F50B", - "digest": "7d4d475c1d5b1be55c319953e3363ff864fe4fcd921a8aa649b9a547c0894deb" + "digest": "949ae06648667fb13d9121a6dfdd03bf8692794b28c36e9a8e8ac4515664449a" }, { "name": "beach", "unicode": "1F3D6", - "digest": "52855d75cfa4476ccc23c58b4afcb76ee48abb22a9a6081210c8accefdf33099" + "digest": "37fa2158977d470186caaa1aa06669b6dc5026ba49a0c44c5255541f8e974e26" }, { "name": "beach_with_umbrella", "unicode": "1F3D6", - "digest": "52855d75cfa4476ccc23c58b4afcb76ee48abb22a9a6081210c8accefdf33099" + "digest": "37fa2158977d470186caaa1aa06669b6dc5026ba49a0c44c5255541f8e974e26" }, { "name": "beach_umbrella", "unicode": "26F1", - "digest": "cefe8e195d21d3e0769d3bfe15170db9e57c86db9d31cacb19fcdc8d2191b661" + "digest": "d045f1de10038b9fb1eaa2529b2f80b7e3be1cff503efcc2d680663d1fbbc18f" }, { "name": "umbrella_on_ground", "unicode": "26F1", - "digest": "cefe8e195d21d3e0769d3bfe15170db9e57c86db9d31cacb19fcdc8d2191b661" + "digest": "d045f1de10038b9fb1eaa2529b2f80b7e3be1cff503efcc2d680663d1fbbc18f" }, { "name": "bear", "unicode": "1F43B", - "digest": "b5ac126875c20c82b9e3140b143233944a2e4132d781d0b575e83673988523cb" + "digest": "a4b9066eaa5681e6af06e596a96a5217037460ffc3b013e8db4d34d762413246" }, { "name": "bed", "unicode": "1F6CF", - "digest": "1919245d7a76799aad0533eb72db2cbaa1f32ee8231a0c1989d3f233f2d42370" + "digest": "08f6e20db51b1fb650b390a0a3074938646772f3fcee8c295d47742e44fe1e30" }, { "name": "bee", "unicode": "1F41D", - "digest": "69ada63403c8dabae39c63ba143143aeb59b66faae6aa82d8342337925a9e6b5" + "digest": "5beb9a1650681b4adf69999d4808231c38f41a3ec693480b807cda86f964c570" }, { "name": "beer", "unicode": "1F37A", - "digest": "b71dd6efdb4ce7d9d71fdbf82a2ccf83841fb0cceb119ee7da1e575d3bfa853c" + "digest": "69e227104976548ee0f37375fe1526fd65ef0a328d2d92db2feb1edfd7032bd4" }, { "name": "beers", "unicode": "1F37B", - "digest": "994108cebfe0c614c05967af4e3864d8adbbfcf7cccef1cbd42a47b7dfabf80c" + "digest": "db8b32d93bf6d161a3b027e55651d8f51231b13928b3610987ef62bb634d7501" }, { "name": "beetle", "unicode": "1F41E", - "digest": "ec351ce238a81711eef00e5be1de2e198423cf524b60e531d435902b44420edc" + "digest": "5aaa428e3f63f7cd1696839ab05be03fa0cd0cbed30a05c36cb270da330c3849" }, { "name": "beginner", "unicode": "1F530", - "digest": "13288d9fc221dc02f4181b998104e13c3c5c98d3c4e650186bef59a46d39f6f0" + "digest": "2de4fdf92f182c42b12b7527034eaf767d996848b61f31ee69167728411ca0b1" }, { "name": "bell", "unicode": "1F514", - "digest": "784b9a82814ce14a264e54b3a8f8e706f3c7b763646d9f8174c4aa84ad41ef09" + "digest": "18d419417746ead408072b78fe2edb6314cdb49492873966fa9f9f06be09899b" }, { "name": "bellhop", "unicode": "1F6CE", - "digest": "c15455f1b52ac26404b5c13a0e1070212ed1830026422873f4f6335e26e31259" + "digest": "b8187bc4059f6a0924a47fe3f6c07f656bed0334bbcbfa1e89f800fe6594ff08" }, { "name": "bellhop_bell", "unicode": "1F6CE", - "digest": "c15455f1b52ac26404b5c13a0e1070212ed1830026422873f4f6335e26e31259" + "digest": "b8187bc4059f6a0924a47fe3f6c07f656bed0334bbcbfa1e89f800fe6594ff08" }, { "name": "bento", "unicode": "1F371", - "digest": "d59314b17a8646d4a78fefb7b79f289f33d4aaea893fed4cad0b890df63395e7" + "digest": "d46d4f681c5da7f7678b51be3445454a8ed18d917e132ae79077f05310e485f1" }, { "name": "bicyclist", "unicode": "1F6B4", - "digest": "e7359d615d40325bb08a145cfebde2ecef448deeb21695a34b55d3ccb971447f" + "digest": "3302147b6b47c16adb97d78b7b761a1ca80e6d0b41d0b60f4da338d2f55f968b" }, { "name": "bicyclist_tone1", "unicode": "1F6B4-1F3FB", - "digest": "e45808faa32f4ffb881d3569c0b8e2c69d4a64665f4d1fae24d7a1e5f1d3ea4b" + "digest": "27eaae0eb61f5e7b3cd9faf02c042d6643a368051a7c9d7da4e0fb9802d39242" }, { "name": "bicyclist_tone2", "unicode": "1F6B4-1F3FC", - "digest": "92a3494270d1da6a117e92402c7898d4a7fffbe3d6143fb9ae445c4827c0c8a4" + "digest": "39ee9e1071700da7079ad0146bf5711c3a222991eeca8b29b72a65677604444d" }, { "name": "bicyclist_tone3", "unicode": "1F6B4-1F3FD", - "digest": "6fdf1db2bbd08d06b643b08f0f29daeaa20e0b8c8abec21132191f435cc05e42" + "digest": "03e1d2c4232c896147a9d4bf43becd61edbb5c84fc7193ecea474c0f9fb36817" }, { "name": "bicyclist_tone4", "unicode": "1F6B4-1F3FE", - "digest": "d9c27848e1bcc8197c858e1ef12a537f4ed6c77fb211b6731388dc88c2bb7a61" + "digest": "61393d9c4805be0379d86dd5bec9a1b02314433ab36cfd85bb48dfd073746617" }, { "name": "bicyclist_tone5", "unicode": "1F6B4-1F3FF", - "digest": "4892af1a8a0229a813d7b8e3d88481c2365e3e1a5ce2e0e27ce432c5336da810" + "digest": "2b46d5f8303e5710dbf5db3a4edc9d88a032fe123fe79158024c9f51df5458c6" }, { "name": "bike", "unicode": "1F6B2", - "digest": "e726f97b5432f46ed51328c0930d1d63b3a2d7b67c5c2303a5ca997083cfcac1" + "digest": "b41daa7c549d483e2336186a28baaa8ecb11986f490c0c54c793c44900c8f652" }, { "name": "bikini", "unicode": "1F459", - "digest": "7612fcb72c005ae7172260825f588d6995f2bc919cb3d283dd4591f6872a1855" + "digest": "07fe156f64673818d69ce3bf03950ca59e3b5d346e45ca541da4078ab791f5ae" }, { "name": "biohazard", "unicode": "2623", - "digest": "81f8309318051255ed4dc18855a3cd3f8657a6f3b2d368caa531a57ce0e34235" + "digest": "96163e31f0b8dc5a59772133ede9cc2f40f94330d0b15e3d044b28747e2be788" }, { "name": "biohazard_sign", "unicode": "2623", - "digest": "81f8309318051255ed4dc18855a3cd3f8657a6f3b2d368caa531a57ce0e34235" + "digest": "96163e31f0b8dc5a59772133ede9cc2f40f94330d0b15e3d044b28747e2be788" }, { "name": "bird", "unicode": "1F426", - "digest": "3f219e5aa18e2f1febfd368ec133786cd2eab357db79984cb8ba07fed0eec7cd" + "digest": "f916eaf8f271b3767ade9eabb69594c0479f45472d471cabaf59f6e965c161e0" }, { "name": "birthday", "unicode": "1F382", - "digest": "9eb1adb0170ab851042cb3da8b64f02f4e4b63e7a07db405b55b50f5bbd3cacf" + "digest": "89e7c4c598ebee8ec8ab11ebe4ccc6defb7c4d2987ee2379a19b3b59827dd98a" }, { "name": "black_circle", @@ -762,82 +762,82 @@ { "name": "black_joker", "unicode": "1F0CF", - "digest": "1eb85b8e2b93dec221a97a1c309dee3683408f6166e1a1a1bd83cf2f64f007dd" + "digest": "d004b25f186494d5b2c65204caa9daecd749c840a0bea5718735e18109e5394d" }, { "name": "black_large_square", "unicode": "2B1B", - "digest": "0ff2112227c38ed8c30b0bddf2300e87d2a244cd7fe81886a1cb1a287a7e8bb6" + "digest": "cbd90dcbc2f674eafa53820548b5263c18c9845ab39937f085e85aca0aebb479" }, { "name": "black_medium_small_square", "unicode": "25FE", - "digest": "f1010aa694084ad4655a9d4ce5a1711eaab21029e31bf8798253f0ad644e8abb" + "digest": "ab38363c2e862b8f67c719397a09a18e1ef996eec190691fdf769f5cfb209660" }, { "name": "black_medium_square", "unicode": "25FC", - "digest": "06bf48ffbc84e71bbb90aa0f6c3f9f53533c6fd063ff168cefdb0a050dcf8302" + "digest": "c9ffa87c37e8ee65fadcf755176949901aec7367e02abb85e63cad60cd922116" }, { "name": "black_nib", "unicode": "2712", - "digest": "c1361df4a5ae9f2ed121d26928021e96c6865331861e1960700d39cb1bd49355" + "digest": "58fb23b1155102970eaa23765e7d529a21e8e545e076ec1158bf11b4de5f51a8" }, { "name": "black_small_square", "unicode": "25AA", - "digest": "d430ec419869fa1b5ba980ddeecb4c5ad5050a2b3421e45048cc184a6fc46899" + "digest": "f69be6de578fffce5a3e60eda690104b2ef6a855c630040104fb760a02ff1aef" }, { "name": "black_square_button", "unicode": "1F532", - "digest": "85b6587b6b2c3544ddb7bc07207b0740e437744ba134835836153899ae396135" + "digest": "9d818fcd08ed38cd0bbbcfd83e665aa29b3761c0d8b9806d8954d36785e267a8" }, { "name": "blossom", "unicode": "1F33C", - "digest": "029bbe385e07e2017dd918d685e107678c9c0e919a3bd1521b7a0d7c9172da05" + "digest": "e8cf369d4e4cdb4eccc2ebcbb35439b0344221115701daae642e58dff8544922" }, { "name": "blowfish", "unicode": "1F421", - "digest": "b5ee9f6ffabb74e3024067f016d17a631ee98536cb9c7269d55fa867f95a54fb" + "digest": "e706849ed00f08a82312381c76f6f9ba6cc261fbf87a839c85e7dd54138f9dc3" }, { "name": "blue_book", "unicode": "1F4D8", - "digest": "6fbf227fb9facc1957bb9dfb31749cbfe66c3afe8081347f2471fd64ef2e6b3a" + "digest": "4c845748fe890516b32981b0b62bf3e8e9d906840c2060179f4f844100780615" }, { "name": "blue_car", "unicode": "1F699", - "digest": "e61ef2299d11fc01e9d6c496d188a7211633946706f6e771c412368346ca16f4" + "digest": "eca91934eb5481726cfd897b1ed5eac306e14d02499fbe49316aaec6c72b6707" }, { "name": "blue_heart", "unicode": "1F499", - "digest": "1af8d04173e0a984360786f6031220000dd548b8c912a68fd51f2ba490a9e16a" + "digest": "2caa0c8d18538cc871c6fe328a52f71e1df8aabf4d1cc2f5324b261d1b8cb99a" }, { "name": "blush", "unicode": "1F60A", - "digest": "d615cda0f7c185ed8a92008204043ef769f3b7fb5424d595aeaaf3827bcdbd73" + "digest": "3bfe8d603cfa39999c164779f666d39bbc507f124ba80233ee72da7b3b0c0457" }, { "name": "boar", "unicode": "1F417", - "digest": "c23a06db0337597e361ae581eacd4faf9926c6b7db0510d3599eb2e2a73315cb" + "digest": "c9d67479cace427ac3c30460fcffa1bf9a8e5262c0390962405dbbe6bf830fa6" }, { "name": "bomb", "unicode": "1F4A3", - "digest": "0099e7435eba35f4f3ad273993293693a8b5cd110567c95ed83e5b4e2d0978ff" + "digest": "0155559abc4084f80e9b0b2a2091b8710ddd6369993b7fdd0685f4f8c2fd7e6c" }, { "name": "book", "unicode": "1F4D6", - "digest": "152408f2ff9949b7cbe57f623e4f875aa8dd0b02317e03cc914e1ea3712b3fc7" + "digest": "9d912a9d1bb10dc7f2645b345ed09e90461e83df0de275acb806f1f75cef1fcf" }, { "name": "book2", @@ -847,32 +847,32 @@ { "name": "bookmark", "unicode": "1F516", - "digest": "a2e0c6f5466c1b2fc148b20f6afcf4a878f4df55b0181f61fffa3ff727dcb251" + "digest": "5705e3108259d6900649157843c50e22d0086c3630b291d3f942da1a736e3e3d" }, { "name": "bookmark_tabs", "unicode": "1F4D1", - "digest": "16135d62ff440722bd1ce8f84219be6a5eb3120a1597bfda4aeed4a2d9e7d7b2" + "digest": "c8fc7c9f3f82e1ccc97fc591345fdd88b09eec0fca428d8d4632a121cf1bc39a" }, { "name": "books", "unicode": "1F4DA", - "digest": "ba019e4174639440caec424b30dfa016fe71a6f7436fe63025a2e3609ebfc012" + "digest": "cbcf55d39dd05d26ef7350bc51e0e2f064f78bb8f59d407b516d63f68558f8e4" }, { "name": "boom", "unicode": "1F4A5", - "digest": "ec26246935c99749950612d69c06435ccdc126f14426a48a7599c5b6b91d9d58" + "digest": "f5400e9583f7f997cd2385f21379f6229424a9b221445bc8f36c0bb64bdb3168" }, { "name": "boot", "unicode": "1F462", - "digest": "7ed639d52e285b0f46064dd4e1f4a8fb5814e1b2dc47c6f93cb349a6ac7ea97a" + "digest": "b4706ff35909a6fb759a3b8a797e90cb67ffc60e4853386a7d89ace9693a9364" }, { "name": "bouquet", "unicode": "1F490", - "digest": "b699f13af218560344f3571436f87b6f8c5c9f0fa0308836937667241b3fc7aa" + "digest": "b93751a27b40f6185a22b3e8b413f0fe09b6010d1057c672e1a23088e0b8286f" }, { "name": "bouquet2", @@ -887,77 +887,77 @@ { "name": "bow", "unicode": "1F647", - "digest": "5e260c38cfc80cd2f20ef78d982126dbf90934f7afa12c96d0b7b413beb6d4e0" + "digest": "33cd6da4d408f18d98bebc6a277dea8b914150e32ee472586ce3f1eb814462bd" }, { "name": "bow_and_arrow", "unicode": "1F3F9", - "digest": "1c23469256331ea4ff03c036f89f0e63ad3228c51faecba50129da99b7eaddf3" + "digest": "051b4d50ab21a68b8583a6313ec183e3e1e96f493b0f4541fbb888f0b95fdd4d" }, { "name": "archery", "unicode": "1F3F9", - "digest": "1c23469256331ea4ff03c036f89f0e63ad3228c51faecba50129da99b7eaddf3" + "digest": "051b4d50ab21a68b8583a6313ec183e3e1e96f493b0f4541fbb888f0b95fdd4d" }, { "name": "bow_tone1", "unicode": "1F647-1F3FB", - "digest": "d3ec7ef70b355ba310d6fae7130a4e4cd11526b6e219474b5678a2b3ba1077f0" + "digest": "995c8400ad60d5adc66c9ae5e3c0ecf56c48b478ad79418d45b6289933d25bdd" }, { "name": "bow_tone2", "unicode": "1F647-1F3FC", - "digest": "c2905c0feba15fbc533cc6b36038eeda30f729182aa544f1d9164f5ccfed64d5" + "digest": "af89eec2fccda99d9bdd373b2345595882fee1c0a15d29af9028089e20255325" }, { "name": "bow_tone3", "unicode": "1F647-1F3FD", - "digest": "298fc646d96c307eaa137c80b403d8355539ed8af13d3954a4ccacef67d341fa" + "digest": "015d8122abdf2d0caa03815545f50fb7a71e05dacd46aaa133cc9ace5192f266" }, { "name": "bow_tone4", "unicode": "1F647-1F3FE", - "digest": "27db8401aa62a2544b24ff839b332958b5e8c3ab3fd7a289d3c62c654705da60" + "digest": "e8409096a795b775def654d36aeccb8eb91e83d7d1b32145cd73fd0b7b9e885c" }, { "name": "bow_tone5", "unicode": "1F647-1F3FF", - "digest": "168cdf834edb54723cf1c32311d4117c288132c5f76d6c415726c7484158c52a" + "digest": "d87042cde8dbad9fb1a91a2ec60116e27b4a76388b5779d771a0bbae12a2814d" }, { "name": "bowling", "unicode": "1F3B3", - "digest": "0e888bcd1a5cc1ea7b07cea255ccb04dcdc87b0337b74cdc96a708aad7975768" + "digest": "737f2cdfa4ac964baade585a39771b18080bd5e9b55c8661d3518f468f344662" }, { "name": "boy", "unicode": "1F466", - "digest": "f349ab3e1015b4ccda5faab6a355f9c38e36e7c1cd667084563a14a2b11036ea" + "digest": "7bc0173d8c88f3f12d41f213f7a3a9f5ebf65efad610fd5a2a31935128a6a6c1" }, { "name": "boy_tone1", "unicode": "1F466-1F3FB", - "digest": "4d04a5e45c9f9749de580321a212e14304b4ffcd229fa971fb59d97e6124262f" + "digest": "c0e2f0483715b239fe145b0056566f7a3a722319d9a87c1e66733dff1916a19f" }, { "name": "boy_tone2", "unicode": "1F466-1F3FC", - "digest": "0c9d6b6b1b3da68b9ef1f0f01efa4d170a48cfc66de4f577f8669c160b81cc97" + "digest": "0001d0bd1ff4dbd898604ba965b4039d09667d955bc0349301b992f9ab6dd7fd" }, { "name": "boy_tone3", "unicode": "1F466-1F3FD", - "digest": "7dbecace78edb2aceffce6cb4d49ca132b93d80c26a8f1526a18832a2f23454a" + "digest": "e0f08755955fd2e0bd1c5d5e84429b2a234b24a744bb50bb9f1148495b2b29f9" }, { "name": "boy_tone4", "unicode": "1F466-1F3FE", - "digest": "49f9c633afa8ff81068c78717e0012f8936fb3dcdb8b57342410f57f0635ae7c" + "digest": "04b6bfee58a26b1ce2e5b403504a7033aaf395f03f5cd23e824f32c90c395fe6" }, { "name": "boy_tone5", "unicode": "1F466-1F3FF", - "digest": "17e2ec379c7b542e6c2c5deef992af5f1fbaa3e288d1f71c8c984fb91a698cd4" + "digest": "0f76e97237203950da36c737dcc6f56dcd6c123401a8c817a0636376c7f38ef5" }, { "name": "boys_symbol", @@ -967,72 +967,72 @@ { "name": "bread", "unicode": "1F35E", - "digest": "43697495538bfed11ed75213af8b1bdc14ef359d9b472cd7f9130fcb0a198680" + "digest": "81739830f16f33e6a1dd7cc17c25df207846062bb5167bb8abed7fdd49268b86" }, { "name": "bride_with_veil", "unicode": "1F470", - "digest": "37e75fbb2b0d06c900d51269b99107c60b61453dbf218b54df3011a455cd6dc3" + "digest": "8e24bd91c3f564cf6148f2b3b4a7d692c11dd059e76a13331fdfb04ae060ea70" }, { "name": "bride_with_veil_tone1", "unicode": "1F470-1F3FB", - "digest": "44072e54e0618d2675a5bfd6572108590e51e8e733381e091e8754ee96c2cf20" + "digest": "0bd2f16f72586f50e768b14b9b353f2e98ccbb2581a568c33b06be56e70ca063" }, { "name": "bride_with_veil_tone2", "unicode": "1F470-1F3FC", - "digest": "f0acd961e108db9d9dd5d1b06e708b2eb6a7ef7235d6c8678b9319077faf4fa8" + "digest": "e5463f811b2075754f0718b891757cd2e81071edf7af2215581227e1aad1d068" }, { "name": "bride_with_veil_tone3", "unicode": "1F470-1F3FD", - "digest": "3f7adddb41ead3cd07098799ab2a5b8e8842344307d9045264403fb685f20555" + "digest": "e5a053a26f7ccebae7eb12f638be5ed80f77b744708d783eab2eb8aa091cf516" }, { "name": "bride_with_veil_tone4", "unicode": "1F470-1F3FE", - "digest": "5f7199fd99319651f3a7b3553cc5387c59b65cac1eb020441e19b5c12c807dc7" + "digest": "410e23825e4401460946dc67a618bd3ace6e1a7c07dd88580a2349423685261f" }, { "name": "bride_with_veil_tone5", "unicode": "1F470-1F3FF", - "digest": "4b1f6c33dd72a3a11c764bb00e7be7441b39c7af78aae52141276a279d63ab78" + "digest": "454e87e5a74e13e5b4993541231516fbbe6dbe9f990e1a6f3f4a744d7d4c1615" }, { "name": "bridge_at_night", "unicode": "1F309", - "digest": "f81cc36de8edbdf3fe4d55932d5c6c8ad429487ec1f7af044611b6dc950ee09c" + "digest": "9d3cda5a59e27e3c90939f1ddbe7e998b3ea4fcacfa1467dea0edf39613c2d7f" }, { "name": "briefcase", "unicode": "1F4BC", - "digest": "a3c3e802191f3e131683dac1fcd81e294dea72af8e65c94972990924c79c5619" + "digest": "9d00d6a92632aaadc71b017f448c883b27eb31a7554ebb51f7e3a9841f0f7f2b" }, { "name": "broken_heart", "unicode": "1F494", - "digest": "4dee349274c2ea44d1c0395cbd39356b88897b0c45040aa40d8cb2607ee67420" + "digest": "c7ca53f444d72e596af46b61ffbc9e7c18a645020c22691e44f967db98dbf853" }, { "name": "bug", "unicode": "1F41B", - "digest": "bac4660ee8dcbef0023691804ee3fad3ea3d4bac20d847a5913cee6e7dca826c" + "digest": "0dccb1d5eb91769377b4c5b310f007b60f54a5c48ba9e467b3a06898a4831b90" }, { "name": "bulb", "unicode": "1F4A1", - "digest": "af5394230f95781c7eb8054b1a13732a6e6170318599c79e9ca2a816a5b821a2" + "digest": "ccdaa2dfde5a88a347035a94b9d4d86cfc335ce0a73292423f5788a4bd21a5a8" }, { "name": "bullettrain_front", "unicode": "1F685", - "digest": "59afcd289500bd4148b1b91f560a5ce8ac9e1b52eddb8fec857ff5d171f017fb" + "digest": "5195a6a6d23f28e1aa5ebac6ede0f6c6a8b7ff33a9edf034814f227fe976177a" }, { "name": "bullettrain_side", "unicode": "1F684", - "digest": "79ff8f579081a2f1c3b05311a18ca432adb026a7860875cea4a5460e49b2a474" + "digest": "96e74842e919716b7bbbab57339bfd70f099a9bcb4710dffd7c80cf38a7bbff7" }, { "name": "bullhorn", @@ -1052,37 +1052,37 @@ { "name": "burrito", "unicode": "1F32F", - "digest": "4babb1af1136ab2334d26495b0be779d0bcc9516fd956fc07ffde427d11122f0" + "digest": "b2cf81f1efdf87e674461f73f67cd4b58a5f695e65598d0dd3899f2597da43cf" }, { "name": "bus", "unicode": "1F68C", - "digest": "476e7a5e92f64038e5012205395efead51f1c10b3edb25380f38da97e2412edd" + "digest": "192850b762edad21ac8770df38b9cae6d2bc1697a838462f3e36066bfb4eee50" }, { "name": "busstop", "unicode": "1F68F", - "digest": "3bcf82872ab6abb0278238c71bd004a40c46696bdda05f54c153d45d6fe88f15" + "digest": "adabb1ec36402b33feb636eae3656e5a8b51ff1071bcb14125d8ab80d6d12d2a" }, { "name": "bust_in_silhouette", "unicode": "1F464", - "digest": "2230844993ab011fe2756a1aa3873ff7d5f7d888bddec408ba0b32e4f6003570" + "digest": "277ae43301f1e49e0be03c8e52f0dc7b70c67f9d146bca0a14172e0098f115e6" }, { "name": "busts_in_silhouette", "unicode": "1F465", - "digest": "d1c3cb6d437616834425a53621c0bc0a6b368d745dd9da2300a3db4543d57660" + "digest": "7fee96f1b68bb2c6002e47f2ed13c06baa6a3168441b9aca572db7ec45612f7b" }, { "name": "cactus", "unicode": "1F335", - "digest": "e87588e6548d201db903dc0523b3ccc83c6b559981d743eae1504ce668cd8be4" + "digest": "2c5c4c35f26c7046fdc002b337e0d939729b33a26980e675950f9934c91e40fd" }, { "name": "cake", "unicode": "1F370", - "digest": "3947783d128018f5e396602d0492cb5c31e8e8df98af01eda7cade71aea8d989" + "digest": "b928902df8084210d51c1da36f9119164a325393c391b28cd8ea914e0b95c17b" }, { "name": "calculator", @@ -1097,42 +1097,42 @@ { "name": "calendar", "unicode": "1F4C6", - "digest": "00bb700dd88efbc43bc64263491cdf77965130b1dc23f31e682905c3dfe4040c" + "digest": "9d990be27778daab041a3583edbd8f83fc8957e42a3aec729c0e2e224a8d05e3" }, { "name": "calendar_spiral", "unicode": "1F5D3", - "digest": "1dd5da98bb435c0c3f632bc0a5c9fdde694de7aee752bf4bb85def086e788a2a" + "digest": "441a0750eade7ce33e28e58bec76958990c412b68409fcdde59ebad1f25361bb" }, { "name": "spiral_calendar_pad", "unicode": "1F5D3", - "digest": "1dd5da98bb435c0c3f632bc0a5c9fdde694de7aee752bf4bb85def086e788a2a" + "digest": "441a0750eade7ce33e28e58bec76958990c412b68409fcdde59ebad1f25361bb" }, { "name": "calling", "unicode": "1F4F2", - "digest": "2375828085f2efd17b8a5ebb3cfec1e420190913328a7a0dd9ff0f67c7249ffb" + "digest": "acf668c75c11c36686005788266524a972fa1c5bcf666ff3403d909edc5cee91" }, { "name": "camel", "unicode": "1F42B", - "digest": "9ff789ab50b51cd9e7fdc7fbe8d6f913fda95dfd425949f97974548652a53ce1" + "digest": "5f927927a7ab1277d0dc8b8211436957968b1e11365a8bf535e9bb94f92c5631" }, { "name": "camera", "unicode": "1F4F7", - "digest": "d95192b9ba0f566d8874099125def031e15297d1306989ea9b6a49f7b9b56661" + "digest": "fde03e396822a36cd6ae756ede885b945a074395264162731ca5db47a3b39d80" }, { "name": "camera_with_flash", "unicode": "1F4F8", - "digest": "4db6fb3fdb9a004537dff97f4197c7ed87c9c978ba9ac562ed8bb7c1fa260d38" + "digest": "9afd380208187780f00244c45d4db6c5ea1ea088d4a1bd8fc92a8f3877149750" }, { "name": "camping", "unicode": "1F3D5", - "digest": "f0855dc78bf6f3d06b3c2fc19180c8ff23d9e22871658fcc26a8fde08d328a0a" + "digest": "a42a4ff9521affa72db7b0f01da169b4cb6afb9db1c5dfad47dd4c507bfc30d9" }, { "name": "cancellation_x", @@ -1142,47 +1142,47 @@ { "name": "cancer", "unicode": "264B", - "digest": "b990f85e9f62017d99526244eaef5c5e56f8808698011e85d44de1d2ed87f1a2" + "digest": "528c6f21df99a756b553d93a7f395b0f662b30a323affd05f0cedee8ff7b41d6" }, { "name": "candle", "unicode": "1F56F", - "digest": "5eefd555951e65298583009a307acc6fb6d02c88325ef3adf231717e75e5a333" + "digest": "211c04dc3a91b071c284d4180ed09f9d3320e3fd6ba8a9fddd0677bc97fd12cb" }, { "name": "candy", "unicode": "1F36C", - "digest": "f14203c408173fbb94b4ee69d6de67226a17dc51b0cbd776f62623ee03fd2eb3" + "digest": "9cff4538918f60f770fceb96e964f5dc3ce31fd08ddd2ab3bfdf2981bfa74100" }, { "name": "capital_abcd", "unicode": "1F520", - "digest": "2a7cc876218b8c244b9802448ee25ce5004671a4f00ea950a636d8c3b766dbef" + "digest": "a416d0b3f564037b680f801fb773b6eaf67225e2cbbfd2cb8a5db0de044321fa" }, { "name": "capricorn", "unicode": "2651", - "digest": "03a5fd064c10f47c7fd0ae318c573bb559c269b1b2d61b45aa5b8ce9b5fbd9df" + "digest": "f11abad102603737b55486fe2ea4d01f28b203394bcd84f19a7948156e6c4b96" }, { "name": "card_box", "unicode": "1F5C3", - "digest": "7d760ae1d44e6f4b2aac00895ca86b5743f8b5ca157ec2bd21ce2665e50ad23a" + "digest": "7a6199d562f30e02ed31094de6aebeb99eae8ac156f6910463dfed73256f4c9a" }, { "name": "card_file_box", "unicode": "1F5C3", - "digest": "7d760ae1d44e6f4b2aac00895ca86b5743f8b5ca157ec2bd21ce2665e50ad23a" + "digest": "7a6199d562f30e02ed31094de6aebeb99eae8ac156f6910463dfed73256f4c9a" }, { "name": "card_index", "unicode": "1F4C7", - "digest": "150950903eccb468981c58b87ed7c1ba44e17f52627d695f660ce96b3d9d6e8e" + "digest": "86e187e0a72ca5d00207d6ef34d66ce15046848a831c2b5184fb840c5332a2a8" }, { "name": "carousel_horse", "unicode": "1F3A0", - "digest": "d6862085550fa139a147dceb1b2b9f950a08dcd01cecd8b8697f9c7992ca054e" + "digest": "c0e7059efc39a64233f774c02ddb1ab51888fff180f906ce13a6e4f9509672fe" }, { "name": "cartridge", @@ -1197,17 +1197,17 @@ { "name": "cat", "unicode": "1F431", - "digest": "002208c0c9165971853ee05cd05513175a913376a462a345a939d73401c6acb7" + "digest": "e52d0d3a205a0ba99094717e171a7f572b713a0e21b276ffa4a826596fe5cafc" }, { "name": "cat2", "unicode": "1F408", - "digest": "fbdb726cc035f83784dcfe2d9adb85f8aeec429064aed5c5ca0b8be406068aa5" + "digest": "46aa67a99f782935932c77b8de93287142297abe52928c173191cf55bb8f4339" }, { "name": "cd", "unicode": "1F4BF", - "digest": "bd4d4eef2cc0b1e4ee1f5280f922743e76f27d35836987801b2b48969eac17d8" + "digest": "16363d8a34b873c12df6354b99f575cae3d80e0d27100ed7eea70f0310953c7b" }, { "name": "celtic_cross", @@ -1217,302 +1217,302 @@ { "name": "chains", "unicode": "26D3", - "digest": "a6a915d9c361e1564e13cf2d33ad5df3d684aa349b8dc5909e6343d67401beb9" + "digest": "3884cdbc6f2b433062af06f942552e563231c24727a2f10fa280b3bb7aa614e2" }, { "name": "champagne", "unicode": "1F37E", - "digest": "77395d3afe5cc10bfdc381120bae2ae4aefdaa96c529536413873a696c5fa713" + "digest": "9e6e8987f30a37ae0f3d7dab2f5eeb50aa32b4f31402b29315eb2994afc72457" }, { "name": "bottle_with_popping_cork", "unicode": "1F37E", - "digest": "77395d3afe5cc10bfdc381120bae2ae4aefdaa96c529536413873a696c5fa713" + "digest": "9e6e8987f30a37ae0f3d7dab2f5eeb50aa32b4f31402b29315eb2994afc72457" }, { "name": "chart", "unicode": "1F4B9", - "digest": "9fd5f8cd99988bbe0fabc89a0b23e28d1468641d2f9468e82b7148a1948d8236" + "digest": "a092dbc08f925b028286b2b495a5f59033b8537a586a694f46f4c1e7c3a1e27f" }, { "name": "chart_with_downwards_trend", "unicode": "1F4C9", - "digest": "6fe456d76c0a996c12049057b5d60129098a9deddfa2d133cff5c4400e4595a0" + "digest": "5db7ccbc37665736a9c0b2f50247dcc09e404ec37f39db45b7b8b9464172a18c" }, { "name": "chart_with_upwards_trend", "unicode": "1F4C8", - "digest": "e83cc4cf4228bd77e030a19755b11cf75cf671f40973c23e240afa54d9de478e" + "digest": "bc4ea250b102fe5c09847e471478aff065ad3df755d9717896d38d887d9c6733" }, { "name": "checkered_flag", "unicode": "1F3C1", - "digest": "77501c2c66af31f72f5c05f21e87598cd59740b5cfc02926c66dc755bab3c3cf" + "digest": "0e77180e0cf9fc87e755a5a42cf23aec6bf30931db41331311e97ba0be178b78" }, { "name": "cheese", "unicode": "1F9C0", - "digest": "5897036ba97b557868bb314fcee83b9d8a609c8447b270a0b3d34a29ce7496d1" + "digest": "50a6cb906c2120e2bbc0e22105924262007cfe1554d7b02b8cc84b6adedc6a0b" }, { "name": "cheese_wedge", "unicode": "1F9C0", - "digest": "5897036ba97b557868bb314fcee83b9d8a609c8447b270a0b3d34a29ce7496d1" + "digest": "50a6cb906c2120e2bbc0e22105924262007cfe1554d7b02b8cc84b6adedc6a0b" }, { "name": "cherries", "unicode": "1F352", - "digest": "5a0ba73039e4b56e3d16a1c70ad992f41af7a16f6d5ba4b5337bdf338276f0ff" + "digest": "13b8db9e7e6eec8509aa80c762966e1bf3538fcb1ac3d6eab18ee4da1528cf84" }, { "name": "cherry_blossom", "unicode": "1F338", - "digest": "b40533225291f539ffe97e4ab1d70d07e179b2f9345b2814355164d0407cf3bf" + "digest": "af3083f5f8dd94936113f2e16caba5aec7a774d5589aa08bf5de82a2d278cc66" }, { "name": "chestnut", "unicode": "1F330", - "digest": "6a2a37899d28326daf36965b343b2646492c2c0cee8871321cc17315d6252a9a" + "digest": "9f85b79b207a69ab81ab88dcef04954000965b039b4cf57de5f1b381745ab98b" }, { "name": "chicken", "unicode": "1F414", - "digest": "13d770684a11ea10c0ae7570a98c5dfafd4bfb78ac3f72f46729aef9060b85c0" + "digest": "57ceb4459d183740009caac6ebed089d2f1e12f67c138e1be1d0f992313c0ac4" }, { "name": "children_crossing", "unicode": "1F6B8", - "digest": "654d2502c1edc57c5ab4237df76db3121f6b8735eb13d30bffd305605a083445" + "digest": "0ded7d9aca0161e8ef8e2858c3c198e70e4badc7105ac3a6886e06975de19106" }, { "name": "chipmunk", "unicode": "1F43F", - "digest": "1ae3c838450afcbbe8a96992481dde252e343ab83546d0789ebed81a78ca9188" + "digest": "5b0dc1a859163097727ba2ba5ffca38b0a54d925eebb089977d28d0b4d917a3f" }, { "name": "chocolate_bar", "unicode": "1F36B", - "digest": "2486b7265048eb2294d6be0a0a8a4d6067df95721ace9d131d8f715a27ba8cf0" + "digest": "dd273e5050488acaf885f8a18b6e2b3901f69c5b39fa6465fb60621783d4109a" }, { "name": "christmas_tree", "unicode": "1F384", - "digest": "454c08870eaa84283c19731ed3b10c4868d2e2f0cc44f2feba0de9ba4cc9c4e1" + "digest": "ce60cbe2ebbe8057be8edea2392455fedd2bcda64a0a831f6a1942028af7e747" }, { "name": "church", "unicode": "26EA", - "digest": "b62e838ffb0dfefeced1707359437b6815e0721783b549212282e08617402f6f" + "digest": "2c328456528f7336e59443e20ec3ab22fe71f1fccb1dd50d0ad68eb206937557" }, { "name": "cinema", "unicode": "1F3A6", - "digest": "6df56f6a0008d0352740d1e045ffdb702e80c2a6d88b6db1a8bcd27eb3c12dcc" + "digest": "4c26dcdc76f93dbc2a1dc49ed4e132b8e8f2b7cdc1acf5e09b3dfd99430d97cd" }, { "name": "circus_tent", "unicode": "1F3AA", - "digest": "f8b7a7f4cf4f9efd20423acc30abb3a28e2a5183b3e39f5cc88e7e0ed7757d64" + "digest": "fec5f2a06222be8be549178b29720343cc00145177ec387ca4e6f3432481fe77" }, { "name": "city_dusk", "unicode": "1F306", - "digest": "8779066dc9386d05c951b1df1753983c2937a5f3b84d5fc09ed0b172d4ef914e" + "digest": "bba345e949dcc51f5f018220f000223797970c82ead2ab9c822f9dc0847aa155" }, { "name": "city_sunset", "unicode": "1F307", - "digest": "c2530d12204eb518c5a3c8d7deba11170b1412fdf406aea05a69d4c026210d1b" + "digest": "a846df1a4c7c778f8e1729804aece86eb29d2fcb95dc39eaaf2aae1897f3dcc7" }, { "name": "city_sunrise", "unicode": "1F307", - "digest": "c2530d12204eb518c5a3c8d7deba11170b1412fdf406aea05a69d4c026210d1b" + "digest": "a846df1a4c7c778f8e1729804aece86eb29d2fcb95dc39eaaf2aae1897f3dcc7" }, { "name": "cityscape", "unicode": "1F3D9", - "digest": "15251a708d50fc721bd67d8abb2a517c0bade196df3b736e21d79191d749241f" + "digest": "ee360be7514c4bfb0d539dd28f3b2031ebcef04e850723ec0685fb54bd8e6d5f" }, { "name": "cl", "unicode": "1F191", - "digest": "104591d8e7b980cf38dcf8326d36c845384b7a4e6d94c49f36e9946484712a95" + "digest": "fcec2855dbad9fda11d6e2802bc0dcaabab0b5be233508f5e439f156f07602c1" }, { "name": "clap", "unicode": "1F44F", - "digest": "ed6ef8bb78ca1fa295b87222c440c6d5ba4f154f2752bf0d428941260d66aaac" + "digest": "a1860ce7812a9f6fb55e45761e1b79a2f8f0620eb04f80748a38420889d58a2a" }, { "name": "clap_tone1", "unicode": "1F44F-1F3FB", - "digest": "57a1fd1fa2578c30b8a47abb84e81af5f5bbc6c301a5daf0c53d4d07b017e777" + "digest": "18a7022e08223fb2109af5a9b9a5b4f47dc870ce4453f4987d2d0b729ef54586" }, { "name": "clap_tone2", "unicode": "1F44F-1F3FC", - "digest": "2ad4dcd513e55486f21151bf3792e1febf116574d238545b07b4290901430fdd" + "digest": "5954c8658b15e755d2018d8674df84d38e22ffededc4d726c6a33b709f71426a" }, { "name": "clap_tone3", "unicode": "1F44F-1F3FD", - "digest": "2d8c705d4fcc162fb65cd51e2c6683f1129ebc72fba13343533f64ede1c62687" + "digest": "22639b6bd3c53784a2f855d6db7bdf31621519f19dfc29a6bc310eee6421f742" }, { "name": "clap_tone4", "unicode": "1F44F-1F3FE", - "digest": "40ffd41b2b4f59d0040e9d20497e57c4e47f18aeae43fcae02be5c2f50069102" + "digest": "e55248dc163d1bbd118b50cd8767750ead86d082151febbc0a75b32d63abceec" }, { "name": "clap_tone5", "unicode": "1F44F-1F3FF", - "digest": "be55df1ac7600ba086c2ef6ea223ebc62271fa47876c53ade1a1c0151fdc994c" + "digest": "76046b8157dabbe048a07fc318122456020c9c980fc1b8ab76802330e07b3b53" }, { "name": "clapper", "unicode": "1F3AC", - "digest": "a8748398f56fd2c1e6e87fe0c77edec444df7c7dd462d43dbcea6d8de97c81c5" + "digest": "8149752a0e3e8abede2d433d1afab6d217877d0c76adb1e2845a0142c0cdcbaa" }, { "name": "classical_building", "unicode": "1F3DB", - "digest": "6a607b0666141b51d6e944b04f3f6188a5c026396e6105f1d2a5e6b6350cd66b" + "digest": "9ee0d00c43d6e22b6a3ddea67619737270cc7e9294797a19c7c60d5f92aa44fa" }, { "name": "clipboard", "unicode": "1F4CB", - "digest": "4ca1a0b864a962b111d6bdb65373b779f3fff571ffd32d029666f9b708e1ab73" + "digest": "bdd7f7d973c714e59d2903d401a876e6018794c7987c9ca57108c137c5edc25f" }, { "name": "clock", "unicode": "1F570", - "digest": "c48314ccde8bf01acc2b1bc9a6b5aa7d796fc0c8769f80398bc74545fcef31ed" + "digest": "302835eab2637db799acf69b3d795571ef3432251267050db0704f2954e8b190" }, { "name": "mantlepiece_clock", "unicode": "1F570", - "digest": "c48314ccde8bf01acc2b1bc9a6b5aa7d796fc0c8769f80398bc74545fcef31ed" + "digest": "302835eab2637db799acf69b3d795571ef3432251267050db0704f2954e8b190" }, { "name": "clock1", "unicode": "1F550", - "digest": "c0550fa0c385920cbdb775bdaaa5e812097a484c4a32e35ebbafe3a364a4a438" + "digest": "1778eec07ce061c9393e5abee5ca83b24e1ce61d8a75fa2e39efcb31aa160395" }, { "name": "clock10", "unicode": "1F559", - "digest": "25651ac5520505f326457364428de3679cc22ca57278d4c54cc4b60420fa7b74" + "digest": "601fc12ea5280a54c2e69dbb685f454e4165fe771756ed6f89016e29e683a24f" }, { "name": "clock1030", "unicode": "1F565", - "digest": "dbf682bac968fc5a3959af2b96eaaa5ee78306f6341c43c1345b94bc561a3d04" + "digest": "4fd155f08f797542d52cff4b0aa3ca9f080f37a41c301b82f90ff6d4693c890e" }, { "name": "clock11", "unicode": "1F55A", - "digest": "333732dd6c3184f257964bcf5a20a6111f9adb04560b5d12dc613636e846df5b" + "digest": "5c79dc812e812e8a01993ea633b323d654ce3a7ea258692781a4896e4ad2017e" }, { "name": "clock1130", "unicode": "1F566", - "digest": "005999cb37998adea1645d7df63b2705a42db3b4f1a734891d79af3e833764ff" + "digest": "41497ee2020ee5ac9aa5f9b07560f7afca7c422b04214449cfc5cea9f020f52e" }, { "name": "clock12", "unicode": "1F55B", - "digest": "6690e591bec1751e1c5472e0bf52f66779b2113e5b8c6c578e65dbb83d091b16" + "digest": "046bb7ffa5f5d27c2e3411ba543484d9dabb8ebf6d6e7a7e9bfb088c1813500c" }, { "name": "clock1230", "unicode": "1F567", - "digest": "549f3921bcff7f330c5a41e6756d8c15601f1f8278b35b369148771c60be2a6f" + "digest": "bbfe9db5a2043aaba19a7a2a0185c7efcebf1e8c9263b8233f75b53c4825f0f4" }, { "name": "clock130", "unicode": "1F55C", - "digest": "9332ef07a9dde8ccaa1e58a3e97edee0601a1152fc6d351b782816c838d2a408" + "digest": "8662cb395ee680c2781123305c4c8ce8c0df9565c2c942668940be540cc0c094" }, { "name": "clock2", "unicode": "1F551", - "digest": "9d1ec8fbdae627880e1c067c10d6a40f1e4494a246c77224b3cd7b287554c4b4" + "digest": "42f7429748b612dce7de77221cbbc710655811f7bb23e2a986c36e6d662f0ec4" }, { "name": "clock230", "unicode": "1F55D", - "digest": "3578a39c28695d4e617a648a1eb44e0bb5a8a11dcbe04fa2eb2aea0a60589067" + "digest": "e710b6ef14227cd240ea3e2a867c8ef45b5c060adf3cb30ba9077c2351fe6677" }, { "name": "clock3", "unicode": "1F552", - "digest": "c2e2a27301b6ac27dc359be590448eb1e65fe87211f1af30a473d8bde4f3db47" + "digest": "7340d465b398a378211dff9ec806db579d061206fd6fc238623d070cfe0a55ce" }, { "name": "clock330", "unicode": "1F55E", - "digest": "7a77cf8cf9a98f4767a2dca1d3795be45938eee185db81120d85cedebe128899" + "digest": "7aa4a15cc8de04ed3bdeb0f8a54a7915065f2809a07054e002d89926c9766831" }, { "name": "clock4", "unicode": "1F553", - "digest": "0945c4199400d546350cfff25bc9e9160789d1cf9890b3318bdc462ac6cc9782" + "digest": "36fd88e81ad488b0ec49a911a838693281573fa14736ae4a6dd1c40a4ff69bb1" }, { "name": "clock430", "unicode": "1F55F", - "digest": "9fdb6f1fa076c4c6a395dbf6db27499ee447b3558f3aa64d913686c360e428a8" + "digest": "7bd5dd71e89d95dcf18b9e8c1fe2a353a7da3b69aadb8dda80ee9bafb05da58d" }, { "name": "clock5", "unicode": "1F554", - "digest": "855b3500eb6d20bb6e51d3a6c9d1a5131c06404c6c149841c7cca52201036428" + "digest": "aa406409e56a0bfd8c850e44efe45fd190ffd7bf7061e934ed7928dfbdfc9eba" }, { "name": "clock530", "unicode": "1F560", - "digest": "a6ebd9f884d45a1f43650351a1f1da9724bc044d7da2f6d99ffb3d1fa0c31c5d" + "digest": "25dd3bcc53ddd98eeea498d7dbd4c306ef39dd033f15909063388a0800febf41" }, { "name": "clock6", "unicode": "1F555", - "digest": "e38f9fc4f87f12ee602dcf2285d59dbc343fc0fc37662992cfe9866c20f58e87" + "digest": "0a321eaf1bc5db8436bbadac66c45ba257fc98ad4c7569ce3fc6602c824b6d7c" }, { "name": "clock630", "unicode": "1F561", - "digest": "735954a650791fc38c845c43998023e652d36e55534850e43952878b8804b2f1" + "digest": "55a4c5a665fdd38a724e9357a93c55401fcd5f1b13078c25754bd70c3fc4ccec" }, { "name": "clock7", "unicode": "1F556", - "digest": "2c4244ec4019e9624e6ea5a751bb735ab87bead33b1ea160265c81bba3c2f736" + "digest": "6154306545716e865da0ec537ee4f22bfe6c7294502a64a2dcf425c587d0e2a2" }, { "name": "clock730", "unicode": "1F562", - "digest": "0bcf20e30be1bb23394696770301867e307f8e5014e0ed7d75ed96efe34d625d" + "digest": "6925654de642e50f84661f94364a96c87757d73fffe766aacbf4bbd70130547b" }, { "name": "clock8", "unicode": "1F557", - "digest": "af454047a1765ef1c8355969302a826d4c47f5c61a6ec47fdec3510a8003b0d8" + "digest": "9be2d189c7ea56d39fd259f84853d753c1cf33e64f8ed57f86f822d9ae23a1ee" }, { "name": "clock830", "unicode": "1F563", - "digest": "e48b81dac055dc6d5f7832cf34368329c573d03b35bfe076fed1c6e6d48a82e7" + "digest": "16878613c0000d2f558c88d080551f424a8bd9df1358e0f931dd25c3da68f2d9" }, { "name": "clock9", "unicode": "1F558", - "digest": "f2a3d1bc029dc0e6406cdaa96542e77503e4cfb79d99c69cb454b8cf635a73fc" + "digest": "1d1e7e3c9d085ffa5b7c0f3d9fd394b734f16ae3b60df09af50fe6c8d4f3c8bb" }, { "name": "clock930", "unicode": "1F564", - "digest": "bb1b2b83052e8e6fb97c48c13bce0d950907e044eb2dabf21d7fed321f75110b" + "digest": "9fdef6a4939315c017b165e1dbac7710fb335df8c309be3fe2a011ef7fc28d74" }, { "name": "clockwise_arrows", @@ -1527,102 +1527,102 @@ { "name": "closed_book", "unicode": "1F4D5", - "digest": "afd6dae5fa0f59330fc2adb922e92b3410a33a80a2667651718c7dac588010bc" + "digest": "b18288629d201bfdfc5d66ec47df89809d00642b15732757e6a04789f36a7d9f" }, { "name": "closed_lock_with_key", "unicode": "1F510", - "digest": "d0ed5c00f939111ce86f9c741b733b22e04ebbd871aa33da3eb0f46a6f38b707" + "digest": "e39adfe9b30973bca16472c2b7e6462b064a93b9d452aa48edd74c727641a83d" }, { "name": "closed_umbrella", "unicode": "1F302", - "digest": "3ef08b299f9170007a5433fe82d0953bf0f75b6685d0ce58972f9af032dc471a" + "digest": "2cc0592c74601f7439e88c3c1ec4f05e3459608ef1ea6558c5824ed7c3889727" }, { "name": "cloud", "unicode": "2601", - "digest": "d1e7932551e85c6e86bfb3b41f0c936a6d0953bf9f9119b8cca3eaed22ac0c01" + "digest": "5b3a19718dfa8a381929665afdc2284464d24020c8dd0caff4dad465a1f536ba" }, { "name": "cloud_lightning", "unicode": "1F329", - "digest": "fc9c85cc95f9c456635692c974f72b6d40e14943824b8129a21c47265c3416f4" + "digest": "2b32f6d87726df2935ad81870879ccec30ce9b4fd5861d1a6317f9eca2f013d9" }, { "name": "cloud_with_lightning", "unicode": "1F329", - "digest": "fc9c85cc95f9c456635692c974f72b6d40e14943824b8129a21c47265c3416f4" + "digest": "2b32f6d87726df2935ad81870879ccec30ce9b4fd5861d1a6317f9eca2f013d9" }, { "name": "cloud_rain", "unicode": "1F327", - "digest": "f4406e62ed98f6141ab70736f6d5c540023e805396db0346ee6b7082c3f5e8e2" + "digest": "1e1e8bc59e168e1d2e72bf11f2d43cb578cbf0a5f1daf383bba5c56fb750ee71" }, { "name": "cloud_with_rain", "unicode": "1F327", - "digest": "f4406e62ed98f6141ab70736f6d5c540023e805396db0346ee6b7082c3f5e8e2" + "digest": "1e1e8bc59e168e1d2e72bf11f2d43cb578cbf0a5f1daf383bba5c56fb750ee71" }, { "name": "cloud_snow", "unicode": "1F328", - "digest": "948990cd13dd927917208c026089519fcf8e258a8a284684ace67c9a2f9a8149" + "digest": "2d364f859b83e684213e8eece1640208d80a8de0a49d0fc8e0e24c5a8493a3b1" }, { "name": "cloud_with_snow", "unicode": "1F328", - "digest": "948990cd13dd927917208c026089519fcf8e258a8a284684ace67c9a2f9a8149" + "digest": "2d364f859b83e684213e8eece1640208d80a8de0a49d0fc8e0e24c5a8493a3b1" }, { "name": "cloud_tornado", "unicode": "1F32A", - "digest": "44753516d0bd05d47cfa6eb922aba570ba6a87f805f325772b2cff071460ead1" + "digest": "7cbed2343c280ba3996082b3d0fb9d8cd57d6e62fe6c9ecb159f46b4a2e49151" }, { "name": "cloud_with_tornado", "unicode": "1F32A", - "digest": "44753516d0bd05d47cfa6eb922aba570ba6a87f805f325772b2cff071460ead1" + "digest": "7cbed2343c280ba3996082b3d0fb9d8cd57d6e62fe6c9ecb159f46b4a2e49151" }, { "name": "clubs", "unicode": "2663", - "digest": "5fd19fadd3b0887a6a59819ffbbe33a061055c043200700c31be30e14a5d36d5" + "digest": "b8cf72ecd8568ced077b475d94788fb282bdb06d25031b5d54dd63e25effb138" }, { "name": "cocktail", "unicode": "1F378", - "digest": "cf096ebe15b4053702d490cd96f04d565b4993529bcd6d8d50cb821200d1cd92" + "digest": "3792def2cde885cf32167f04904d3b0b788388e8af410c63e4cd31550feba775" }, { "name": "coffee", "unicode": "2615", - "digest": "6ea6128e353d9f74aee99caaaaa30c53f996fb242bf3bffb0fa92e6b4d373e57" + "digest": "0d29615a7a67d3aafa257b909bb915dc74fa8f854acb0d9a2c29e94eedf80326" }, { "name": "coffin", "unicode": "26B0", - "digest": "b59772d7aa262c4d7433f9cdf76d50011f4c63421b730c8ab4a08675f730c39f" + "digest": "78eccc1aad2a822649fba8503d4d30354bef367c4271193c40ddb692308f9db8" }, { "name": "cold_sweat", "unicode": "1F630", - "digest": "f0d0057bf01db8d930f6e4632c5bf8d0b1bc709bcfb6463a1f1973b5f1d70a83" + "digest": "f53aab523ed3fa2224a16881d263fb5e039f163380f92feb2c63c20f9b14dcd2" }, { "name": "comet", "unicode": "2604", - "digest": "00252ec55d1846d95c8d4c704b35251232d9810029fc215a7da08262dd1f3541" + "digest": "40ce93e55c6e57a88d80670b37171190bd5ffc87b7078891d8de5b15795385c5" }, { "name": "compression", "unicode": "1F5DC", - "digest": "432fbe66e5e3c38ebfeb4eb03465667a1e1be868b4afe510ec95eadda6481bde" + "digest": "c8841f7afb5345f1c31da116a7fb41d07232ea58d3f7f1a75c5890aa1a80bfd6" }, { "name": "computer", "unicode": "1F4BB", - "digest": "99777be010488867c7872b2e235be7c35b1a6f28d92baa921b61ced5491c0257" + "digest": "c970ce76b5607434895b0407bdaa93140f887930781a17dd7dcf16f711451d93" }, { "name": "computer_old", @@ -1637,237 +1637,237 @@ { "name": "confetti_ball", "unicode": "1F38A", - "digest": "e77d0c0970d3d12e123e548639fc0fa3ce41668667e4be55baefc09dfaa22cb0" + "digest": "a638b16f1acdbcf69edf760161b1bd7ff1fd5426c5b1203ad9d294dcc0701f10" }, { "name": "confounded", "unicode": "1F616", - "digest": "0f51db64149151d3d7ae5dce08c9af3d064123524fa36fe1f51a78cbd966b6ea" + "digest": "e2ff3b4df65d00c1ca9ae0cb379f959ea2cecefb3d676d4f8c2c5f2c103da4f6" }, { "name": "confused", "unicode": "1F615", - "digest": "ed23587432c1be98356156784ca4fe0b374b7b3b371660d45cfb0a1efd44e322" + "digest": "118d7f830ec08a3ac4b798eebb77a989b8c142f2588727181be4a2548e3c4f06" }, { "name": "congratulations", "unicode": "3297", - "digest": "2a46d640bf24fd4dc7649baf4b28c4adb30eda8d24d70eda07036c85b48195e0" + "digest": "02fd1338c54fe5f9a0fd861f23c56edc1d39bcd3140b68f0f626f9e2494d2d1c" }, { "name": "construction", "unicode": "1F6A7", - "digest": "73fac9fb5eb91954b0f998f9d05fb953241eed988c134fa42477393159fa34fa" + "digest": "c3a0401331111b9eda1206bee5f322db80b0870547d307b10dcac1314e4078c8" }, { "name": "construction_site", "unicode": "1F3D7", - "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" + "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb" }, { "name": "construction_worker", "unicode": "1F477", - "digest": "2be436fa7ad0a31e328fc6f776044bd1eec35c99541ced891792e3bef738d0a0" + "digest": "8c094733987e7c4da8d3aa4588b530ae07042bd70cf337b1fd412a70ee8f0ed6" }, { "name": "construction_worker_tone1", "unicode": "1F477-1F3FB", - "digest": "172cebc84f91237a85292c5ab0a105cc3abbb96e7423c4ae81feffd00bdb3b26" + "digest": "fcd927405fef4486105cd3aff62155467d21cebbc013924d4b52b717b566602b" }, { "name": "construction_worker_tone2", "unicode": "1F477-1F3FC", - "digest": "3e9b96ddfd639eefda99ad3a0ad26a28a0f2c8be72988c2bdbd648e6104638b6" + "digest": "d1ec773828936c703dd6e334e696dc3cf7c34c0a8ec691564a384b735cdeaaba" }, { "name": "construction_worker_tone3", "unicode": "1F477-1F3FD", - "digest": "11f83c565168dce5ac2387b873769d85ec4087171d6e92fc766c209ea06cd4f3" + "digest": "37c114d6879b9b32b800b0d4cf770dcbe04d1455698130ecd709a0cb9dea880b" }, { "name": "construction_worker_tone4", "unicode": "1F477-1F3FE", - "digest": "09e320e78e3a2940f0c5a0ef9a235ab72c51e053fd8ff433843fdb62571c8e70" + "digest": "5264996c1bedb6061a0dfdddce233d863bf308d27127ad152b63bfd983162cf7" }, { "name": "construction_worker_tone5", "unicode": "1F477-1F3FF", - "digest": "7ac2a1a0038e7aefea889380be604a98255823587e90799165f7db39dd03a0cc" + "digest": "87051aec81fd5dfd4dc44ff0411a528ee08253e9494d37efa550694e28dde6d3" }, { "name": "control_knobs", "unicode": "1F39B", - "digest": "9f10e578b410ff6aa7cc7fe806a0f1181893765303c0ca3867b652f1392a8a22" + "digest": "0d7f33ff7acc1cc3a81e6a786ff007df20da145e3070f338505dfed5100e9fcb" }, { "name": "contruction_site", "unicode": "1F3D7", - "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" + "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb" }, { "name": "building_construction", "unicode": "1F3D7", - "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" + "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb" }, { "name": "convenience_store", "unicode": "1F3EA", - "digest": "1ff4351e4a4503f58ed5d35074a2112c681337e35ffe55332187481685573606" + "digest": "975dcf9b8e9e3fb1e29574b41300b9d96fd64703b3c18ff52f9f1875d1cf1b52" }, { "name": "cookie", "unicode": "1F36A", - "digest": "5c78ce2e721b0a3767d6ce0b59c1e88fdf94a7edc94e98c4d6b7aadb5b2aeea7" + "digest": "4bed3522bd50091ac5b68ca760661eb484d7f1b9c9d564d2097bd812b7f28ae4" }, { "name": "cool", "unicode": "1F192", - "digest": "54a96697a5070388ce8364a5ee2e0d78a53acc8b4f6755b1359fd67252cc41e8" + "digest": "5739a37341c782a4736adfce804e12776ae33081098a3d052d8ae9a64b4d22d1" }, { "name": "cop", "unicode": "1F46E", - "digest": "16bee252c2a133bcf57f6d7b8372a61364744a2f662acb90e2005732555135fa" + "digest": "78996521bbe231d03ebea355226d8a1515f47cde7b2fbeca1037e7b7e5133466" }, { "name": "cop_tone1", "unicode": "1F46E-1F3FB", - "digest": "2fc52f3ed735e327d12dadb15f9feb7b7f720fc6857b551548a2a84809053817" + "digest": "8a38cd107f5f4c0b821ac43f32df5dc57facaf39fbafb98483ec00fd7df41baf" }, { "name": "cop_tone2", "unicode": "1F46E-1F3FC", - "digest": "6208f3174ced4f07ba3820ba838b247d7438d69d86eb04927333e7436e56af7e" + "digest": "8ab8ab086f3ff82aa4bf4760c3c822846ec2696c41d21dffdac12d5afbe398b7" }, { "name": "cop_tone3", "unicode": "1F46E-1F3FD", - "digest": "2427d30bdfe127be4d8c3870472cae191eece142c784a5c2809df938f43e7c53" + "digest": "fce710a99fd44a7c8af3ea01b2007e46d3ff38d7a0dff1ef26d6f893ede7e6d2" }, { "name": "cop_tone4", "unicode": "1F46E-1F3FE", - "digest": "6e73f8abdf816f3cb2728b971a5a8d006a236c1d71b2ee1788ab60329f406323" + "digest": "3017dd73ef475379911c5e6c79bd0f9f533dbbc5057bce6a11244faa12996ba0" }, { "name": "cop_tone5", "unicode": "1F46E-1F3FF", - "digest": "4b146465cc95ade7e9ca722e31a1b06311214dae8f7f4d95c6329d56c45b451f" + "digest": "a3b8807b3f2a8d6ee9bcec0339355bda486e8c930f727139f5447a4b046a6307" }, { "name": "copyright", "unicode": "00A9", - "digest": "8143583821085dfc8ac21079fe220288ba3a3b6ca3014dc5dc98b18da77589c1" + "digest": "cc28663cdd3f8333d9bb57b511348cde4e51bda19cf0629dccb05c8fc425e079" }, { "name": "corn", "unicode": "1F33D", - "digest": "0160502226b5f9af81763545f288dbbb20632039d7509f347c751cfdb49dc5b5" + "digest": "a099a0b291fa758690e6ee6c762b9ade9a0e3350a707c52d968dfffbcc467de5" }, { "name": "couch", "unicode": "1F6CB", - "digest": "a93fffed194b404200495abda8772bb35539cfc8499eb0a9bf09c508afad6676" + "digest": "84cd734dbaa7f9f519438036d687e7a53217130779bc3de30258f163521b9474" }, { "name": "couch_and_lamp", "unicode": "1F6CB", - "digest": "a93fffed194b404200495abda8772bb35539cfc8499eb0a9bf09c508afad6676" + "digest": "84cd734dbaa7f9f519438036d687e7a53217130779bc3de30258f163521b9474" }, { "name": "couple", "unicode": "1F46B", - "digest": "97fe611a613216a1788f9bd88a9deb4714ee123a66b5fd3d0ac916fbb4da7304" + "digest": "c897ba76e24e2f43a4aa261c2754800a8473f43c7ce53f9909a6af2c4897732a" }, { "name": "couple_mm", "unicode": "1F468-2764-1F468", - "digest": "3ae6fbf3ba168256ea85c756ac1e7b83fdb8b780d33f06128ed80706ff627eea" + "digest": "c812471d35d46e12270653039a907d1dfa2dea0defd65596283e5b8e03cea803" }, { "name": "couple_with_heart_mm", "unicode": "1F468-2764-1F468", - "digest": "3ae6fbf3ba168256ea85c756ac1e7b83fdb8b780d33f06128ed80706ff627eea" + "digest": "c812471d35d46e12270653039a907d1dfa2dea0defd65596283e5b8e03cea803" }, { "name": "couple_with_heart", "unicode": "1F491", - "digest": "d9701173a5e8dff052ab6a15a42494dbb61dc7146d3734c82916abc9c05f76db" + "digest": "420bfa81bad10365550c77a98e1c07eb00d03663fe7b610fab1aca8a0a9d201b" }, { "name": "couple_ww", "unicode": "1F469-2764-1F469", - "digest": "d2a2ec29c1a1234ea0aa1d9fc6707cf8be8bb36ea8b92523ffa1c3071bcf0b06" + "digest": "7ac49153a612d63302299eee996308b7dcafa0a152473dab679215036fe6567e" }, { "name": "couple_with_heart_ww", "unicode": "1F469-2764-1F469", - "digest": "d2a2ec29c1a1234ea0aa1d9fc6707cf8be8bb36ea8b92523ffa1c3071bcf0b06" + "digest": "7ac49153a612d63302299eee996308b7dcafa0a152473dab679215036fe6567e" }, { "name": "couplekiss", "unicode": "1F48F", - "digest": "e722730de82397da7c8f88d79319b391e8f01fbe4a9133850cc92ad34e77bd82" + "digest": "1acfef9d375c4c1deb235babd856b0f90ad4f3194751694cb6abb44f00f29e42" }, { "name": "cow", "unicode": "1F42E", - "digest": "dcc1efef2f02588806a156ed43da959c587d4c576ff6badec77f820ed3ba507f" + "digest": "d71c854ff8b343ee24b8c2b9d56c7cb3fc6fa1a6dc0d7a137841b9f646e6d71b" }, { "name": "cow2", "unicode": "1F404", - "digest": "dcf59f92fd0a37b2ca720bcda606defa4357b58d8f4ad15c1288ad8d814b2bc7" + "digest": "e7a5131d7dee0f3356814b0ac1ea8ff280b12a7b580181e20ddb0b7eeb7e7339" }, { "name": "crab", "unicode": "1F980", - "digest": "59d34a4e92326ebeab188d9e33b25c20f4d54d187c274713fa3256b03b9e662a" + "digest": "e6be16699fdb5d87f42f28f6cc141a44b7ffd834ecdd536813c4b5b86d3fc4a5" }, { "name": "crayon", "unicode": "1F58D", - "digest": "0f3351c2e68a8d47d27b45a9901be6160de0f9a291bd8680df84d0fc679bcb31" + "digest": "b180d6afa4777861222a4228164ce284230fe90c589f52ffa9351bac777e901a" }, { "name": "lower_left_crayon", "unicode": "1F58D", - "digest": "0f3351c2e68a8d47d27b45a9901be6160de0f9a291bd8680df84d0fc679bcb31" + "digest": "b180d6afa4777861222a4228164ce284230fe90c589f52ffa9351bac777e901a" }, { "name": "credit_card", "unicode": "1F4B3", - "digest": "708c0e7008e06e5d1b3b4e68a7e0ada9f4ae22ab6c28285d81a340f913fd9a84" + "digest": "808cd120fd3738eb2be1f6c6c029d98387b0e03fca7d1451e8fbf9c5ab3f643f" }, { "name": "crescent_moon", "unicode": "1F319", - "digest": "0959f838a410e8bfeebf00aa9658df56e515dbd2361142021071e17244662bfc" + "digest": "042e7e01e6e88b97a763b7cc41e2a2b3fe68a649bacf4a090cd28fc653baf640" }, { "name": "cricket", "unicode": "1F3CF", - "digest": "00eb11254e887c71db5e8945ad211e9e0280f1e02f4b77a4799b64bba2bbe9b3" + "digest": "4c4559d0b4efe24cc248fa57f413541307992e519d0cb9fb8828637ac2f4cc16" }, { "name": "cricket_bat_ball", "unicode": "1F3CF", - "digest": "00eb11254e887c71db5e8945ad211e9e0280f1e02f4b77a4799b64bba2bbe9b3" + "digest": "4c4559d0b4efe24cc248fa57f413541307992e519d0cb9fb8828637ac2f4cc16" }, { "name": "crocodile", "unicode": "1F40A", - "digest": "99abcb42264d40d2450aaca8c3759a019bfd600a311cf3027243f1ca200d4639" + "digest": "59cb4164c50b6bc9ae311ce6f7610467c1aaafa848b5fff7614f064715f91992" }, { "name": "cross", "unicode": "271D", - "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42" + "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653" }, { "name": "latin_cross", "unicode": "271D", - "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42" + "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653" }, { "name": "cross_heavy", @@ -1902,157 +1902,157 @@ { "name": "crossed_flags", "unicode": "1F38C", - "digest": "d4da057db289bec83f0106a94c89bd0cd9b52c7c7f8bc69bc8cbce480d53e12b" + "digest": "2841c671075e6f1a79c61c2d716423159fb0bc0786e3fb0049697766533bf262" }, { "name": "crossed_swords", "unicode": "2694", - "digest": "f159978583fa77c73ba6de85d35c4195cbd55963e537bd2bfd8f98ab8ff3559a" + "digest": "3771a5b26b514236521ce44e15f7730fa9148c6a782b9b600ab870a1f7de6f9f" }, { "name": "crown", "unicode": "1F451", - "digest": "e6fe2a28b7d80749ca121cabbe89321dcecdd760a122e73fb1562ea9bb40e90d" + "digest": "6741e58d8f823194e0a3484ac1563e20d9e0b44c1bc46d82444dfffa092cdfc7" }, { "name": "cruise_ship", "unicode": "1F6F3", - "digest": "90519c46ddfb63e71bc76661953da9041e5f0b97e9f8a7a8696518b4d529f3dd" + "digest": "2b7b62db5d118a632673564099e3405ea6d61ea9b8e123b5a2aaf011bb2a54a4" }, { "name": "passenger_ship", "unicode": "1F6F3", - "digest": "90519c46ddfb63e71bc76661953da9041e5f0b97e9f8a7a8696518b4d529f3dd" + "digest": "2b7b62db5d118a632673564099e3405ea6d61ea9b8e123b5a2aaf011bb2a54a4" }, { "name": "cry", "unicode": "1F622", - "digest": "2d6a096796222c29b050f74db6b5aff9b9f61390c5eb56e45d1801918751002f" + "digest": "fc3307ec4fe75539770c1123a0e8e721d9e021009a502655132f68d7cc453816" }, { "name": "crying_cat_face", "unicode": "1F63F", - "digest": "df057d4e3e5c5c87caedf87ea3a6f936811b93f228f46bb7018d2bb5afaa6d35" + "digest": "4942c24935c22babdcb8af41d2c0a7588356b6b674bc238902e2f10ad03e2c5b" }, { "name": "crystal_ball", "unicode": "1F52E", - "digest": "7de438f88134c32c4db67d705e5fecf2a6187a87f56ebbb5bcc5ba09626e2935" + "digest": "05f73b30b1e5b0fc66fb5dc6caddd2d547ee7b9d2f97513dc908ba1a2e352e30" }, { "name": "cupid", "unicode": "1F498", - "digest": "7cb3f7d1ddf9678982197ef0e65735fb465ae8e3652d611f37d3bcccf4d7e2c1" + "digest": "246e71f44c6ebc2e4f887e25438e4f894e8cc92e06069e711b893ff391abb658" }, { "name": "curly_loop", "unicode": "27B0", - "digest": "881a43ae406cb74b2ef136bf970db9928bcdc3bbbb7393e90d2c597fe1dd9a96" + "digest": "9e4eb98d6597888f91208080c6a79824adb432ea34f46c85da26cb630bd1cc73" }, { "name": "currency_exchange", "unicode": "1F4B1", - "digest": "c4d76e9e61fac8d3c0cb9e07f1fbf1a7fcac6f4d4c78776ff7f04fc9391ce689" + "digest": "b85377265b9876888969aa42b65bba0be523a370175baf226f20131e535af554" }, { "name": "curry", "unicode": "1F35B", - "digest": "ebe41ee864c873e3a371888c0087b11dbcb124335812895002ed81fe2b6ba571" + "digest": "a01c0a713662817720b485f7739f57e61afc025f5c43792f4de961c94f92f31e" }, { "name": "custard", "unicode": "1F36E", - "digest": "afc192f405c30e2d529ec0f4b31a7faf474bcd01fded5294dc38880b8bb22155" + "digest": "85c2b9ac904134a6c3587eb0a0806f2ab4282c5ed5c79d41734f3203998f757e" }, { "name": "customs", "unicode": "1F6C3", - "digest": "5abb98151a79cebc1032c0ea149617093e42f41e50574a790a91074cabaa4c3a" + "digest": "eb2546e1e617d4c1a1f614318af5e5dacf3e8d9479ffa08108977defa83ded32" }, { "name": "cyclone", "unicode": "1F300", - "digest": "ae77e15bf2f312f03dbc5c7813d304005bbb549953482db9beb91810c585dc0e" + "digest": "7a0f8564d76adf2d0ed272f56dc0d01fb7b557852e0ca797e73f5472b8630bf3" }, { "name": "dagger", "unicode": "1F5E1", - "digest": "377060a7ce930566a4732b361be98e8a193a546846dfbba2a00abeeef41d1976" + "digest": "35a179168198d03295e626cc27d3b92d30a732c55a2ca75d7a11a0fbed414772" }, { "name": "dagger_knife", "unicode": "1F5E1", - "digest": "377060a7ce930566a4732b361be98e8a193a546846dfbba2a00abeeef41d1976" + "digest": "35a179168198d03295e626cc27d3b92d30a732c55a2ca75d7a11a0fbed414772" }, { "name": "dancer", "unicode": "1F483", - "digest": "e050db55afbb968e02219a58c7e82b824848d299a4df64f0d08d4e1872816203" + "digest": "66ffa86827e85acae4aa870c0859fe3a9dad03d21ff4bc800b61c95c902a8a90" }, { "name": "dancer_tone1", "unicode": "1F483-1F3FB", - "digest": "350f6b2e4589fdd436173163035621b8da0bd49c7b9ec9f39593aae5e0ed0641" + "digest": "bdbee740addc890e369d3469a3585eb0d1e4fbc7e04dd6f6aca762d8aeee6a8c" }, { "name": "dancer_tone2", "unicode": "1F483-1F3FC", - "digest": "a9efc84ec80582f286147ca34162a27fd5989f4030084acdbc309d4368660f5b" + "digest": "9f7b4c627241eaa2def9717a5286a423f0b9c1b044dd9ea4442a76f1858d14a4" }, { "name": "dancer_tone3", "unicode": "1F483-1F3FD", - "digest": "ef187f44278fdb8605c80f5cf199e0b3de8a49085dada2e215bb91e1d7d3be5d" + "digest": "a6bd49a377ce6c2004bf126b6f66d0b21d8c14103c2add7b10f12ed9e1c2d302" }, { "name": "dancer_tone4", "unicode": "1F483-1F3FE", - "digest": "5195bc352dc9d24cc5505a167c756038e55c05048c61799ea1bfdf2debe44ac2" + "digest": "4ec2a7629c01b0e9006b5cda4deae3bf297ce3b71d18063f93eeb5c14be19a1a" }, { "name": "dancer_tone5", "unicode": "1F483-1F3FF", - "digest": "55cb7eee9fa11a16a3932800a19e334546f7396df6aadde22e58fe3185926b16" + "digest": "2b48e3a6b366c6f55f73b816e6fb03c39e9890f586f7e9c9043cf0c013d9cdd5" }, { "name": "dancers", "unicode": "1F46F", - "digest": "39e7dfd9dafeee20f2968960b1179ee4bf3f2b63a3035fc1944024d0ae8b5de1" + "digest": "12be66ed19d232bb387270f40bece68bd0cb2342b318f6c9bb8b49c64ff7d0ad" }, { "name": "dango", "unicode": "1F361", - "digest": "2a1b50abe5dc72335344878d9b701028ccad651964d9e3affeedbf3c2bfd652a" + "digest": "34e8cd153c50f2d725abe8934c35c96a3ab533f0cc5fbb1e1474eafad1dc1fc2" }, { "name": "dark_sunglasses", "unicode": "1F576", - "digest": "6bb1e911a93d5eb0581d3ce8f8929125d3d8fc04e086f3263cfd25af1348ce6c" + "digest": "d0a735ad5bf0ece00af2a21abf950b89292ebd8ca6e28b1dbb1368252fb44afe" }, { "name": "dart", "unicode": "1F3AF", - "digest": "6f28741543a4c1eead21856128ffea1fcf772954fe6af40844dfde47f092ed32" + "digest": "998642f06a875905e0a6bf30963c025baff1cf55b8e76884b9119f2d71188b0c" }, { "name": "dash", "unicode": "1F4A8", - "digest": "25aef37611f1c2f2e96518bf8aeba80580dca9634c8505d390c147388adf6746" + "digest": "f7aae7d3887c67d76f3329c2dc9e6807dc580a4b07ab35599c7805e41823a345" }, { "name": "date", "unicode": "1F4C5", - "digest": "de591b8fad608be761b839beefe9e4c2316320bcf0c44c543a1bc4b89923d938" + "digest": "d0b695e4a7cfbbe71b4fbebf345b66ca98f0cf1c751362928e54c23ca78d4c7b" }, { "name": "deciduous_tree", "unicode": "1F333", - "digest": "ff31a52096ac1eae770f7f71b6d802198add2c8b4d9d7c9327071b6d6ab86c7b" + "digest": "3c70f1a77f2754f41c830e88d43b7d53c14311d64626ded164aa9ac7d2695790" }, { "name": "department_store", "unicode": "1F3EC", - "digest": "c1e200d5fdd792121acabdb17bbcfe8e28a63757cfd895c72d4909f14de95ac2" + "digest": "4be910d2efe74d8ce2c1f41d7753c8873579faca83fcf779a4887d8ab9e5923b" }, { "name": "descending_notes", @@ -2062,17 +2062,17 @@ { "name": "desert", "unicode": "1F3DC", - "digest": "e45815250bfc5411de516f87efa218874bcda4b0420b4c17182efc22ba0ce80d" + "digest": "d4b1a11c5130debe042df6cc2b3389f15c68a5cb32dc1b3a82b78f733d0c9e4e" }, { "name": "desktop", "unicode": "1F5A5", - "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de" + "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e" }, { "name": "desktop_computer", "unicode": "1F5A5", - "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de" + "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e" }, { "name": "desktop_window", @@ -2082,47 +2082,47 @@ { "name": "diamond_shape_with_a_dot_inside", "unicode": "1F4A0", - "digest": "4e0e6364b8682dec9a9e20676161c9c9c0faf0a5fdd5402ca2668b18f2bb850a" + "digest": "e91323577ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3" }, { "name": "diamonds", "unicode": "2666", - "digest": "42b13b2ed8e5fc63fbe81263c06cc203ba18a45ed5cc2a4fdbf617d219a0d3b4" + "digest": "bf3d9a020afe8aa226db73590bc193a9c2c3e6e642edd2445c5960c3e67cf153" }, { "name": "disappointed", "unicode": "1F61E", - "digest": "7f1a619fef84960a9f312d17a58aa58105a4f20a4072efb10227892ab22475d8" + "digest": "c0f406c6beea0fd1328adefc097d04aa16b72f7a5afa0867967d8ea25d72db17" }, { "name": "disappointed_relieved", "unicode": "1F625", - "digest": "a389f5e0a4b619dbc406217967fb1f8f3d0e49b3f790e554ae0ececadbf98967" + "digest": "c826f5dd4f2f7e5289d720851d4826ab8284d915606c1b152ab229b7fadbba14" }, { "name": "dividers", "unicode": "1F5C2", - "digest": "bf4c303452a4c0b4986925041dbec5b7e478060d560630b7c5bc2f997fcad668" + "digest": "4b2c653b18cf0fa31f1f0ac94a6fbd214ea0d1b0a90a450ab6e169906fc5764f" }, { "name": "card_index_dividers", "unicode": "1F5C2", - "digest": "bf4c303452a4c0b4986925041dbec5b7e478060d560630b7c5bc2f997fcad668" + "digest": "4b2c653b18cf0fa31f1f0ac94a6fbd214ea0d1b0a90a450ab6e169906fc5764f" }, { "name": "dizzy", "unicode": "1F4AB", - "digest": "d6fba9b906f0eabd46686e416273a2ca6634249374385f2abf7ed284f0eef995" + "digest": "d577545c2de42389695447c6ebbfef895f30f0fda84eef45684f9bf4a9c27ff1" }, { "name": "dizzy_face", "unicode": "1F635", - "digest": "b55e20c1551a2912bb5ec64a66c788c9d6f21594cc1da66032188f3814b03f40" + "digest": "7b3aeaffb4e15ccf633b91dda4a44847a1eb28d78ce58b4d171b20a771bde414" }, { "name": "do_not_litter", "unicode": "1F6AF", - "digest": "126f8c4085e0a8de8241f211f96c3f42c3e3400ea7d8fdf79a14443c3eceb972" + "digest": "98b07fbbcdb438d1b8a755869fa2de8e180a77fce359ec830eb46d38ec3e67cb" }, { "name": "document", @@ -2142,182 +2142,182 @@ { "name": "dog", "unicode": "1F436", - "digest": "c7b729de8a0967b1f38c3fa5ded94e77e329588caeaaf43abfd1090f420e62bf" + "digest": "3b31ce067b13e463284ce85536512cb1f8cd8b52fe73659f69971d0d6c1dfc11" }, { "name": "dog2", "unicode": "1F415", - "digest": "e1897ca60bb3d2662cbe7933352e2b9c50739adf5901d3328797bf399575b97a" + "digest": "0a8901bce5ed994533ff84299b2a1364de28d872c9f9510d3426a83e8a9d2e34" }, { "name": "dollar", "unicode": "1F4B5", - "digest": "7db1e57f799439df1295d42b5249393f1e8cacc8df54caf30499c967a7282742" + "digest": "52438e38867aedc021740bb41f9ba336e75a50faa148419412a01d75d8c93155" }, { "name": "dolls", "unicode": "1F38E", - "digest": "398e7ff5780328700aadded7ce8c50757b1096af5cec66cc4d813a6714686b6d" + "digest": "a687184e9a0915deef44bb3cacfb19d3f3f19cf2c110f1da90191dd567333c57" }, { "name": "dolphin", "unicode": "1F42C", - "digest": "27385af08848d93acdd13f72751074c2cbccb5ab3c6047e334598af74ed4862d" + "digest": "0b7ee08f4236232ca533ed3a3023d28020d36f178efaec5ce8b0e13a84778512" }, { "name": "door", "unicode": "1F6AA", - "digest": "3365d7834086328ecbf1da0037f1cf1d0eb49534e173f7962a9e8f4b2ab87e26" + "digest": "984a9ca88852ebdb539e0c385d9c6ffe5010e9189bc372a3d00f5c8d44c8e6f5" }, { "name": "doughnut", "unicode": "1F369", - "digest": "b4b99fdfe8d07b49cbdd78f8c57e4424819a4ffc8a3ba4867da44cbb3b3a5cca" + "digest": "27634587e6a53807baa32157bb06b0e115c8ad8aefebba7ebb0b65a084170e3a" }, { "name": "dove", "unicode": "1F54A", - "digest": "4e2e9c47e5632efe6ccf945d61dbc2f1155a2e52905e17f307b502a2c951bdb8" + "digest": "7c665f8594ffa53e72b01647e9d27360fb87d52d02fe9f20fc5fda08f9797dc3" }, { "name": "dove_of_peace", "unicode": "1F54A", - "digest": "4e2e9c47e5632efe6ccf945d61dbc2f1155a2e52905e17f307b502a2c951bdb8" + "digest": "7c665f8594ffa53e72b01647e9d27360fb87d52d02fe9f20fc5fda08f9797dc3" }, { "name": "dragon", "unicode": "1F409", - "digest": "d7d016568b54d67017681a075fb799d4a2a790ecfa2946d02dbcee629eb4975d" + "digest": "2abcb3d945d848e34ffc76203b29ef26df7458856166fffd155611f7bbe72652" }, { "name": "dragon_face", "unicode": "1F432", - "digest": "4d0025f1df63b62448477a8f08a50704e15caafb10fea476b529113f41797ab9" + "digest": "0030548931b931e3b51f26cf660394aee36499e688ba83ce9cfccb635dcd4d54" }, { "name": "dress", "unicode": "1F457", - "digest": "02d56ed227280eaf5ad92830ee304afb81f74bb5a13c855397bcd04dd7fa51fb" + "digest": "96ceba928fb356f7c0ae99bf22552321f08a65d5f1c0340ab89641219ad366ad" }, { "name": "dromedary_camel", "unicode": "1F42A", - "digest": "5afe8a0b73f9f4560264020b1e02a566149dbc38c15a00d2fb5cd90b32d09a75" + "digest": "e06ef69c29f0fb12481727c0b4124e700572d3d7955e173279320f43f286518d" }, { "name": "droplet", "unicode": "1F4A7", - "digest": "a92c419792cbd3ba90ed21547362134cfac3e17a5304ee4e3872c9f7b561f834" + "digest": "6475b4a4460a672c436a68f282ac97fb31e2934db4b80620063ee816159aa7c3" }, { "name": "dvd", "unicode": "1F4C0", - "digest": "1ba23e2f01ced5e192e4c1d2f766d9bce400470e81c81410139fd3c0739422df" + "digest": "3b7903285d91277181c26fdc9df857761bbac509d352e320c2519ea3b132704f" }, { "name": "e-mail", "unicode": "1F4E7", - "digest": "12135310cfedc091d120426f5b132df82b538c5fcad458bf6b21588f353c3adb" + "digest": "39b5a57a2376e4a1137e381be02a1775bd580e0371438f5297a401ea634f1830" }, { "name": "email", "unicode": "1F4E7", - "digest": "12135310cfedc091d120426f5b132df82b538c5fcad458bf6b21588f353c3adb" + "digest": "39b5a57a2376e4a1137e381be02a1775bd580e0371438f5297a401ea634f1830" }, { "name": "ear", "unicode": "1F442", - "digest": "70ba1103a34e68590d91a3b6f8acdbad3b1c65e46e31e26ee1cb855c1e21095e" + "digest": "4fdeb5a46e69311ecfd09c5b45c9018c24b625e28475cca8fa516b086ef952f8" }, { "name": "ear_of_rice", "unicode": "1F33E", - "digest": "ddd5f3cc83dbdafd9115861eecd0128e52165bb1dd0049df06ffc564b650d384" + "digest": "2997c340c2b333d6ba9b73f94ff1a1881735fe0cc4f0c72d7719b305499fc425" }, { "name": "ear_tone1", "unicode": "1F442-1F3FB", - "digest": "72977be94f5d287a09d175f98fba8b7955ae13aa12ce8e029c0ca875c02ee820" + "digest": "5ca759b8569a377a4e63e30d94b585b9f76d15348a8a0c1ba19fdc522790615e" }, { "name": "ear_tone2", "unicode": "1F442-1F3FC", - "digest": "5ff2e46cb3be7f13b8b94092246b58dab4c2a9ee2a5a46e0b84cf35a6928141f" + "digest": "12aafb3ef2cfcdc892b2877c2e24920620f0f77f850e12afbfe55eadce9e37df" }, { "name": "ear_tone3", "unicode": "1F442-1F3FD", - "digest": "19b523f5ada2acaea94b922059c458a3303f4da1dd4c197cf25d31a0e6ecc4b2" + "digest": "f4d28d9f72cf116ac92d80061eb84c918d6523bf53b2ad526f5457aba487d527" }, { "name": "ear_tone4", "unicode": "1F442-1F3FE", - "digest": "6a5cca9f49c539ef7d0883a2f39652f33ee2d3b25dca0234e4ba027ebbb2b466" + "digest": "eaa9453670f7e3adc6ec6934ee70efc9bf60fe6c99c5804b7ba9e3804aec65de" }, { "name": "ear_tone5", "unicode": "1F442-1F3FF", - "digest": "a0a56e8abd36e9be6e2448bcee6f56ecb8bf62d728b19ab6e8f9c6338e226b67" + "digest": "54bd0782419489556b80e9e0d15b05df74757aa4e04ba565f45c20d3dd60e3f1" }, { "name": "earth_africa", "unicode": "1F30D", - "digest": "d4921b543d7cf0c7344fa50c5e4d5a76c208d900be852adc1ee82ed4e8861a39" + "digest": "c691a6f591f5a07b268fd64efe113e81cec8d5963ad83ced2537422343ff7ecf" }, { "name": "earth_americas", "unicode": "1F30E", - "digest": "61691e6aa9b8d90fc7f75fbc6cc7add5c36022d38f3e05c9d7c54dc44cf865bb" + "digest": "a9c60cf8341ff59a9cc1a715b7144af734fcd28915a8e003a31ebf2abf9aedb1" }, { "name": "earth_asia", "unicode": "1F30F", - "digest": "262904cb552c7f5cf828a11071b3d430a74824b7464e8759ef93ee23b1705767" + "digest": "ee2beb61fb8c87279161c5a8c4ad17bb71ce790123f8fa33522941d027e060a5" }, { "name": "egg", "unicode": "1F373", - "digest": "a7dd617cad489c481ffd14937d9ed491cdd5756903e00473f42600c2fbefb600" + "digest": "563ffd6cae381ce1e318cdacc54e70040d6a01a50d0db8aeb50edbbe413eac58" }, { "name": "eggplant", "unicode": "1F346", - "digest": "e5402e8ae5b7f9699ed86b97c242f7939d5731c5a364a2d5b9d04ea5d293cda1" + "digest": "ec0a460e0cf0e615f51279677594a899672e1b4ecd9396e17a8cfa2a3efe5238" }, { "name": "eight", "unicode": "0038-20E3", - "digest": "34e293d3228e4643a0132d592f96db91b651fe6ced056ac3c8a3fd49c5ed3416" + "digest": "57ff905033a32747690adba6486d12b09eb4d45de556f4e1ab6fb04e1fb861a8" }, { "name": "eight_pointed_black_star", "unicode": "2734", - "digest": "c3c2da75731a9a0f4f0a8d1f9cffef75c35e19b7f5d4081da33ac12b46be5fc2" + "digest": "7bf11f6e28591e3d0625296aaabf4ecb75c982e425abf3049339e93494acc17e" }, { "name": "eight_spoked_asterisk", "unicode": "2733", - "digest": "cc69618c1074d2b00e6f2c49df5e2c5ff6f4c0fae305505eb8c9daa65a0ea340" + "digest": "bb0758e7cc0e357285937671a91489bd32ce9d248eecdcc9c275a53a66325b26" }, { "name": "electric_plug", "unicode": "1F50C", - "digest": "732e1d1675233a0b4643cb73d0c352f8a5a56a11ee90d26627ad1e43c2e4a8e5" + "digest": "b10ce87af86fa4f4022572ceb5ecd73bea867347a86832a7ea248364b0aad8d0" }, { "name": "elephant", "unicode": "1F418", - "digest": "08df3910c4d5d8f49a72c47dd938195e495bde8fd8b3e7b17098a2c1afc41634" + "digest": "b7750f4b013fbd28ac5330e1694ef4d3b4a9c6fc7b807879db0c24b035a16c29" }, { "name": "end", "unicode": "1F51A", - "digest": "05844ab9dcb43deff86f04617af6ea09215595de1415dcfaae018bced57938fe" + "digest": "dd93aee6986eb637a8b58f234da47568b88525599f73246e322af030351997a2" }, { "name": "envelope", "unicode": "2709", - "digest": "aad272511d0db910437ba25cf1fb9c806d47aad92a232edb87055916daf4676a" + "digest": "f5a512022a2f5280f372ff39c22cbda815f698710ca66f8f8c4d08418f98ca78" }, { "name": "envelope_back", @@ -2362,192 +2362,192 @@ { "name": "envelope_with_arrow", "unicode": "1F4E9", - "digest": "c1ba19b5e7cf64c547ac46eee139e6af70700d49ab511a96e6828c30feb116bc" + "digest": "f8643212e6a94f58ccf2bcedc54c5fda8ebeab274f4a8803f253de5f50ddb1d6" }, { "name": "euro", "unicode": "1F4B6", - "digest": "f571952583ffecfa5777065e4d1b680c423d25bc80e567a48fb5d7a1c1b5e735" + "digest": "3af3e223e8f26468a94f6f5c17198432656e8d20b3bab31566c2b5a86e717df4" }, { "name": "european_castle", "unicode": "1F3F0", - "digest": "db82e383975d079a7bb006e7868035088d75c33bd4031cf8466b71089b65426f" + "digest": "21082d0be7e3b2794e59ff0170da0cfe42a9b734cf02704603e3b52ff48202ba" }, { "name": "european_post_office", "unicode": "1F3E4", - "digest": "d9b38e0f0ca3ad8895b40c767bdbb2b142ccaf03a86c2f275f57a31ed478801a" + "digest": "02b4c7602939f0cb9cb2b4e05996bcdb6bd93cf8025c2ea02db8cbe13ca397d0" }, { "name": "evergreen_tree", "unicode": "1F332", - "digest": "60d8b2d86b20255341f7ecad6d0f178ba9db5fa6b3de92f1b439cdb19f2fc0b1" + "digest": "74b226098e66c0a94a92e0f22b9d631736e12dca72c34182c9d0ba56aa593172" }, { "name": "exclamation", "unicode": "2757", - "digest": "cd900ecf82de2b26f0d7783dac4b3232ae94d2cddad5bfacea2eaf65b7ac0a09" + "digest": "45b87ae4593656d7da49ff5645fb6a2a18d582553295358da9f09f1ae8272445" }, { "name": "expressionless", "unicode": "1F611", - "digest": "2ec9466b2d629907ce4c3e24e57f7ee556d2258ff011d972e14d0ae969a40c51" + "digest": "34e2a1c8121f4f0bc4ce33d226d8cc1a4ebf5260746df2b23e29eef24ee9372e" }, { "name": "eye", "unicode": "1F441", - "digest": "790841e8fce647173eec3c5019440ad9c7e916c535f92acb3132bd92df148cad" + "digest": "79ecff79c2edee630e72725b54e67ee2e96d24ca03fef2954a56a09c0a2227f8" }, { "name": "eye_in_speech_bubble", "unicode": "1F441-1F5E8", - "digest": "bcde5a89a7653bff302685d9d632dd2723796a7ac73125fb7b9493d1ca848e0a" + "digest": "c0050c026c2a3060723cab2df2603c1c7da7ed81faedb9ebe16cd89721928a55" }, { "name": "eyeglasses", "unicode": "1F453", - "digest": "fd140bef19c420bafe59368d35dd58a58a53e7145b104bae94be10f90679213b" + "digest": "d4a9585d6c43ef514a97c45c64607162e775a45544821f1470c6f8f25b93ab81" }, { "name": "eyes", "unicode": "1F440", - "digest": "57ed1f87ebe2485ea32ea69abdb8c5f7ccdcc149b33e74230d801f0883c68c5d" + "digest": "1d5cae0b9b2e51e1de54295685d7f0c72ee794e2e6335a95b1d056c7e77260e8" }, { "name": "factory", "unicode": "1F3ED", - "digest": "6e6b35ae013e5dd26852c9a95d05c39e89c1c1950a33f47e7b951c34af18f37c" + "digest": "c7aeb61ed8b0ac5c91d5197c73f1e2bb801921c22a76bb82c7659d990680dcb0" }, { "name": "fallen_leaf", "unicode": "1F342", - "digest": "28ba8628065ffa973b525dd1455691c828d49c2b8c814af387880c13f6707f7e" + "digest": "81fce04231d48db0e55f3697f930e9a7e3306bed5e35f1234e98c40a24ac5626" }, { "name": "family", "unicode": "1F46A", - "digest": "b5307f86e54cfea581e8406f4b95c801e250a893a9d208cc9a69a6d910b90932" + "digest": "06f2ce63768ffe43b3d9b2a9660b34d043f37b3c91610dd62343ba21df8ecbe5" }, { "name": "family_mmb", "unicode": "1F468-1F468-1F466", - "digest": "49a753c3fcd4420800dd1cda585dae6bfa81615ad4862b477246456f86dc9e82" + "digest": "41a18405be796699a7eb7c36ab6f7d898e322749997f45387377acf5bb16a50f" }, { "name": "family_mmbb", "unicode": "1F468-1F468-1F466-1F466", - "digest": "882a3a0048efd666b0ab3a07b9f08041aa3a2acdab02664d0feff30bbfa70d68" + "digest": "87255d1d18c6971c8c083c818e598424c1bd717eed892478b7e9516639dbfb45" }, { "name": "family_mmg", "unicode": "1F468-1F468-1F467", - "digest": "45dd75c19d260a658c8ac93cf878976b96d2000f0efc9c59e72dacc80afb08fa" + "digest": "a132b1b8f10b318d8e23aee15dab4caa14528aeb3c89966d4bcc25fb54af72ad" }, { "name": "family_mmgb", "unicode": "1F468-1F468-1F467-1F466", - "digest": "910f44a348a951d36ee1f1484d237085bec5083c3875a4d908831dfc64530eaf" + "digest": "eb2bc1966df406aaf38ce5a58db9324162799cdacf31f74f40e6384807a8efc2" }, { "name": "family_mmgg", "unicode": "1F468-1F468-1F467-1F467", - "digest": "012e75ad0d1b16c2ce63bf80a1ebfb1fc194229cfaf1241039599b82832f6aee" + "digest": "24f3d60f98fbd6b687f7cacfb629390b90509a754036e5439ae5294759c0606b" }, { "name": "family_mwbb", "unicode": "1F468-1F469-1F466-1F466", - "digest": "049a32f61c54f093d2124e25f8b2ec7eac13161e2f2ebf6dc067797698cbe831" + "digest": "2f77692bcb9275c4df501b64a18401dcaf8c68b21f26fbdad59b1feab0c98fd1" }, { "name": "family_mwg", "unicode": "1F468-1F469-1F467", - "digest": "ba32c637caba634bda99ccba2a1a2a4b6f33aaaed933c30c7d5a51e8de1790d0" + "digest": "1a976d13127665d9386cebfdb24e5572dc499bda484c0ee05585886edc616130" }, { "name": "family_mwgb", "unicode": "1F468-1F469-1F467-1F466", - "digest": "198faba987f45429329b93bbce4f111329f284558bf0eecfa1424186b5f009fe" + "digest": "960ec2cbac13ef208e73644cd36711b83e6c070c36950f834f3669812839b7f8" }, { "name": "family_mwgg", "unicode": "1F468-1F469-1F467-1F467", - "digest": "3fa2e57cba314dcff04cf8186914823e1e081aabf34fa7437b05c58015df400c" + "digest": "8353b03dfa5c24aba75a0abdfdac01603f593819d54b4c7f2f88aafb31da0c6a" }, { "name": "family_wwb", "unicode": "1F469-1F469-1F466", - "digest": "b9592fc110a25a478569075deaa520308ef74579cd47aa44df9836599d68143f" + "digest": "07a5dd397718c553573689f6512f386729c13a12d5dc78be47c06405769cd98a" }, { "name": "family_wwbb", "unicode": "1F469-1F469-1F466-1F466", - "digest": "88f398997835fcf5153f17f6baf0deeb2a9c25ce2f8422192c18ac23e90b3193" + "digest": "b627f460f1da0d47b0b662402940b2b77c9538d380d05436dfca4b456c50c939" }, { "name": "family_wwg", "unicode": "1F469-1F469-1F467", - "digest": "c8d859d3c957fe0d535efccde295fe99bab76e3d28ab5a49c8e736608461cb2e" + "digest": "2d6f373bed53f1028f0fbe9caf036465a351f37b9e00fca7d722cc5a1984f251" }, { "name": "family_wwgb", "unicode": "1F469-1F469-1F467-1F466", - "digest": "006506e4a3d0c82642a0c8481ce95e5e3b969e20fe2def0a16dd686afddbc705" + "digest": "72be5c85e1621f73d6794edd6e428febdb366b9e4c816f7829897fd1ab34642b" }, { "name": "family_wwgg", "unicode": "1F469-1F469-1F467-1F467", - "digest": "2553f0deab133aad09b99411d9dd68b56fede30f55ee1f354358767765e36673" + "digest": "c39e0916069460d2d9741bddf58e76f5d6a09254cba0eeb262345adf8630bc32" }, { "name": "fast_forward", "unicode": "23E9", - "digest": "1baaed10969b60c083da754ee056bb71df36182cc65af40640acfb76f6b39200" + "digest": "e7d2d8085cfd406c2b096e8dd147dd3722290a5727b1f7df185989526a2335ec" }, { "name": "fax", "unicode": "1F4E0", - "digest": "b0a392192d03bd5d1ad5ee8eea933cf64725b1776819537bbed27561d78192e7" + "digest": "ff85ffa440c5379c9b138ebe2d7912d6098da3b37a051b80442d5557b7f993b0" }, { "name": "fearful", "unicode": "1F628", - "digest": "7c4cc4de3357c2a6d6e779342b09dabb3ef832a32f2778a0ba074b446f588e8f" + "digest": "b72bdf7d075d5c4e38bbd8512fb45fda2e85c9c8732a47e67575ae9f2ed4c5df" }, { "name": "feet", "unicode": "1F43E", - "digest": "cae13fb54ec64dbcf86ea25bebe2b79877e2d4f5d810b867f095f1d3dfc7f144" + "digest": "45aca538d3a9831a0c7de491e5656c17705c07b8f4ac8e85254656b608976016" }, { "name": "ferris_wheel", "unicode": "1F3A1", - "digest": "a710a8a0fb039d953313b75330db37e3228d856593547b1f04dc83c00168b987" + "digest": "24b4551b7b79a2a5fd73de61542f2b444f896a52030c5f29791c8fcfcc28b95c" }, { "name": "ferry", "unicode": "26F4", - "digest": "21ea239b5adb68dc1ce6c5a1993b0a0b835ef6cc7a0a27cb890838d8475504f6" + "digest": "5002a72af2e3c4cef9a36ad5987aeed7d99f96bfd13e56f78957315ec7e749a3" }, { "name": "field_hockey", "unicode": "1F3D1", - "digest": "1e46c7f0b5b79c90a5d211ea14cd7e358b1a26a3c8294439253f2b08d0e5c92e" + "digest": "4ee091d96161ba719ab8fd6f2b03f96d902a6f22cffe0563b930618bb8ac2b67" }, { "name": "file_cabinet", "unicode": "1F5C4", - "digest": "c0b7bdab6c98909eb0fbf1ac89da0008bb00ddb1cb57fe64b4a5ac993eeb18c9" + "digest": "92914147bf93e6d64271ff99d217a18a9850a367d08a5f9f458ecf9311a5bbe9" }, { "name": "file_folder", "unicode": "1F4C1", - "digest": "d98f93c6d7283df0c45f08d3d31ecf5b91b6db1b735959f19e42bfada500a0d1" + "digest": "62a42a929267cfbfdb795ead381c9657c343458bc5fca95ea8a0ab892c61d4f6" }, { "name": "film_frames", "unicode": "1F39E", - "digest": "754a0a60e978f8299a0c4f8959e1f9260f01683e15ae943db430036f01a79b18" + "digest": "4da212148cadb9c4ea91e60d2d8316e38cea99ef4f14afc023711dd7c54ade5a" }, { "name": "finger_pointing_down", @@ -2602,17 +2602,17 @@ { "name": "fire", "unicode": "1F525", - "digest": "b44311874681135acbb5e7226febe4365c732da3a9617f10d7074a3b1ade1641" + "digest": "b3e67c913903d900f5e50e7e7e4d7e9370bb6ceedfbee548be39e4c9e4b69416" }, { "name": "flame", "unicode": "1F525", - "digest": "b44311874681135acbb5e7226febe4365c732da3a9617f10d7074a3b1ade1641" + "digest": "b3e67c913903d900f5e50e7e7e4d7e9370bb6ceedfbee548be39e4c9e4b69416" }, { "name": "fire_engine", "unicode": "1F692", - "digest": "3ae03fa34a7088ada95458eb4ee3e97691b3489149f6bbc168086f0483ed3bb2" + "digest": "c3a518f27d625e3b62dffa227eb82764bf0a147f10ec0e7f4f43f3f96751af20" }, { "name": "fire_engine_oncoming", @@ -2627,507 +2627,507 @@ { "name": "fireworks", "unicode": "1F386", - "digest": "3dee83a27c406960253ca1460eb88a599c7b81506051b69605a421b17fe8282c" + "digest": "b62ae08a00c0cc6eba8f9666c8fd9946ce57c3cfc01fe99542a8690a4a566a65" }, { "name": "first_quarter_moon", "unicode": "1F313", - "digest": "8fa066362d77bd889090bbe0904ca47f34704e29781c67133c6eaa521c3e1972" + "digest": "a207ce93084448622a4a5c49c85c566a9fda6be7337c86a013eeb713fe47fd29" }, { "name": "first_quarter_moon_with_face", "unicode": "1F31B", - "digest": "8877edb366f8eaa00fd83200acf5a17c3b84d246a250519d565dda3aea866ec3" + "digest": "1d1f54a5075f2311bcc017c44898b9d8c58edc13b298d58c238fff9ab8ee2ef3" }, { "name": "fish", "unicode": "1F41F", - "digest": "9ce742108794cc15e59f7719623ae938efbd8155c93ad72585a32f4e32ea9414" + "digest": "8f62f08fbeaf39694c19816b5c7d4f292017fe5bf9f8dd7e40f1630f5f83b28b" }, { "name": "fish_cake", "unicode": "1F365", - "digest": "1b5b14509287e30da9b8d7abcec376b247f9095aea4bf3fc320349f061a4c321" + "digest": "5a6ca2100c8830927b22afa6f1d2fc821f5692cd23507fe5a776f6e085cbbfb2" }, { "name": "fishing_pole_and_fish", "unicode": "1F3A3", - "digest": "35db56776db1fcec7c8479922d57d54da2577cfe44a894bfd78c51c950c450fb" + "digest": "f8fb84eccceec88321b0a2a46f732ecfc378f787c19c27ac1327735f1ca9a48b" }, { "name": "fist", "unicode": "270A", - "digest": "6b80ac2e4d8b830ae06f7c1626d456460094e4ba20c20fb82dabb6b3d2ce7605" + "digest": "557f96d85615b8d78436bc67266115bfc8556c97c14f7909dfda1cf134e8344f" }, { "name": "fist_tone1", "unicode": "270A-1F3FB", - "digest": "d7c79f4f988dd68f064baa5a3a568ab299f8d409db45c8463f39b80e5dd6081f" + "digest": "6c1b946f9e01abc39b5085e24e8b6077fc0e34188e8daa30c6a3adddd387413e" }, { "name": "fist_tone2", "unicode": "270A-1F3FC", - "digest": "d1108194e2d962f9ccd00131876d769a8e003117a460d18b2ccbf93e0a0ea346" + "digest": "e9b9e1ec638dca4d5e1519bca7338f58cce2f2a282ee4c3581e8643166fc415f" }, { "name": "fist_tone3", "unicode": "270A-1F3FD", - "digest": "12f5644b632c95a5c2e41cc9af299e286e266db8b3860091ef5be5f0c4ccc026" + "digest": "8c14d24055c143960b3d2a27fe23c55d2d3ac5f84f87e4e876616235e8698c7f" }, { "name": "fist_tone4", "unicode": "270A-1F3FE", - "digest": "521a3ac573381f3bc37a08ddd2d122767aaa0b6b7a38050d3671a12343351816" + "digest": "923f034f481e952e6e5d1664588f99f79bd5416d4197b0ade6621f2669ce5765" }, { "name": "fist_tone5", "unicode": "270A-1F3FF", - "digest": "604e5a234da1b9160e506b3c9026faf9e04268fced7b44baa1ef5e3d4efa83a4" + "digest": "d691d2902216080916a29047e07d7a5bf2aed07e062067ca9d01cbf6fdf48c8d" }, { "name": "five", "unicode": "0035-20E3", - "digest": "0cbd6cd11eb6c2d67749112750d125f4f0a07b53bb7bfb1de0986d943ea9d632" + "digest": "8f03f62fdbf744ae49c8a60fbf715ebfccbd6b62d91148e0923907006f3c2726" }, { "name": "flag_ac", "unicode": "1F1E6-1F1E8", - "digest": "d9db1edeb709824a1083c2bba79ca5f683ed0edded35918bb167d1ee7396c8da" + "digest": "2e5c08535dc8ea96422d56a36b4fffc0b3bd2a13f2ab0d8dbd0e3a29bf3fc40c" }, { "name": "ac", "unicode": "1F1E6-1F1E8", - "digest": "d9db1edeb709824a1083c2bba79ca5f683ed0edded35918bb167d1ee7396c8da" + "digest": "2e5c08535dc8ea96422d56a36b4fffc0b3bd2a13f2ab0d8dbd0e3a29bf3fc40c" }, { "name": "flag_ad", "unicode": "1F1E6-1F1E9", - "digest": "04a8c1745d9b8b20e903302379f2557e8082f72e33878db4cb2cd6b33eb97952" + "digest": "184fdcf790b8e2fd851b2b2b32f8636c595dd289734d12dc01ae4aa177e2043a" }, { "name": "ad", "unicode": "1F1E6-1F1E9", - "digest": "04a8c1745d9b8b20e903302379f2557e8082f72e33878db4cb2cd6b33eb97952" + "digest": "184fdcf790b8e2fd851b2b2b32f8636c595dd289734d12dc01ae4aa177e2043a" }, { "name": "flag_ae", "unicode": "1F1E6-1F1EA", - "digest": "868324ac2e7bea1547f5de95f39633b77b8d62f3b3433b3d1a4ee96d169a09cd" + "digest": "4a3257a9ce118e97567e76280f24d60fb555f1bada2eb26a2442a47f9398d21e" }, { "name": "ae", "unicode": "1F1E6-1F1EA", - "digest": "868324ac2e7bea1547f5de95f39633b77b8d62f3b3433b3d1a4ee96d169a09cd" + "digest": "4a3257a9ce118e97567e76280f24d60fb555f1bada2eb26a2442a47f9398d21e" }, { "name": "flag_af", "unicode": "1F1E6-1F1EB", - "digest": "9a94458519e9db5d6cf1557e54fdf62d7e48aaf7de25744a093ec8f284656226" + "digest": "0f6c719cac7ab3140694f6b580787ecdbf503e38f16de7ec5803f7d06a088ec3" }, { "name": "af", "unicode": "1F1E6-1F1EB", - "digest": "9a94458519e9db5d6cf1557e54fdf62d7e48aaf7de25744a093ec8f284656226" + "digest": "0f6c719cac7ab3140694f6b580787ecdbf503e38f16de7ec5803f7d06a088ec3" }, { "name": "flag_ag", "unicode": "1F1E6-1F1EC", - "digest": "ea59fabc2bd9024df06a59a34412f52bebfeb03eb6abd73d8fe153e3a68e28f4" + "digest": "92bf5a0e74564739862e9ba79331ffa656b7bae2ace0fc8dfd288984e4d510d4" }, { "name": "ag", "unicode": "1F1E6-1F1EC", - "digest": "ea59fabc2bd9024df06a59a34412f52bebfeb03eb6abd73d8fe153e3a68e28f4" + "digest": "92bf5a0e74564739862e9ba79331ffa656b7bae2ace0fc8dfd288984e4d510d4" }, { "name": "flag_ai", "unicode": "1F1E6-1F1EE", - "digest": "75676ded736ad2ebb921e9fd8ebfef49819a35c3dcf005bbc3b7e8c6e75178f2" + "digest": "aeaadc7ffafd8a1e01fdabc69d35f725d5f737b4c284a36191d96729f4e66e8f" }, { "name": "ai", "unicode": "1F1E6-1F1EE", - "digest": "75676ded736ad2ebb921e9fd8ebfef49819a35c3dcf005bbc3b7e8c6e75178f2" + "digest": "aeaadc7ffafd8a1e01fdabc69d35f725d5f737b4c284a36191d96729f4e66e8f" }, { "name": "flag_al", "unicode": "1F1E6-1F1F1", - "digest": "77b835dcff399b609e2479cbf10f08344c8fc277370ba8e4540165ca15563847" + "digest": "5ce7866d214d18c5f3438d480d14e77d104c4de679f0fdfca8cf0a44ce48eeea" }, { "name": "al", "unicode": "1F1E6-1F1F1", - "digest": "77b835dcff399b609e2479cbf10f08344c8fc277370ba8e4540165ca15563847" + "digest": "5ce7866d214d18c5f3438d480d14e77d104c4de679f0fdfca8cf0a44ce48eeea" }, { "name": "flag_am", "unicode": "1F1E6-1F1F2", - "digest": "3b820c628dd5a93137f7288a43553778f60b0beea4c0a239d063893c0723e73d" + "digest": "b40f5705f0cf9ef0fa7ffff0b371c4099319001ce79f894c317912f4dc5de4c8" }, { "name": "am", "unicode": "1F1E6-1F1F2", - "digest": "3b820c628dd5a93137f7288a43553778f60b0beea4c0a239d063893c0723e73d" + "digest": "b40f5705f0cf9ef0fa7ffff0b371c4099319001ce79f894c317912f4dc5de4c8" }, { "name": "flag_ao", "unicode": "1F1E6-1F1F4", - "digest": "d26439d4ecbe8b67bb1ae9753454505358ebb6b802624f19800471e53ee27187" + "digest": "eab6fbc1824d6e3cd152e8ec1d82e1beaebe02b53b35c6f7a883b8548af02f3a" }, { "name": "ao", "unicode": "1F1E6-1F1F4", - "digest": "d26439d4ecbe8b67bb1ae9753454505358ebb6b802624f19800471e53ee27187" + "digest": "eab6fbc1824d6e3cd152e8ec1d82e1beaebe02b53b35c6f7a883b8548af02f3a" }, { "name": "flag_aq", "unicode": "1F1E6-1F1F6", - "digest": "6b0b4e800d88ab289ae4b6d449bfa115e92543958b477d13ad348468a74e4616" + "digest": "367f6677a683a5f0e7248ab3a8f46d06ba146a0fd75004c70bac0e913147cdaa" }, { "name": "aq", "unicode": "1F1E6-1F1F6", - "digest": "6b0b4e800d88ab289ae4b6d449bfa115e92543958b477d13ad348468a74e4616" + "digest": "367f6677a683a5f0e7248ab3a8f46d06ba146a0fd75004c70bac0e913147cdaa" }, { "name": "flag_ar", "unicode": "1F1E6-1F1F7", - "digest": "ca76db601dd3f5794f1caace8ab5641fe3786b86e4ae030706162f0ce07d27b3" + "digest": "f0dc466b3216957f2679d7208c2d7cf288448b0739b9270a7c5fa717577bdf25" }, { "name": "ar", "unicode": "1F1E6-1F1F7", - "digest": "ca76db601dd3f5794f1caace8ab5641fe3786b86e4ae030706162f0ce07d27b3" + "digest": "f0dc466b3216957f2679d7208c2d7cf288448b0739b9270a7c5fa717577bdf25" }, { "name": "flag_as", "unicode": "1F1E6-1F1F8", - "digest": "170e1dde0e3fd2e0f2149de5cc8845e15580cc0412e81a643d61bd387de16141" + "digest": "fcb7a865c7763c63b23485cc27207b99a3a8492e83d5b5ee2df259a9f68f77d6" }, { "name": "as", "unicode": "1F1E6-1F1F8", - "digest": "170e1dde0e3fd2e0f2149de5cc8845e15580cc0412e81a643d61bd387de16141" + "digest": "fcb7a865c7763c63b23485cc27207b99a3a8492e83d5b5ee2df259a9f68f77d6" }, { "name": "flag_at", "unicode": "1F1E6-1F1F9", - "digest": "0ab3675a16b4988e87c81e87453c160d6616c7be76247f54c471dc63aa8b42ba" + "digest": "1d3d58e9abc034f9a093a94716eddf9811d54dfaf27969fd322b3809fac70217" }, { "name": "at", "unicode": "1F1E6-1F1F9", - "digest": "0ab3675a16b4988e87c81e87453c160d6616c7be76247f54c471dc63aa8b42ba" + "digest": "1d3d58e9abc034f9a093a94716eddf9811d54dfaf27969fd322b3809fac70217" }, { "name": "flag_au", "unicode": "1F1E6-1F1FA", - "digest": "b6f17d3dfd3547c069a0b6cddd4cf44fb8ce1d1d300e24284fb292ac142537e3" + "digest": "789563b64c71a5ad49078d335dc166ef614edb56d1e401885d32fb191c198fbd" }, { "name": "au", "unicode": "1F1E6-1F1FA", - "digest": "b6f17d3dfd3547c069a0b6cddd4cf44fb8ce1d1d300e24284fb292ac142537e3" + "digest": "789563b64c71a5ad49078d335dc166ef614edb56d1e401885d32fb191c198fbd" }, { "name": "flag_aw", "unicode": "1F1E6-1F1FC", - "digest": "7857bc907f04dfb7ccc4401c05034ad8afb6383a022db77973cfcafa4d6c16c8" + "digest": "1504dc3fd8457b44fdf75c15e136dc46a13e8342d1f98949728cdc1238843e0c" }, { "name": "aw", "unicode": "1F1E6-1F1FC", - "digest": "7857bc907f04dfb7ccc4401c05034ad8afb6383a022db77973cfcafa4d6c16c8" + "digest": "1504dc3fd8457b44fdf75c15e136dc46a13e8342d1f98949728cdc1238843e0c" }, { "name": "flag_ax", "unicode": "1F1E6-1F1FD", - "digest": "ab8f1fd4af7c220a54d478cec5a9f7f3beb5fc83439c448f3ac9848af8391ac1" + "digest": "e96fa3525f3be25016a4cf8428261735f3ed5fc9fe5b827b461746a3f08877bf" }, { "name": "ax", "unicode": "1F1E6-1F1FD", - "digest": "ab8f1fd4af7c220a54d478cec5a9f7f3beb5fc83439c448f3ac9848af8391ac1" + "digest": "e96fa3525f3be25016a4cf8428261735f3ed5fc9fe5b827b461746a3f08877bf" }, { "name": "flag_az", "unicode": "1F1E6-1F1FF", - "digest": "187cc7b6d39800c5910a34409db1e6b1d8aac808c72a93e922a419d9b054fd0b" + "digest": "12c366ac2c38b91314fb29056e09fa6e7417766cebde3045859cdb127549f4a2" }, { "name": "az", "unicode": "1F1E6-1F1FF", - "digest": "187cc7b6d39800c5910a34409db1e6b1d8aac808c72a93e922a419d9b054fd0b" + "digest": "12c366ac2c38b91314fb29056e09fa6e7417766cebde3045859cdb127549f4a2" }, { "name": "flag_ba", "unicode": "1F1E7-1F1E6", - "digest": "cd22c744213087384cf79ed314742026787212c9ceb6999ed166534670f7864a" + "digest": "0819ea3901510ac20c7f10e67e5f6c818210f17a362c1d12e299c41feb07f828" }, { "name": "ba", "unicode": "1F1E7-1F1E6", - "digest": "cd22c744213087384cf79ed314742026787212c9ceb6999ed166534670f7864a" + "digest": "0819ea3901510ac20c7f10e67e5f6c818210f17a362c1d12e299c41feb07f828" }, { "name": "flag_bb", "unicode": "1F1E7-1F1E7", - "digest": "44ff0a48ac2d2180374baa58b1b7c64f26d0d151a48811eb08ffa20758104512" + "digest": "cf32778a272ed6cbc8e783b59befd9b204009c69c61a425e148d867808b7fab9" }, { "name": "bb", "unicode": "1F1E7-1F1E7", - "digest": "44ff0a48ac2d2180374baa58b1b7c64f26d0d151a48811eb08ffa20758104512" + "digest": "cf32778a272ed6cbc8e783b59befd9b204009c69c61a425e148d867808b7fab9" }, { "name": "flag_bd", "unicode": "1F1E7-1F1E9", - "digest": "c18793d2b963458607a0bab94c57e62c8278fce870e96fd8dda78067a8fbde18" + "digest": "e6ed186644a874588e879513aec92f8107220dcdd14c766dee61f266ce045665" }, { "name": "bd", "unicode": "1F1E7-1F1E9", - "digest": "c18793d2b963458607a0bab94c57e62c8278fce870e96fd8dda78067a8fbde18" + "digest": "e6ed186644a874588e879513aec92f8107220dcdd14c766dee61f266ce045665" }, { "name": "flag_be", "unicode": "1F1E7-1F1EA", - "digest": "6e6ccfca064a43b93c8acc04a9425f95af204198022ca20b9ee6c491e99ad950" + "digest": "4d941011d15d9f6e755d6f7694884758baf17ac0691bf5d63700f8d6dbcdb948" }, { "name": "be", "unicode": "1F1E7-1F1EA", - "digest": "6e6ccfca064a43b93c8acc04a9425f95af204198022ca20b9ee6c491e99ad950" + "digest": "4d941011d15d9f6e755d6f7694884758baf17ac0691bf5d63700f8d6dbcdb948" }, { "name": "flag_bf", "unicode": "1F1E7-1F1EB", - "digest": "d69c0394a1c7cb6323f54f024b7d740c728f229ca5e1b54ac374d5024f5470a5" + "digest": "fcc57dbda9a86f725f558b6c6309484c97e65f1644aae4f9fb5e642681f6c2e0" }, { "name": "bf", "unicode": "1F1E7-1F1EB", - "digest": "d69c0394a1c7cb6323f54f024b7d740c728f229ca5e1b54ac374d5024f5470a5" + "digest": "fcc57dbda9a86f725f558b6c6309484c97e65f1644aae4f9fb5e642681f6c2e0" }, { "name": "flag_bg", "unicode": "1F1E7-1F1EC", - "digest": "413a270caf4a9155e84bdba6c9512277f5642246f6ba8d701383a5eeb02f7e95" + "digest": "816c47ed96c36c90723da150645902ea8ba18b44757fdd776c7b3542cfecfb18" }, { "name": "bg", "unicode": "1F1E7-1F1EC", - "digest": "413a270caf4a9155e84bdba6c9512277f5642246f6ba8d701383a5eeb02f7e95" + "digest": "816c47ed96c36c90723da150645902ea8ba18b44757fdd776c7b3542cfecfb18" }, { "name": "flag_bh", "unicode": "1F1E7-1F1ED", - "digest": "9243ed65d7f24c824c2a3207335a2d4ad25251258547c16d0b7b7cbb9df6f8de" + "digest": "2cd5c21775a6e73f59d08c9ee0cedf4e8241e562eab939573501d47681987737" }, { "name": "bh", "unicode": "1F1E7-1F1ED", - "digest": "9243ed65d7f24c824c2a3207335a2d4ad25251258547c16d0b7b7cbb9df6f8de" + "digest": "2cd5c21775a6e73f59d08c9ee0cedf4e8241e562eab939573501d47681987737" }, { "name": "flag_bi", "unicode": "1F1E7-1F1EE", - "digest": "63056519030524b2d2dcd47448267d817205dbd6b98075c97f011a8f1d4d1a4b" + "digest": "2da82acbec5518360633c1b0b56d55a79b67237f67d92af5e5cd75a2f3bd550e" }, { "name": "bi", "unicode": "1F1E7-1F1EE", - "digest": "63056519030524b2d2dcd47448267d817205dbd6b98075c97f011a8f1d4d1a4b" + "digest": "2da82acbec5518360633c1b0b56d55a79b67237f67d92af5e5cd75a2f3bd550e" }, { "name": "flag_bj", "unicode": "1F1E7-1F1EF", - "digest": "93b245eed85d22260d27d1a8c77f51fb3439309e09b2aeca6cd504dbea77b509" + "digest": "8fe8c34651eb4e28ab395261a5b72b6f37579535ed676d15de131914e19c0436" }, { "name": "bj", "unicode": "1F1E7-1F1EF", - "digest": "93b245eed85d22260d27d1a8c77f51fb3439309e09b2aeca6cd504dbea77b509" + "digest": "8fe8c34651eb4e28ab395261a5b72b6f37579535ed676d15de131914e19c0436" }, { "name": "flag_bl", "unicode": "1F1E7-1F1F1", - "digest": "5e1e478deaf02bbaa26595e4cefc5f5c9bec6105ce521b7b9ab4fa5e7a452c14" + "digest": "d37f2a215ee7ef5b5ab62d2a0c87e90553b17c6ee310f803a71e9fd72db880e7" }, { "name": "bl", "unicode": "1F1E7-1F1F1", - "digest": "5e1e478deaf02bbaa26595e4cefc5f5c9bec6105ce521b7b9ab4fa5e7a452c14" + "digest": "d37f2a215ee7ef5b5ab62d2a0c87e90553b17c6ee310f803a71e9fd72db880e7" }, { "name": "flag_black", "unicode": "1F3F4", - "digest": "df131e5c28e9f51dea53fe7f33551f91d420f7d686b7a62980f0154c6b5357a6" + "digest": "3740bfc9bcb3b46b697b8b7c47ab2c3e95eca9dbcba12f2bf98a01302704f203" }, { "name": "waving_black_flag", "unicode": "1F3F4", - "digest": "df131e5c28e9f51dea53fe7f33551f91d420f7d686b7a62980f0154c6b5357a6" + "digest": "3740bfc9bcb3b46b697b8b7c47ab2c3e95eca9dbcba12f2bf98a01302704f203" }, { "name": "flag_bm", "unicode": "1F1E7-1F1F2", - "digest": "9dcd9e60faebe7f93eb19157e99f2ad654a8145c61738de96e6ecd11a246764a" + "digest": "ccd21655573f3c955d616c5c7b1eac2be1d4772ff611648d6713ba55d9e4aa9b" }, { "name": "bm", "unicode": "1F1E7-1F1F2", - "digest": "9dcd9e60faebe7f93eb19157e99f2ad654a8145c61738de96e6ecd11a246764a" + "digest": "ccd21655573f3c955d616c5c7b1eac2be1d4772ff611648d6713ba55d9e4aa9b" }, { "name": "flag_bn", "unicode": "1F1E7-1F1F3", - "digest": "078af6ca481a77871ba005e251a46ce63951c27b1b0cd33b9c1d0d31d349bc1a" + "digest": "54330c3d7a37392e69098c213fd8c78f3faab4e7e5909c039188110422514228" }, { "name": "bn", "unicode": "1F1E7-1F1F3", - "digest": "078af6ca481a77871ba005e251a46ce63951c27b1b0cd33b9c1d0d31d349bc1a" + "digest": "54330c3d7a37392e69098c213fd8c78f3faab4e7e5909c039188110422514228" }, { "name": "flag_bo", "unicode": "1F1E7-1F1F4", - "digest": "92516d04e922a3bcbabe2e7619194bc972c09ba97576e8155f9829c397a71d8c" + "digest": "32aff973b26f4f91ca19dddd7861b564da43cfbee87603d8c004f1111342366c" }, { "name": "bo", "unicode": "1F1E7-1F1F4", - "digest": "92516d04e922a3bcbabe2e7619194bc972c09ba97576e8155f9829c397a71d8c" + "digest": "32aff973b26f4f91ca19dddd7861b564da43cfbee87603d8c004f1111342366c" }, { "name": "flag_bq", "unicode": "1F1E7-1F1F6", - "digest": "7832df5267a2bb8dddb83aeb11162ce79aeebdb718f2ac0e54adcf3d87936171" + "digest": "b1ebc959c43f706ca430d8633d9efaa9c60133871506b5f030b730cfb4c19e6f" }, { "name": "bq", "unicode": "1F1E7-1F1F6", - "digest": "7832df5267a2bb8dddb83aeb11162ce79aeebdb718f2ac0e54adcf3d87936171" + "digest": "b1ebc959c43f706ca430d8633d9efaa9c60133871506b5f030b730cfb4c19e6f" }, { "name": "flag_br", "unicode": "1F1E7-1F1F7", - "digest": "aabcc1c082124045ed214f7d9778d8e2ed791ebb8433defea91db458658abeec" + "digest": "64fb154d71fa34ff4838bc405f3e58a4102cf0cb49ca4b06fc3c7a6bf39671f0" }, { "name": "br", "unicode": "1F1E7-1F1F7", - "digest": "aabcc1c082124045ed214f7d9778d8e2ed791ebb8433defea91db458658abeec" + "digest": "64fb154d71fa34ff4838bc405f3e58a4102cf0cb49ca4b06fc3c7a6bf39671f0" }, { "name": "flag_bs", "unicode": "1F1E7-1F1F8", - "digest": "f628f39003608e181696634929522884165e27ccef55270293f92eeef991635f" + "digest": "c4b07e5f652ab06ece95d3774ce8b1399a935f8a28d440cb13cc8bd0b9728ed5" }, { "name": "bs", "unicode": "1F1E7-1F1F8", - "digest": "f628f39003608e181696634929522884165e27ccef55270293f92eeef991635f" + "digest": "c4b07e5f652ab06ece95d3774ce8b1399a935f8a28d440cb13cc8bd0b9728ed5" }, { "name": "flag_bt", "unicode": "1F1E7-1F1F9", - "digest": "af24a8ab34815da04c3e5af49a47449e0de93b068957cbda695816d0f830ca12" + "digest": "901ddbd999dd89a87c1e1208b1470cb4e604a9bc023d0cbcdee64e1bc54079ba" }, { "name": "bt", "unicode": "1F1E7-1F1F9", - "digest": "af24a8ab34815da04c3e5af49a47449e0de93b068957cbda695816d0f830ca12" + "digest": "901ddbd999dd89a87c1e1208b1470cb4e604a9bc023d0cbcdee64e1bc54079ba" }, { "name": "flag_bv", "unicode": "1F1E7-1F1FB", - "digest": "ff0037f6eed95d4bb5f2b502902360e1ff41426e2896daf3e0730cef1f8f7e41" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "bv", "unicode": "1F1E7-1F1FB", - "digest": "ff0037f6eed95d4bb5f2b502902360e1ff41426e2896daf3e0730cef1f8f7e41" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "flag_bw", "unicode": "1F1E7-1F1FC", - "digest": "3e3241ecb97946cc3e467b083d113a57dd305595e1512d4da18cc403e8689c1d" + "digest": "05aa351bc04dc0fe2669441ab500e000d48b1f0d7ad9e885c7abfb898aa0eb3f" }, { "name": "bw", "unicode": "1F1E7-1F1FC", - "digest": "3e3241ecb97946cc3e467b083d113a57dd305595e1512d4da18cc403e8689c1d" + "digest": "05aa351bc04dc0fe2669441ab500e000d48b1f0d7ad9e885c7abfb898aa0eb3f" }, { "name": "flag_by", "unicode": "1F1E7-1F1FE", - "digest": "bdd21885c6fac475241884a44149b887297772e17617ee59dd9fe8518d52cf3d" + "digest": "6eda3b87336ecf0aae4963986d86b916a055d8268c70520303288f235a93b0d9" }, { "name": "by", "unicode": "1F1E7-1F1FE", - "digest": "bdd21885c6fac475241884a44149b887297772e17617ee59dd9fe8518d52cf3d" + "digest": "6eda3b87336ecf0aae4963986d86b916a055d8268c70520303288f235a93b0d9" }, { "name": "flag_bz", "unicode": "1F1E7-1F1FF", - "digest": "21c16e1da641af004576000bf1db44b9a1e0fccfddc775e96022721c2f18eeea" + "digest": "d76ed945b1408558a30a99b8eed6712de968fc49fba1721b5660b8f48087e45a" }, { "name": "bz", "unicode": "1F1E7-1F1FF", - "digest": "21c16e1da641af004576000bf1db44b9a1e0fccfddc775e96022721c2f18eeea" + "digest": "d76ed945b1408558a30a99b8eed6712de968fc49fba1721b5660b8f48087e45a" }, { "name": "flag_ca", "unicode": "1F1E8-1F1E6", - "digest": "0d00e459084d58d3ea9c60488a9e51bf45f71b77f1600f190225d5ca6ca6c796" + "digest": "2fd036047d89751c05de5577909b58347883bc89c3b7d90bec28ad4770a98ecd" }, { "name": "ca", "unicode": "1F1E8-1F1E6", - "digest": "0d00e459084d58d3ea9c60488a9e51bf45f71b77f1600f190225d5ca6ca6c796" + "digest": "2fd036047d89751c05de5577909b58347883bc89c3b7d90bec28ad4770a98ecd" }, { "name": "flag_cc", "unicode": "1F1E8-1F1E8", - "digest": "86ab27164603ef0f1f83fe898eda6fbb7bc5709f2518f5577f00817860806a7b" + "digest": "837ba181a01c71f05d438d205efaaee99f93b2370c97b13e6132f99860323e36" }, { "name": "cc", "unicode": "1F1E8-1F1E8", - "digest": "86ab27164603ef0f1f83fe898eda6fbb7bc5709f2518f5577f00817860806a7b" + "digest": "837ba181a01c71f05d438d205efaaee99f93b2370c97b13e6132f99860323e36" }, { "name": "flag_cd", "unicode": "1F1E8-1F1E9", - "digest": "fdc2796530ada4bd0bae37ace4bbe707b321b287dcd64568f8e01d3a9df56066" + "digest": "318689274b4b3b58aed7fc1654127499a9da69bff1b83e592e86e69d167ce16f" }, { "name": "congo", "unicode": "1F1E8-1F1E9", - "digest": "fdc2796530ada4bd0bae37ace4bbe707b321b287dcd64568f8e01d3a9df56066" + "digest": "318689274b4b3b58aed7fc1654127499a9da69bff1b83e592e86e69d167ce16f" }, { "name": "flag_cf", "unicode": "1F1E8-1F1EB", - "digest": "5943bec02bede0931e21e7c34a68f375499f60a34883cc1edf2f21e9834b15ce" + "digest": "06d6042849d3b7b217c2b18ba787aae449e8c7d2537e2e5974744ec196062228" }, { "name": "cf", "unicode": "1F1E8-1F1EB", - "digest": "5943bec02bede0931e21e7c34a68f375499f60a34883cc1edf2f21e9834b15ce" + "digest": "06d6042849d3b7b217c2b18ba787aae449e8c7d2537e2e5974744ec196062228" }, { "name": "flag_cg", "unicode": "1F1E8-1F1EC", - "digest": "54498482e2772371e148e05cfb7c5eb55f6a22cd528662abdea10bad47d157da" + "digest": "09f45d2dcb5a24d8349ef86e7405cc29ef3d65a908c0bff3221c3b4546547813" }, { "name": "cg", "unicode": "1F1E8-1F1EC", - "digest": "54498482e2772371e148e05cfb7c5eb55f6a22cd528662abdea10bad47d157da" + "digest": "09f45d2dcb5a24d8349ef86e7405cc29ef3d65a908c0bff3221c3b4546547813" }, { "name": "flag_ch", @@ -3142,2162 +3142,2162 @@ { "name": "flag_ci", "unicode": "1F1E8-1F1EE", - "digest": "3a173a3058a5c0174dc88750852cafec264e901ce82a6c69db122c8c0ea71a3a" + "digest": "7d85a0c314b7397c9397a54ce2f3a4dc5f40d0234e586dbd8a541a8666f0f51e" }, { "name": "ci", "unicode": "1F1E8-1F1EE", - "digest": "3a173a3058a5c0174dc88750852cafec264e901ce82a6c69db122c8c0ea71a3a" + "digest": "7d85a0c314b7397c9397a54ce2f3a4dc5f40d0234e586dbd8a541a8666f0f51e" }, { "name": "flag_ck", "unicode": "1F1E8-1F1F0", - "digest": "42f395ff53c618b72b8a224cd4343d1a32f5ad82ced56bf590170a5ff0d5134c" + "digest": "c1aa105fe106ed09ed59a596859a0ce4e65a415c59f63df51961491cb947b136" }, { "name": "ck", "unicode": "1F1E8-1F1F0", - "digest": "42f395ff53c618b72b8a224cd4343d1a32f5ad82ced56bf590170a5ff0d5134c" + "digest": "c1aa105fe106ed09ed59a596859a0ce4e65a415c59f63df51961491cb947b136" }, { "name": "flag_cl", "unicode": "1F1E8-1F1F1", - "digest": "9d6255feb690596904d800e72d5acdb5cda941c5a741b031ea39a3c7650ac46f" + "digest": "0fffdad0d892f5c08aaa332af1ed2c228583d89a43190e979a3c3cb020d5a723" }, { "name": "chile", "unicode": "1F1E8-1F1F1", - "digest": "9d6255feb690596904d800e72d5acdb5cda941c5a741b031ea39a3c7650ac46f" + "digest": "0fffdad0d892f5c08aaa332af1ed2c228583d89a43190e979a3c3cb020d5a723" }, { "name": "flag_cm", "unicode": "1F1E8-1F1F2", - "digest": "ffc99d14e0a8b46a980331090ed9f36f31a87f1b0f8dd8c09007a31c6127c69e" + "digest": "e9f55e41a1fd2735a82ad7a7ac39326a944cb20423ffba3608ac53a46036caad" }, { "name": "cm", "unicode": "1F1E8-1F1F2", - "digest": "ffc99d14e0a8b46a980331090ed9f36f31a87f1b0f8dd8c09007a31c6127c69e" + "digest": "e9f55e41a1fd2735a82ad7a7ac39326a944cb20423ffba3608ac53a46036caad" }, { "name": "flag_cn", "unicode": "1F1E8-1F1F3", - "digest": "869a98c52bdc33591f87e2aab6cb4f13e98bb19136250ff25805d0312a8b7c8a" + "digest": "e2c8fee7e3bd51b13d6083d5bf344abe6b9b642e3cbb099d38b4ce341c99d890" }, { "name": "cn", "unicode": "1F1E8-1F1F3", - "digest": "869a98c52bdc33591f87e2aab6cb4f13e98bb19136250ff25805d0312a8b7c8a" + "digest": "e2c8fee7e3bd51b13d6083d5bf344abe6b9b642e3cbb099d38b4ce341c99d890" }, { "name": "flag_co", "unicode": "1F1E8-1F1F4", - "digest": "6aa458440eb2500ad307fea40fd8f1171a1506a6e32af144a4fd51545bb56151" + "digest": "51c60d0979bf8342eaff7cda9faf4b0dfab38efaf5ddf3717eb8f0e2a595b15f" }, { "name": "co", "unicode": "1F1E8-1F1F4", - "digest": "6aa458440eb2500ad307fea40fd8f1171a1506a6e32af144a4fd51545bb56151" + "digest": "51c60d0979bf8342eaff7cda9faf4b0dfab38efaf5ddf3717eb8f0e2a595b15f" }, { "name": "flag_cp", "unicode": "1F1E8-1F1F5", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "cp", "unicode": "1F1E8-1F1F5", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "flag_cr", "unicode": "1F1E8-1F1F7", - "digest": "0f3b54d8330c5bb136647547dafc598bda755697cfd6b7d872a2443ba7b5cad4" + "digest": "907905971b219e617a34eef4839b0bd08d98f3480e2631bce523120dcef95196" }, { "name": "cr", "unicode": "1F1E8-1F1F7", - "digest": "0f3b54d8330c5bb136647547dafc598bda755697cfd6b7d872a2443ba7b5cad4" + "digest": "907905971b219e617a34eef4839b0bd08d98f3480e2631bce523120dcef95196" }, { "name": "flag_cu", "unicode": "1F1E8-1F1FA", - "digest": "69bc973002475bb3d9b54cb0ba9ec9cb85f144c1cf54689da0ee8f414ebb0d83" + "digest": "d88cea729dc9dbbbcadac0409ec561995f061b2280577c01c6c6b37de347f150" }, { "name": "cu", "unicode": "1F1E8-1F1FA", - "digest": "69bc973002475bb3d9b54cb0ba9ec9cb85f144c1cf54689da0ee8f414ebb0d83" + "digest": "d88cea729dc9dbbbcadac0409ec561995f061b2280577c01c6c6b37de347f150" }, { "name": "flag_cv", "unicode": "1F1E8-1F1FB", - "digest": "af2e135cf3c1b03a5937c068a75061b5cd332e95902fd0f8dffb2ac2dc89692a" + "digest": "5ce97944adfce09e96387e6f872256482ac99ccbc60017c4d58ddd15b6fb67a7" }, { "name": "cv", "unicode": "1F1E8-1F1FB", - "digest": "af2e135cf3c1b03a5937c068a75061b5cd332e95902fd0f8dffb2ac2dc89692a" + "digest": "5ce97944adfce09e96387e6f872256482ac99ccbc60017c4d58ddd15b6fb67a7" }, { "name": "flag_cw", "unicode": "1F1E8-1F1FC", - "digest": "df4b2228a82f766c5c64c13c1388482a68549e59dd843671ee0eb43506e33411" + "digest": "a6fc31bd66ddc2ee8e7bde3aeabfe1c4ad00c9688abae234a541cc1236d68c1b" }, { "name": "cw", "unicode": "1F1E8-1F1FC", - "digest": "df4b2228a82f766c5c64c13c1388482a68549e59dd843671ee0eb43506e33411" + "digest": "a6fc31bd66ddc2ee8e7bde3aeabfe1c4ad00c9688abae234a541cc1236d68c1b" }, { "name": "flag_cx", "unicode": "1F1E8-1F1FD", - "digest": "db12e513345a7be53954167d359ede0b3effbfb292508ee4d726123e3a8f83d7" + "digest": "1261b32bfa22fa1441f5390ff499ac6b921d7ac59cc8acda3deb3a2beb4fb345" }, { "name": "cx", "unicode": "1F1E8-1F1FD", - "digest": "db12e513345a7be53954167d359ede0b3effbfb292508ee4d726123e3a8f83d7" + "digest": "1261b32bfa22fa1441f5390ff499ac6b921d7ac59cc8acda3deb3a2beb4fb345" }, { "name": "flag_cy", "unicode": "1F1E8-1F1FE", - "digest": "0cea41d4820746e2c6eb408f7ec7419afba9f7396401d92e6c1d77382f721d0b" + "digest": "82b1baa05ecffa0ea1f9a83b518163cbd7910985a21955740520bb16b7bb624f" }, { "name": "cy", "unicode": "1F1E8-1F1FE", - "digest": "0cea41d4820746e2c6eb408f7ec7419afba9f7396401d92e6c1d77382f721d0b" + "digest": "82b1baa05ecffa0ea1f9a83b518163cbd7910985a21955740520bb16b7bb624f" }, { "name": "flag_cz", "unicode": "1F1E8-1F1FF", - "digest": "a1c2405916963be306f761539123486a2845af53716c9dfe94ad5420e14d36c4" + "digest": "a169b18968992a52299b67c24fba495e84de28dec2ebb947a08e0d615ac54a5a" }, { "name": "cz", "unicode": "1F1E8-1F1FF", - "digest": "a1c2405916963be306f761539123486a2845af53716c9dfe94ad5420e14d36c4" + "digest": "a169b18968992a52299b67c24fba495e84de28dec2ebb947a08e0d615ac54a5a" }, { "name": "flag_de", "unicode": "1F1E9-1F1EA", - "digest": "74a80b64437bc4e31bdd7cbb753ecd2d719bf34c506cbac535db83a644174cce" + "digest": "99d1906944966a188c72ae592362ed907e2a0bfe95263955c34a0941507b30c1" }, { "name": "de", "unicode": "1F1E9-1F1EA", - "digest": "74a80b64437bc4e31bdd7cbb753ecd2d719bf34c506cbac535db83a644174cce" + "digest": "99d1906944966a188c72ae592362ed907e2a0bfe95263955c34a0941507b30c1" }, { "name": "flag_dg", "unicode": "1F1E9-1F1EC", - "digest": "13cb5ea872f94a9c3fb579cef417e2d1ed38e8cbe95059576380cacd59bc4b9d" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "dg", "unicode": "1F1E9-1F1EC", - "digest": "13cb5ea872f94a9c3fb579cef417e2d1ed38e8cbe95059576380cacd59bc4b9d" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "flag_dj", "unicode": "1F1E9-1F1EF", - "digest": "5b479654c28d3eeb70055c5e25dc46ccaba9eeea7537cc45ca9dbb8186b743b6" + "digest": "e90ba4e98fca71ff0ca5e65c28b911cc52f043428f375d8f954ecbd3b0c8f4dd" }, { "name": "dj", "unicode": "1F1E9-1F1EF", - "digest": "5b479654c28d3eeb70055c5e25dc46ccaba9eeea7537cc45ca9dbb8186b743b6" + "digest": "e90ba4e98fca71ff0ca5e65c28b911cc52f043428f375d8f954ecbd3b0c8f4dd" }, { "name": "flag_dk", "unicode": "1F1E9-1F1F0", - "digest": "dee7fa9644a9b447417518a353e7edcbb37b2af8bc7d13a6ed71d7210c43ca3c" + "digest": "65b3b5f31935a4969d81fedbb8279c7ad32da454d15c5eafcceba5d140927c77" }, { "name": "dk", "unicode": "1F1E9-1F1F0", - "digest": "dee7fa9644a9b447417518a353e7edcbb37b2af8bc7d13a6ed71d7210c43ca3c" + "digest": "65b3b5f31935a4969d81fedbb8279c7ad32da454d15c5eafcceba5d140927c77" }, { "name": "flag_dm", "unicode": "1F1E9-1F1F2", - "digest": "2e339190a8a0a238140f42e329f6646af5be75763a787ea268488a2e0440dc4c" + "digest": "f6225ded6d2cfd6c182ab1a53b8c49dc9df195df11eb7ff27b15f5d3721ba0eb" }, { "name": "dm", "unicode": "1F1E9-1F1F2", - "digest": "2e339190a8a0a238140f42e329f6646af5be75763a787ea268488a2e0440dc4c" + "digest": "f6225ded6d2cfd6c182ab1a53b8c49dc9df195df11eb7ff27b15f5d3721ba0eb" }, { "name": "flag_do", "unicode": "1F1E9-1F1F4", - "digest": "be5dafcd32d7197a96d37299a91835a8009299452f05a66d91c5fdec17448230" + "digest": "dc2ad6856cebbe47c5bd7f5dcf087e4f680d396b2d49440a9b71f0ad49fb8102" }, { "name": "do", "unicode": "1F1E9-1F1F4", - "digest": "be5dafcd32d7197a96d37299a91835a8009299452f05a66d91c5fdec17448230" + "digest": "dc2ad6856cebbe47c5bd7f5dcf087e4f680d396b2d49440a9b71f0ad49fb8102" }, { "name": "flag_dz", "unicode": "1F1E9-1F1FF", - "digest": "cf525d56bac45fe689f92d441274fc0ecbed4f95591d2c066598f72b1ee8d618" + "digest": "ea69fffc4d545f9c0fcef6768257501952955ba4d274c9b81843229a1265c5ed" }, { "name": "dz", "unicode": "1F1E9-1F1FF", - "digest": "cf525d56bac45fe689f92d441274fc0ecbed4f95591d2c066598f72b1ee8d618" + "digest": "ea69fffc4d545f9c0fcef6768257501952955ba4d274c9b81843229a1265c5ed" }, { "name": "flag_ea", "unicode": "1F1EA-1F1E6", - "digest": "1acb13950f7c3692f9a36e618d8ec10a73ead5d7fa80fb52b6b2a18e3d456002" + "digest": "e63bfe15428c481dd23b569e7aaf0a76106e58a946995b4415a81097ecd53b7d" }, { "name": "ea", "unicode": "1F1EA-1F1E6", - "digest": "1acb13950f7c3692f9a36e618d8ec10a73ead5d7fa80fb52b6b2a18e3d456002" + "digest": "e63bfe15428c481dd23b569e7aaf0a76106e58a946995b4415a81097ecd53b7d" }, { "name": "flag_ec", "unicode": "1F1EA-1F1E8", - "digest": "4d9d35450efc6026651ccc2278e70fb90b001ca5e5eecd31361b1e4e23253dbd" + "digest": "0cdabf85cd567047fda1d9a4508220cab829943a7c542c315078db0aac33edac" }, { "name": "ec", "unicode": "1F1EA-1F1E8", - "digest": "4d9d35450efc6026651ccc2278e70fb90b001ca5e5eecd31361b1e4e23253dbd" + "digest": "0cdabf85cd567047fda1d9a4508220cab829943a7c542c315078db0aac33edac" }, { "name": "flag_ee", "unicode": "1F1EA-1F1EA", - "digest": "86ec7b2f618fe71dddec3d5a621b56b878d683780f1e0ad446f965326d42df48" + "digest": "6dc4e3377e8e2af3ff40cf940a914bc7840980b4a14e7da86954343f2b1025fe" }, { "name": "ee", "unicode": "1F1EA-1F1EA", - "digest": "86ec7b2f618fe71dddec3d5a621b56b878d683780f1e0ad446f965326d42df48" + "digest": "6dc4e3377e8e2af3ff40cf940a914bc7840980b4a14e7da86954343f2b1025fe" }, { "name": "flag_eg", "unicode": "1F1EA-1F1EC", - "digest": "f06d36a6fec15af4c1a76de30e8469847dde2728bb5a48956b4e466098b778a4" + "digest": "2ed6bc056015694d75993eb5ee3c1850921d5630681207b04dfbdb982ab346a2" }, { "name": "eg", "unicode": "1F1EA-1F1EC", - "digest": "f06d36a6fec15af4c1a76de30e8469847dde2728bb5a48956b4e466098b778a4" + "digest": "2ed6bc056015694d75993eb5ee3c1850921d5630681207b04dfbdb982ab346a2" }, { "name": "flag_eh", "unicode": "1F1EA-1F1ED", - "digest": "eb63f5b92c62c98dc008dfa7ad8830aa17fa23964f812a28055bd8b6f5960c5b" + "digest": "72adb55943e4df99c00843c65463718609d937480f73dcf4a4451d46b9967a5e" }, { "name": "eh", "unicode": "1F1EA-1F1ED", - "digest": "eb63f5b92c62c98dc008dfa7ad8830aa17fa23964f812a28055bd8b6f5960c5b" + "digest": "72adb55943e4df99c00843c65463718609d937480f73dcf4a4451d46b9967a5e" }, { "name": "flag_er", "unicode": "1F1EA-1F1F7", - "digest": "e901195f7b37b22a6872d36713de0ec176f6424c209e261e5c849ce318c772f6" + "digest": "3fa59331eb5300c8c1f7b1f1bc15cfcfe688da6fa4a79341854598086a44eebc" }, { "name": "er", "unicode": "1F1EA-1F1F7", - "digest": "e901195f7b37b22a6872d36713de0ec176f6424c209e261e5c849ce318c772f6" + "digest": "3fa59331eb5300c8c1f7b1f1bc15cfcfe688da6fa4a79341854598086a44eebc" }, { "name": "flag_es", "unicode": "1F1EA-1F1F8", - "digest": "27ab5cc6c2e9f26ccdfa632887533eebcd9b514f80cec9e721cf8e5e2544339c" + "digest": "1fa1d5cb0a7e8b14aaec758b2e7bf49cdf8f3d09bbcc7dfd589053a432eeae25" }, { "name": "es", "unicode": "1F1EA-1F1F8", - "digest": "27ab5cc6c2e9f26ccdfa632887533eebcd9b514f80cec9e721cf8e5e2544339c" + "digest": "1fa1d5cb0a7e8b14aaec758b2e7bf49cdf8f3d09bbcc7dfd589053a432eeae25" }, { "name": "flag_et", "unicode": "1F1EA-1F1F9", - "digest": "6cdb3718c9b3ec713258dd36781db58b7da53f3017445056c1a76233e3b4a7de" + "digest": "72771decfb214394e4beb594e848ea590c3615800adbba24b5df4c5db6ee9617" }, { "name": "et", "unicode": "1F1EA-1F1F9", - "digest": "6cdb3718c9b3ec713258dd36781db58b7da53f3017445056c1a76233e3b4a7de" + "digest": "72771decfb214394e4beb594e848ea590c3615800adbba24b5df4c5db6ee9617" }, { "name": "flag_eu", "unicode": "1F1EA-1F1FA", - "digest": "363f60e8a747166d5cec8d70bfdf266411eec2ff07933b6187975075caadfd74" + "digest": "4bfa1b2ef23764ead5ef7899806f93e13fd29a09c75e61431579a4116c836aa4" }, { "name": "eu", "unicode": "1F1EA-1F1FA", - "digest": "363f60e8a747166d5cec8d70bfdf266411eec2ff07933b6187975075caadfd74" + "digest": "4bfa1b2ef23764ead5ef7899806f93e13fd29a09c75e61431579a4116c836aa4" }, { "name": "flag_fi", "unicode": "1F1EB-1F1EE", - "digest": "1a1959cb551a0e8bdaee8c04657fb7387a4d83173f7759f89468da12e1818a9e" + "digest": "d0208cdd5b153a2865f9f674179c62871d4675abb0fb639fba88fcd62553f54e" }, { "name": "fi", "unicode": "1F1EB-1F1EE", - "digest": "1a1959cb551a0e8bdaee8c04657fb7387a4d83173f7759f89468da12e1818a9e" + "digest": "d0208cdd5b153a2865f9f674179c62871d4675abb0fb639fba88fcd62553f54e" }, { "name": "flag_fj", "unicode": "1F1EB-1F1EF", - "digest": "f26dc36ea9c1f32d9bb54874ea384e7118b6e2585be69245fdd73acd8304ae78" + "digest": "6c5ec41114af3846b093a418f6e2b5ff7a83cb72cecde75a7dc62e8cb6dcfe45" }, { "name": "fj", "unicode": "1F1EB-1F1EF", - "digest": "f26dc36ea9c1f32d9bb54874ea384e7118b6e2585be69245fdd73acd8304ae78" + "digest": "6c5ec41114af3846b093a418f6e2b5ff7a83cb72cecde75a7dc62e8cb6dcfe45" }, { "name": "flag_fk", "unicode": "1F1EB-1F1F0", - "digest": "0479e233499b704f91a9b13d083e66296efe2f28ed917ab1496b223bfb09adb8" + "digest": "c69ad641d53785deff5c3934b7dcfcd3dc32ffc31b6d3e799d0555b03c23fc15" }, { "name": "fk", "unicode": "1F1EB-1F1F0", - "digest": "0479e233499b704f91a9b13d083e66296efe2f28ed917ab1496b223bfb09adb8" + "digest": "c69ad641d53785deff5c3934b7dcfcd3dc32ffc31b6d3e799d0555b03c23fc15" }, { "name": "flag_fm", "unicode": "1F1EB-1F1F2", - "digest": "142ea7b4b4a7004329925b495da43ab82351cbaac383c8da6e614b39ba58d05e" + "digest": "1e29fb06b273f253c23a9e4aa8ff84bfe22cffb5fa158a0c6f4cdeabe0216990" }, { "name": "fm", "unicode": "1F1EB-1F1F2", - "digest": "142ea7b4b4a7004329925b495da43ab82351cbaac383c8da6e614b39ba58d05e" + "digest": "1e29fb06b273f253c23a9e4aa8ff84bfe22cffb5fa158a0c6f4cdeabe0216990" }, { "name": "flag_fo", "unicode": "1F1EB-1F1F4", - "digest": "f1c800d4f4d39e2aead9a11ed500f16108d6bc48bd24bd2a1af7b966d8e76752" + "digest": "f4907d2f606f4f9d3bef06c6d38e8e88f2a148197b1573668866431a007afc2e" }, { "name": "fo", "unicode": "1F1EB-1F1F4", - "digest": "f1c800d4f4d39e2aead9a11ed500f16108d6bc48bd24bd2a1af7b966d8e76752" + "digest": "f4907d2f606f4f9d3bef06c6d38e8e88f2a148197b1573668866431a007afc2e" }, { "name": "flag_fr", "unicode": "1F1EB-1F1F7", - "digest": "6f52f36b5199c65ab1cad13ff4e77d2d8b48a8ff79b92166976674ffdc7829ee" + "digest": "5a1308ab3cbf6bffcab12588cf3325151a6c72990db7408c2b8605d89f94ed6e" }, { "name": "fr", "unicode": "1F1EB-1F1F7", - "digest": "6f52f36b5199c65ab1cad13ff4e77d2d8b48a8ff79b92166976674ffdc7829ee" + "digest": "5a1308ab3cbf6bffcab12588cf3325151a6c72990db7408c2b8605d89f94ed6e" }, { "name": "flag_ga", "unicode": "1F1EC-1F1E6", - "digest": "50a0d5a07466e419b74a4d532738f7958de9baa37df6191be4f3755dccc3b326" + "digest": "ddc32dee2976507be878ec3d3d2408632ca21bc434cd9f58db4f6ac9774a2db5" }, { "name": "ga", "unicode": "1F1EC-1F1E6", - "digest": "50a0d5a07466e419b74a4d532738f7958de9baa37df6191be4f3755dccc3b326" + "digest": "ddc32dee2976507be878ec3d3d2408632ca21bc434cd9f58db4f6ac9774a2db5" }, { "name": "flag_gb", "unicode": "1F1EC-1F1E7", - "digest": "220f7da6d5a231b766c79f2e1b7d3fdb74ec0c0c17558cc00a8a8ccdf2afc2e0" + "digest": "6b3bb254d134870b02cb066b06e206f652638a915c84b8649ceb30ec67fbebde" }, { "name": "gb", "unicode": "1F1EC-1F1E7", - "digest": "220f7da6d5a231b766c79f2e1b7d3fdb74ec0c0c17558cc00a8a8ccdf2afc2e0" + "digest": "6b3bb254d134870b02cb066b06e206f652638a915c84b8649ceb30ec67fbebde" }, { "name": "flag_gd", "unicode": "1F1EC-1F1E9", - "digest": "3e162b0d13f4ceea7f663b1d425f13863d104e80df75a640f526e276bcd04081" + "digest": "b6a210541ca22d816405f2a7d0d5241dc4d5488c8a36e15bd1e3063f9c41327f" }, { "name": "gd", "unicode": "1F1EC-1F1E9", - "digest": "3e162b0d13f4ceea7f663b1d425f13863d104e80df75a640f526e276bcd04081" + "digest": "b6a210541ca22d816405f2a7d0d5241dc4d5488c8a36e15bd1e3063f9c41327f" }, { "name": "flag_ge", "unicode": "1F1EC-1F1EA", - "digest": "35897f8254675d2efe9e3070c88af9ef214f08440e6ee75ebe81d28cdb57ea2b" + "digest": "e9a5035b7a46b925737e7f7b0ae2419cc4af0e980fbee5bd916edeef13823367" }, { "name": "ge", "unicode": "1F1EC-1F1EA", - "digest": "35897f8254675d2efe9e3070c88af9ef214f08440e6ee75ebe81d28cdb57ea2b" + "digest": "e9a5035b7a46b925737e7f7b0ae2419cc4af0e980fbee5bd916edeef13823367" }, { "name": "flag_gf", "unicode": "1F1EC-1F1EB", - "digest": "3a34df321635f71a0f2cc4e1eda58d85c29230c77456362345196351bf56533d" + "digest": "ce1bcd8c303897c1c22c5994182f21240b4aa635f0d7ce9944f76cbdbf0e4956" }, { "name": "gf", "unicode": "1F1EC-1F1EB", - "digest": "3a34df321635f71a0f2cc4e1eda58d85c29230c77456362345196351bf56533d" + "digest": "ce1bcd8c303897c1c22c5994182f21240b4aa635f0d7ce9944f76cbdbf0e4956" }, { "name": "flag_gg", "unicode": "1F1EC-1F1EC", - "digest": "c972f8d190b4e9ca8890df41503d202ffd73981833d3f3750f563302167bcd66" + "digest": "a435aab3609533ab2d68acd97deba844bfb0fc27b2adac68668223011f23ae5d" }, { "name": "gg", "unicode": "1F1EC-1F1EC", - "digest": "c972f8d190b4e9ca8890df41503d202ffd73981833d3f3750f563302167bcd66" + "digest": "a435aab3609533ab2d68acd97deba844bfb0fc27b2adac68668223011f23ae5d" }, { "name": "flag_gh", "unicode": "1F1EC-1F1ED", - "digest": "9c3d3569bd411389fa0af7c6938d4325cedeb9c0e8f059dc1d5a74c6b8d6d01b" + "digest": "7cad43b40f69b9b00cc1b38036789ce774fd3d597c89f0bf38433847ea69be26" }, { "name": "gh", "unicode": "1F1EC-1F1ED", - "digest": "9c3d3569bd411389fa0af7c6938d4325cedeb9c0e8f059dc1d5a74c6b8d6d01b" + "digest": "7cad43b40f69b9b00cc1b38036789ce774fd3d597c89f0bf38433847ea69be26" }, { "name": "flag_gi", "unicode": "1F1EC-1F1EE", - "digest": "ede638bc6fedc30a01821025d87ec19297500da9c04a7a155984fca186118649" + "digest": "70e9b17d18bf3e0e4d03f4f824323a57909416e4082ca9d8a0796a6959de4f07" }, { "name": "gi", "unicode": "1F1EC-1F1EE", - "digest": "ede638bc6fedc30a01821025d87ec19297500da9c04a7a155984fca186118649" + "digest": "70e9b17d18bf3e0e4d03f4f824323a57909416e4082ca9d8a0796a6959de4f07" }, { "name": "flag_gl", "unicode": "1F1EC-1F1F1", - "digest": "a2ce3371eff1da8331671925f707232aa593ac7400d59555c9ca689729ce24ec" + "digest": "1963d8cca1c1f06b7536b7fb8f5a4782ac0bb05afdf6e481101bce45c58cdd4b" }, { "name": "gl", "unicode": "1F1EC-1F1F1", - "digest": "a2ce3371eff1da8331671925f707232aa593ac7400d59555c9ca689729ce24ec" + "digest": "1963d8cca1c1f06b7536b7fb8f5a4782ac0bb05afdf6e481101bce45c58cdd4b" }, { "name": "flag_gm", "unicode": "1F1EC-1F1F2", - "digest": "932bf6eb75ddd4278268dd2f09d8fffcfef89f8fd6b6e86a08a414cd3ceec94d" + "digest": "6c776a8daa3f4daa2597b0025aec06fc0a53aed262e845d4da3897cd7a89c6a1" }, { "name": "gm", "unicode": "1F1EC-1F1F2", - "digest": "932bf6eb75ddd4278268dd2f09d8fffcfef89f8fd6b6e86a08a414cd3ceec94d" + "digest": "6c776a8daa3f4daa2597b0025aec06fc0a53aed262e845d4da3897cd7a89c6a1" }, { "name": "flag_gn", "unicode": "1F1EC-1F1F3", - "digest": "ebf543713895adaa09d64897f24bd461191191b8fcbbcede52bdaf4bd2dc67a8" + "digest": "134cf7c839370d171ae80a72e5d18d32ea1967df19c191d1a4ea446d649e9558" }, { "name": "gn", "unicode": "1F1EC-1F1F3", - "digest": "ebf543713895adaa09d64897f24bd461191191b8fcbbcede52bdaf4bd2dc67a8" + "digest": "134cf7c839370d171ae80a72e5d18d32ea1967df19c191d1a4ea446d649e9558" }, { "name": "flag_gp", "unicode": "1F1EC-1F1F5", - "digest": "2e6c48d80c571b34f31fa9b3622dcc51e1707c0118e991e9c177742ff02a8a96" + "digest": "be3e906b039ba4884053c78f4f14de9aa87c5573860ccb69ec766068ae3887c2" }, { "name": "gp", "unicode": "1F1EC-1F1F5", - "digest": "2e6c48d80c571b34f31fa9b3622dcc51e1707c0118e991e9c177742ff02a8a96" + "digest": "be3e906b039ba4884053c78f4f14de9aa87c5573860ccb69ec766068ae3887c2" }, { "name": "flag_gq", "unicode": "1F1EC-1F1F6", - "digest": "b0f5810180d12fc48faf75e73f882dc59072d7bf957f8455bf7e1e336539dc41" + "digest": "d476059c4ab41f5a1ef88583087362a5bc57cede930126f37041d1546564ab70" }, { "name": "gq", "unicode": "1F1EC-1F1F6", - "digest": "b0f5810180d12fc48faf75e73f882dc59072d7bf957f8455bf7e1e336539dc41" + "digest": "d476059c4ab41f5a1ef88583087362a5bc57cede930126f37041d1546564ab70" }, { "name": "flag_gr", "unicode": "1F1EC-1F1F7", - "digest": "8d60d6f8910f5179d851dbea0798b56a492c6be85f3d55e1a1126cd1d6663a3b" + "digest": "b9fa9304647aaa08167a07858bb18d778dcc399375f86f580b8d4244794678bc" }, { "name": "gr", "unicode": "1F1EC-1F1F7", - "digest": "8d60d6f8910f5179d851dbea0798b56a492c6be85f3d55e1a1126cd1d6663a3b" + "digest": "b9fa9304647aaa08167a07858bb18d778dcc399375f86f580b8d4244794678bc" }, { "name": "flag_gs", "unicode": "1F1EC-1F1F8", - "digest": "7b07915af0e2364ebc386a162d44846f3a7986fdd24e20ad2bc56d64a103fe9c" + "digest": "de33fbef6e294eb7af36e5b94d8ff573b354a4ff1ebdccf50ca528b86ed601d9" }, { "name": "gs", "unicode": "1F1EC-1F1F8", - "digest": "7b07915af0e2364ebc386a162d44846f3a7986fdd24e20ad2bc56d64a103fe9c" + "digest": "de33fbef6e294eb7af36e5b94d8ff573b354a4ff1ebdccf50ca528b86ed601d9" }, { "name": "flag_gt", "unicode": "1F1EC-1F1F9", - "digest": "0c78108ede45bf34917b409a0867f5ec8253c74b694beda083f3e8d04d7a10d8" + "digest": "4160843e5d642df597c8423eb8e3b74deafe304f3d141c8a4d2fc07509e44832" }, { "name": "gt", "unicode": "1F1EC-1F1F9", - "digest": "0c78108ede45bf34917b409a0867f5ec8253c74b694beda083f3e8d04d7a10d8" + "digest": "4160843e5d642df597c8423eb8e3b74deafe304f3d141c8a4d2fc07509e44832" }, { "name": "flag_gu", "unicode": "1F1EC-1F1FA", - "digest": "909f1bc98fa1507adb787eb3875503b21ea937d6ae8bb152153916c2da5e13bb" + "digest": "3b0cb257ba5b1c3e15d9102410c5f7418da03372e91ce90513de25b9f45283e3" }, { "name": "gu", "unicode": "1F1EC-1F1FA", - "digest": "909f1bc98fa1507adb787eb3875503b21ea937d6ae8bb152153916c2da5e13bb" + "digest": "3b0cb257ba5b1c3e15d9102410c5f7418da03372e91ce90513de25b9f45283e3" }, { "name": "flag_gw", "unicode": "1F1EC-1F1FC", - "digest": "f5f34410c7b22d5ed9994b47d0e7a9d9a6a1f05c4d3142f7fef3e4409725f5e6" + "digest": "bdf07a8f93c0f0a573af5f5361be404a3ba65b729c1a4c05b7632c03d85efc72" }, { "name": "gw", "unicode": "1F1EC-1F1FC", - "digest": "f5f34410c7b22d5ed9994b47d0e7a9d9a6a1f05c4d3142f7fef3e4409725f5e6" + "digest": "bdf07a8f93c0f0a573af5f5361be404a3ba65b729c1a4c05b7632c03d85efc72" }, { "name": "flag_gy", "unicode": "1F1EC-1F1FE", - "digest": "4939cf52ab34a924a31032b42668960a2c7d8d4f998b16b065c247110df334be" + "digest": "b47d8c98b747556f827ad0d1169264eb68ecaf9d2fb76595e8c31866361cbfc6" }, { "name": "gy", "unicode": "1F1EC-1F1FE", - "digest": "4939cf52ab34a924a31032b42668960a2c7d8d4f998b16b065c247110df334be" + "digest": "b47d8c98b747556f827ad0d1169264eb68ecaf9d2fb76595e8c31866361cbfc6" }, { "name": "flag_hk", "unicode": "1F1ED-1F1F0", - "digest": "bde0916df6d62f6b1cf8f85a8a39526c97fc6ef6fedb0b0cae2adb127a08eafe" + "digest": "8e5a54b2e4bd4f5182085299b9648062463da05d535cf0e46a7d9c58eaeb171f" }, { "name": "hk", "unicode": "1F1ED-1F1F0", - "digest": "bde0916df6d62f6b1cf8f85a8a39526c97fc6ef6fedb0b0cae2adb127a08eafe" + "digest": "8e5a54b2e4bd4f5182085299b9648062463da05d535cf0e46a7d9c58eaeb171f" }, { "name": "flag_hm", "unicode": "1F1ED-1F1F2", - "digest": "603e6c9bff9a0dc941970a313fe98fbf53ff5a57028f1a2766420be4211711cc" + "digest": "63c3e080c5e82a72c6d4cf5997ac823dc02184719ec59aadea6dd41b127abf22" }, { "name": "hm", "unicode": "1F1ED-1F1F2", - "digest": "603e6c9bff9a0dc941970a313fe98fbf53ff5a57028f1a2766420be4211711cc" + "digest": "63c3e080c5e82a72c6d4cf5997ac823dc02184719ec59aadea6dd41b127abf22" }, { "name": "flag_hn", "unicode": "1F1ED-1F1F3", - "digest": "2953ad0909bc32c02615f6ad5a4e5f331ba794a41632b1f0fc366e1c640cc2b9" + "digest": "87c1d160db810b5ed208fb33add54f96c17b0f08d87b81f6f09429abf6ec93ac" }, { "name": "hn", "unicode": "1F1ED-1F1F3", - "digest": "2953ad0909bc32c02615f6ad5a4e5f331ba794a41632b1f0fc366e1c640cc2b9" + "digest": "87c1d160db810b5ed208fb33add54f96c17b0f08d87b81f6f09429abf6ec93ac" }, { "name": "flag_hr", "unicode": "1F1ED-1F1F7", - "digest": "41c9ffc4f0faaa2d77e5cffb781329e7d2489ce879bd8eb9c503621e834abc50" + "digest": "8b68112f79baea38565673acf4f1cb90675a5829ff17e4cf9415c928b62aed88" }, { "name": "hr", "unicode": "1F1ED-1F1F7", - "digest": "41c9ffc4f0faaa2d77e5cffb781329e7d2489ce879bd8eb9c503621e834abc50" + "digest": "8b68112f79baea38565673acf4f1cb90675a5829ff17e4cf9415c928b62aed88" }, { "name": "flag_ht", "unicode": "1F1ED-1F1F9", - "digest": "6a56c3d71b4f858e1774aa2134a9f5584087fec968e9ee8bb1046d2ec93bf059" + "digest": "05dbd548c310ef1ebd1724aa85d821f8320106b16ddbf1f6442ea37e4407d5e1" }, { "name": "ht", "unicode": "1F1ED-1F1F9", - "digest": "6a56c3d71b4f858e1774aa2134a9f5584087fec968e9ee8bb1046d2ec93bf059" + "digest": "05dbd548c310ef1ebd1724aa85d821f8320106b16ddbf1f6442ea37e4407d5e1" }, { "name": "flag_hu", "unicode": "1F1ED-1F1FA", - "digest": "72f5809818d4cab8c0cee73df7f67b820fb8471eea4199911a5917ac099795e8" + "digest": "5079f3d6f1459e6df8dda5c19d2367ead8f5a755b8874ac999bae58e3c9f47a7" }, { "name": "hu", "unicode": "1F1ED-1F1FA", - "digest": "72f5809818d4cab8c0cee73df7f67b820fb8471eea4199911a5917ac099795e8" + "digest": "5079f3d6f1459e6df8dda5c19d2367ead8f5a755b8874ac999bae58e3c9f47a7" }, { "name": "flag_ic", "unicode": "1F1EE-1F1E8", - "digest": "7e2a7667fcd05f927af47e64c5790c104a9956dd9f1a45f03cb0fdcc85d866d3" + "digest": "8dcb18c4b75a60867a68d2f6edbf81e782aafb4b9a0404c8081f872dfe71e432" }, { "name": "ic", "unicode": "1F1EE-1F1E8", - "digest": "7e2a7667fcd05f927af47e64c5790c104a9956dd9f1a45f03cb0fdcc85d866d3" + "digest": "8dcb18c4b75a60867a68d2f6edbf81e782aafb4b9a0404c8081f872dfe71e432" }, { "name": "flag_id", "unicode": "1F1EE-1F1E9", - "digest": "4721f616fae2e443e52f1e9cc96e4835bddca16a2d75d7d5afea57cdee866b7f" + "digest": "1b0eb69a158ed3afe24be448d44751f95dcc5cbc7d1393a5753293f16ef0a66c" }, { "name": "indonesia", "unicode": "1F1EE-1F1E9", - "digest": "4721f616fae2e443e52f1e9cc96e4835bddca16a2d75d7d5afea57cdee866b7f" + "digest": "1b0eb69a158ed3afe24be448d44751f95dcc5cbc7d1393a5753293f16ef0a66c" }, { "name": "flag_ie", "unicode": "1F1EE-1F1EA", - "digest": "84b19833e6c9fb43187f8a28d85045a3df58816f20a07edab90474323174b1f3" + "digest": "5fc8c101ad7296224455f72f73c335aa4f676023b68645bafaf69087f69af390" }, { "name": "ie", "unicode": "1F1EE-1F1EA", - "digest": "84b19833e6c9fb43187f8a28d85045a3df58816f20a07edab90474323174b1f3" + "digest": "5fc8c101ad7296224455f72f73c335aa4f676023b68645bafaf69087f69af390" }, { "name": "flag_il", "unicode": "1F1EE-1F1F1", - "digest": "c99d4bd8c2541cf3a7392c4faf4477d96bc47065dd1423b9e06450483e69b34f" + "digest": "5aea4207415b7615dcdd69413705aefda700aefd0d27010cd0a0a338d879d9b8" }, { "name": "il", "unicode": "1F1EE-1F1F1", - "digest": "c99d4bd8c2541cf3a7392c4faf4477d96bc47065dd1423b9e06450483e69b34f" + "digest": "5aea4207415b7615dcdd69413705aefda700aefd0d27010cd0a0a338d879d9b8" }, { "name": "flag_im", "unicode": "1F1EE-1F1F2", - "digest": "5eeb12c0315b527ce61649a38b64d76af726a73b2d381d1a1ddd1366bafb1bfc" + "digest": "1ee9b3a5f1a52fc6d8369bfd81995fc0567e7a61deacd013701b3ec5fd64502e" }, { "name": "im", "unicode": "1F1EE-1F1F2", - "digest": "5eeb12c0315b527ce61649a38b64d76af726a73b2d381d1a1ddd1366bafb1bfc" + "digest": "1ee9b3a5f1a52fc6d8369bfd81995fc0567e7a61deacd013701b3ec5fd64502e" }, { "name": "flag_in", "unicode": "1F1EE-1F1F3", - "digest": "ecc3cfcff3368fe0875a51a8be9f4dfd449a187e5beb41a2b34241736247f73b" + "digest": "202ede502f34d55d180726ac2f29141c6875516f1b3e7ee99f266b16c2fe4bfd" }, { "name": "in", "unicode": "1F1EE-1F1F3", - "digest": "ecc3cfcff3368fe0875a51a8be9f4dfd449a187e5beb41a2b34241736247f73b" + "digest": "202ede502f34d55d180726ac2f29141c6875516f1b3e7ee99f266b16c2fe4bfd" }, { "name": "flag_io", "unicode": "1F1EE-1F1F4", - "digest": "26243d60e04ba3bc9eb8f008bfc77b2a64bcf1a3d0073eb0449a8c8121618c9c" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "io", "unicode": "1F1EE-1F1F4", - "digest": "26243d60e04ba3bc9eb8f008bfc77b2a64bcf1a3d0073eb0449a8c8121618c9c" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "flag_iq", "unicode": "1F1EE-1F1F6", - "digest": "a1fb5e59575081920b3be5290f654d57a9be099deb56d4ed69eba81a2b531cb3" + "digest": "bef294772b5ffccd6c061c19d60af66f61b248d78705faf347ade9ebfca2b46d" }, { "name": "iq", "unicode": "1F1EE-1F1F6", - "digest": "a1fb5e59575081920b3be5290f654d57a9be099deb56d4ed69eba81a2b531cb3" + "digest": "bef294772b5ffccd6c061c19d60af66f61b248d78705faf347ade9ebfca2b46d" }, { "name": "flag_ir", "unicode": "1F1EE-1F1F7", - "digest": "ab89488b934af1d4bdae7ed16dfc74fffe658bb8e95d5161b48cdd06de44ae85" + "digest": "d4faca93577a5546330ab6a09252307e19fb420d89912c0b48ceb90bf409d48e" }, { "name": "ir", "unicode": "1F1EE-1F1F7", - "digest": "ab89488b934af1d4bdae7ed16dfc74fffe658bb8e95d5161b48cdd06de44ae85" + "digest": "d4faca93577a5546330ab6a09252307e19fb420d89912c0b48ceb90bf409d48e" }, { "name": "flag_is", "unicode": "1F1EE-1F1F8", - "digest": "55db1fc9e6c56d4c9bcb9a46e5e4300cf2a0c32fa91dc24b487a1d56c8097268" + "digest": "b2fc04226b274009b4d99d92bcb72b255b534b6fd4b76d82dce1575ad975a456" }, { "name": "is", "unicode": "1F1EE-1F1F8", - "digest": "55db1fc9e6c56d4c9bcb9a46e5e4300cf2a0c32fa91dc24b487a1d56c8097268" + "digest": "b2fc04226b274009b4d99d92bcb72b255b534b6fd4b76d82dce1575ad975a456" }, { "name": "flag_it", "unicode": "1F1EE-1F1F9", - "digest": "36fc993fb00ab607578a4d0e573e988e17b9459a68a000a48de905a8238589d0" + "digest": "735760f193855d55460a0fb93dad55ff67253cab63176eceb90b9bde1faead1e" }, { "name": "it", "unicode": "1F1EE-1F1F9", - "digest": "36fc993fb00ab607578a4d0e573e988e17b9459a68a000a48de905a8238589d0" + "digest": "735760f193855d55460a0fb93dad55ff67253cab63176eceb90b9bde1faead1e" }, { "name": "flag_je", "unicode": "1F1EF-1F1EA", - "digest": "c608dbfd1259330e2f8c40dc5d12ffd0489396f4fc5f3ca57bcb2f0d9d05c20c" + "digest": "671a487a60571d928d2abaf306d0a9ba50239ec54ada14ea29a9a99df658d3cc" }, { "name": "je", "unicode": "1F1EF-1F1EA", - "digest": "c608dbfd1259330e2f8c40dc5d12ffd0489396f4fc5f3ca57bcb2f0d9d05c20c" + "digest": "671a487a60571d928d2abaf306d0a9ba50239ec54ada14ea29a9a99df658d3cc" }, { "name": "flag_jm", "unicode": "1F1EF-1F1F2", - "digest": "a8224b68b2d324f848d75e4376875ef76a8174e6ba32790d9ca622fe1eabfd5f" + "digest": "fb9047199d030b78fc0dcfc58d9b524fdb929238d922809da88147b7cebf4211" }, { "name": "jm", "unicode": "1F1EF-1F1F2", - "digest": "a8224b68b2d324f848d75e4376875ef76a8174e6ba32790d9ca622fe1eabfd5f" + "digest": "fb9047199d030b78fc0dcfc58d9b524fdb929238d922809da88147b7cebf4211" }, { "name": "flag_jo", "unicode": "1F1EF-1F1F4", - "digest": "2403563dc2ab4ed0e7e3a0761cc09f96801550bba6b177b54d651d8804ad987d" + "digest": "19f7d536d0293ebf3db49e05a158097cbde467115ef96523a0553808fd0b4178" }, { "name": "jo", "unicode": "1F1EF-1F1F4", - "digest": "2403563dc2ab4ed0e7e3a0761cc09f96801550bba6b177b54d651d8804ad987d" + "digest": "19f7d536d0293ebf3db49e05a158097cbde467115ef96523a0553808fd0b4178" }, { "name": "flag_jp", "unicode": "1F1EF-1F1F5", - "digest": "aea8eebd0a0139818cb7629d9c9a8e55160b458eb8ffeee2f36c5cff4b507fd3" + "digest": "51e971f777fe481ca9f7e077ecb2ce252c3cc0086b76384e7b965cdc337f3f9e" }, { "name": "jp", "unicode": "1F1EF-1F1F5", - "digest": "aea8eebd0a0139818cb7629d9c9a8e55160b458eb8ffeee2f36c5cff4b507fd3" + "digest": "51e971f777fe481ca9f7e077ecb2ce252c3cc0086b76384e7b965cdc337f3f9e" }, { "name": "flag_ke", "unicode": "1F1F0-1F1EA", - "digest": "9c8365f74858743bcdce4a9cf6a6f4110faf2dc6433e5dc7d98c24bb3b32a36d" + "digest": "0cec8f068548cfd3e7a20c10af84f97ca415fd6f8ab8b50783bf982e77d7260e" }, { "name": "ke", "unicode": "1F1F0-1F1EA", - "digest": "9c8365f74858743bcdce4a9cf6a6f4110faf2dc6433e5dc7d98c24bb3b32a36d" + "digest": "0cec8f068548cfd3e7a20c10af84f97ca415fd6f8ab8b50783bf982e77d7260e" }, { "name": "flag_kg", "unicode": "1F1F0-1F1EC", - "digest": "0c72bdb1d64b1e3be3d9516a50655a6162d8501851d2cf2fadb8c6ef7740df4e" + "digest": "5803ea6ab028261923fd7570c670a50518c6f462a2fb4d463531b12c3e382e6f" }, { "name": "kg", "unicode": "1F1F0-1F1EC", - "digest": "0c72bdb1d64b1e3be3d9516a50655a6162d8501851d2cf2fadb8c6ef7740df4e" + "digest": "5803ea6ab028261923fd7570c670a50518c6f462a2fb4d463531b12c3e382e6f" }, { "name": "flag_kh", "unicode": "1F1F0-1F1ED", - "digest": "49e41e488732d789e395091e144cd6215c6818ba2073e5e22ea21203a737d03c" + "digest": "287d357afe47179853fd485fb102834ead145598ed892664fc62d245cac16080" }, { "name": "kh", "unicode": "1F1F0-1F1ED", - "digest": "49e41e488732d789e395091e144cd6215c6818ba2073e5e22ea21203a737d03c" + "digest": "287d357afe47179853fd485fb102834ead145598ed892664fc62d245cac16080" }, { "name": "flag_ki", "unicode": "1F1F0-1F1EE", - "digest": "9d7f168adbcf5f4cfe28470addfdb0a8b231438d593edb70f633981bfa4c7638" + "digest": "ae4aee0d9cd7a21d4e250d45a484f5f641acdab3d79b437337b25fe34a0b49b0" }, { "name": "ki", "unicode": "1F1F0-1F1EE", - "digest": "9d7f168adbcf5f4cfe28470addfdb0a8b231438d593edb70f633981bfa4c7638" + "digest": "ae4aee0d9cd7a21d4e250d45a484f5f641acdab3d79b437337b25fe34a0b49b0" }, { "name": "flag_km", "unicode": "1F1F0-1F1F2", - "digest": "9318c28957fa7a19eba5ec452c1cbce01a5a83d41d29d081614d3abb0585d478" + "digest": "2d1730acbf5421fd02bd5483e26a86d82ec2fa99f0ff75bfd728a9df7914ad3b" }, { "name": "km", "unicode": "1F1F0-1F1F2", - "digest": "9318c28957fa7a19eba5ec452c1cbce01a5a83d41d29d081614d3abb0585d478" + "digest": "2d1730acbf5421fd02bd5483e26a86d82ec2fa99f0ff75bfd728a9df7914ad3b" }, { "name": "flag_kn", "unicode": "1F1F0-1F1F3", - "digest": "eac7e7d0f023dee5c0c8559bc2c9a96273adda54ce47598025120b30d8d6ebc1" + "digest": "b9ed979db9c6d243b00f61f19a9ec0f2c2390b2e5cace5ad61d9371dc8c670ac" }, { "name": "kn", "unicode": "1F1F0-1F1F3", - "digest": "eac7e7d0f023dee5c0c8559bc2c9a96273adda54ce47598025120b30d8d6ebc1" + "digest": "b9ed979db9c6d243b00f61f19a9ec0f2c2390b2e5cace5ad61d9371dc8c670ac" }, { "name": "flag_kp", "unicode": "1F1F0-1F1F5", - "digest": "d4d53db6f8363174de6db864c056267ba8a7d7e87b5527f2f42bb9b8ac3f362b" + "digest": "1bab0b9cab8028a95ce7231ad8d88ebcd31601cfa321284bba017ead47f6c729" }, { "name": "kp", "unicode": "1F1F0-1F1F5", - "digest": "d4d53db6f8363174de6db864c056267ba8a7d7e87b5527f2f42bb9b8ac3f362b" + "digest": "1bab0b9cab8028a95ce7231ad8d88ebcd31601cfa321284bba017ead47f6c729" }, { "name": "flag_kr", "unicode": "1F1F0-1F1F7", - "digest": "5c7e61ab4a2aae70cbe51f0ca4718516002bc943b35d870bd853a0c98c4e0ed5" + "digest": "33be8c09ebe273e203aa703cc827d52a6d9bf1699f5445bba13a77af2df45fa6" }, { "name": "kr", "unicode": "1F1F0-1F1F7", - "digest": "5c7e61ab4a2aae70cbe51f0ca4718516002bc943b35d870bd853a0c98c4e0ed5" + "digest": "33be8c09ebe273e203aa703cc827d52a6d9bf1699f5445bba13a77af2df45fa6" }, { "name": "flag_kw", "unicode": "1F1F0-1F1FC", - "digest": "5d229cd99d25f4285bd30d98cfcc3cd8346648897476e2905a1811ceeef48d37" + "digest": "04d901a92ea55b13dc4983a9e3adb52dc89c9f3decee86fd06022aa902678b6d" }, { "name": "kw", "unicode": "1F1F0-1F1FC", - "digest": "5d229cd99d25f4285bd30d98cfcc3cd8346648897476e2905a1811ceeef48d37" + "digest": "04d901a92ea55b13dc4983a9e3adb52dc89c9f3decee86fd06022aa902678b6d" }, { "name": "flag_ky", "unicode": "1F1F0-1F1FE", - "digest": "9ce3d8dfc273d3a400960876c434b702f93df92c6c00682dbed2ec8e3966d8a8" + "digest": "10f4d02f33cadd34da89de71a3b763809bad480cd9ae9d2ec000db026bd94cd1" }, { "name": "ky", "unicode": "1F1F0-1F1FE", - "digest": "9ce3d8dfc273d3a400960876c434b702f93df92c6c00682dbed2ec8e3966d8a8" + "digest": "10f4d02f33cadd34da89de71a3b763809bad480cd9ae9d2ec000db026bd94cd1" }, { "name": "flag_kz", "unicode": "1F1F0-1F1FF", - "digest": "a6f0be0a767fa4824495d568d9fc2bd8d4c1a26f363873d3b65362e9383e2a50" + "digest": "dfaff69a78cf635f7fad41bd5bdcc8003298454708a6178ba7348b1b40c360c1" }, { "name": "kz", "unicode": "1F1F0-1F1FF", - "digest": "a6f0be0a767fa4824495d568d9fc2bd8d4c1a26f363873d3b65362e9383e2a50" + "digest": "dfaff69a78cf635f7fad41bd5bdcc8003298454708a6178ba7348b1b40c360c1" }, { "name": "flag_la", "unicode": "1F1F1-1F1E6", - "digest": "ab2ae96da87f7b53ab212f8dcd897a591cff9ea6666270097a8e739ee0b8f8cb" + "digest": "4fcfbdc694cf99ae3f832500cdcdedb88c444b6df88bc9b7141f4f26ba3d5bfd" }, { "name": "la", "unicode": "1F1F1-1F1E6", - "digest": "ab2ae96da87f7b53ab212f8dcd897a591cff9ea6666270097a8e739ee0b8f8cb" + "digest": "4fcfbdc694cf99ae3f832500cdcdedb88c444b6df88bc9b7141f4f26ba3d5bfd" }, { "name": "flag_lb", "unicode": "1F1F1-1F1E7", - "digest": "0c3fcab22e9fae1c78658290aff97de785d0b6adb5e3702d00073ce774b7ed54" + "digest": "af4b1f784bea0ec7a712495491dffbd1152cc857a99fd433f76bfeb313819a62" }, { "name": "lb", "unicode": "1F1F1-1F1E7", - "digest": "0c3fcab22e9fae1c78658290aff97de785d0b6adb5e3702d00073ce774b7ed54" + "digest": "af4b1f784bea0ec7a712495491dffbd1152cc857a99fd433f76bfeb313819a62" }, { "name": "flag_lc", "unicode": "1F1F1-1F1E8", - "digest": "e154b0b3a1635a36e0d9ad518c0ea12259320e5f1ebbda982248486492065d28" + "digest": "40784aa558b75d07ae499c004e2cc5d0b2efdfc3e5be705b5a9f6b70d681c396" }, { "name": "lc", "unicode": "1F1F1-1F1E8", - "digest": "e154b0b3a1635a36e0d9ad518c0ea12259320e5f1ebbda982248486492065d28" + "digest": "40784aa558b75d07ae499c004e2cc5d0b2efdfc3e5be705b5a9f6b70d681c396" }, { "name": "flag_li", "unicode": "1F1F1-1F1EE", - "digest": "bbc393a89e73cc8c29a0a9297428d07aa1d4717ea9b7d4dd9d69f21ac7d0605d" + "digest": "c4eb4c43f457ce60ff9d046adb512c1d3462203403eeb595bff3ebc010ed6633" }, { "name": "li", "unicode": "1F1F1-1F1EE", - "digest": "bbc393a89e73cc8c29a0a9297428d07aa1d4717ea9b7d4dd9d69f21ac7d0605d" + "digest": "c4eb4c43f457ce60ff9d046adb512c1d3462203403eeb595bff3ebc010ed6633" }, { "name": "flag_lk", "unicode": "1F1F1-1F1F0", - "digest": "376bd501d113a844971ca1006ab31aa086cd55d74842ea5f3dedaba997b58693" + "digest": "a5285cdfdc3715fa3941f5f0eb03dc425969eaaf22c719c27ab4418628d09bc5" }, { "name": "lk", "unicode": "1F1F1-1F1F0", - "digest": "376bd501d113a844971ca1006ab31aa086cd55d74842ea5f3dedaba997b58693" + "digest": "a5285cdfdc3715fa3941f5f0eb03dc425969eaaf22c719c27ab4418628d09bc5" }, { "name": "flag_lr", "unicode": "1F1F1-1F1F7", - "digest": "9a6ebe1c9d9a53079ee77292a5ad0965f96409b0417f92876a1c3bd463d6a9bc" + "digest": "ed04334264953b4da570db8c392b99d2fab4e0b7efc2331427016c6a08e818be" }, { "name": "lr", "unicode": "1F1F1-1F1F7", - "digest": "9a6ebe1c9d9a53079ee77292a5ad0965f96409b0417f92876a1c3bd463d6a9bc" + "digest": "ed04334264953b4da570db8c392b99d2fab4e0b7efc2331427016c6a08e818be" }, { "name": "flag_ls", "unicode": "1F1F1-1F1F8", - "digest": "e2f4b05414f6e0c3d629a92b0534d4145475f0214a83a62c902fe0884c833c89" + "digest": "cd56022106d027317cc9bf4c848758cf29ffe277ce71fdb9c1cf89ac4fd6e6db" }, { "name": "ls", "unicode": "1F1F1-1F1F8", - "digest": "e2f4b05414f6e0c3d629a92b0534d4145475f0214a83a62c902fe0884c833c89" + "digest": "cd56022106d027317cc9bf4c848758cf29ffe277ce71fdb9c1cf89ac4fd6e6db" }, { "name": "flag_lt", "unicode": "1F1F1-1F1F9", - "digest": "d5e2f8b2ffa820a33ea6d612fccd61e32467d25154342f5be134d3520e48387f" + "digest": "3c4395b068e421100fd97a102f170cb8d5c093885eef7cb40d3faff4f4e47fe9" }, { "name": "lt", "unicode": "1F1F1-1F1F9", - "digest": "d5e2f8b2ffa820a33ea6d612fccd61e32467d25154342f5be134d3520e48387f" + "digest": "3c4395b068e421100fd97a102f170cb8d5c093885eef7cb40d3faff4f4e47fe9" }, { "name": "flag_lu", "unicode": "1F1F1-1F1FA", - "digest": "f43277103292195b51981d08e2dde68eab660a65c7875f510e09a8b2370f1b5c" + "digest": "df15a2c47eecad17e0cc169bdf0d31c6a51eb22de7ca4e70d2431359a33f930d" }, { "name": "lu", "unicode": "1F1F1-1F1FA", - "digest": "f43277103292195b51981d08e2dde68eab660a65c7875f510e09a8b2370f1b5c" + "digest": "df15a2c47eecad17e0cc169bdf0d31c6a51eb22de7ca4e70d2431359a33f930d" }, { "name": "flag_lv", "unicode": "1F1F1-1F1FB", - "digest": "e1288ac5c80d6e9d577d652e34be247ca39bf9d3d7cfc8a6cae13c1f9ac9dc47" + "digest": "9b53c6ce23287935200da8ca8a8af78013a4b1572f9821e7e1724cbad248e7e2" }, { "name": "lv", "unicode": "1F1F1-1F1FB", - "digest": "e1288ac5c80d6e9d577d652e34be247ca39bf9d3d7cfc8a6cae13c1f9ac9dc47" + "digest": "9b53c6ce23287935200da8ca8a8af78013a4b1572f9821e7e1724cbad248e7e2" }, { "name": "flag_ly", "unicode": "1F1F1-1F1FE", - "digest": "5122294b769a174e3b6e3d238bb846b3e760929f5bb3c1a708d8a429f3f32f68" + "digest": "42efa9f3526ef006d6723fa17538a98ab9556ae25f14df1b06d21361bf7e1a44" }, { "name": "ly", "unicode": "1F1F1-1F1FE", - "digest": "5122294b769a174e3b6e3d238bb846b3e760929f5bb3c1a708d8a429f3f32f68" + "digest": "42efa9f3526ef006d6723fa17538a98ab9556ae25f14df1b06d21361bf7e1a44" }, { "name": "flag_ma", "unicode": "1F1F2-1F1E6", - "digest": "615a6447ff284de7689b4fd7b04fdda308f65dbbec958cfb96d2977514981d16" + "digest": "96c07296cfd7aa1cb642faed8ace26744105b81ca880157a4ef4caee0befe26e" }, { "name": "ma", "unicode": "1F1F2-1F1E6", - "digest": "615a6447ff284de7689b4fd7b04fdda308f65dbbec958cfb96d2977514981d16" + "digest": "96c07296cfd7aa1cb642faed8ace26744105b81ca880157a4ef4caee0befe26e" }, { "name": "flag_mc", "unicode": "1F1F2-1F1E8", - "digest": "08b48b28938acbfc0fbc15c25ee14dbad7164c5165d03df2eee370755ee7b4cf" + "digest": "6b44608842fe849ae2b4bae5eb87ccd436459a427051dfda25080196273d4b9f" }, { "name": "mc", "unicode": "1F1F2-1F1E8", - "digest": "08b48b28938acbfc0fbc15c25ee14dbad7164c5165d03df2eee370755ee7b4cf" + "digest": "6b44608842fe849ae2b4bae5eb87ccd436459a427051dfda25080196273d4b9f" }, { "name": "flag_md", "unicode": "1F1F2-1F1E9", - "digest": "93d61de68f821e1e08b30e63d91e8b4a657766475128538894cf9da9a3b4e3c0" + "digest": "78c7b01c698873a9129d52ba38b3eb4cfc683ef2ae10b7b922b17c07f1c938c8" }, { "name": "md", "unicode": "1F1F2-1F1E9", - "digest": "93d61de68f821e1e08b30e63d91e8b4a657766475128538894cf9da9a3b4e3c0" + "digest": "78c7b01c698873a9129d52ba38b3eb4cfc683ef2ae10b7b922b17c07f1c938c8" }, { "name": "flag_me", "unicode": "1F1F2-1F1EA", - "digest": "ee55c0eb78241aec2baf1822a47fa46d63209ceae3db7617ae886b823ae229ff" + "digest": "01aa0f9df89302edc4ae319b5dd78069ba8807c3f38cc7bfe01bff67c8efd416" }, { "name": "me", "unicode": "1F1F2-1F1EA", - "digest": "ee55c0eb78241aec2baf1822a47fa46d63209ceae3db7617ae886b823ae229ff" + "digest": "01aa0f9df89302edc4ae319b5dd78069ba8807c3f38cc7bfe01bff67c8efd416" }, { "name": "flag_mf", "unicode": "1F1F2-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "mf", "unicode": "1F1F2-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "flag_mg", "unicode": "1F1F2-1F1EC", - "digest": "86ec8140e2c4854f52cff74757baf0cbb75a4aacca8be6af8c8f9c939a7b866c" + "digest": "56ebcd2a2e144d656d3b38a62595138fe6e50f9c1144f70b0a120cce7a72eb5b" }, { "name": "mg", "unicode": "1F1F2-1F1EC", - "digest": "86ec8140e2c4854f52cff74757baf0cbb75a4aacca8be6af8c8f9c939a7b866c" + "digest": "56ebcd2a2e144d656d3b38a62595138fe6e50f9c1144f70b0a120cce7a72eb5b" }, { "name": "flag_mh", "unicode": "1F1F2-1F1ED", - "digest": "8311ea3422c9d5e94b55e19b03bedd6fe6e2a191b7657e15ac75a48932958a5b" + "digest": "008660adc4c2e4d04830498988184d1ef8a372a6c085da369a94ee6b820dbbb7" }, { "name": "mh", "unicode": "1F1F2-1F1ED", - "digest": "8311ea3422c9d5e94b55e19b03bedd6fe6e2a191b7657e15ac75a48932958a5b" + "digest": "008660adc4c2e4d04830498988184d1ef8a372a6c085da369a94ee6b820dbbb7" }, { "name": "flag_mk", "unicode": "1F1F2-1F1F0", - "digest": "5c6f504f88c5a875c06ac8b26fa6e81a9d79c42a1c7d1fad9a5d4c8ad06ca502" + "digest": "f3c4c5106ace81c21fc0c6a7cc5c5e04e9453468fbc6ccbc851bb8dd61ff237f" }, { "name": "mk", "unicode": "1F1F2-1F1F0", - "digest": "5c6f504f88c5a875c06ac8b26fa6e81a9d79c42a1c7d1fad9a5d4c8ad06ca502" + "digest": "f3c4c5106ace81c21fc0c6a7cc5c5e04e9453468fbc6ccbc851bb8dd61ff237f" }, { "name": "flag_ml", "unicode": "1F1F2-1F1F1", - "digest": "d08a4973db40cf28e58ca3c80e8bd4e50d68ba1080b31917aeefdb0e210b5c50" + "digest": "e70a6b30e46adc2e19684308a848fef2c3ad76e2cac4bb493ee3270ad39f9d1b" }, { "name": "ml", "unicode": "1F1F2-1F1F1", - "digest": "d08a4973db40cf28e58ca3c80e8bd4e50d68ba1080b31917aeefdb0e210b5c50" + "digest": "e70a6b30e46adc2e19684308a848fef2c3ad76e2cac4bb493ee3270ad39f9d1b" }, { "name": "flag_mm", "unicode": "1F1F2-1F1F2", - "digest": "5e95089514ca09bb93afb481b317477c9d053adcf450e0b711d78ed1078c7470" + "digest": "720f5d38887202ba049cd5a46c183679be6a01f169d99e6e656c73b515793a7d" }, { "name": "mm", "unicode": "1F1F2-1F1F2", - "digest": "5e95089514ca09bb93afb481b317477c9d053adcf450e0b711d78ed1078c7470" + "digest": "720f5d38887202ba049cd5a46c183679be6a01f169d99e6e656c73b515793a7d" }, { "name": "flag_mn", "unicode": "1F1F2-1F1F3", - "digest": "7a0ca72715dd2a36eeeed2f8c888497cb752f0000af8f07d6930743caf6e4273" + "digest": "5f0fd6fcb2ed73a5a6d9396c3703612503c1f16283bbb4e9362a1c8324b762ad" }, { "name": "mn", "unicode": "1F1F2-1F1F3", - "digest": "7a0ca72715dd2a36eeeed2f8c888497cb752f0000af8f07d6930743caf6e4273" + "digest": "5f0fd6fcb2ed73a5a6d9396c3703612503c1f16283bbb4e9362a1c8324b762ad" }, { "name": "flag_mo", "unicode": "1F1F2-1F1F4", - "digest": "d2c7c2191bc1bc83d85f2270968cb4de5cf26a11f70e166a8b32c108287ef729" + "digest": "fc2a9e7323867cf195f551e59afdab778c56b84c96af28c20207c9870caa2c39" }, { "name": "mo", "unicode": "1F1F2-1F1F4", - "digest": "d2c7c2191bc1bc83d85f2270968cb4de5cf26a11f70e166a8b32c108287ef729" + "digest": "fc2a9e7323867cf195f551e59afdab778c56b84c96af28c20207c9870caa2c39" }, { "name": "flag_mp", "unicode": "1F1F2-1F1F5", - "digest": "89ad06121fd7981338fe188464491bea371f85125bfb4fc01fb5cad606613b1e" + "digest": "ddce3be9d72914240c42e1b97ea97af01016d0a3879999cb0e447552682c06ba" }, { "name": "mp", "unicode": "1F1F2-1F1F5", - "digest": "89ad06121fd7981338fe188464491bea371f85125bfb4fc01fb5cad606613b1e" + "digest": "ddce3be9d72914240c42e1b97ea97af01016d0a3879999cb0e447552682c06ba" }, { "name": "flag_mq", "unicode": "1F1F2-1F1F6", - "digest": "98176f3af823b26a3657a17c5073ee22367898b40bd3973de76329aa87ca5a2e" + "digest": "888f455b1322d6fb83dc9f469f5505fea3dd6ece77d17d0d7345319c3ebcec0e" }, { "name": "mq", "unicode": "1F1F2-1F1F6", - "digest": "98176f3af823b26a3657a17c5073ee22367898b40bd3973de76329aa87ca5a2e" + "digest": "888f455b1322d6fb83dc9f469f5505fea3dd6ece77d17d0d7345319c3ebcec0e" }, { "name": "flag_mr", "unicode": "1F1F2-1F1F7", - "digest": "cc3e705ad84f83fe2d544385c39564743024dab26595d62469b35fdb791f6015" + "digest": "72621914c92dd9c9f3ac9973ee3589583bfe42b841cdd35f47af75e2f629726c" }, { "name": "mr", "unicode": "1F1F2-1F1F7", - "digest": "cc3e705ad84f83fe2d544385c39564743024dab26595d62469b35fdb791f6015" + "digest": "72621914c92dd9c9f3ac9973ee3589583bfe42b841cdd35f47af75e2f629726c" }, { "name": "flag_ms", "unicode": "1F1F2-1F1F8", - "digest": "465e3d5700b557f2589bd6e34a0c6b12c634a6ed4dcfbee3c1c841c5de3413f0" + "digest": "5944996295132f41ec55261ff7927518bd47aec95d274a6ff257c357b43657bc" }, { "name": "ms", "unicode": "1F1F2-1F1F8", - "digest": "465e3d5700b557f2589bd6e34a0c6b12c634a6ed4dcfbee3c1c841c5de3413f0" + "digest": "5944996295132f41ec55261ff7927518bd47aec95d274a6ff257c357b43657bc" }, { "name": "flag_mt", "unicode": "1F1F2-1F1F9", - "digest": "e610ba22d8d8ad750ed10dff8e1b4d89bc34f066c3424bfa77dbdc1a5d79743a" + "digest": "95f0550e8823441a4e69b26c540baea94f3ddcc282100fd0239021c00df0b469" }, { "name": "mt", "unicode": "1F1F2-1F1F9", - "digest": "e610ba22d8d8ad750ed10dff8e1b4d89bc34f066c3424bfa77dbdc1a5d79743a" + "digest": "95f0550e8823441a4e69b26c540baea94f3ddcc282100fd0239021c00df0b469" }, { "name": "flag_mu", "unicode": "1F1F2-1F1FA", - "digest": "3daf015d3b95218677dafbb282b7804686aa68875a6bd1d70c165b7b149e19cb" + "digest": "5fda78a6df0ea7f5cac5fb4c8fd68529c14c5e15bac4e0b167493cb6ac459253" }, { "name": "mu", "unicode": "1F1F2-1F1FA", - "digest": "3daf015d3b95218677dafbb282b7804686aa68875a6bd1d70c165b7b149e19cb" + "digest": "5fda78a6df0ea7f5cac5fb4c8fd68529c14c5e15bac4e0b167493cb6ac459253" }, { "name": "flag_mv", "unicode": "1F1F2-1F1FB", - "digest": "d30e4bfd04f08177de92f3c175600aaafa89b9668bbe2b83f35f07a74382065c" + "digest": "f75c8f6fd3a68f2944a04c833c649d4b576997f491100cf3f3160fe77117fabb" }, { "name": "mv", "unicode": "1F1F2-1F1FB", - "digest": "d30e4bfd04f08177de92f3c175600aaafa89b9668bbe2b83f35f07a74382065c" + "digest": "f75c8f6fd3a68f2944a04c833c649d4b576997f491100cf3f3160fe77117fabb" }, { "name": "flag_mw", "unicode": "1F1F2-1F1FC", - "digest": "f364b1c8bfda3f86b5e26422eedc571ba11e312dcc634197631a6840cb22aede" + "digest": "d46b484a97e5b90b6b259f8de1712b553f93f0dfb6391209200358bb9429ebf5" }, { "name": "mw", "unicode": "1F1F2-1F1FC", - "digest": "f364b1c8bfda3f86b5e26422eedc571ba11e312dcc634197631a6840cb22aede" + "digest": "d46b484a97e5b90b6b259f8de1712b553f93f0dfb6391209200358bb9429ebf5" }, { "name": "flag_mx", "unicode": "1F1F2-1F1FD", - "digest": "eafb02ec0be9cefab7cef7c426c7d860d98e4947f4da04054154dc86d8f487c4" + "digest": "dc57c10307fc0aa09bd7fcd25ee0fca561f3b382276faa8432a927c1baea53fd" }, { "name": "mx", "unicode": "1F1F2-1F1FD", - "digest": "eafb02ec0be9cefab7cef7c426c7d860d98e4947f4da04054154dc86d8f487c4" + "digest": "dc57c10307fc0aa09bd7fcd25ee0fca561f3b382276faa8432a927c1baea53fd" }, { "name": "flag_my", "unicode": "1F1F2-1F1FE", - "digest": "9a690b357bc6b970781bd122c1e546ade3ccb73d930c2af1008b82027e36c7cf" + "digest": "15ca00660a1eb0096fdaa00b85a7b95fcf192bf2ee4781ba72c36d2d2cb015ef" }, { "name": "my", "unicode": "1F1F2-1F1FE", - "digest": "9a690b357bc6b970781bd122c1e546ade3ccb73d930c2af1008b82027e36c7cf" + "digest": "15ca00660a1eb0096fdaa00b85a7b95fcf192bf2ee4781ba72c36d2d2cb015ef" }, { "name": "flag_mz", "unicode": "1F1F2-1F1FF", - "digest": "36d0548ebfef9e0443ec1d0597ebfa6e95c25b997381f30c8c74008820743bb9" + "digest": "0c8605a9319dcf86672a833b4c4d6acea5f6aa25a3f8e1dfac78fbf7c452ba97" }, { "name": "mz", "unicode": "1F1F2-1F1FF", - "digest": "36d0548ebfef9e0443ec1d0597ebfa6e95c25b997381f30c8c74008820743bb9" + "digest": "0c8605a9319dcf86672a833b4c4d6acea5f6aa25a3f8e1dfac78fbf7c452ba97" }, { "name": "flag_na", "unicode": "1F1F3-1F1E6", - "digest": "4989dc9452b0bdfa101cfd3b7c83ef1195a7e45128b9ed00193fe712a6d02fca" + "digest": "e63cde5ee49d3ada1e33d2ab15dc24fbb129b90d65b6fd1d7c07455f71a53601" }, { "name": "na", "unicode": "1F1F3-1F1E6", - "digest": "4989dc9452b0bdfa101cfd3b7c83ef1195a7e45128b9ed00193fe712a6d02fca" + "digest": "e63cde5ee49d3ada1e33d2ab15dc24fbb129b90d65b6fd1d7c07455f71a53601" }, { "name": "flag_nc", "unicode": "1F1F3-1F1E8", - "digest": "7fc9d865eebf729d5496c4cd7576476ec599f65b379d4a6df66b4e399553c2eb" + "digest": "a4a350ce7404ba7bdda9a341e7a48fcfe16312be4964b1bd6eed7115acd2e329" }, { "name": "nc", "unicode": "1F1F3-1F1E8", - "digest": "7fc9d865eebf729d5496c4cd7576476ec599f65b379d4a6df66b4e399553c2eb" + "digest": "a4a350ce7404ba7bdda9a341e7a48fcfe16312be4964b1bd6eed7115acd2e329" }, { "name": "flag_ne", "unicode": "1F1F3-1F1EA", - "digest": "d3f10fb44ec44a04112bc66d05f0a44c6ec46dae73cfd3fe26cdc8b32ec06713" + "digest": "6b32483b4445bc52855509f618c570b9c9606de5649e4878b71b44ff2acbc9fd" }, { "name": "ne", "unicode": "1F1F3-1F1EA", - "digest": "d3f10fb44ec44a04112bc66d05f0a44c6ec46dae73cfd3fe26cdc8b32ec06713" + "digest": "6b32483b4445bc52855509f618c570b9c9606de5649e4878b71b44ff2acbc9fd" }, { "name": "flag_nf", "unicode": "1F1F3-1F1EB", - "digest": "d390e0d52215a025380af221ba9e955e5886edbb4c9f4b124f2fb60a8e019e42" + "digest": "96b1ec33acbd2b1ffe42703c11a2a633b036e6779849b0e6fa8f399167820584" }, { "name": "nf", "unicode": "1F1F3-1F1EB", - "digest": "d390e0d52215a025380af221ba9e955e5886edbb4c9f4b124f2fb60a8e019e42" + "digest": "96b1ec33acbd2b1ffe42703c11a2a633b036e6779849b0e6fa8f399167820584" }, { "name": "flag_ng", "unicode": "1F1F3-1F1EC", - "digest": "e69d1bb8f1db4a0c295c90dda23d8f97c2dea59f9a2da2ecb0e9a1dc4dbea101" + "digest": "f97d0630cbfa5e75440251df7529a67b58c22598643390cbeea82fb04a1cd956" }, { "name": "nigeria", "unicode": "1F1F3-1F1EC", - "digest": "e69d1bb8f1db4a0c295c90dda23d8f97c2dea59f9a2da2ecb0e9a1dc4dbea101" + "digest": "f97d0630cbfa5e75440251df7529a67b58c22598643390cbeea82fb04a1cd956" }, { "name": "flag_ni", "unicode": "1F1F3-1F1EE", - "digest": "dbaccc942637469b0ee75bd5f956958c3c5a89d8f69b69c96f02ab6594124894" + "digest": "c52fb5f9134122a91defa75425be2c6b3c909e051d546244e0e7bdf5f9ee1710" }, { "name": "ni", "unicode": "1F1F3-1F1EE", - "digest": "dbaccc942637469b0ee75bd5f956958c3c5a89d8f69b69c96f02ab6594124894" + "digest": "c52fb5f9134122a91defa75425be2c6b3c909e051d546244e0e7bdf5f9ee1710" }, { "name": "flag_nl", "unicode": "1F1F3-1F1F1", - "digest": "bda2eb0315763c3c19d37c664dab1ee4280f20888a0ca57677fd33cfa4240910" + "digest": "b8918f9c0c92513aa0ec6ba6cee5448270168cbe6f0a970fb06e7ceb9f52ec71" }, { "name": "nl", "unicode": "1F1F3-1F1F1", - "digest": "bda2eb0315763c3c19d37c664dab1ee4280f20888a0ca57677fd33cfa4240910" + "digest": "b8918f9c0c92513aa0ec6ba6cee5448270168cbe6f0a970fb06e7ceb9f52ec71" }, { "name": "flag_no", "unicode": "1F1F3-1F1F4", - "digest": "42b49dec756a220781ea271ca8fbcaba524dc3b38d5d8f999bfaa40ef9ebd302" + "digest": "05ce84095f8d93407d611b39d8b6a67fd9f11df6cfab7a185bcb4eec186d85ef" }, { "name": "no", "unicode": "1F1F3-1F1F4", - "digest": "42b49dec756a220781ea271ca8fbcaba524dc3b38d5d8f999bfaa40ef9ebd302" + "digest": "05ce84095f8d93407d611b39d8b6a67fd9f11df6cfab7a185bcb4eec186d85ef" }, { "name": "flag_np", "unicode": "1F1F3-1F1F5", - "digest": "b5259257db079235310d5d9537d2b5b61ae0326bc8920ba13084b009844e2957" + "digest": "cc41c2f97ec2b38fe5781d553792f6aab5d37cc3be02586f361fe89d12683bee" }, { "name": "np", "unicode": "1F1F3-1F1F5", - "digest": "b5259257db079235310d5d9537d2b5b61ae0326bc8920ba13084b009844e2957" + "digest": "cc41c2f97ec2b38fe5781d553792f6aab5d37cc3be02586f361fe89d12683bee" }, { "name": "flag_nr", "unicode": "1F1F3-1F1F7", - "digest": "1bd7d1fe2c3a5e98cfd4dff6e8d6dd6d3c74f0051ad615587d77d2291a9784cc" + "digest": "7837edf59ec33a25380d76afea5f04cfcab4f17df4e33fca0dcaacb517c5cbec" }, { "name": "nr", "unicode": "1F1F3-1F1F7", - "digest": "1bd7d1fe2c3a5e98cfd4dff6e8d6dd6d3c74f0051ad615587d77d2291a9784cc" + "digest": "7837edf59ec33a25380d76afea5f04cfcab4f17df4e33fca0dcaacb517c5cbec" }, { "name": "flag_nu", "unicode": "1F1F3-1F1FA", - "digest": "e2a7a398e07d2232147cc0917d72d18b519246d3d314e9f6f03dcf98d312d4ce" + "digest": "fd9ab45c6f32bc4da47542392e5beba73ddac302a4a9a00e6deedc913a4c087d" }, { "name": "nu", "unicode": "1F1F3-1F1FA", - "digest": "e2a7a398e07d2232147cc0917d72d18b519246d3d314e9f6f03dcf98d312d4ce" + "digest": "fd9ab45c6f32bc4da47542392e5beba73ddac302a4a9a00e6deedc913a4c087d" }, { "name": "flag_nz", "unicode": "1F1F3-1F1FF", - "digest": "ce8b1cb87dae3a3ec865575b57a0b4987a7f4bd3f170e7b210dd764fc2588cd4" + "digest": "0719830dcca400cefb30ce399bb03f49dd84c9a98f7d6a28270f9278e2a7af75" }, { "name": "nz", "unicode": "1F1F3-1F1FF", - "digest": "ce8b1cb87dae3a3ec865575b57a0b4987a7f4bd3f170e7b210dd764fc2588cd4" + "digest": "0719830dcca400cefb30ce399bb03f49dd84c9a98f7d6a28270f9278e2a7af75" }, { "name": "flag_om", "unicode": "1F1F4-1F1F2", - "digest": "29da72505a276a8a372a00c197388ebc5098c221cab26b3ff755bd62b10f740f" + "digest": "3f9039becd52e3454fdf7611cdb0d7fb1196e053eea29ef87daab6c21a94f1ee" }, { "name": "om", "unicode": "1F1F4-1F1F2", - "digest": "29da72505a276a8a372a00c197388ebc5098c221cab26b3ff755bd62b10f740f" + "digest": "3f9039becd52e3454fdf7611cdb0d7fb1196e053eea29ef87daab6c21a94f1ee" }, { "name": "flag_pa", "unicode": "1F1F5-1F1E6", - "digest": "180b673c9aceea43a8b55823a82d80600257e4982d0757d129860e3d8a14f458" + "digest": "1adf0e5d4084e072aa44bd9978829e77546e0be75785e9be69f92e326bd714a7" }, { "name": "pa", "unicode": "1F1F5-1F1E6", - "digest": "180b673c9aceea43a8b55823a82d80600257e4982d0757d129860e3d8a14f458" + "digest": "1adf0e5d4084e072aa44bd9978829e77546e0be75785e9be69f92e326bd714a7" }, { "name": "flag_pe", "unicode": "1F1F5-1F1EA", - "digest": "b61823ea2cd91e371e40832df5764558b81d44fac41030827a3f6d2564643c00" + "digest": "f8a4e257676f4ab8962ffe5509b8417777a8be2f0e9dc7735d3e014ff221aab1" }, { "name": "pe", "unicode": "1F1F5-1F1EA", - "digest": "b61823ea2cd91e371e40832df5764558b81d44fac41030827a3f6d2564643c00" + "digest": "f8a4e257676f4ab8962ffe5509b8417777a8be2f0e9dc7735d3e014ff221aab1" }, { "name": "flag_pf", "unicode": "1F1F5-1F1EB", - "digest": "e560421911f4af90c73a0dbdf8f42e69316003799304c9394fb127e3b83326fa" + "digest": "1ace6cc71d130cdf09246297740a911f14828c322e35330cc548ca5975015c23" }, { "name": "pf", "unicode": "1F1F5-1F1EB", - "digest": "e560421911f4af90c73a0dbdf8f42e69316003799304c9394fb127e3b83326fa" + "digest": "1ace6cc71d130cdf09246297740a911f14828c322e35330cc548ca5975015c23" }, { "name": "flag_pg", "unicode": "1F1F5-1F1EC", - "digest": "880e87db2ce0eac38db037683a5db46fd6ce30623cf56ae4a93a747103570044" + "digest": "9c37719d9f51ef31fec0f898d38e522b4253cd00344408e3f660132514efddb7" }, { "name": "pg", "unicode": "1F1F5-1F1EC", - "digest": "880e87db2ce0eac38db037683a5db46fd6ce30623cf56ae4a93a747103570044" + "digest": "9c37719d9f51ef31fec0f898d38e522b4253cd00344408e3f660132514efddb7" }, { "name": "flag_ph", "unicode": "1F1F5-1F1ED", - "digest": "49aae2f56bfd1385741dc76857aa1f1459778b2d39a1c955e469c5367585bfd5" + "digest": "f1af628cf6d1d290cedef3d564b2386e2d6f14ba4426d3fefc0312cb8772e517" }, { "name": "ph", "unicode": "1F1F5-1F1ED", - "digest": "49aae2f56bfd1385741dc76857aa1f1459778b2d39a1c955e469c5367585bfd5" + "digest": "f1af628cf6d1d290cedef3d564b2386e2d6f14ba4426d3fefc0312cb8772e517" }, { "name": "flag_pk", "unicode": "1F1F5-1F1F0", - "digest": "64379dbfc932df3a07935b5cfa11ca151f761d3728939e982604e12c663cd646" + "digest": "61c77f73d2a10a5acb289fadfe0d25d1a1c343e1223bd802099ff4e0e9356521" }, { "name": "pk", "unicode": "1F1F5-1F1F0", - "digest": "64379dbfc932df3a07935b5cfa11ca151f761d3728939e982604e12c663cd646" + "digest": "61c77f73d2a10a5acb289fadfe0d25d1a1c343e1223bd802099ff4e0e9356521" }, { "name": "flag_pl", "unicode": "1F1F5-1F1F1", - "digest": "3b688b074c2735d3dea0b7ab74b80eba243ce50cb05d68e585c9d701c1f14617" + "digest": "38c2c8618446e1f72cf983ab33e736d943f0db7c4cce52a187299e8cec2ea895" }, { "name": "pl", "unicode": "1F1F5-1F1F1", - "digest": "3b688b074c2735d3dea0b7ab74b80eba243ce50cb05d68e585c9d701c1f14617" + "digest": "38c2c8618446e1f72cf983ab33e736d943f0db7c4cce52a187299e8cec2ea895" }, { "name": "flag_pm", "unicode": "1F1F5-1F1F2", - "digest": "a13a69ee3131501dd8138173cfb669a35ee8039d84aa665e69dd7f0d0aa3e717" + "digest": "656be9ea1a79c3885a759c7ce353d338345a198d7939556949affaf5490cb644" }, { "name": "pm", "unicode": "1F1F5-1F1F2", - "digest": "a13a69ee3131501dd8138173cfb669a35ee8039d84aa665e69dd7f0d0aa3e717" + "digest": "656be9ea1a79c3885a759c7ce353d338345a198d7939556949affaf5490cb644" }, { "name": "flag_pn", "unicode": "1F1F5-1F1F3", - "digest": "d7ae3985cf66024e4a3001e79a8efbb3e75571f2b0abbd0fb87fc1efc795a2b3" + "digest": "2792260d8087ab0253b1214c1420f0160ab2eef9afe7315f9e7ff0b87cd15d72" }, { "name": "pn", "unicode": "1F1F5-1F1F3", - "digest": "d7ae3985cf66024e4a3001e79a8efbb3e75571f2b0abbd0fb87fc1efc795a2b3" + "digest": "2792260d8087ab0253b1214c1420f0160ab2eef9afe7315f9e7ff0b87cd15d72" }, { "name": "flag_pr", "unicode": "1F1F5-1F1F7", - "digest": "4910dc984bc908158506b770f28af56150cbb4509a4291947dfa2479b9e4b308" + "digest": "c4cfa1f2201dcda9de310a8247e6ce32d2798ae426a14dd70a9ebb00a2804d46" }, { "name": "pr", "unicode": "1F1F5-1F1F7", - "digest": "4910dc984bc908158506b770f28af56150cbb4509a4291947dfa2479b9e4b308" + "digest": "c4cfa1f2201dcda9de310a8247e6ce32d2798ae426a14dd70a9ebb00a2804d46" }, { "name": "flag_ps", "unicode": "1F1F5-1F1F8", - "digest": "b2bca7619fced25de94d7bd398537857460348a552e7d73d189aef3f428e6a13" + "digest": "197f2ec6294bf0ee4a08cf2f2d1e237ee867c98b3085454a3f42abc955eeb289" }, { "name": "ps", "unicode": "1F1F5-1F1F8", - "digest": "b2bca7619fced25de94d7bd398537857460348a552e7d73d189aef3f428e6a13" + "digest": "197f2ec6294bf0ee4a08cf2f2d1e237ee867c98b3085454a3f42abc955eeb289" }, { "name": "flag_pt", "unicode": "1F1F5-1F1F9", - "digest": "177282613b4b8b4d9551f1da6a1c3f66f1b96cf67c71c7d164213b26b3237395" + "digest": "86a50827963756b5bf471ed9df5b3f2a2058b4c5d778a303414b6b0556e2082b" }, { "name": "pt", "unicode": "1F1F5-1F1F9", - "digest": "177282613b4b8b4d9551f1da6a1c3f66f1b96cf67c71c7d164213b26b3237395" + "digest": "86a50827963756b5bf471ed9df5b3f2a2058b4c5d778a303414b6b0556e2082b" }, { "name": "flag_pw", "unicode": "1F1F5-1F1FC", - "digest": "2ff42a14bdc7df76b5f989dca381f94765032b26ae47d47b97844abde458cefe" + "digest": "a6321c47a0cd188fbfdf3b55f17a7170c63080d28d50e4f5463eb1ee09af2412" }, { "name": "pw", "unicode": "1F1F5-1F1FC", - "digest": "2ff42a14bdc7df76b5f989dca381f94765032b26ae47d47b97844abde458cefe" + "digest": "a6321c47a0cd188fbfdf3b55f17a7170c63080d28d50e4f5463eb1ee09af2412" }, { "name": "flag_py", "unicode": "1F1F5-1F1FE", - "digest": "80169b69a46c4c67d0090dc2c6bf05d1a14f133ac7ae56f811547e8e8f70d81b" + "digest": "1a169e8d8703c510c5a2265b57dbed2f811b03ec375bcb341ab4cd0b100a9dd6" }, { "name": "py", "unicode": "1F1F5-1F1FE", - "digest": "80169b69a46c4c67d0090dc2c6bf05d1a14f133ac7ae56f811547e8e8f70d81b" + "digest": "1a169e8d8703c510c5a2265b57dbed2f811b03ec375bcb341ab4cd0b100a9dd6" }, { "name": "flag_qa", "unicode": "1F1F6-1F1E6", - "digest": "589b44b975aa97426afb8db7f8b355491fca246b693903485824bf0f5a6953a2" + "digest": "de6283965cd98a244b7fa6288174f9ff0d8feb497f191f2e4ab3b690138a3d5d" }, { "name": "qa", "unicode": "1F1F6-1F1E6", - "digest": "589b44b975aa97426afb8db7f8b355491fca246b693903485824bf0f5a6953a2" + "digest": "de6283965cd98a244b7fa6288174f9ff0d8feb497f191f2e4ab3b690138a3d5d" }, { "name": "flag_re", "unicode": "1F1F7-1F1EA", - "digest": "77d242261742831a142c9ec74cd17d76b1e6d1af751ff3c6a356646744bc798a" + "digest": "260e1b97abc1562e5a73d7e53652ffed8059fc9b1c969741c466f48ec6ab0e80" }, { "name": "re", "unicode": "1F1F7-1F1EA", - "digest": "77d242261742831a142c9ec74cd17d76b1e6d1af751ff3c6a356646744bc798a" + "digest": "260e1b97abc1562e5a73d7e53652ffed8059fc9b1c969741c466f48ec6ab0e80" }, { "name": "flag_ro", "unicode": "1F1F7-1F1F4", - "digest": "d7d17026ea81f27456983722540f9a23343a3a1b22e7697c4fba118ce8b4719e" + "digest": "6d648e03955fa2a9fd2bad6f60ec96d3e20ee57f5855f3721a4d4e0c8e99f95c" }, { "name": "ro", "unicode": "1F1F7-1F1F4", - "digest": "d7d17026ea81f27456983722540f9a23343a3a1b22e7697c4fba118ce8b4719e" + "digest": "6d648e03955fa2a9fd2bad6f60ec96d3e20ee57f5855f3721a4d4e0c8e99f95c" }, { "name": "flag_rs", "unicode": "1F1F7-1F1F8", - "digest": "e466a18cc0368e623d3fe33a036c1e88db91ae24f7510e17caacc85c41f1bac8" + "digest": "95cd5e197ed364e403eeb7f1d18a83487d89166910ba8119ea994e5e19d6a7ee" }, { "name": "rs", "unicode": "1F1F7-1F1F8", - "digest": "e466a18cc0368e623d3fe33a036c1e88db91ae24f7510e17caacc85c41f1bac8" + "digest": "95cd5e197ed364e403eeb7f1d18a83487d89166910ba8119ea994e5e19d6a7ee" }, { "name": "flag_ru", "unicode": "1F1F7-1F1FA", - "digest": "86bf53a62dfc4c434d910f43df70f430fc67c0070fe3fc466c4fbfd6a5d8e646" + "digest": "a4a81617a59d9eaf3c526431ca6f90ed334a7c1f516bf70cbd3f1fdc6e6103d7" }, { "name": "ru", "unicode": "1F1F7-1F1FA", - "digest": "86bf53a62dfc4c434d910f43df70f430fc67c0070fe3fc466c4fbfd6a5d8e646" + "digest": "a4a81617a59d9eaf3c526431ca6f90ed334a7c1f516bf70cbd3f1fdc6e6103d7" }, { "name": "flag_rw", "unicode": "1F1F7-1F1FC", - "digest": "38ec5a01896c9747a8dbf865d5e8584770e587253b7af3d3b9c36cd993f67518" + "digest": "7a369f60db0876ffef111c319a3e8c9eaed620c875c51b98ed9ad5207b836dca" }, { "name": "rw", "unicode": "1F1F7-1F1FC", - "digest": "38ec5a01896c9747a8dbf865d5e8584770e587253b7af3d3b9c36cd993f67518" + "digest": "7a369f60db0876ffef111c319a3e8c9eaed620c875c51b98ed9ad5207b836dca" }, { "name": "flag_sa", "unicode": "1F1F8-1F1E6", - "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7" }, { "name": "saudiarabia", "unicode": "1F1F8-1F1E6", - "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7" }, { "name": "saudi", "unicode": "1F1F8-1F1E6", - "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7" }, { "name": "flag_sb", "unicode": "1F1F8-1F1E7", - "digest": "8ffa24c5cb92be4dbe43f6cd85b61b9608a3101bd78ebccff4fe99c209b3e241" + "digest": "526b411260024ea7b6ea6c47f2549345c6cc6960e9a29bfa9aaec0772664d2dc" }, { "name": "sb", "unicode": "1F1F8-1F1E7", - "digest": "8ffa24c5cb92be4dbe43f6cd85b61b9608a3101bd78ebccff4fe99c209b3e241" + "digest": "526b411260024ea7b6ea6c47f2549345c6cc6960e9a29bfa9aaec0772664d2dc" }, { "name": "flag_sc", "unicode": "1F1F8-1F1E8", - "digest": "227d090ac2cbf317e594567b6114b5063a13cfe33abf990d37b200debcfadabb" + "digest": "d036b0d068745926120eaf746fa2e4433306e2e14c6b540d0cd6265e34471056" }, { "name": "sc", "unicode": "1F1F8-1F1E8", - "digest": "227d090ac2cbf317e594567b6114b5063a13cfe33abf990d37b200debcfadabb" + "digest": "d036b0d068745926120eaf746fa2e4433306e2e14c6b540d0cd6265e34471056" }, { "name": "flag_sd", "unicode": "1F1F8-1F1E9", - "digest": "350f3332e8ea1138e54facc870dd0fea5f2ab7d3fd4baa02ed8627ae79642f6c" + "digest": "889615bdb9b1f9c59c5f83ed4d22d54a0ed5dd5de263e729c58544cb06c55885" }, { "name": "sd", "unicode": "1F1F8-1F1E9", - "digest": "350f3332e8ea1138e54facc870dd0fea5f2ab7d3fd4baa02ed8627ae79642f6c" + "digest": "889615bdb9b1f9c59c5f83ed4d22d54a0ed5dd5de263e729c58544cb06c55885" }, { "name": "flag_se", "unicode": "1F1F8-1F1EA", - "digest": "c1b09f36c263727de83b54376f05e083a17a61941af9a1640b826629256a280d" + "digest": "f471d80cfff340960a752c8c152ed4fb482df2a3712b0a56dfab31b9b806926a" }, { "name": "se", "unicode": "1F1F8-1F1EA", - "digest": "c1b09f36c263727de83b54376f05e083a17a61941af9a1640b826629256a280d" + "digest": "f471d80cfff340960a752c8c152ed4fb482df2a3712b0a56dfab31b9b806926a" }, { "name": "flag_sg", "unicode": "1F1F8-1F1EC", - "digest": "e6fc26920dfc07e4fd3c8d897de9c607e0bf48a3b64a13630c858d707a8e7660" + "digest": "82f58a09f98593cc87e545f7e5c03d2aedaf82e54e73f71f58c18e994c3085ac" }, { "name": "sg", "unicode": "1F1F8-1F1EC", - "digest": "e6fc26920dfc07e4fd3c8d897de9c607e0bf48a3b64a13630c858d707a8e7660" + "digest": "82f58a09f98593cc87e545f7e5c03d2aedaf82e54e73f71f58c18e994c3085ac" }, { "name": "flag_sh", "unicode": "1F1F8-1F1ED", - "digest": "f2c22ab0eb49e3104c35f1c0268b1e63c3a67f41b0cfa9861b189525988e53b6" + "digest": "53914b1fa8c1b4f30bae6c1f6717f138fb4dbf482c3e20e33f7aea4ecfc0438d" }, { "name": "sh", "unicode": "1F1F8-1F1ED", - "digest": "f2c22ab0eb49e3104c35f1c0268b1e63c3a67f41b0cfa9861b189525988e53b6" + "digest": "53914b1fa8c1b4f30bae6c1f6717f138fb4dbf482c3e20e33f7aea4ecfc0438d" }, { "name": "flag_si", "unicode": "1F1F8-1F1EE", - "digest": "1ef0b10e498f71591322f9d8ec122d39838f479370cf7ee922560986ef6c4f2e" + "digest": "65d491daa69f9a11cec9ccc4df3a669f12ef95a5c312137776d4472719940ba3" }, { "name": "si", "unicode": "1F1F8-1F1EE", - "digest": "1ef0b10e498f71591322f9d8ec122d39838f479370cf7ee922560986ef6c4f2e" + "digest": "65d491daa69f9a11cec9ccc4df3a669f12ef95a5c312137776d4472719940ba3" }, { "name": "flag_sj", "unicode": "1F1F8-1F1EF", - "digest": "ce913b007f84a9cba2add8d754aa791901624c60e4200de426dfa25271cb0f78" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "sj", "unicode": "1F1F8-1F1EF", - "digest": "ce913b007f84a9cba2add8d754aa791901624c60e4200de426dfa25271cb0f78" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "flag_sk", "unicode": "1F1F8-1F1F0", - "digest": "d8f8fc4024c82f906effe98facbef9d543fb3708b1134dc502c74dc4a442b30a" + "digest": "d4fd03eca5bd3c9fb324ee04fae37c9a2d852bac8335369e3e720ef9b98fff36" }, { "name": "sk", "unicode": "1F1F8-1F1F0", - "digest": "d8f8fc4024c82f906effe98facbef9d543fb3708b1134dc502c74dc4a442b30a" + "digest": "d4fd03eca5bd3c9fb324ee04fae37c9a2d852bac8335369e3e720ef9b98fff36" }, { "name": "flag_sl", "unicode": "1F1F8-1F1F1", - "digest": "dd7fd0452498d8d1c894cf0d5a662ddff9c5bcc02148bdc3dc7e6f25d0bb586e" + "digest": "1455c98c11c248623d82be5484ab1c4dcd1dae449adc393eb1aa2d8c74aa3f02" }, { "name": "sl", "unicode": "1F1F8-1F1F1", - "digest": "dd7fd0452498d8d1c894cf0d5a662ddff9c5bcc02148bdc3dc7e6f25d0bb586e" + "digest": "1455c98c11c248623d82be5484ab1c4dcd1dae449adc393eb1aa2d8c74aa3f02" }, { "name": "flag_sm", "unicode": "1F1F8-1F1F2", - "digest": "2b499606aee2b5cbf4037338753c80a4c8f75f4abcef2c8657bd9337e602bbd3" + "digest": "daec5864ac50c625d7bf49d6c1a170a094cf0d1b9a0bdf62a62406e7ec500a94" }, { "name": "sm", "unicode": "1F1F8-1F1F2", - "digest": "2b499606aee2b5cbf4037338753c80a4c8f75f4abcef2c8657bd9337e602bbd3" + "digest": "daec5864ac50c625d7bf49d6c1a170a094cf0d1b9a0bdf62a62406e7ec500a94" }, { "name": "flag_sn", "unicode": "1F1F8-1F1F3", - "digest": "03b46a9d8b129da13f60c23b820b04fba52050ca58a41b859ad57d5c3cc2515d" + "digest": "4e4d43c467e5eb84c70f535f37f4f468319bd4b06c6ec3db3b54f69efdafd334" }, { "name": "sn", "unicode": "1F1F8-1F1F3", - "digest": "03b46a9d8b129da13f60c23b820b04fba52050ca58a41b859ad57d5c3cc2515d" + "digest": "4e4d43c467e5eb84c70f535f37f4f468319bd4b06c6ec3db3b54f69efdafd334" }, { "name": "flag_so", "unicode": "1F1F8-1F1F4", - "digest": "ea416b6a05ddc5b16291ebe5101735360b08c834d55ac82c663ac1dd3e459048" + "digest": "c1434dca361563a8e3ba88f1ad19c3f6c9cbb8f3ebc17ce128fde2351ff67d0c" }, { "name": "so", "unicode": "1F1F8-1F1F4", - "digest": "ea416b6a05ddc5b16291ebe5101735360b08c834d55ac82c663ac1dd3e459048" + "digest": "c1434dca361563a8e3ba88f1ad19c3f6c9cbb8f3ebc17ce128fde2351ff67d0c" }, { "name": "flag_sr", "unicode": "1F1F8-1F1F7", - "digest": "012179fbcbcb7343e7b09d33e283fb63c7964a6eca35ccb9407d468e495a9874" + "digest": "f3c6bfee2a052f03d56ba917b88595450cef111ffa9e92c7f39ef8c3c3bd12d1" }, { "name": "sr", "unicode": "1F1F8-1F1F7", - "digest": "012179fbcbcb7343e7b09d33e283fb63c7964a6eca35ccb9407d468e495a9874" + "digest": "f3c6bfee2a052f03d56ba917b88595450cef111ffa9e92c7f39ef8c3c3bd12d1" }, { "name": "flag_ss", "unicode": "1F1F8-1F1F8", - "digest": "6723150482c640643c9dd7e33ea749f4a8b46aceacbd4f5e11aa33b3ee13aab7" + "digest": "c0ed7e4f41206f5363e8ebdc6c3f28080e2f07d99e6fb73c1f6226d83310e69d" }, { "name": "ss", "unicode": "1F1F8-1F1F8", - "digest": "6723150482c640643c9dd7e33ea749f4a8b46aceacbd4f5e11aa33b3ee13aab7" + "digest": "c0ed7e4f41206f5363e8ebdc6c3f28080e2f07d99e6fb73c1f6226d83310e69d" }, { "name": "flag_st", "unicode": "1F1F8-1F1F9", - "digest": "0947fcec2e3cb1b0e9943c3d00891e8ee226e8d0532e9b1fe807ddf2e8fbc49d" + "digest": "b022ae5d6885e28c6e9c83c17dd0c24c731d4f3d5773c49051768cdd4df51330" }, { "name": "st", "unicode": "1F1F8-1F1F9", - "digest": "0947fcec2e3cb1b0e9943c3d00891e8ee226e8d0532e9b1fe807ddf2e8fbc49d" + "digest": "b022ae5d6885e28c6e9c83c17dd0c24c731d4f3d5773c49051768cdd4df51330" }, { "name": "flag_sv", "unicode": "1F1F8-1F1FB", - "digest": "ce7e583db833c4b10e2f7a2d09b97bb522c02e96ea0b3f3a48a955f7d8f970d8" + "digest": "5bafdd04d243ee3f3998f4ec0a3d03ff5a3975e771b1f94f89d7713193d7a242" }, { "name": "sv", "unicode": "1F1F8-1F1FB", - "digest": "ce7e583db833c4b10e2f7a2d09b97bb522c02e96ea0b3f3a48a955f7d8f970d8" + "digest": "5bafdd04d243ee3f3998f4ec0a3d03ff5a3975e771b1f94f89d7713193d7a242" }, { "name": "flag_sx", "unicode": "1F1F8-1F1FD", - "digest": "c01fb238c7ba439f24a5ef821b6457f2a0fd0b99a1b2d02395bed87f0a4a88e5" + "digest": "fb92e9f514bcc2f7abbd4e146edde50f030c940c833f184618cbb48e56af22bd" }, { "name": "sx", "unicode": "1F1F8-1F1FD", - "digest": "c01fb238c7ba439f24a5ef821b6457f2a0fd0b99a1b2d02395bed87f0a4a88e5" + "digest": "fb92e9f514bcc2f7abbd4e146edde50f030c940c833f184618cbb48e56af22bd" }, { "name": "flag_sy", "unicode": "1F1F8-1F1FE", - "digest": "a77d87ef98c96140c59998d10d94837e2a056dd3ac5c7522e89e5c62eac69e69" + "digest": "ee330da644d4ce1fdba98be5eaab5054aed8d91a34ab617199a4b2b77f62a10b" }, { "name": "sy", "unicode": "1F1F8-1F1FE", - "digest": "a77d87ef98c96140c59998d10d94837e2a056dd3ac5c7522e89e5c62eac69e69" + "digest": "ee330da644d4ce1fdba98be5eaab5054aed8d91a34ab617199a4b2b77f62a10b" }, { "name": "flag_sz", "unicode": "1F1F8-1F1FF", - "digest": "2904ad01040a9107ad556ec4c2561781d96746005cca250babb1127b8ba21050" + "digest": "7fe0c7429efd9682cc39e57f4bba8d1491d301643ba999d57c4e1bc37517ed64" }, { "name": "sz", "unicode": "1F1F8-1F1FF", - "digest": "2904ad01040a9107ad556ec4c2561781d96746005cca250babb1127b8ba21050" + "digest": "7fe0c7429efd9682cc39e57f4bba8d1491d301643ba999d57c4e1bc37517ed64" }, { "name": "flag_ta", "unicode": "1F1F9-1F1E6", - "digest": "eda84db90e1a8854e8ff3c15b3b38ee65f7d6532b76970a6fbac304c30d8c959" + "digest": "b47e245a2708072a4dbaf190c9606baa4daf02e51627eeae6f20c3b4c95024c0" }, { "name": "ta", "unicode": "1F1F9-1F1E6", - "digest": "eda84db90e1a8854e8ff3c15b3b38ee65f7d6532b76970a6fbac304c30d8c959" + "digest": "b47e245a2708072a4dbaf190c9606baa4daf02e51627eeae6f20c3b4c95024c0" }, { "name": "flag_tc", "unicode": "1F1F9-1F1E8", - "digest": "4628fdf6dc598a2846beefe97f7d4c6812f4961394cec132924b44bbe79b3322" + "digest": "18cfff14c2503b9d24c91c668583d4a14efb17657d800eca86ae49b547c9da5c" }, { "name": "tc", "unicode": "1F1F9-1F1E8", - "digest": "4628fdf6dc598a2846beefe97f7d4c6812f4961394cec132924b44bbe79b3322" + "digest": "18cfff14c2503b9d24c91c668583d4a14efb17657d800eca86ae49b547c9da5c" }, { "name": "flag_td", "unicode": "1F1F9-1F1E9", - "digest": "125ff31e4285cb2a5493a52a2703ebe8e7138b918ec4dae3d0f8693632372df6" + "digest": "73d1db3365736915c4cdf9ba9343d9fd78962203b60334e8f3724d4b330b17db" }, { "name": "td", "unicode": "1F1F9-1F1E9", - "digest": "125ff31e4285cb2a5493a52a2703ebe8e7138b918ec4dae3d0f8693632372df6" + "digest": "73d1db3365736915c4cdf9ba9343d9fd78962203b60334e8f3724d4b330b17db" }, { "name": "flag_tf", "unicode": "1F1F9-1F1EB", - "digest": "489d591e11764ac341f2234020f7879db782b8f673fc9aae425fd713e4082334" + "digest": "3bffeb4bc9ceb9cbb150de88e957b6e46509862ca7d616d5693124af084eb435" }, { "name": "tf", "unicode": "1F1F9-1F1EB", - "digest": "489d591e11764ac341f2234020f7879db782b8f673fc9aae425fd713e4082334" + "digest": "3bffeb4bc9ceb9cbb150de88e957b6e46509862ca7d616d5693124af084eb435" }, { "name": "flag_tg", "unicode": "1F1F9-1F1EC", - "digest": "4ceedfcfcc22cd14d9add9d86d6748447995f19f7095fa4be883e21eb1aa86bc" + "digest": "eb13a0e85baf73326f3ae3bc75e8406eca42000d7e42b0641120e64c0ab7ebaa" }, { "name": "tg", "unicode": "1F1F9-1F1EC", - "digest": "4ceedfcfcc22cd14d9add9d86d6748447995f19f7095fa4be883e21eb1aa86bc" + "digest": "eb13a0e85baf73326f3ae3bc75e8406eca42000d7e42b0641120e64c0ab7ebaa" }, { "name": "flag_th", "unicode": "1F1F9-1F1ED", - "digest": "2798cc660af1c5dc4891c30aded3a53d7cfa0af128cc495df8141907b165902d" + "digest": "a4e42efa4bb94e90f3a92ae9ce14affaacd3a142c1e0da40d8cc839500e771fd" }, { "name": "th", "unicode": "1F1F9-1F1ED", - "digest": "2798cc660af1c5dc4891c30aded3a53d7cfa0af128cc495df8141907b165902d" + "digest": "a4e42efa4bb94e90f3a92ae9ce14affaacd3a142c1e0da40d8cc839500e771fd" }, { "name": "flag_tj", "unicode": "1F1F9-1F1EF", - "digest": "0483506fc5b5f2d4fc18ea3cd2f8a5da985d68fe4bf90bd3fd05e67e38f32398" + "digest": "ff926fa3e86e095683a61c4754355a5b4dd0ecb74393306bd791d130fd1a909d" }, { "name": "tj", "unicode": "1F1F9-1F1EF", - "digest": "0483506fc5b5f2d4fc18ea3cd2f8a5da985d68fe4bf90bd3fd05e67e38f32398" + "digest": "ff926fa3e86e095683a61c4754355a5b4dd0ecb74393306bd791d130fd1a909d" }, { "name": "flag_tk", "unicode": "1F1F9-1F1F0", - "digest": "d5d4a8c6ce3207731b7c154a9d8d8fa2af055a48f03b3cbbcfd3317d3b8a75f2" + "digest": "3fa732d457ded6c83cd5f73d934f64c4e687eb0cde7c157d2fdcdccaf3b5fb52" }, { "name": "tk", "unicode": "1F1F9-1F1F0", - "digest": "d5d4a8c6ce3207731b7c154a9d8d8fa2af055a48f03b3cbbcfd3317d3b8a75f2" + "digest": "3fa732d457ded6c83cd5f73d934f64c4e687eb0cde7c157d2fdcdccaf3b5fb52" }, { "name": "flag_tl", "unicode": "1F1F9-1F1F1", - "digest": "7a2ba8f91a6b627c60c88244223a9b9d0c12707f50b174f9c2eca07dd3440df7" + "digest": "0ec2a4d22fb832060693089e518bbe370a4e13bfc28748f110fc13726409f473" }, { "name": "tl", "unicode": "1F1F9-1F1F1", - "digest": "7a2ba8f91a6b627c60c88244223a9b9d0c12707f50b174f9c2eca07dd3440df7" + "digest": "0ec2a4d22fb832060693089e518bbe370a4e13bfc28748f110fc13726409f473" }, { "name": "flag_tm", "unicode": "1F1F9-1F1F2", - "digest": "adcf5f23adcf983ce626b44559482f8728251eab34b3ff5d8b125112f3a1010f" + "digest": "b4724aa7ad13352f16a0936e61cbb85f0bd147583fc66597aff7e8ee7cf19c21" }, { "name": "turkmenistan", "unicode": "1F1F9-1F1F2", - "digest": "adcf5f23adcf983ce626b44559482f8728251eab34b3ff5d8b125112f3a1010f" + "digest": "b4724aa7ad13352f16a0936e61cbb85f0bd147583fc66597aff7e8ee7cf19c21" }, { "name": "flag_tn", "unicode": "1F1F9-1F1F3", - "digest": "5ee690ee1f3c3c0cba9b36efdef902894ec59cefbc60c4baa341efd3d7bb9ba2" + "digest": "5ab308ffdde40f504d6ee080817bbddbe4f3f4ddb71f508c75e0144a8c8044d9" }, { "name": "tn", "unicode": "1F1F9-1F1F3", - "digest": "5ee690ee1f3c3c0cba9b36efdef902894ec59cefbc60c4baa341efd3d7bb9ba2" + "digest": "5ab308ffdde40f504d6ee080817bbddbe4f3f4ddb71f508c75e0144a8c8044d9" }, { "name": "flag_to", "unicode": "1F1F9-1F1F4", - "digest": "cde8672ca25b0e3a423865283fab9bc3ab10f472e04979b3b2f8032b71e96300" + "digest": "75b7e7198fa42f87986882b8ca251a229afcaa0a1188ae7b9f5ece87dc31a723" }, { "name": "to", "unicode": "1F1F9-1F1F4", - "digest": "cde8672ca25b0e3a423865283fab9bc3ab10f472e04979b3b2f8032b71e96300" + "digest": "75b7e7198fa42f87986882b8ca251a229afcaa0a1188ae7b9f5ece87dc31a723" }, { "name": "flag_tr", "unicode": "1F1F9-1F1F7", - "digest": "3d83c03ed084cfc81fa633310382acd7213e1eaa19d0ed97d142e7824032b55d" + "digest": "9cc48a8f8fa9c17c1627272f68d4740da0e7ce17a2cf8c6b5c08cc9b95e1390c" }, { "name": "tr", "unicode": "1F1F9-1F1F7", - "digest": "3d83c03ed084cfc81fa633310382acd7213e1eaa19d0ed97d142e7824032b55d" + "digest": "9cc48a8f8fa9c17c1627272f68d4740da0e7ce17a2cf8c6b5c08cc9b95e1390c" }, { "name": "flag_tt", "unicode": "1F1F9-1F1F9", - "digest": "d66d272ac27e2b398289d6b60128ccd3508aeb1f4a00a3920c5e6a21bfe357ed" + "digest": "f9e63543121bb3cd2e41bc7b0c2c4ba662bc1cc0520b79fc4e201ec6456fdf59" }, { "name": "tt", "unicode": "1F1F9-1F1F9", - "digest": "d66d272ac27e2b398289d6b60128ccd3508aeb1f4a00a3920c5e6a21bfe357ed" + "digest": "f9e63543121bb3cd2e41bc7b0c2c4ba662bc1cc0520b79fc4e201ec6456fdf59" }, { "name": "flag_tv", "unicode": "1F1F9-1F1FB", - "digest": "8716527383854cf1569f737d0f0f9ad77b46747255f24e02f5b2fbc850c2e35c" + "digest": "6431e5f06cc7995ae7208c429ecf39339b545854cb6d6b7447f465fe53614dfc" }, { "name": "tuvalu", "unicode": "1F1F9-1F1FB", - "digest": "8716527383854cf1569f737d0f0f9ad77b46747255f24e02f5b2fbc850c2e35c" + "digest": "6431e5f06cc7995ae7208c429ecf39339b545854cb6d6b7447f465fe53614dfc" }, { "name": "flag_tw", "unicode": "1F1F9-1F1FC", - "digest": "fb17b97e18e4423c5f60d60ec3ec60b917be579fc4dd9b5b23236786dcb35108" + "digest": "8395ab3c6a595023b006518a5345ac3612f2893d3a8f011b7e5802414236b03c" }, { "name": "tw", "unicode": "1F1F9-1F1FC", - "digest": "fb17b97e18e4423c5f60d60ec3ec60b917be579fc4dd9b5b23236786dcb35108" + "digest": "8395ab3c6a595023b006518a5345ac3612f2893d3a8f011b7e5802414236b03c" }, { "name": "flag_tz", "unicode": "1F1F9-1F1FF", - "digest": "a8a8cf57ae5227cb54620bf31d2d6e154d2067d6d049b8db64bc4e538222948b" + "digest": "716181733cd9ac7a8f51a9a64bc5d21020e8112f6768e8c49c4d651a3ee0b8a4" }, { "name": "tz", "unicode": "1F1F9-1F1FF", - "digest": "a8a8cf57ae5227cb54620bf31d2d6e154d2067d6d049b8db64bc4e538222948b" + "digest": "716181733cd9ac7a8f51a9a64bc5d21020e8112f6768e8c49c4d651a3ee0b8a4" }, { "name": "flag_ua", "unicode": "1F1FA-1F1E6", - "digest": "03aca4b3ffd60d944a5793eb7530f8d8ae527782f642f6606194e46ee314b12c" + "digest": "304570736345e28734f5ff84a2b0481c2bb00bf29d9892bd749b57dec7741e30" }, { "name": "ua", "unicode": "1F1FA-1F1E6", - "digest": "03aca4b3ffd60d944a5793eb7530f8d8ae527782f642f6606194e46ee314b12c" + "digest": "304570736345e28734f5ff84a2b0481c2bb00bf29d9892bd749b57dec7741e30" }, { "name": "flag_ug", "unicode": "1F1FA-1F1EC", - "digest": "70226a1585e88390b3b815b8b79a0ddb36d2961c6b465c4ff72aa444abfe982e" + "digest": "a1bafb74c54ee8c92cb025b55aebdb6081eec3fda6a7f86f2ee14d1b801a8e9c" }, { "name": "ug", "unicode": "1F1FA-1F1EC", - "digest": "70226a1585e88390b3b815b8b79a0ddb36d2961c6b465c4ff72aa444abfe982e" + "digest": "a1bafb74c54ee8c92cb025b55aebdb6081eec3fda6a7f86f2ee14d1b801a8e9c" }, { "name": "flag_um", "unicode": "1F1FA-1F1F2", - "digest": "aa83bf051149acf907140a860de5de1700710e4164ae5549ad1040b24d0a142b" + "digest": "b3c9ac72211f481f50cde09e10b92aa03b1ea90abf85418e60a35b84963273ee" }, { "name": "um", "unicode": "1F1FA-1F1F2", - "digest": "aa83bf051149acf907140a860de5de1700710e4164ae5549ad1040b24d0a142b" + "digest": "b3c9ac72211f481f50cde09e10b92aa03b1ea90abf85418e60a35b84963273ee" }, { "name": "flag_us", "unicode": "1F1FA-1F1F8", - "digest": "32ba2aa09a30514247e91d60762791b582f547a37d9151f98b700dff50f355ea" + "digest": "da79f9af0a188178a82e7dc3a62298fa416f4cfbcae432838df1abebca5c0d63" }, { "name": "us", "unicode": "1F1FA-1F1F8", - "digest": "32ba2aa09a30514247e91d60762791b582f547a37d9151f98b700dff50f355ea" + "digest": "da79f9af0a188178a82e7dc3a62298fa416f4cfbcae432838df1abebca5c0d63" }, { "name": "flag_uy", "unicode": "1F1FA-1F1FE", - "digest": "0e01b3f1df4bdf6d616dacc9c5825151b941bf074be750e8b24a07ea5d5bcacb" + "digest": "8348e901d775722497ee911c9c9b4bd767710760c507630a67ecb6d47cc646c7" }, { "name": "uy", "unicode": "1F1FA-1F1FE", - "digest": "0e01b3f1df4bdf6d616dacc9c5825151b941bf074be750e8b24a07ea5d5bcacb" + "digest": "8348e901d775722497ee911c9c9b4bd767710760c507630a67ecb6d47cc646c7" }, { "name": "flag_uz", "unicode": "1F1FA-1F1FF", - "digest": "903029ce83812a2134f24b65db35b183443a440ea5fecaa6ef7dcaaf65b2519c" + "digest": "2a1dc1e9469e01c58ea91f545ef3fe0bdfe5544a73a80407f8960d01b1e5db5c" }, { "name": "uz", "unicode": "1F1FA-1F1FF", - "digest": "903029ce83812a2134f24b65db35b183443a440ea5fecaa6ef7dcaaf65b2519c" + "digest": "2a1dc1e9469e01c58ea91f545ef3fe0bdfe5544a73a80407f8960d01b1e5db5c" }, { "name": "flag_va", "unicode": "1F1FB-1F1E6", - "digest": "fd3c1c5d0ac030e838f807288912c98a3e258f87901e252e46942a4dab9f8cb7" + "digest": "0e8134ec94bff032bfc63b0b08587d5298c9b7f31edd5a5b35633ae911434e61" }, { "name": "va", "unicode": "1F1FB-1F1E6", - "digest": "fd3c1c5d0ac030e838f807288912c98a3e258f87901e252e46942a4dab9f8cb7" + "digest": "0e8134ec94bff032bfc63b0b08587d5298c9b7f31edd5a5b35633ae911434e61" }, { "name": "flag_vc", "unicode": "1F1FB-1F1E8", - "digest": "7cd554ea8ca817b5366701160274587ab44167ae5a89c430bbaf237ea18b7421" + "digest": "e0290e1be72c8939ee6c398f00a107703b21b97d91b9bf465e553ffbf00304a7" }, { "name": "vc", "unicode": "1F1FB-1F1E8", - "digest": "7cd554ea8ca817b5366701160274587ab44167ae5a89c430bbaf237ea18b7421" + "digest": "e0290e1be72c8939ee6c398f00a107703b21b97d91b9bf465e553ffbf00304a7" }, { "name": "flag_ve", "unicode": "1F1FB-1F1EA", - "digest": "72930094fb088c1facabea07616035ec4771374358a90c3045219d087b350dd8" + "digest": "76a6a6c2353def1f984d1a6980831e63f3aea5af2201b574197834e7c203d57a" }, { "name": "ve", "unicode": "1F1FB-1F1EA", - "digest": "72930094fb088c1facabea07616035ec4771374358a90c3045219d087b350dd8" + "digest": "76a6a6c2353def1f984d1a6980831e63f3aea5af2201b574197834e7c203d57a" }, { "name": "flag_vg", "unicode": "1F1FB-1F1EC", - "digest": "78a59afd368b7a8312bfdb2f49927ff09e6b8f46aab0136c0453e3319e81df49" + "digest": "56fc9317b8dd62cccc60010819f8b895dd4569a9b06368a9250f815c39177b8a" }, { "name": "vg", "unicode": "1F1FB-1F1EC", - "digest": "78a59afd368b7a8312bfdb2f49927ff09e6b8f46aab0136c0453e3319e81df49" + "digest": "56fc9317b8dd62cccc60010819f8b895dd4569a9b06368a9250f815c39177b8a" }, { "name": "flag_vi", "unicode": "1F1FB-1F1EE", - "digest": "e070879f9605a9bae66bb84f2abf5a40c8b264baee65cd4f7a6720b826739f29" + "digest": "2526a3e13b8ccd301f0763580430898c227bd209e3ce482c7951140b28948375" }, { "name": "vi", "unicode": "1F1FB-1F1EE", - "digest": "e070879f9605a9bae66bb84f2abf5a40c8b264baee65cd4f7a6720b826739f29" + "digest": "2526a3e13b8ccd301f0763580430898c227bd209e3ce482c7951140b28948375" }, { "name": "flag_vn", "unicode": "1F1FB-1F1F3", - "digest": "100ddf06e0f239b170f4d6cb459450bf4945281ee818f7d3c061828b80562219" + "digest": "0cf6b9896bbe4da8ed7718d0abfd56cef1a8321e26f89d3ad1b48488eaffb7a5" }, { "name": "vn", "unicode": "1F1FB-1F1F3", - "digest": "100ddf06e0f239b170f4d6cb459450bf4945281ee818f7d3c061828b80562219" + "digest": "0cf6b9896bbe4da8ed7718d0abfd56cef1a8321e26f89d3ad1b48488eaffb7a5" }, { "name": "flag_vu", "unicode": "1F1FB-1F1FA", - "digest": "59fc9d16818295bba4f7f551598f85378cd07f2bd7e31a4eef2589aaa3847563" + "digest": "9dfa282ce1aafc62beacab76e1fc19a141c8bdeaa30898f69b083067b775d362" }, { "name": "vu", "unicode": "1F1FB-1F1FA", - "digest": "59fc9d16818295bba4f7f551598f85378cd07f2bd7e31a4eef2589aaa3847563" + "digest": "9dfa282ce1aafc62beacab76e1fc19a141c8bdeaa30898f69b083067b775d362" }, { "name": "flag_wf", "unicode": "1F1FC-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "wf", "unicode": "1F1FC-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "flag_white", "unicode": "1F3F3", - "digest": "96307e3a28e92d1e7147a06f154ffc291ee3cd1765cf8b7bfb06412294112559" + "digest": "d9be4b7ceb8309c48f88cfd07a9f7ce6758ea6e620e73293cf14baec03ca381c" }, { "name": "waving_white_flag", "unicode": "1F3F3", - "digest": "96307e3a28e92d1e7147a06f154ffc291ee3cd1765cf8b7bfb06412294112559" + "digest": "d9be4b7ceb8309c48f88cfd07a9f7ce6758ea6e620e73293cf14baec03ca381c" }, { "name": "flag_ws", "unicode": "1F1FC-1F1F8", - "digest": "0c95271d0f4b23f0d215ee0fba05cf08ecb70665d4c028e17463ecda2754b164" + "digest": "53addd0dc304a3c8893389ed227986ef2431828b8c071926aa09f9efd815b649" }, { "name": "ws", "unicode": "1F1FC-1F1F8", - "digest": "0c95271d0f4b23f0d215ee0fba05cf08ecb70665d4c028e17463ecda2754b164" + "digest": "53addd0dc304a3c8893389ed227986ef2431828b8c071926aa09f9efd815b649" }, { "name": "flag_xk", "unicode": "1F1FD-1F1F0", - "digest": "713aa7d228e96f4a06d58d1fb8c2a55296c3e56842f8177ca936f3e09f50da1e" + "digest": "eba1a832e489e1c2734e773e685df5d128271fa5559d23c060e68be067bf6469" }, { "name": "xk", "unicode": "1F1FD-1F1F0", - "digest": "713aa7d228e96f4a06d58d1fb8c2a55296c3e56842f8177ca936f3e09f50da1e" + "digest": "eba1a832e489e1c2734e773e685df5d128271fa5559d23c060e68be067bf6469" }, { "name": "flag_ye", "unicode": "1F1FE-1F1EA", - "digest": "3bb65bae9c913357bcae8b8b5878efc9e194ca308442ab69639c29716b49f078" + "digest": "edfa14266785042b6d5fe0f64fafa630b16a3ee7d010501de7cc8554c959afb0" }, { "name": "ye", "unicode": "1F1FE-1F1EA", - "digest": "3bb65bae9c913357bcae8b8b5878efc9e194ca308442ab69639c29716b49f078" + "digest": "edfa14266785042b6d5fe0f64fafa630b16a3ee7d010501de7cc8554c959afb0" }, { "name": "flag_yt", "unicode": "1F1FE-1F1F9", - "digest": "f86c86f4c194610a3af78971fcf221ad97b9499d08f6d64476e417a2f52a611e" + "digest": "472ebc676b5d31dec2ac5e02ce69014a3dd94609d30a95f39f3a752f49c85e8b" }, { "name": "yt", "unicode": "1F1FE-1F1F9", - "digest": "f86c86f4c194610a3af78971fcf221ad97b9499d08f6d64476e417a2f52a611e" + "digest": "472ebc676b5d31dec2ac5e02ce69014a3dd94609d30a95f39f3a752f49c85e8b" }, { "name": "flag_za", "unicode": "1F1FF-1F1E6", - "digest": "4dd4fa49a01fdcfc7c1c099a7869e0e9acba83a6a3debf6c8505ada4c796b872" + "digest": "dad162942a43392b4cff6929bd5cbf58c382a03dbc0e552f03c07ad2d8ff08ce" }, { "name": "za", "unicode": "1F1FF-1F1E6", - "digest": "4dd4fa49a01fdcfc7c1c099a7869e0e9acba83a6a3debf6c8505ada4c796b872" + "digest": "dad162942a43392b4cff6929bd5cbf58c382a03dbc0e552f03c07ad2d8ff08ce" }, { "name": "flag_zm", "unicode": "1F1FF-1F1F2", - "digest": "ab6790d89875447de3d1c7f4713b102761bc3e9afdd714b818689e175ca03011" + "digest": "1521ecaf1d1fdc8c15f0c96a6b04e6d4050f26f943a826b3d3d661f6ded6d438" }, { "name": "zm", "unicode": "1F1FF-1F1F2", - "digest": "ab6790d89875447de3d1c7f4713b102761bc3e9afdd714b818689e175ca03011" + "digest": "1521ecaf1d1fdc8c15f0c96a6b04e6d4050f26f943a826b3d3d661f6ded6d438" }, { "name": "flag_zw", "unicode": "1F1FF-1F1FC", - "digest": "9d39b934fe922174b2250f2cd1b174a548d2904091d3298f35b7cc59fbceb181" + "digest": "46d05b597c5c77c8e2dc7bd6d8dd62ebca01bc9c9dc9915dafe694ca56402825" }, { "name": "zw", "unicode": "1F1FF-1F1FC", - "digest": "9d39b934fe922174b2250f2cd1b174a548d2904091d3298f35b7cc59fbceb181" + "digest": "46d05b597c5c77c8e2dc7bd6d8dd62ebca01bc9c9dc9915dafe694ca56402825" }, { "name": "flags", "unicode": "1F38F", - "digest": "c3f4a66786e524a5562919afcba9486113091ed205f1342e91d2f6439845ad61" + "digest": "f860aa4df587cf140c3e9735bbd101e9fd5a1bfcea42e420d85ac0a9877fa21d" }, { "name": "flashlight", "unicode": "1F526", - "digest": "5f641b8fd1c7f1dcd43ec3b1ef78d14ef9929d723789c5567aca8b95d3d39803" + "digest": "e929bbe76e0fd2dc5bd6476858a0bbc717fd21467710435d35d80efb38033d73" }, { "name": "fleur-de-lis", "unicode": "269C", - "digest": "d6ddeeea355ed55103b7fc65ac1ee0dbaa79d01e0d136b265363a6b92284c073" + "digest": "ebf49007f367dc05580e9dab942e93e9dda12fa1dc2caa410ac7f8d8cd55d2a3" }, { "name": "flip_phone", @@ -5322,7 +5322,7 @@ { "name": "floppy_disk", "unicode": "1F4BE", - "digest": "e987961ca516032a90942ef6c398836f2da68a5981714bd172acfe7b0e369d0a" + "digest": "4ee0b5bba41b9e301ed125d3ee1c263bef171ca499e6e1b89276b09af2bc03a0" }, { "name": "floppy_white", @@ -5337,22 +5337,22 @@ { "name": "flower_playing_cards", "unicode": "1F3B4", - "digest": "451f361050b96ba9ed8dc5b64c8a90c1316fd9b83fb818152881a54e100eea6c" + "digest": "edba47c2e3051b2c7effd98794ec977174052782edcb491daec82a2b0d853869" }, { "name": "flushed", "unicode": "1F633", - "digest": "39cf51f9dec2a910c66ecd39a7bd616fea09d67e81801e57e84f03ed1e917750" + "digest": "e759d46bab92af5494d78b6c712c06568759afe397e7828ca0a0de1e3eab0165" }, { "name": "fog", "unicode": "1F32B", - "digest": "da6fdb9b682ed9a3368adcd7531f1a29e22755a620e3cca163fc3f33a6a78107" + "digest": "0cbd4733961d30fe0f40f95dd1f37254aebbef26f82dd18ad2000e799eb2898e" }, { "name": "foggy", "unicode": "1F301", - "digest": "b599f3178db289c6e30017f3f0a9d30b00a75417057c7a10c0c9eedac78edbf1" + "digest": "bc3631a4e9e8473b92e842008937add2cd9ffad5b7d772ce759fb5ff6c0e3dca" }, { "name": "folder", @@ -5372,52 +5372,52 @@ { "name": "football", "unicode": "1F3C8", - "digest": "834fe5f431d6aa8ef1186aa79e71f813393535d273483b6af4cc4bdb8380e5b4" + "digest": "ebd790471c3a28d3077818e3b31d915ffe443e06e299bc5cf0dd2534d080634c" }, { "name": "footprints", "unicode": "1F463", - "digest": "60dc938f6769ea21b05b5afcc481d3ddacf1f565e04f33310b271d5422e7ceb9" + "digest": "85bbf2bc0ae8e6259d83a06f513600095d7fcfc44372670f5b2405d380b78811" }, { "name": "fork_and_knife", "unicode": "1F374", - "digest": "7e07c9dc555d172fa2eaa41cefd8d46d9624be0137aff196dd003a8a82610ec3" + "digest": "f228accd36ddccb4ec636207c19d7185191ec79723b780a1bd5c3d00a4b1ef3b" }, { "name": "fork_knife_plate", "unicode": "1F37D", - "digest": "b4081b9edea6cdab5112fdd17535051ba17710953013f5020c7c40f84a1e3247" + "digest": "ec6be99dac8efd3d145807fa60d2b6d8f6d3c02cb95552b55cc0fac39a4db48e" }, { "name": "fork_and_knife_with_plate", "unicode": "1F37D", - "digest": "b4081b9edea6cdab5112fdd17535051ba17710953013f5020c7c40f84a1e3247" + "digest": "ec6be99dac8efd3d145807fa60d2b6d8f6d3c02cb95552b55cc0fac39a4db48e" }, { "name": "fountain", "unicode": "26F2", - "digest": "0acdca5e8f6d745a8d582d96012ec8fc55b9f5447e657ebfd998a4e332d99322" + "digest": "87043f9256e1d4615159307fcfd21bf6ae2aba0bada7de2bd50d7d6f2ab82395" }, { "name": "four", "unicode": "0034-20E3", - "digest": "36bd4ea6e2ae689835a79f8e60466eccd62fce7e91e84ed768cffd87dac628dd" + "digest": "c2c82a966bbb599aae557d930a4fc42604f2081aa45528872f5caf4942ee79d9" }, { "name": "four_leaf_clover", "unicode": "1F340", - "digest": "12ee2343df25bbd9077fdc12314c1edb51c0cdb556af7e22590e8a578ef57f17" + "digest": "ebee16e86bc9be843dfc72ab5372fb462f06be4486b5b25d7d4cac9b2c8b01c8" }, { "name": "frame_photo", "unicode": "1F5BC", - "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349" + "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c" }, { "name": "frame_with_picture", "unicode": "1F5BC", - "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349" + "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c" }, { "name": "frame_tiles", @@ -5442,122 +5442,122 @@ { "name": "free", "unicode": "1F193", - "digest": "c1d9172a656717f78d941303c5da8790c6cd9827838d8f7dc3719afb53bcab80" + "digest": "9973522457158362fc5bdd7da858e6371e28a8403d1ef9e4b6427195c7f72cfa" }, { "name": "fried_shrimp", "unicode": "1F364", - "digest": "c0c19e95f2c38f6cf870920bf3c2d4d69c36ea6e7dc9a5c45c3e8b285269d40a" + "digest": "0792bdc4484852de970c8f43bc3a1a339dc0e48090ec77d6de97cbfcdd17f9e1" }, { "name": "fries", "unicode": "1F35F", - "digest": "0f546534684de29d319cbcbab4162acb321c4f8f3202fe17d69e1894ab7c8195" + "digest": "47915aea67251d358d91a0e4dc3dcc347155336007d6b931a192be72a743b4e9" }, { "name": "frog", "unicode": "1F438", - "digest": "6a417757fa6ee39e7a277cbd53c690ff88af0b1d76728d56f9bc645cb628aeb7" + "digest": "d024b2ce771df64040534fb0906737d18b562bc3578dee62c2f25ec03c7caffd" }, { "name": "frowning", "unicode": "1F626", - "digest": "fb39f5c2aea98054adb02a3a0ac34a2e38d83f32cd590e9d2449e06a9702f2f5" + "digest": "c01af48537b0011d313d8f65103e1401fce4f5c0269c68e0e9806926c59acc44" }, { "name": "anguished", "unicode": "1F626", - "digest": "fb39f5c2aea98054adb02a3a0ac34a2e38d83f32cd590e9d2449e06a9702f2f5" + "digest": "c01af48537b0011d313d8f65103e1401fce4f5c0269c68e0e9806926c59acc44" }, { "name": "frowning2", "unicode": "2639", - "digest": "7bb6c682a6c9f98bf3a5ae986e317fd26d1af497c857500deec2f06b6a3af5da" + "digest": "6568ee393b950c852d440112e86908c456b89fb7780e27778c5fcec168373fbf" }, { "name": "white_frowning_face", "unicode": "2639", - "digest": "7bb6c682a6c9f98bf3a5ae986e317fd26d1af497c857500deec2f06b6a3af5da" + "digest": "6568ee393b950c852d440112e86908c456b89fb7780e27778c5fcec168373fbf" }, { "name": "fuelpump", "unicode": "26FD", - "digest": "9cbb2646c93b255bd3de87dc01aa1193ab96e39a3013975d250472ab8aae61d6" + "digest": "105e736469f19911b8bab4ab6d29f949ded4b061b54e3dd763726577d6453095" }, { "name": "full_moon", "unicode": "1F315", - "digest": "0b4f08ef2089397ead034b444a60e6e9810073454581b52a46b2369e3b9cd5f9" + "digest": "aaa87f4676a5aaa29c1b721a3b582e89db6c1f35a25c52e4b480bd193ef39c43" }, { "name": "full_moon_with_face", "unicode": "1F31D", - "digest": "a371cb9e1f28a7db739dd058234642a2e333dff4b6df9882df85a6d984e4b5e8" + "digest": "05c4b9c339fcdf81ae67027641522baa99c370d87873ff4c8133b8349e627e33" }, { "name": "game_die", "unicode": "1F3B2", - "digest": "6584909a4348c350c04417421b63eace1245087f7d239051b30a0cd37fe929f9" + "digest": "00d19ce8e21dba2cdfeb18709fa8741f3af9d6207f81d5657b68e05e64f105a8" }, { "name": "gear", "unicode": "2699", - "digest": "b0ff5fd007daa366a9eecb7422dbeb8a973e123a04267b88fef96c7453238294" + "digest": "c5ba354c0f7a36dce95477091984e352ecc59af8c9f26a94ad8e296dc042b9de" }, { "name": "gem", "unicode": "1F48E", - "digest": "d75d854f35975e4e291c3b9fcaf8437467f6d7eb27b29e2d7c0f0038fc666fe2" + "digest": "180e66f19d9285e02d0a5e859722c608206826e80323942b9938fc49d44973b1" }, { "name": "gemini", "unicode": "264A", - "digest": "392abe62872736a0bf92979a8c25a814985d0ff0a08dc7ab2a5c058aeda7e685" + "digest": "278239c598d490a110f1f3f52fc3b85259be8e76034b38228ef3f68d7ddd8cdd" }, { "name": "ghost", "unicode": "1F47B", - "digest": "f084b14483476e2d07563840f8c33b46da9c17f791da07fde3acffeb77342947" + "digest": "80d528fcf8ef9198631527547e43a608a4332a799f9e5550b8318dec67c9c4d2" }, { "name": "gift", "unicode": "1F381", - "digest": "c9a2ae6ea05c02e78e9567dcbd971701a2f869eb46c62d85cef23d0834388d8c" + "digest": "4061a84a59f0300473299678c43e533341eb965db09597fffc6e221fd7b77376" }, { "name": "gift_heart", "unicode": "1F49D", - "digest": "e0c5aacf1ce89117d86b148f10a02dc18fe0cd22a75fbf6f0f88f2fad3ca80fe" + "digest": "5420199b515b9b32c964a3c19d87e07461639e3068a939dae26c6436335c0cee" }, { "name": "girl", "unicode": "1F467", - "digest": "0758cbc4cbc7d72d6df8f66fc3a6b2b283c6634b053e59d61c6cac44cf8bffda" + "digest": "8d2d0b72a91e6e44921b71030ffc4c89c0f50f1364787784afe1e7e568cf1bc6" }, { "name": "girl_tone1", "unicode": "1F467-1F3FB", - "digest": "7afdece55cb64e8056e2202de8c17b66ddb616f224ac374ec9a160d06b3138cc" + "digest": "bda12a6b38994a578ee65166bbdd93ea04df4101697b52ed236de8d687df09de" }, { "name": "girl_tone2", "unicode": "1F467-1F3FC", - "digest": "c160aa65fee70ad52930d01246ac9f282ff6abf1d93c5cc5b299fc257ee81db1" + "digest": "de7a0925c30b7181a289f71b1a849c1b7751ee8c104e8f2029bd9c2fe3f91c64" }, { "name": "girl_tone3", "unicode": "1F467-1F3FD", - "digest": "b8a5687cd637855a41b8c7dc686f0e69fda379875408cd269f1b330a805c72f4" + "digest": "e41272816db0e642d003dce7cb262e1593a592251f46729f7830f4515149e1f2" }, { "name": "girl_tone4", "unicode": "1F467-1F3FE", - "digest": "a9cf743936b733634f323790a1abe3a410601b6841484baebea484b392f4e98e" + "digest": "8d6a4513ecbf08408c0ecc5336767777a2216f7a19437faf9e51f65101822469" }, { "name": "girl_tone5", "unicode": "1F467-1F3FF", - "digest": "c902170e67b81eee35eeefb6a5c62c6109cb423dcae88d4e036ddd50b240c072" + "digest": "f55e4b16a41b6f5e3c817a301420360ba4486e4e82e1092a56a3e3cc4069087d" }, { "name": "girls_symbol", @@ -5567,172 +5567,172 @@ { "name": "globe_with_meridians", "unicode": "1F310", - "digest": "945646de3d8f057760fe374494a253d9a6aa8a132309154b0a5bdbffb5b20c3f" + "digest": "725bebeb3c09a9e3701ebe49e672dcfbf2b73575e05f0821263511577b013b75" }, { "name": "goat", "unicode": "1F410", - "digest": "f99cbc6755d119cb5c1dce08cabd20871f98d009bb773da4a146dae60476a235" + "digest": "d07e384d08529ddcaddd2710f2ad913e5665dc15d5f99c28e16dadd245a111e8" }, { "name": "golf", "unicode": "26F3", - "digest": "74a7876d185f8ff6a6533e4db2e1eb787119b2f8d8b07c36d99ec3163fb48485" + "digest": "eed79364754eec97855e3c7b584f347ae139d9ddb4eb7fb66c00867610b8f1c1" }, { "name": "golfer", "unicode": "1F3CC", - "digest": "6458295a5e4a6e4323c32a7f1f7182fb2d3918083839efc380d995860ce360b1" + "digest": "7d7ecc6e226596f646030a4109c2b0001ef0cc690e4863e450bf5d29e7a90344" }, { "name": "grapes", "unicode": "1F347", - "digest": "7f6873d65180ab476f49d207ac2d1f7dbaf6c8b0b561d50b64325e192cf97a86" + "digest": "74d1a09ab411234a84d025a2e717e7ec5791bc02aad29853896d21c0f0283c50" }, { "name": "green_apple", "unicode": "1F34F", - "digest": "effc3fe60f2ab704a034c794bfccfa023b41332f8f16ca44cc8ea41698f03873" + "digest": "457490e9b2b20894f50768262d63f1021717079da104d4847076b3fa779e9a21" }, { "name": "green_book", "unicode": "1F4D7", - "digest": "6652c4d2ccfa4a287a5d45007bd06cadc16d34b0a1ca4b6b13b46f976c8d8319" + "digest": "370f635b200efe5e4a9f17da58bd22500e258e61d17795cef375f19c9a45468f" }, { "name": "green_heart", "unicode": "1F49A", - "digest": "f4bcb660a1d3cf3692238359d8b9de9a725a9af81f166253e487d61b8ccf9d86" + "digest": "f71e30416d9019873f2ed38ef375c48386424ff60b5a07b89b15dc9e0a3970f9" }, { "name": "grey_exclamation", "unicode": "2755", - "digest": "ac8cdab7496d133e7bc9475f2fdb0cf59b3ccba20f2f156c8b693e72b5948078" + "digest": "2fa1d356e12c17cc4025e43afb6c3070385f677102a35223302fda46c47a9b03" }, { "name": "grey_question", "unicode": "2754", - "digest": "c173e1b2a16ab62b0abd7a58deb7a6df709b072d30d001627b92d0123a3a3e4a" + "digest": "e1035bcbf0f66d238ef478ba451f5cf2c51627fbf101ed03bad3b2bf38db8aa2" }, { "name": "grimacing", "unicode": "1F62C", - "digest": "8c54b73f5d2c1c6347e2c0ab01616519e0fb34490daa9c36664d442c6851c57e" + "digest": "2cedad13b8b2a1d4385ca6fa88a251eb7757a4c65dd6d362267864a01247846b" }, { "name": "grin", "unicode": "1F601", - "digest": "916eabdabd8b7ca698e638bbbd14affff97464ec11a3b59c0cb96cd7705600d8" + "digest": "634b2f37e32e57ed6edc7f371993a92e34137dd21ba393de5227cfbbe2422815" }, { "name": "grinning", "unicode": "1F600", - "digest": "3d8665c03f272ca3063e96145989926355a7ac315ed1a032d30fcefa6f0c3923" + "digest": "cef76aa41771db9fd1d6bd9b4233c22c1fb1931494af54cab29e6347ed9b678d" }, { "name": "guardsman", "unicode": "1F482", - "digest": "ebbd29fa138005232d64fca4a8ec015d097fa14e6ded57b35ac257b4570b3c36" + "digest": "17bc7fad6b8c8dbd015bb709380d129f8b8e1e971062d15e6ab0b2e63e500564" }, { "name": "guardsman_tone1", "unicode": "1F482-1F3FB", - "digest": "b6082c8fee5dbc3ce2540f3939d5e344b5366c9f07827345facaba438e7017ff" + "digest": "c531ecb101bdf9ce1db18e1567882e6db927410237100b0a2492a1401860246e" }, { "name": "guardsman_tone2", "unicode": "1F482-1F3FC", - "digest": "2b813afe1c2bbdaf9a47493393a0e6c400a16e453ed25a9a9c0035197927b56e" + "digest": "602168c5204af0f1de8b4aa5863b192ef20c19d263999377aa5eb60f98311732" }, { "name": "guardsman_tone3", "unicode": "1F482-1F3FD", - "digest": "49b2fa1ad0bc50a5ef6d73fb140aa1876506b9ebb9d45782ccb8dbb6818f8dde" + "digest": "d0a85de46dd02c7bd6cb14bff0f22d2db9083d4b171a8806c83363b49f3dd9ef" }, { "name": "guardsman_tone4", "unicode": "1F482-1F3FE", - "digest": "a584e1e3a8ad7be4871a6bdb7996d4f649abeaa77eb5d1cae998058d8b23ca0f" + "digest": "1c9d4d72b6b50bdac8271613b6d2a38340ec2067bc344e8ee2a3c863fd5c23a1" }, { "name": "guardsman_tone5", "unicode": "1F482-1F3FF", - "digest": "e853b67ee13fda99e98f47083529ca80c404df1b19352c78b9c69850eb8f2c76" + "digest": "9899a796d01842e495d716fbe737a16d85724f7d3e23f50807ec2bc70f057318" }, { "name": "guitar", "unicode": "1F3B8", - "digest": "8c041b961649cc5917f56f2fb543f9a5280724647ed2fc67bc94a05eff9da805" + "digest": "a1027ceae4dd3ea270740587c9d373329e5677e375c9e00af6ae3275e0b67500" }, { "name": "gun", "unicode": "1F52B", - "digest": "d7f5aa657cc0ba04d878511820632b89c305a9b4d6c4a4b90ff691dad9906607" + "digest": "fc12b577df2283e7b336f23774f9cfe5b79f1d26ddd28a64a560519b28d94ca5" }, { "name": "haircut", "unicode": "1F487", - "digest": "369dbab1b138c31d3eca04c950fdab4ec9f085272268c241f100d44e7b0f229e" + "digest": "b243a04f5ca889accd45e7abe095ac5caa92274ed95103f5966a36b415fff412" }, { "name": "haircut_tone1", "unicode": "1F487-1F3FB", - "digest": "c56f32d7c1d8a92d22429133f87f31a159818939cfdc570cb48b6d243cc58cf2" + "digest": "a58d0cff1427b80dfd7a9ea5267b4a181e9faaac6a51a0165db522f668b4cf91" }, { "name": "haircut_tone2", "unicode": "1F487-1F3FC", - "digest": "e916e040ffb8e869e930d1256343af2ad2bbaa683f01a11564d0777019944bec" + "digest": "675083ff40001405f8de99268477d50dd8594ff6ca40ddfd442dd42ad76e8216" }, { "name": "haircut_tone3", "unicode": "1F487-1F3FD", - "digest": "f07cdfbea964ac42a9a050f832107ef0f2fa8115b27689f93d1be954de07b7c1" + "digest": "70d7581e49c315a3771dd61a3713229886db32aaaeb3af078a69cc042f809150" }, { "name": "haircut_tone4", "unicode": "1F487-1F3FE", - "digest": "32ec7f5e999f7c43676768c8320ffaa346c713d340a94b948b1f564b345a2d11" + "digest": "ec5e3e909eb3bc375ef9cc0fe0e0f90b33f44f273ada91ccf415bbc43b8ffbfc" }, { "name": "haircut_tone5", "unicode": "1F487-1F3FF", - "digest": "5aad997d09e7975700927906d41a10bae774356ccddbe5197980bde670272262" + "digest": "7c89739ee458546a808fded7f96d9354c47a76883ebb262d5f5abeafd021260e" }, { "name": "hamburger", "unicode": "1F354", - "digest": "24ebae9a69cf283ab198499cb38d0cdcd82bac74c8e8d1e769ad78eb320a4294" + "digest": "48204235238bd89d3a69f319f65135102f3d6b181eec241d4d86b302bbffa9bf" }, { "name": "hammer", "unicode": "1F528", - "digest": "a43a66b0efdc4cd2c84fd0ccc2cb8e9ede1f89c5d62eefa6ae521d3aed9d81b3" + "digest": "d0e7830539d935fcd82820c4e0c1d724f0756dfc83a51171fe0f4b36b69fac42" }, { "name": "hammer_pick", "unicode": "2692", - "digest": "2e4fe33406ca03fbb0df1596d63e903d8ee6bd78ecc3ec38a67dd2cecbc584e2" + "digest": "aa0445f43bca58d17afa7f3577632ca7775f5a28336385b3020b268b15b18142" }, { "name": "hammer_and_pick", "unicode": "2692", - "digest": "2e4fe33406ca03fbb0df1596d63e903d8ee6bd78ecc3ec38a67dd2cecbc584e2" + "digest": "aa0445f43bca58d17afa7f3577632ca7775f5a28336385b3020b268b15b18142" }, { "name": "hamster", "unicode": "1F439", - "digest": "f47da088ff5792532a382b6e3a47d2dd7c5e6fc19abd5ff6c5ba3ce420b4192e" + "digest": "a7e7582e8b1bccd5b7df27ccb05e353a3f0e39bdeb40877732706b9d74a70de1" }, { "name": "hand_splayed", "unicode": "1F590", - "digest": "a43e52f7cdec5e9d51497888b0988d7bbd42846ad7e492b196293fbce576d197" + "digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15" }, { "name": "raised_hand_with_fingers_splayed", "unicode": "1F590", - "digest": "a43e52f7cdec5e9d51497888b0988d7bbd42846ad7e492b196293fbce576d197" + "digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15" }, { "name": "hand_splayed_reverse", @@ -5747,52 +5747,52 @@ { "name": "hand_splayed_tone1", "unicode": "1F590-1F3FB", - "digest": "73cceec7117280d330f8a149979190f0f355dd8d0a92821be89fb70344bb8dfe" + "digest": "c31fb44a982ed8808e1c311ec1b0b9c5afcb47f16bb1fc731dc483adf8f0d049" }, { "name": "raised_hand_with_fingers_splayed_tone1", "unicode": "1F590-1F3FB", - "digest": "73cceec7117280d330f8a149979190f0f355dd8d0a92821be89fb70344bb8dfe" + "digest": "c31fb44a982ed8808e1c311ec1b0b9c5afcb47f16bb1fc731dc483adf8f0d049" }, { "name": "hand_splayed_tone2", "unicode": "1F590-1F3FC", - "digest": "b06fac698128f4c3a7b8ea56e8bc4de088bb5461aa0f9c84553f16b43d347145" + "digest": "56a236881184e9ffad54613fa08a67368c432af738f5254fb1cd87b20368acdf" }, { "name": "raised_hand_with_fingers_splayed_tone2", "unicode": "1F590-1F3FC", - "digest": "b06fac698128f4c3a7b8ea56e8bc4de088bb5461aa0f9c84553f16b43d347145" + "digest": "56a236881184e9ffad54613fa08a67368c432af738f5254fb1cd87b20368acdf" }, { "name": "hand_splayed_tone3", "unicode": "1F590-1F3FD", - "digest": "a94ee9a2f8cdec6d2f7dd6887d1c7b8e064fcad63030c2c7c001742d72b5603e" + "digest": "9242ca97dfd2bbc1947228f6535029afb31f8feb72c14ff4b7f2deea30217425" }, { "name": "raised_hand_with_fingers_splayed_tone3", "unicode": "1F590-1F3FD", - "digest": "a94ee9a2f8cdec6d2f7dd6887d1c7b8e064fcad63030c2c7c001742d72b5603e" + "digest": "9242ca97dfd2bbc1947228f6535029afb31f8feb72c14ff4b7f2deea30217425" }, { "name": "hand_splayed_tone4", "unicode": "1F590-1F3FE", - "digest": "501792b4126c6f32e755accee0fc8b4d1915e1d36c4ceaa40f3bd0066efe76c3" + "digest": "43348d9fd3d43b3c45cebaf663bf181bcad3b6df841a5aeed838180db2cdd481" }, { "name": "raised_hand_with_fingers_splayed_tone4", "unicode": "1F590-1F3FE", - "digest": "501792b4126c6f32e755accee0fc8b4d1915e1d36c4ceaa40f3bd0066efe76c3" + "digest": "43348d9fd3d43b3c45cebaf663bf181bcad3b6df841a5aeed838180db2cdd481" }, { "name": "hand_splayed_tone5", "unicode": "1F590-1F3FF", - "digest": "22ed533d587cf44f286e2d6ad77be20b4b5f133c422af4ca51e9af86a75002d8" + "digest": "4b3a0aba7829772fec09f26d6facc19a2f822d2998015297b18b5cab85190ee2" }, { "name": "raised_hand_with_fingers_splayed_tone5", "unicode": "1F590-1F3FF", - "digest": "22ed533d587cf44f286e2d6ad77be20b4b5f133c422af4ca51e9af86a75002d8" + "digest": "4b3a0aba7829772fec09f26d6facc19a2f822d2998015297b18b5cab85190ee2" }, { "name": "hand_victory", @@ -5807,7 +5807,7 @@ { "name": "handbag", "unicode": "1F45C", - "digest": "f1e2822c67f659b52c76821dd9db001332215a8566fc1846c89b6019c9758038" + "digest": "45410a3eed0c2e3f68748d7649fa9e33a90f4e80d5291206bdd0b40380c6da45" }, { "name": "hard_disk", @@ -5817,67 +5817,67 @@ { "name": "hash", "unicode": "0023-20E3", - "digest": "5bd5c7180485fa71accdec5378bdc196ce0602f594f91e4eadc1e7514d5d0f90" + "digest": "01c8b577953010bff0c20f797c2c96ab5d98d4e6ac179c4895a78f34ea904655" }, { "name": "hatched_chick", "unicode": "1F425", - "digest": "7995c3eb503a8b9662694eba80a9b551216473a31928091e35cd6ebc21cee083" + "digest": "006571b9e9e839ec9fcb1a911b935c8ca71eb8bcdce9775bee6a2a4c7c927277" }, { "name": "hatching_chick", "unicode": "1F423", - "digest": "22905b42fa65dbc9aad8940d2db13691cacc62014f54e0960978ee0002178e1b" + "digest": "fd7f69fa186407f80de59dec5116e318325a5743ee0e8bba1db541f1e57e7f74" }, { "name": "head_bandage", "unicode": "1F915", - "digest": "d690b740ff4f58e89dfc764c6411a4e84cfedffd7694eb5efa839a642dbabd08" + "digest": "d09019a73e203b38cc43729a96163147de88e09eab8adb073888e55366854c72" }, { "name": "face_with_head_bandage", "unicode": "1F915", - "digest": "d690b740ff4f58e89dfc764c6411a4e84cfedffd7694eb5efa839a642dbabd08" + "digest": "d09019a73e203b38cc43729a96163147de88e09eab8adb073888e55366854c72" }, { "name": "headphones", "unicode": "1F3A7", - "digest": "219da138032c01c97a94f02b211049418191a3beb3d159804b9033f5916fd3c8" + "digest": "34f9d5598158d5d6f978a5ea5c5aa9948bb2990625565a3afad7710f864fbe2f" }, { "name": "hear_no_evil", "unicode": "1F649", - "digest": "8120060238eaca645809dd113862a144f10395afcb3837ab60c0f04009b49a2f" + "digest": "53b030b6d6f4ed1a734fa7d48b46f42eb1b2b01653202c1838b742082f08c4bf" }, { "name": "heart", "unicode": "2764", - "digest": "a646a25a36f431cadc7e56afd1a4d1b7cbae5292a25d7783bd31462d0d3d719b" + "digest": "92be652ec3e50c6e7393440b5d52b88a367f98a28dffe12660095ed3253aa6c0" }, { "name": "heart_decoration", "unicode": "1F49F", - "digest": "a83989669347c98cb74065d4f0befedbc37f82c91214e773245cb6810ab359b4" + "digest": "6ec5bbf3aa75c6f43eb3dc05e9204366936e8b6b4219310bacdc2fc45f51e245" }, { "name": "heart_exclamation", "unicode": "2763", - "digest": "9751c89dcf10805f2011949ff3ddcb6bcb13de8c32ae5de9e03955e8a4235df2" + "digest": "5985ea4d82232a2a07052a59db268aed9ac943895d0c82f637595bb5386329a6" }, { "name": "heavy_heart_exclamation_mark_ornament", "unicode": "2763", - "digest": "9751c89dcf10805f2011949ff3ddcb6bcb13de8c32ae5de9e03955e8a4235df2" + "digest": "5985ea4d82232a2a07052a59db268aed9ac943895d0c82f637595bb5386329a6" }, { "name": "heart_eyes", "unicode": "1F60D", - "digest": "335ea73efca4824e623a5a51ccdb494c8b1f5f10b4139b39b250a2a771876b0d" + "digest": "0eff616517a6252ec89d47d9b4ad85589bcf2bdc7f490578934350acb84b2fcc" }, { "name": "heart_eyes_cat", "unicode": "1F63B", - "digest": "9346b85afb80f7b498cc255426ea15a287f81d8fb3c26dab61337635f439d3ce" + "digest": "8a1f28b97d661ca4cff5ee13889ca61b5fa745ccb590e80832b7d7701df101d6" }, { "name": "heart_tip", @@ -5892,257 +5892,257 @@ { "name": "heartbeat", "unicode": "1F493", - "digest": "cd6921ce55c155873220a09416d695c4bcca1556007066d6d185e93d6561e825" + "digest": "c9ec024943439d476df6f5ec3a6b30508365a7af3427671a80de3ef2f4f95ffe" }, { "name": "heartpulse", "unicode": "1F497", - "digest": "f869357b9e678d9671ec38c569fc88efec48006c159b69297277cee795dc4dc9" + "digest": "281d8aebfea37db5b7fe82d9115be167006881fe29ab64a5b09ac92ac27a2309" }, { "name": "hearts", "unicode": "2665", - "digest": "17dc9b2941561f58ca0f04d0754b1eff3490b63b17241580b3d4aa4638fa85e8" + "digest": "271429d12c40be921897005b7bdd08f9518960af1e1e6f56bb0060f1f183651e" }, { "name": "heavy_check_mark", "unicode": "2714", - "digest": "b5fa24f6e0f1dcbd6278e9125154522f2efd79e6dd0836ccb792a1f3aeeff2b2" + "digest": "e347728e1290eb9e7b0742d628e2fd124fc049e0774f8a6ddf8e5286e7318718" }, { "name": "heavy_division_sign", "unicode": "2797", - "digest": "59a6983d788f347c64eecb3df6f7d3b36779d92df6cc811820993ff9e18d77e1" + "digest": "c1e8c40f0788f140b1c5fcb81ed9b5ce1bcfa5988bb8140ed2808e9cb7e0d651" }, { "name": "heavy_dollar_sign", "unicode": "1F4B2", - "digest": "d2e89c54b3fdeda4d1fd4d29454b69dcf750181110894e6e71a40df99c95bfe8" + "digest": "7cdeef38348654b93d566e01a48973281cb404a63d0b75b3bad51032887f3f55" }, { "name": "heavy_minus_sign", "unicode": "2796", - "digest": "dd5ab3722fe49cfdbc5e1fbab5b342dc960de7b412d4fba59d66e06ce3dc3bcd" + "digest": "e5335cc6b22abdce49a6127c34269b65a4a6643ddd3253d9baac425089143e7d" }, { "name": "heavy_multiplication_x", "unicode": "2716", - "digest": "7d77742f91377785675802f40bd8dde9bd1feeb513735760a58ea9bee8a65d44" + "digest": "64bbe9e9716a922e405d2f6d3b6d803863a53fac80ff8cd775899971046cb1ca" }, { "name": "heavy_plus_sign", "unicode": "2795", - "digest": "9aa9dcdbba120a4b485c21f67589609b789c6e3edf08479ff8268fa0db973ad7" + "digest": "d0d8ade2020ceb252205180b85c66e665856e6cb505518d395b9913b0b24b746" }, { "name": "helicopter", "unicode": "1F681", - "digest": "b259ea8d2bdca36766075894da650b1d3ff4c8602259cd0d30cb8214cd585340" + "digest": "4bd6fd13650fbe3a19cfffeffe6c21b1cda74bd6af64c5dc5999185e35444bc3" }, { "name": "helmet_with_cross", "unicode": "26D1", - "digest": "affbe9dd87b87ff9235b4858c59c2a73e9ed30dd5221e5b666b8d7747378a9c4" + "digest": "8286107391d44b9cd7fce5dc83bfdebbcdcf5a8214c46a8990732ec40263ed77" }, { "name": "helmet_with_white_cross", "unicode": "26D1", - "digest": "affbe9dd87b87ff9235b4858c59c2a73e9ed30dd5221e5b666b8d7747378a9c4" + "digest": "8286107391d44b9cd7fce5dc83bfdebbcdcf5a8214c46a8990732ec40263ed77" }, { "name": "herb", "unicode": "1F33F", - "digest": "3c452106b1966f643751bf161fa7d1762a33e6fff381b2109bb53b55c4fdd129" + "digest": "9fe8ed65515ede59d0926dcf98f14e2498785e1965610aa0dd56eca9b4bedad9" }, { "name": "hibiscus", "unicode": "1F33A", - "digest": "268963a1f3cdad9050d9ae31c558e010f33812e3b09bbf9088ba876c033d8b2f" + "digest": "c442e8eacbd8727bd154bd39692a9a2a03ea2f674b9670ad8361f78a038afe49" }, { "name": "high_brightness", "unicode": "1F506", - "digest": "d607f6269d95dd16c2a7932e49ac09e44f4c19e0a34f6c0f21ecb945a2316361" + "digest": "35ced42426dcfd5214c2c6c577dce84bb708156433945e6b6adaff7ea530cc57" }, { "name": "high_heel", "unicode": "1F460", - "digest": "5c320d5954bf4f4dacacddd562c1598ab101731077a6656ac5d2bfd41405483e" + "digest": "1e7c7aba50eb1d02cf1d9aa372caca741a6005cf47f68dfa75b7310c3cb18f05" }, { "name": "hockey", "unicode": "1F3D2", - "digest": "008904c1b8db139215492a6d96c09f2c3eeda769f858a9bbae13f8c54d439d0e" + "digest": "2d00fb17baa617e799db8e9b1771cc365bb4545c7633df0123e66e1a6e2ed25d" }, { "name": "hole", "unicode": "1F573", - "digest": "36bbafa5e89b1410ec74919aaf60b09ac3525a421cb5b475b9bb2f20357db8de" + "digest": "8b5539f6f24f09d5d68ffd56be5aa2a8a2f753a8dfbf64892fb02c8f2703e920" }, { "name": "homes", "unicode": "1F3D8", - "digest": "9980d6dd6cbd23b820747ecac4cb10974dd24b0c94b4acfe21fa87793ad065c9" + "digest": "cd512f2b4ce747325607d47da48e083dbfe38a44b85b2522bc372bd105afd25f" }, { "name": "house_buildings", "unicode": "1F3D8", - "digest": "9980d6dd6cbd23b820747ecac4cb10974dd24b0c94b4acfe21fa87793ad065c9" + "digest": "cd512f2b4ce747325607d47da48e083dbfe38a44b85b2522bc372bd105afd25f" }, { "name": "honey_pot", "unicode": "1F36F", - "digest": "94cb1624491076b5cb145e7a309f91a7be3d4c0bed712af6a51d641eb73edee7" + "digest": "f6eec8c32fbd1b461446dc6c5d5031c43e6ee9685dc9b1ea1b839114e48c4eee" }, { "name": "horse", "unicode": "1F434", - "digest": "624ad9dc9ed7af3f6e1a2f9d4ed483702ae64ed5fbcf5e9918af6bfef24e76f9" + "digest": "e377649a9549835770a2a721a92570f699255f88efa646029638eb8ec5f10e3d" }, { "name": "horse_racing", "unicode": "1F3C7", - "digest": "c2702b7225e9839a789dda7c43f0cc86dced2b4d5d3787116106396633362de6" + "digest": "3b98e94e9c028ad85b9a750cc61db5ee3ac23cf5ad9243ea3e996b1f772bad54" }, { "name": "horse_racing_tone1", "unicode": "1F3C7-1F3FB", - "digest": "a7ed284f9d5cd8a4fe4a09cb91c3f99e5db99c7e31c5f525c14de97b06857d92" + "digest": "382d8e4502ed34fc1bbf1779ce483bc2e22b83f89c91746c11a5d7aea656d446" }, { "name": "horse_racing_tone2", "unicode": "1F3C7-1F3FC", - "digest": "20b4d61b21ee6ba860b029f0ad0e38f5ecb6dd2c774f7b7801fba07ed33f96be" + "digest": "198df9973b492ea63e5cfc210dd9591750ccce04a6380adc1dc5b4cb0462a8cd" }, { "name": "horse_racing_tone3", "unicode": "1F3C7-1F3FD", - "digest": "dd65f7bb96ee44507d26e524202d567d2d7679d571245299a2a84f68bd5def4c" + "digest": "a67f95fc92c366750ebad3c4db92982893d67a5ed78163c8cc809ac40d2ab9a3" }, { "name": "horse_racing_tone4", "unicode": "1F3C7-1F3FE", - "digest": "36afaad218a4c820b19c7c9bbbc187119d47b41273d8f48ab14cc3e32dd7c21f" + "digest": "986b1706c4a3395b58a8ae3b7609ffdd4424dfefcbf26c88c8085f4f6379734e" }, { "name": "horse_racing_tone5", "unicode": "1F3C7-1F3FF", - "digest": "2e0efd501a4471428533ce7909972a49ff045369261c27e4abb97ee2aede2f47" + "digest": "66656b5e3d0f43f16f983f9db6214b07aac73b143eeff6475782f98aa5b9ba53" }, { "name": "hospital", "unicode": "1F3E5", - "digest": "df5c774fa36b2601e6960a7b81cdfac71c1d2d71f04dea88068d1c9043e313bb" + "digest": "034573e76df444f5b0eb7aff3a4103e4b49a1813869155ab3ae29a6fc0c6c8a2" }, { "name": "hot_pepper", "unicode": "1F336", - "digest": "62e4dade3c793f6d83530bd1f60f3e3e26c1e10a41786c3a15f5aec0ff2b8e76" + "digest": "0b05777d42698196a10db17d04030175b1dfa772d06288f71d666d5f8d3fddbc" }, { "name": "hotdog", "unicode": "1F32D", - "digest": "58b829e26b5c4642942898d9c7873cb08e048fd7deaacba8292899d5d895cb2b" + "digest": "7a25bbd1a7531fd34a22c654c0931d9e74bea2bbe7baa9f9cbd88f43baa79fb5" }, { "name": "hot_dog", "unicode": "1F32D", - "digest": "58b829e26b5c4642942898d9c7873cb08e048fd7deaacba8292899d5d895cb2b" + "digest": "7a25bbd1a7531fd34a22c654c0931d9e74bea2bbe7baa9f9cbd88f43baa79fb5" }, { "name": "hotel", "unicode": "1F3E8", - "digest": "428120a35b38a217901e10d704751eb8fdbc9f805e6eccd8aab070f4311b2085" + "digest": "2d78e0ad4cfb0caad778c7de49fefd6e8356afe902a43e3f1c40bceb6b0be422" }, { "name": "hotsprings", "unicode": "2668", - "digest": "df4f946218445f97a6f28c6abe4c1d1dac56ff97a8cd81df59f1b3c320e0092f" + "digest": "4c10c3a974b44693e8cbe91365c8b8d7f14f62db234cc516b6e54c08a6bacaed" }, { "name": "hourglass", "unicode": "231B", - "digest": "07aece9413e6898717b4f0757e073d7a593f3e8044c56855127033b796207ccb" + "digest": "f0bae8392aaf6f75a83f5d8914936b8650665b24ba1b232fa546b71545dd9acd" }, { "name": "hourglass_flowing_sand", "unicode": "23F3", - "digest": "92dbc68e9d16fb9f706236367e1882f0d2b6817b83ca490820a000021f2c6483" + "digest": "2d077729f40fc04007a933e97356bd511cbd8be76b8c55962ca3fa0d8b828e23" }, { "name": "house", "unicode": "1F3E0", - "digest": "a6221fc84a9b0e11ae71bfa1e0020982b55ff8c89a374a6d755dba710b4e058c" + "digest": "b4ac25979fbe161ada0d2a75769aa7552d2371d37d78cddba4ffdc7f076d3279" }, { "name": "house_abandoned", "unicode": "1F3DA", - "digest": "e404631e3a296bdeae3de7510da8934c32327bc0fa0f7ae4e676b61932165668" + "digest": "6e1a58533fbfe88a0eb03668c9f17c5c654a6cc7734ed798d4a885400f823610" }, { "name": "derelict_house_building", "unicode": "1F3DA", - "digest": "e404631e3a296bdeae3de7510da8934c32327bc0fa0f7ae4e676b61932165668" + "digest": "6e1a58533fbfe88a0eb03668c9f17c5c654a6cc7734ed798d4a885400f823610" }, { "name": "house_with_garden", "unicode": "1F3E1", - "digest": "22d0d911da96b7ae3bf6692d3cf3590afbca959fc99c13e7a088f7194f43a35d" + "digest": "817463f23ec0a849393ba75c333e822b4d253cd4db998c127e90d1b924f35d20" }, { "name": "hugging", "unicode": "1F917", - "digest": "68ed6c4e0eae9071cf67770a39e07a2290b4f7763170f765b3cd3ac67ae43240" + "digest": "69810a98b1247e1f1e496aa757e428189ef5cc086764fabd8189cf1eef82234f" }, { "name": "hugging_face", "unicode": "1F917", - "digest": "68ed6c4e0eae9071cf67770a39e07a2290b4f7763170f765b3cd3ac67ae43240" + "digest": "69810a98b1247e1f1e496aa757e428189ef5cc086764fabd8189cf1eef82234f" }, { "name": "hushed", "unicode": "1F62F", - "digest": "69faa8e0b170ee8cf41977ca4a5154406360ed9699d5c62ecdaa01f50e8e4276" + "digest": "22586107f7399eff64538a52929dade152633aa268fc5ec4e6fe1c0e00a7bd89" }, { "name": "ice_cream", "unicode": "1F368", - "digest": "d48ec98a8789148b96c30f19595201a0f85ed899659d97d1d3596091162909ff" + "digest": "d1a8e685f2ecf83dead28733859e369d6ce120a2669cdab97dc4423547d472ac" }, { "name": "ice_skate", "unicode": "26F8", - "digest": "6fb044d9fbe62605f6728062c35c345ddd3ae4cc51203c925b0e69f1b3ef2dbf" + "digest": "41ef65c143bc068868fa64080ffd447d91aa3fe2a39e69ecaa97022820af4dcd" }, { "name": "icecream", "unicode": "1F366", - "digest": "abd5774157575dd304dc1a393244757853972c863861a654ca29b2d528e48b28" + "digest": "22cfe17b80cbd2a0377ee90da45bd40d33533c914b2639d363fbb1f00714e194" }, { "name": "id", "unicode": "1F194", - "digest": "860ffb36d37d84e2c1cf0ab991b95c1cf73e458bef0e4d85bb0c1e26115cb2d1" + "digest": "bcf0922e083821d3be7951893084ea0d72a0110ef0b20d11dfec24dd70633893" }, { "name": "ideograph_advantage", "unicode": "1F250", - "digest": "37892a5642cd49ef7828646f36f48b5a83dc02437624c05da428579256118030" + "digest": "0b6bf59f63fda1afa92d652814a778a056c3f4abdd9cf3f6796068bd71783051" }, { "name": "imp", "unicode": "1F47F", - "digest": "f8c93d03bd9f1d5ef86738541e11695d6811bf6fef06759eba98321b6d038814" + "digest": "52598cf2441988f875ccb4e479637baefc679e3ca64e9a6400e56488b0fde811" }, { "name": "inbox_tray", "unicode": "1F4E5", - "digest": "066a2d75633eb50329496f6866b5b0645c2e48135a03118f1bf53244f8529043" + "digest": "d5d9497022b5318fcfbfdfcd56df9c65dd8f4a4cb5e6283ca260836df57da301" }, { "name": "incoming_envelope", "unicode": "1F4E8", - "digest": "ef6e5c5aa679d174181dae77113717f26e295778dde1e2c3bdf1d64de8a4af8c" + "digest": "310b7bdcca93452fe10c72c03d0aafa12b98e5d3408896d275d06d3693812c7a" }, { "name": "info", @@ -6157,97 +6157,97 @@ { "name": "information_desk_person", "unicode": "1F481", - "digest": "acae6d272e348aee87dd60360f16ac58cea7cb4e1ea962cc1655005c7f4aed27" + "digest": "9f12a4a58a650e8e1d3836ef857003c3ccd42ad4203a2479eb95100bf6559064" }, { "name": "information_desk_person_tone1", "unicode": "1F481-1F3FB", - "digest": "709ebb0481ca981d76ece2d4fc68db693ddf18b9c1aaa0b6ac5d3c42e71bf07f" + "digest": "6674f2e059eff7cfd7fd6abc800da37c4f1087feb4ff26c9e4e31aa29fdf9921" }, { "name": "information_desk_person_tone2", "unicode": "1F481-1F3FC", - "digest": "d5bc3563bc721d66b73850db93ac827be3715e7ca6420dc0051396ffe26bef47" + "digest": "9983412ecd130b7e9cfb078167016c06fd043b6f9f3c26d21733ca3f059fd109" }, { "name": "information_desk_person_tone3", "unicode": "1F481-1F3FD", - "digest": "af67fd4ef2fc402bec2d446b2e8ff5e9f636b5a9bbb6639587cdb88bd780d265" + "digest": "d8907bf47af5722127afca8fc0da587eab33044a6c60a94890983deb8d6f7a66" }, { "name": "information_desk_person_tone4", "unicode": "1F481-1F3FE", - "digest": "fd3174d1adfe13e8c0d6b6ae9c3a26ea35bb40f98f0728f91d1798809a74933b" + "digest": "3be086d4edfe9ca8e4a364b4e8d09b81b5b594b5eeb9ffdf6370179fb3118658" }, { "name": "information_desk_person_tone5", "unicode": "1F481-1F3FF", - "digest": "4b773c443830a02de8b4d6471077b5d1387b560b537cabba7cdc667110cbde69" + "digest": "2fde4e98dd11c5c29c89cad7cbb7bd2d5077dfad07913b20e01955b2d0dfad40" }, { "name": "information_source", "unicode": "2139", - "digest": "50cd8bf46d20b7c18d5f00a69fc79452aa32934245ba8d0929e51632d73876bd" + "digest": "b6bf3cce86d42c2e3c46470baab4af01e900b8ae337b605c3da07c3eba671269" }, { "name": "innocent", "unicode": "1F607", - "digest": "a3510fd51c17093ebe2371cfde7611aa44aed2d120a0e5500cfaae0f1d3486a4" + "digest": "20f8d856bc3e46f4b1173cea05d4577e1c61f06b2daba46e57db90f4066bb428" }, { "name": "interrobang", "unicode": "2049", - "digest": "1f843ff672486154f9f3df549bb1b528a5eac8d15264f447649ba57f45ee4d00" + "digest": "92a2d5b4c0bd6714e402f6f12fe19774cb41d081b5e9c23c415ce794224d8117" }, { "name": "iphone", "unicode": "1F4F1", - "digest": "be6f96c02ddae557f700fd20fe7b3f94c9e1c928acb82b2b8b214d231273fece" + "digest": "1ebc54215713cd4bf1c1e50770999f2512bb4fea29e37d0bb3a8aa2460ff875d" }, { "name": "island", "unicode": "1F3DD", - "digest": "17f02b309b62ed9542b1d8943168302846040e420f413e56d799bb5fba7064fa" + "digest": "7f9eb5c0cd865762f7a0f187e09c1be442de7010e7c2e113d56aae998597c90d" }, { "name": "desert_island", "unicode": "1F3DD", - "digest": "17f02b309b62ed9542b1d8943168302846040e420f413e56d799bb5fba7064fa" + "digest": "7f9eb5c0cd865762f7a0f187e09c1be442de7010e7c2e113d56aae998597c90d" }, { "name": "izakaya_lantern", "unicode": "1F3EE", - "digest": "ddb20f475aa119c3a64a55dff40f7a9dbc3a14f7ffc6cfbac89210c652f10d02" + "digest": "fbdc290e666d43d0776a73b955c26df4518692b35e72742e073705fc4ca2ae88" }, { "name": "jack_o_lantern", "unicode": "1F383", - "digest": "62a701ac472619bcb3859e0d9a61b98c7f5c32150d2d04ca8c3e8fc3bec4dbd5" + "digest": "78d666c2e80f64bfb6796f53e5ba4960a83ec36192110e8661031bee2b5e370a" }, { "name": "japan", "unicode": "1F5FE", - "digest": "2535300fff2b2e4b75fc73c187be6c0ea4bc4753e443db498ea55e268e627ab7" + "digest": "e7d9d6ebf9047fdd3c52e074ba259659c6d8e51a6abae3cdb8d6cf6dbf9a93fe" }, { "name": "japanese_castle", "unicode": "1F3EF", - "digest": "70645aa05599e23a9ac4327e4a2e78bffe7ea06c38ec1935c15ae420619c5c1c" + "digest": "938ae132c403330288223b88d28c19a47224d4f254fbc2366ecef73d9633112c" }, { "name": "japanese_goblin", "unicode": "1F47A", - "digest": "59b6901dc6eedc6509c25b4eef6702bf461ded06c5ff12fe2a02a5b3301577c0" + "digest": "63d4bcf58b9d0c29612994432aad2ae35819fdd2890674e60a2f1d51601b742e" }, { "name": "japanese_ogre", "unicode": "1F479", - "digest": "dab7e68cd4cbf99c13d64792c7104c4f0a846bc63aa12950fa8fab028dca301d" + "digest": "434ceedd102e7dcbc07e086811673dd63659ddf8c3ec4d029a3d759a0abfcbdb" }, { "name": "jeans", "unicode": "1F456", - "digest": "ddd032ac77cdfe49152a0e0a0eaaaea9f183590fb1f493ec30e9e39f679e3914" + "digest": "f986ad32e419cca81c995f8371f0189d1490172a97ebbeac60054a1af08949c5" }, { "name": "jet_up", @@ -6262,37 +6262,37 @@ { "name": "joy", "unicode": "1F602", - "digest": "f90cfbcb14f906f8d786b61f022c978f381fc99ca422805f605631314e101805" + "digest": "75d7a05043523d290c46d3b313b19ed3c95271f1110bcf234cf13d4273625b08" }, { "name": "joy_cat", "unicode": "1F639", - "digest": "6ca24a94490de66d1ca2cbc080bcd805f54ca295051d8e6588cae3fe6658c80a" + "digest": "a65c999604147e5e20170fcb14f80a1ff0a633f991492e1f790b2ad4caec7b7e" }, { "name": "joystick", "unicode": "1F579", - "digest": "ec172df88ef8e8a5512d6d906c13296875b7057ed0cca79f4ac8cddd9e1de34b" + "digest": "671ee588f397a96f27056a67e6a06d6e8d22c2109ec57b2859badb5fec9cf8dd" }, { "name": "kaaba", "unicode": "1F54B", - "digest": "30f1a27a148399bbb811586eff795eff858701c42055c23e4d5bef7ae77f5f32" + "digest": "a4618782f9583f077bd383965f1c91b9985a949bb7b6cec7af22914e7f5e9ab6" }, { "name": "key", "unicode": "1F511", - "digest": "c68ed648350d3976c8d27a709020c8873ecf553929e66453acff96231684a1a2" + "digest": "66719fa77a50a0827c8d47237e2704c03e38186e6fef80627a765473b2294c2e" }, { "name": "key2", "unicode": "1F5DD", - "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d" + "digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e" }, { "name": "old_key", "unicode": "1F5DD", - "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d" + "digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e" }, { "name": "keyboard", @@ -6327,132 +6327,132 @@ { "name": "keycap_ten", "unicode": "1F51F", - "digest": "7593aa7ffe7192a2e35c6ccec76522f6243777783c9152c7c03419835ea58c03" + "digest": "c7c9491021740d2c17edddb856f79579b0b943d8dc85a2f48dbaac84f35b8a40" }, { "name": "kimono", "unicode": "1F458", - "digest": "e92bea044fe013f1993c2229d86e9cca9d43f14aab00564ce6ff559bdc5ce93a" + "digest": "637182590e256c8fb74ce4c0565f5180c07f06e3bdebf30138ed3259b209c27f" }, { "name": "kiss", "unicode": "1F48B", - "digest": "c060eb09af2a0d0f77d307b995c15719b0e59c9162a490b8a553fac9b779c8f0" + "digest": "62f9b9ffcb01558cd5bb829344a1d1d399511663ff5235405c1f786c9416a94d" }, { "name": "kiss_mm", "unicode": "1F468-2764-1F48B-1F468", - "digest": "381364ad988ec07cc3708fd60f71838092224009088fff587069b4e8ab01ee63" + "digest": "6b0ae32ecb7ec0f0f43dc7a1350711185cce114c52752395f364ddbfb4f1fff4" }, { "name": "couplekiss_mm", "unicode": "1F468-2764-1F48B-1F468", - "digest": "381364ad988ec07cc3708fd60f71838092224009088fff587069b4e8ab01ee63" + "digest": "6b0ae32ecb7ec0f0f43dc7a1350711185cce114c52752395f364ddbfb4f1fff4" }, { "name": "kiss_ww", "unicode": "1F469-2764-1F48B-1F469", - "digest": "7705ca707b73f44c856ea324bdfe30ed05244c8d192d1111f6e1d62ab3f2f8a5" + "digest": "6de420cf752e706b1b7e9522b1b9be62eda069cb028c8fd587caf39f6a142e6a" }, { "name": "couplekiss_ww", "unicode": "1F469-2764-1F48B-1F469", - "digest": "7705ca707b73f44c856ea324bdfe30ed05244c8d192d1111f6e1d62ab3f2f8a5" + "digest": "6de420cf752e706b1b7e9522b1b9be62eda069cb028c8fd587caf39f6a142e6a" }, { "name": "kissing", "unicode": "1F617", - "digest": "3142617e8b9488689bd9efc67c0e4cc71a1870df8ffc308f949eedc5c3684051" + "digest": "b4a505f9e3d7fbd0ac60111f0e678cf425a5fd1abc65a3e9db59ae4abcfb8e85" }, { "name": "kissing_cat", "unicode": "1F63D", - "digest": "ed26cee8c438ba41365b55c48457cdad3e8d43bf90db3128ac5b277718b82ed3" + "digest": "a00431bf10601db4998e78433279167e52cbd36aed885399482529d5cdab8636" }, { "name": "kissing_closed_eyes", "unicode": "1F61A", - "digest": "22d3369d21b4c2cb4c0c2cab9551cd848dd4f9adecfa64977d3f1a80fc0c8b53" + "digest": "ae474db7daf80fe0b82ae1f2a11672cfcd9f9126e100f6e6d4b8a0d135dce39d" }, { "name": "kissing_heart", "unicode": "1F618", - "digest": "1f089b07447bdcc1baada6a2a9607d4ef4f2de9a6093fcab47a553a64b9acb76" + "digest": "bce372573bd3b347b555c1cd22087e03e650df73c8e0284ab668bf6633251632" }, { "name": "kissing_smiling_eyes", "unicode": "1F619", - "digest": "e37d282861669adfa3953b9af833acfab7d55e787621d4318d77de7e3529d5c5" + "digest": "f0f8636cb1a02b93cc72ce1b194b890fca823d91e35926b889be3ecfae79207f" }, { "name": "knife", "unicode": "1F52A", - "digest": "3fef068a6ada61630dc868e47d25e0e0550b44bc7cf530afe88ca63dc7ab2a39" + "digest": "e6189e4843c6e80875b4952fcddb0c858f7c6039b9214bbec6a261a1358425df" }, { "name": "koala", "unicode": "1F428", - "digest": "fe020ab9048f3c2a881474f8b1335db6bfaf37d115ff9b2d264f668d136122dd" + "digest": "c58f7e0abae42c2218a85efed0e04151df67187815bebca7f3db6f435e0dab4d" }, { "name": "koko", "unicode": "1F201", - "digest": "734a5cb296826a598e02be3f4ec22f318633ede2ce274914586256421e2df97b" + "digest": "5f45eb49bbf298e1fadedfe6cccc297850fcaaa4535e4cc911d48d979af55807" }, { "name": "label", "unicode": "1F3F7", - "digest": "9fe8195c3efab4d905b1cfcba0ae58cda12496030b0908de8076ff5e6777742e" + "digest": "9550ed50cedbc56eb1bd22a8a0809d837048a33d6e2e6e7d65c50d95fa05a85d" }, { "name": "large_blue_circle", "unicode": "1F535", - "digest": "ba4d0f84a9c2be9a65b25c8cfa78f30d4856d021b1853154dd1d2fd0c5bcfb6a" + "digest": "0df3fb3b09a6269459a3d9a1fe78db572190a948680844cfe758f53b6a482ff4" }, { "name": "large_blue_diamond", "unicode": "1F537", - "digest": "d5aa5e315126859c10c83507be6b9e11cbf423f7a27145de089468cff9b94a94" + "digest": "7f646b4e9de2788ed09e45f72cb512c269dda4989029b39bf9a2556659321651" }, { "name": "large_orange_diamond", "unicode": "1F536", - "digest": "108600badd0ef267842325c0fbf326cb3504306332c64f6f5694de2b54c9438a" + "digest": "80ae005ef9d79190c777f00de0993f8b3cb783f7051d76e971640c8c0827c338" }, { "name": "last_quarter_moon", "unicode": "1F317", - "digest": "68315b85bc1cb17bb82629bd1a6024a5124f3641b9878a732a8aad016c587546" + "digest": "3d1f276607c685d50f4b70d00a57750a57ad9ad84256dafd2dc8eef8c72300c3" }, { "name": "last_quarter_moon_with_face", "unicode": "1F31C", - "digest": "146a419109b7f662bf87cf9de299e47d025a8758c8970b7dabf3483e1956b559" + "digest": "d516825ba52dc67f5a01433fb9df2aa77742d38efde4225983ebc4882cbdfe5d" }, { "name": "laughing", "unicode": "1F606", - "digest": "f22d3be77f1daf058d04c3cbc1fd7f76b4dc069d2d300b45e63e768b08d269c5" + "digest": "e9ea994b39650740c4961f070ed492d86b3acf6e6a830a6dadaa3a6872e81b81" }, { "name": "satisfied", "unicode": "1F606", - "digest": "f22d3be77f1daf058d04c3cbc1fd7f76b4dc069d2d300b45e63e768b08d269c5" + "digest": "e9ea994b39650740c4961f070ed492d86b3acf6e6a830a6dadaa3a6872e81b81" }, { "name": "leaves", "unicode": "1F343", - "digest": "f65e2db125564eb04fc427a49fff175d6e2dae847bd12314d5e6a131610d5ccd" + "digest": "56a7a0e767a6f214d340d1b5989efd99fec52c6aa306ec5c3328e32234a1631b" }, { "name": "ledger", "unicode": "1F4D2", - "digest": "62df1772cec10c035ae0646e6cca4ba7d75b10636a520d091c5b42c2dc36b742" + "digest": "e58cb714353e96a2891a5d97910ff79660e637af909b81c49c919d3735db55b4" }, { "name": "left_luggage", "unicode": "1F6C5", - "digest": "62292758715115e55ab6239805b7f99b7b35bdfa8d40da07fe391424f1f083d8" + "digest": "6625077767a51163ea20cbc299f3c13fd5ccf1b5ce365ee702ef1fef6be3dadf" }, { "name": "left_receiver", @@ -6467,107 +6467,107 @@ { "name": "left_right_arrow", "unicode": "2194", - "digest": "28a6945972451b1f4dadec5c55310b8868ffd9f3b0a07803287bc4e07a56e7d4" + "digest": "560fcf1b794eb0d5269c73b3f8da57540cbb8a6f1a9af7a9d10b202252247e34" }, { "name": "leftwards_arrow_with_hook", "unicode": "21A9", - "digest": "d672afc39fd50f78d7370be243173fe76ba50292f0c401305b562898939a8b7f" + "digest": "504714c5559b1bd35aa469be83069a923d1a25f364cac08c10df0195749e7b26" }, { "name": "lemon", "unicode": "1F34B", - "digest": "e0e293a8b8c1b3c87534f5e05cf006671eb3c6d52b4d17d40f2e23bce215a8be" + "digest": "ccca25bb6ac47770dba3aaf75144128f9a73299061969b25a35ad1733dcde5fe" }, { "name": "leo", "unicode": "264C", - "digest": "b0fd4e5f4637de530b62323521c6edcd80312d67ea4043eedd959acb6763474a" + "digest": "f2ed930e279699962f189e0cac519cc29d339b3e82debfdc90c5b0935a7543bb" }, { "name": "leopard", "unicode": "1F406", - "digest": "ede891be8484a17e6277431c64ec1bfd6b742544a41947ebc85005bc2d558bb1" + "digest": "d4a8964b6f2cdf6ddf074d0f1f2f65783a1a43eb4af426905fad0e60899939c7" }, { "name": "level_slider", "unicode": "1F39A", - "digest": "49777cf160d9130d723e3bfef765c3de54033e6b059000fb0e22fb559b5ed190" + "digest": "48842324f54d971ebf548a89a82ac7f29e235702081c91b477b1a92d427290e7" }, { "name": "levitate", "unicode": "1F574", - "digest": "3e4e9a5ac6a8dbd7909c58a9d915f16f1a0fc59cc019714ae5935f18e4704044" + "digest": "453c24bf2544ed3ef3c710a7fabbd5fdace4dc65cddd377274d30d921523b50b" }, { "name": "man_in_business_suit_levitating", "unicode": "1F574", - "digest": "3e4e9a5ac6a8dbd7909c58a9d915f16f1a0fc59cc019714ae5935f18e4704044" + "digest": "453c24bf2544ed3ef3c710a7fabbd5fdace4dc65cddd377274d30d921523b50b" }, { "name": "libra", "unicode": "264E", - "digest": "ec8e2e7a735abc9f2bddb115fc0e09f4bdc7a164679e2b57d127f58eee1155c2" + "digest": "e330ba05bb449db074bc23d1514246ca5e249110f44ddb5804e5510eef6deac1" }, { "name": "lifter", "unicode": "1F3CB", - "digest": "f64db037fd21e5918e5de35d6a561ef4b44668e307ed351338de00fcf3e771e3" + "digest": "d6c94a32eb863d14a2a01add8ab95040f42a55d9e3f90641a0fe143d58127558" }, { "name": "weight_lifter", "unicode": "1F3CB", - "digest": "f64db037fd21e5918e5de35d6a561ef4b44668e307ed351338de00fcf3e771e3" + "digest": "d6c94a32eb863d14a2a01add8ab95040f42a55d9e3f90641a0fe143d58127558" }, { "name": "lifter_tone1", "unicode": "1F3CB-1F3FB", - "digest": "f9e0d161b12c4908ac3409b11c1a77ee38f33ba018f12416545876214bfb7c01" + "digest": "870acf2f554fce360b58d3e98b4c0558d7ec7775587776c0f9d40c6fb1bdacf9" }, { "name": "weight_lifter_tone1", "unicode": "1F3CB-1F3FB", - "digest": "f9e0d161b12c4908ac3409b11c1a77ee38f33ba018f12416545876214bfb7c01" + "digest": "870acf2f554fce360b58d3e98b4c0558d7ec7775587776c0f9d40c6fb1bdacf9" }, { "name": "lifter_tone2", "unicode": "1F3CB-1F3FC", - "digest": "631eb6ed5bd147dc6f1f8b94149abe44d62a0f78e7809e37a4bfe127c40ed98f" + "digest": "1a7ece8512e42241cdd95c85ccc509bc0ff9c7c6ffaff2be343c77f417a27576" }, { "name": "weight_lifter_tone2", "unicode": "1F3CB-1F3FC", - "digest": "631eb6ed5bd147dc6f1f8b94149abe44d62a0f78e7809e37a4bfe127c40ed98f" + "digest": "1a7ece8512e42241cdd95c85ccc509bc0ff9c7c6ffaff2be343c77f417a27576" }, { "name": "lifter_tone3", "unicode": "1F3CB-1F3FD", - "digest": "406b5707a47d9066f016acf0b64fa695e3505acc2453758a0428de21efd7eb6d" + "digest": "4bc633ee82a0fb59feba379fb6901a489e4ac849d758f9c8e7a1a0a26eaa380c" }, { "name": "weight_lifter_tone3", "unicode": "1F3CB-1F3FD", - "digest": "406b5707a47d9066f016acf0b64fa695e3505acc2453758a0428de21efd7eb6d" + "digest": "4bc633ee82a0fb59feba379fb6901a489e4ac849d758f9c8e7a1a0a26eaa380c" }, { "name": "lifter_tone4", "unicode": "1F3CB-1F3FE", - "digest": "d917164ed8c4bb1ffcc887ca256ec329e7fa1b9516eaf8c159f8b43fdb071ed6" + "digest": "d086fe5577b5ba80676f2224d886f8ebe4588314f429f12a34c52c971ed71b5c" }, { "name": "weight_lifter_tone4", "unicode": "1F3CB-1F3FE", - "digest": "d917164ed8c4bb1ffcc887ca256ec329e7fa1b9516eaf8c159f8b43fdb071ed6" + "digest": "d086fe5577b5ba80676f2224d886f8ebe4588314f429f12a34c52c971ed71b5c" }, { "name": "lifter_tone5", "unicode": "1F3CB-1F3FF", - "digest": "f79ea93e8a40b3c895b693bf49eb4ce6e7b3f4413595e5881ea44839fd7fe8e5" + "digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55" }, { "name": "weight_lifter_tone5", "unicode": "1F3CB-1F3FF", - "digest": "f79ea93e8a40b3c895b693bf49eb4ce6e7b3f4413595e5881ea44839fd7fe8e5" + "digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55" }, { "name": "light_check_mark", @@ -6582,27 +6582,27 @@ { "name": "light_rail", "unicode": "1F688", - "digest": "7c2be55456f1332e849ff6699a26dda2e1641c280f45c9ec88dedf6d9b7b7fe2" + "digest": "2f30b23a738371690b2f00d96ddb5ceb90a1442b5478754626a3dfa263ed2fc1" }, { "name": "link", "unicode": "1F517", - "digest": "cc4873f8a612dd721dddcd507a4430b4fb6c4abc15a8848456f0ffd97811b163" + "digest": "7bf567aabd1fc38b3d70422f9db3a13b50950cf6207e70962c9938827c196ccb" }, { "name": "lion_face", "unicode": "1F981", - "digest": "935b1076815f51fafcd860a395d0a03c536acfcea61ffcf542a377da046fa7d9" + "digest": "dd24f2668e973ec973e97dc111f59a2cc14e9b608387401191dd53368d28d4fa" }, { "name": "lion", "unicode": "1F981", - "digest": "935b1076815f51fafcd860a395d0a03c536acfcea61ffcf542a377da046fa7d9" + "digest": "dd24f2668e973ec973e97dc111f59a2cc14e9b608387401191dd53368d28d4fa" }, { "name": "lips", "unicode": "1F444", - "digest": "e3bc20f9e210fa1711271234fe61bf1c9ddf36dd6ffc5b832c6c3a769a1e59a8" + "digest": "8740d8086525c7a836d64625a6915cc1c59af69ba143456dbb59e0179276895e" }, { "name": "lips2", @@ -6612,477 +6612,477 @@ { "name": "lipstick", "unicode": "1F484", - "digest": "335b912e163020df3d6d9f0a19a55d6547bd59b471c5a3e374c2968e49911ccc" + "digest": "751dcb22706a796033b13a2ccb94304236ec13207ad4d011e02d230ae33ab5c1" }, { "name": "lock", "unicode": "1F512", - "digest": "c20eacfb8ccd9bb85919a837c0d4650ee608edb48c85bff46945f613e95d7038" + "digest": "043b4fc0b8c79d47a07d91308e628e1ac262aea6c1ec05e6b84bf7bcdf89dc83" }, { "name": "lock_with_ink_pen", "unicode": "1F50F", - "digest": "5cab25cea08e22d9c3f5de16de6d0ab658ca15cc93d7830f29b0f3e9348ec45f" + "digest": "7b5e959b26cf7296c7b230fc2be9feb9e38391c5001951a019d16b169a71aba9" }, { "name": "lollipop", "unicode": "1F36D", - "digest": "33d2334a00bf0e15869ccc75fadc36f27f89abf0525bb71f859aad9e1dc4ad66" + "digest": "17b6a0df47ec758a2f9c087b46a6902cee344d39407ef4c321e408505cbb72ca" }, { "name": "loop", "unicode": "27BF", - "digest": "fa1174ddc44e317d0796e07868c7ac8ac9c9274fbc8a6c3d0ec78d543c3c6bf0" + "digest": "9f20ecc34b3c871789ba7d0712aa31e7a74b6c1558ac8bea385bc40590056726" }, { "name": "loud_sound", "unicode": "1F50A", - "digest": "fb70229e13b690ffc1031d2e631123f8c908035a15218c297c1c4a3ff3624aa0" + "digest": "64b12db9ddd8adf74a9fc2bd83c7979ea865113347f7ce8666e9ccf5019e715f" }, { "name": "loudspeaker", "unicode": "1F4E2", - "digest": "e2d6cf9ec6412ee62f3128a1afd8c63ec74755c4833f01a4f99722407fe154d6" + "digest": "1e1f35d16dd2898ebaa6f2b2868203df6e09c8a70df069c92d6d1b5cb2ac0976" }, { "name": "love_hotel", "unicode": "1F3E9", - "digest": "184670ebc4045043a7b18d576da3255d216551da522a11cde7df34524e9c7d50" + "digest": "ff8966a50fd47a216855488eb09a367d231fea21f49e7e5325191d32fb494473" }, { "name": "love_letter", "unicode": "1F48C", - "digest": "9a4c52e2622fc7d364995ebc93ca530d972134621d117b72053a659dffc90ffc" + "digest": "037261c8ca4d72f7205e51664591696da2ae7ceb19f1c1c9f6123da5a5979d29" }, { "name": "low_brightness", "unicode": "1F505", - "digest": "c177b7fa9fdbef959cc47e7d16becd71117470b767a81ed6d15f80f464776c02" + "digest": "a065d00a416e297c168b0a675cafcf492fedf94865cb21801a1be5a3914593d4" }, { "name": "m", "unicode": "24C2", - "digest": "2eaf011e74d69613923dad424daaec4c13b592388dbcc5757b645bc058eedecb" + "digest": "54588ac2b7fcd53a96f17124e9de69b617613fcd5af9ad2930a094cb795bb9f4" }, { "name": "mag", "unicode": "1F50D", - "digest": "029427bd73d2c79fffc5194ded01f6011952ec0124b7634c6230e0afa7ad7c95" + "digest": "a6e31a2efa7d9427aaa30b45d9f4181ee55c44be08aea2df165a86e0e6d9eaa1" }, { "name": "mag_right", "unicode": "1F50E", - "digest": "f99de50bb59ec3bf1d4ccb8584ca09d4a7ceb5bf9f600ea8d3f84930efbf01b8" + "digest": "c7d8ceeb05db261e5eaab31dc4da432d0d5592a2ed71e526c5a542daa230bbaf" }, { "name": "mahjong", "unicode": "1F004", - "digest": "da5d1fa980c38e092d414516161ca26046aa65ace3261999ea750f72e676ac6e" + "digest": "755d69f988434ce1c17531a8b7ac92ead6f5607c2635a22f10e0ad70f09fc3e6" }, { "name": "mailbox", "unicode": "1F4EB", - "digest": "14217df8f39a95fc0a0c527f97db1ca8564764034e921614decc5be705629352" + "digest": "2069091be90a530a43ef29d5ec7688c351bf4d5b08d63a0d20d72b67d639ec62" }, { "name": "mailbox_closed", "unicode": "1F4EA", - "digest": "e0c7beb205ec548a66d8afc7f103b64c6c79c08417ab550f19c36cc6d1a62bc4" + "digest": "d88d65bfebb8216535fd055c69f319564b2cf0b0901820f8312f581864557ed4" }, { "name": "mailbox_with_mail", "unicode": "1F4EC", - "digest": "6d381c0c4181be628d9409df1d85f8a9438c21ef5b92d82ef8ae1ff0079236de" + "digest": "69e966b4659128991a70c6a2dd4d647551bedb91bdf5ce688958686bbec56381" }, { "name": "mailbox_with_no_mail", "unicode": "1F4ED", - "digest": "74843d5ea9e03b48323f2252bdd000585f549b7fffe1fe181a25c38b99b5e23d" + "digest": "9e92d8ee88f660ce56da61077c80ec26c5d8f54ebd2306c4cfa16f6c1b981f83" }, { "name": "man", "unicode": "1F468", - "digest": "0275935258b4c832c3fcb06531d3e6972e2c3d46bab2973004750a9f00bd4cb6" + "digest": "42b882d2c6aa095f1afcf901203838d95c1908bdc725519779186b9c33c728d7" }, { "name": "man_tone1", "unicode": "1F468-1F3FB", - "digest": "1f6603d040f4a025f49d384170dd16b8da169663fc3282af1dc8710d9c1a7adf" + "digest": "7053e265fa7d2594de54a6c5d06c21795b9a7dfb36a1c5594ca43c4c6cc56504" }, { "name": "man_tone2", "unicode": "1F468-1F3FC", - "digest": "d65bb03071b483946c69c61769d19b29a2af76fa7e43020e55f0bbc046492221" + "digest": "7ebc64de40d3ac60fb761be5cf94f53fa10b4f03fb66add46c90f5d98eaf71eb" }, { "name": "man_tone3", "unicode": "1F468-1F3FD", - "digest": "9af8ede7211b19a7dc0c60db083dd2bdc4897dda4d71e57feadf2e39d847f060" + "digest": "77ceef4d3740ed4751acb83dd45b6b754cf625c522c6757309cd4d61202d7149" }, { "name": "man_tone4", "unicode": "1F468-1F3FE", - "digest": "6555de60976aafeb024db78addb44eab2a412dd7277013f44d06757d03b6a252" + "digest": "41e6037c393f61cca61b9a81b27ed14a95d75fe380e3a00153c33a371a836ffd" }, { "name": "man_tone5", "unicode": "1F468-1F3FF", - "digest": "b58b97a28a6adc1777acc05194cd917c730f90e37441124c384ded12e9a7d2a4" + "digest": "a8cebfd39a5b9c79af7cc37f205e1135376056fee287af967c9f55d415572d99" }, { "name": "man_with_gua_pi_mao", "unicode": "1F472", - "digest": "88663173a6ccbebec5e24883c90d965447e022c6688773273110fe544d5b1607" + "digest": "3dae285e900c69986a48db0fa89d4f371a49f38608059cdae52be098030c5ac4" }, { "name": "man_with_gua_pi_mao_tone1", "unicode": "1F472-1F3FB", - "digest": "3c8bad3923a619f888e14544d357499a26a517e8fbe7a51027117b960c9eb842" + "digest": "35404d8e266920c78edd9e7143fb052b42f65242a5698494c4f4365e9183cc67" }, { "name": "man_with_gua_pi_mao_tone2", "unicode": "1F472-1F3FC", - "digest": "da125a3310fab19c9282497d53e2fc71ad07920ce60a0ef52dcdb31500023f09" + "digest": "82d4f968665a93c7543372c8a1eeb0f25d0ea6842d5e518bd91c226c6c3ab8c2" }, { "name": "man_with_gua_pi_mao_tone3", "unicode": "1F472-1F3FD", - "digest": "1d5842558847367966bf3ea473ff80fe744359bc5d969f4cc06cf2e452ed2fb6" + "digest": "f44159f0c672b9b833449382896180e799abf574f5b3c6cd9541caa992fa18ce" }, { "name": "man_with_gua_pi_mao_tone4", "unicode": "1F472-1F3FE", - "digest": "92be490f3ba602a43e2be8160d8bfd8a0691b2f81fe017b06df10f476a89ffab" + "digest": "c79060188f9461ca34eaa225b7682d8c410883609509fb731c992db69bfeeb50" }, { "name": "man_with_gua_pi_mao_tone5", "unicode": "1F472-1F3FF", - "digest": "669f6b31bc7a8bf50b169d0600f14e00addaeb24144a1bace8b94950372839b0" + "digest": "de9e4acdb10f7abddeeabc0b48d91139fc8b544a601c530db811f099991b0d38" }, { "name": "man_with_turban", "unicode": "1F473", - "digest": "87d30d35ba40ee39c2df8ce19d975ce34a9c54688bafeac7377d7d481e55f1a4" + "digest": "db72c944e93983f38d00e3e936ebb5b243c6069f1f1236d46f6a9f1beb8d6634" }, { "name": "man_with_turban_tone1", "unicode": "1F473-1F3FB", - "digest": "33b8b8154e0691e2ad66177dbf1e0101411fd8b3a16bf4e54c36d4a874f2a275" + "digest": "b6d7489c4cd151af09fff48b62c54c336303e14866e6ef38f94cd834b085d09e" }, { "name": "man_with_turban_tone2", "unicode": "1F473-1F3FC", - "digest": "1a6b83faa8d6e6a7d12a04898a6f22243287330a1faa081d2626b17dfb07174d" + "digest": "7854ef973c21847f452d7e78e5c460ea300e12b539ce92c69dabe8f1bf3a4382" }, { "name": "man_with_turban_tone3", "unicode": "1F473-1F3FD", - "digest": "5d43da5109e688ff8ca0675f33ebbaf930e206f1f01e3ee773f2844663fe572b" + "digest": "1dbd9bd78f5263cbadee7d0d5754c14cfbc914f7329e25fbd97d9f5b8ce0737e" }, { "name": "man_with_turban_tone4", "unicode": "1F473-1F3FE", - "digest": "bfaf7293c5ea75d0ecdc6fe5afe8f48e7b29b2e0df06ef974d3e1732f5db5dd4" + "digest": "4f4804da4a7c98ad4f9db3ae3eaf674c8977c638e73414e33ef1f65098e413a3" }, { "name": "man_with_turban_tone5", "unicode": "1F473-1F3FF", - "digest": "fba2404dd3d7eab5268519894cc0b386e1b17fdf14a04760c346014aa0e25acd" + "digest": "240282aa346ef9b1d0d475ea93a02597697f0f56f086305879b532b0b933210a" }, { "name": "mans_shoe", "unicode": "1F45E", - "digest": "45dc13ac44c922b4c4b8ecb2e1a870a78e09d53da86843431ab0e9ec96ebcd97" + "digest": "f53fe74abd9906cd3e2dd7e7bddbe1feb9f8f7be28b807fabe452f1f60ca1b84" }, { "name": "map", "unicode": "1F5FA", - "digest": "f56116d09996d6d08fb5cdfb46622b545253f2649008170fc2011a9713fa875b" + "digest": "84f496a062b5c3ae1e8013506175a69036038c8130891bcf780a69ce7fcbe4de" }, { "name": "world_map", "unicode": "1F5FA", - "digest": "f56116d09996d6d08fb5cdfb46622b545253f2649008170fc2011a9713fa875b" + "digest": "84f496a062b5c3ae1e8013506175a69036038c8130891bcf780a69ce7fcbe4de" }, { "name": "maple_leaf", "unicode": "1F341", - "digest": "40c5ee93396301911391cf6e70454b6fa8020fe5c85d3364136bcedb5d052cdb" + "digest": "72629a205e33f89337815ad7e51bb5c73947d1a9f98afe5072bdf4846827ae72" }, { "name": "mask", "unicode": "1F637", - "digest": "e0301cd27eb8c74c9772ff05b880215fc031ac1ae7f3177cd24ba0acb43b3834" + "digest": "1b58af9ae599308aabf41bbd38f599fa896bd9fe5df7a40be9f2dc7e0e230600" }, { "name": "massage", "unicode": "1F486", - "digest": "856d0fb1144ee91c58dfad74f9a2cababf6bae4b3ceba2a95c03ecd44ae3aa21" + "digest": "6ee48b4d8cec0bf31e11d7803ad9fc1f909457c8c00cb320b5671395af3c170c" }, { "name": "massage_tone1", "unicode": "1F486-1F3FB", - "digest": "fd53b06eb0967303c0914ebb79fd872900ec0f71b2852c7238517e192e5023e1" + "digest": "9da162c2f39628156b87db986a6ada59372a9e9a6b3f0488d21c9e65ec3309bb" }, { "name": "massage_tone2", "unicode": "1F486-1F3FC", - "digest": "7ef57359a339ae1ca4488f9a6195a352e74daf5b67d8e1ae1e91fe866921c40c" + "digest": "ac259188549b5b429b8c4929e1da2314859e8857ee49720551467aedfcc96567" }, { "name": "massage_tone3", "unicode": "1F486-1F3FD", - "digest": "e4fb643b6242bedb395e503ae337a88b2a255b5fda88b4aaa93396f948614a6e" + "digest": "cfd9c105b6debc10448f172afcb20d4192899f7ae5aa8af54c834153a5466364" }, { "name": "massage_tone4", "unicode": "1F486-1F3FE", - "digest": "94f007c2daf9455fa8d2b10cc7ccff7db9bc9daf835ef5c3699be091938db833" + "digest": "38ab715c621c58454f3cb09153a96380118cf082568554b6edc5f83fb62e9297" }, { "name": "massage_tone5", "unicode": "1F486-1F3FF", - "digest": "d18e800b728bf45b500f492062dc81312ca1ad7b1a0277a3d5bc150e4632ea1c" + "digest": "32480457734121b0c83e9be6d693ae379c95535f43f963c0c2f0f20434ee12c6" }, { "name": "meat_on_bone", "unicode": "1F356", - "digest": "674a2a58e174b7681eef3b6c5b39c098ed9374cc610d037166c0092ee5269a97" + "digest": "d71a8e0b118d5e6ca60690793ce9649afb78e707fcbd7be890a75564c94434fd" }, { "name": "medal", "unicode": "1F3C5", - "digest": "270d438b6e2155e944dc734ea3e4d02409e51f59db2db636398fbf96e5edb0e6" + "digest": "9600cbe57e08da090c60629bcafd2821c87322e738c2454f8e883ceb756e7391" }, { "name": "sports_medal", "unicode": "1F3C5", - "digest": "270d438b6e2155e944dc734ea3e4d02409e51f59db2db636398fbf96e5edb0e6" + "digest": "9600cbe57e08da090c60629bcafd2821c87322e738c2454f8e883ceb756e7391" }, { "name": "mega", "unicode": "1F4E3", - "digest": "540ab4fd5bab041a681749b85e6de598ebcbfc4fbf5c3cdbd9ca1e8256191733" + "digest": "4b1def6b5b051c5045514063f0ac006222ad81fbfe56d840e14bb950713e331b" }, { "name": "melon", "unicode": "1F348", - "digest": "39dd0ecb23e2d3da6cbb7309333fed5d7e2cb38c0afc526ade78520eca11b5f4" + "digest": "0cdd663e6f2129808856cdf0746e6571b62aac641f224adb553baf3bb63ba3bd" }, { "name": "menorah", "unicode": "1F54E", - "digest": "5f81bc2e5a34bf76481d2958fdb0b4e4540c599aa837a6453609a39023885d8c" + "digest": "49fca8c3bc00ea69653ee2f8d4e21e561856ba39716c13e9d107db3e805a2997" }, { "name": "mens", "unicode": "1F6B9", - "digest": "5ed56cff80e8ee7ed581f2a2e365915db5cb29df89e850e0add0b68db4b0c788" + "digest": "7d92292586ee12a5d1a557c37da4d14708dc3ce701cf32d3280dcc83d91e5df8" }, { "name": "metal", "unicode": "1F918", - "digest": "45e5fac0b9b019cf217dcfd1380cafb0d03063454612178278dac1ca5f8476a6" + "digest": "ffb750caf187f5d821c990108e2699ac3e216492bcff6ee543f4a7aa55b9fd29" }, { "name": "sign_of_the_horns", "unicode": "1F918", - "digest": "45e5fac0b9b019cf217dcfd1380cafb0d03063454612178278dac1ca5f8476a6" + "digest": "ffb750caf187f5d821c990108e2699ac3e216492bcff6ee543f4a7aa55b9fd29" }, { "name": "metal_tone1", "unicode": "1F918-1F3FB", - "digest": "9b3596fe7c063df838f0a43fb680ce10fb88e2b73c5c3324abfa357a224c17aa" + "digest": "5505f0b0340f9ba572db8897e40adf598cfa784686ad5ee360a7351bf44ddc1d" }, { "name": "sign_of_the_horns_tone1", "unicode": "1F918-1F3FB", - "digest": "9b3596fe7c063df838f0a43fb680ce10fb88e2b73c5c3324abfa357a224c17aa" + "digest": "5505f0b0340f9ba572db8897e40adf598cfa784686ad5ee360a7351bf44ddc1d" }, { "name": "metal_tone2", "unicode": "1F918-1F3FC", - "digest": "e15a4898a0efca4354ac48d6b01ff0618ce8b110b1246a4f5d78e19b54658be6" + "digest": "8f9eee3ad5fc7eeeb30118d16d27467b16fd87297e0ecf02656db77e701f5aeb" }, { "name": "sign_of_the_horns_tone2", "unicode": "1F918-1F3FC", - "digest": "e15a4898a0efca4354ac48d6b01ff0618ce8b110b1246a4f5d78e19b54658be6" + "digest": "8f9eee3ad5fc7eeeb30118d16d27467b16fd87297e0ecf02656db77e701f5aeb" }, { "name": "metal_tone3", "unicode": "1F918-1F3FD", - "digest": "c159e8179cb1907c246b432d87c5253b914fd7cebb6ac05292c4e38eff4815b0" + "digest": "8270a7ecf5eb11431a07ef04cc476c2651ac8aacb0d4768e5cb69355f8a5e84e" }, { "name": "sign_of_the_horns_tone3", "unicode": "1F918-1F3FD", - "digest": "c159e8179cb1907c246b432d87c5253b914fd7cebb6ac05292c4e38eff4815b0" + "digest": "8270a7ecf5eb11431a07ef04cc476c2651ac8aacb0d4768e5cb69355f8a5e84e" }, { "name": "metal_tone4", "unicode": "1F918-1F3FE", - "digest": "a8a43a88028c97074321e3da56df1045db41ede58bf286c21d7ae90f222f2011" + "digest": "f24f7b137dd6c7899dc0a8794204bbde7ad43ec1e63b419c90dd70a8b77871e8" }, { "name": "sign_of_the_horns_tone4", "unicode": "1F918-1F3FE", - "digest": "a8a43a88028c97074321e3da56df1045db41ede58bf286c21d7ae90f222f2011" + "digest": "f24f7b137dd6c7899dc0a8794204bbde7ad43ec1e63b419c90dd70a8b77871e8" }, { "name": "metal_tone5", "unicode": "1F918-1F3FF", - "digest": "e6611e826e867e2c73a8cadb138e4aa6365e3583dd229ff24b3e8f161904bf56" + "digest": "07b0726a632653b980df775f460cd3fe1ea8d4a7b0b46fe29e089b66579482d2" }, { "name": "sign_of_the_horns_tone5", "unicode": "1F918-1F3FF", - "digest": "e6611e826e867e2c73a8cadb138e4aa6365e3583dd229ff24b3e8f161904bf56" + "digest": "07b0726a632653b980df775f460cd3fe1ea8d4a7b0b46fe29e089b66579482d2" }, { "name": "metro", "unicode": "1F687", - "digest": "532378cf385f9a7fafe2f5c8203e675be6d38798871f4c8e2c50498a1529f956" + "digest": "b380247b61b5e2ca1b9b70fabff65907b2c3a5191a14b169ae094af94659b9b1" }, { "name": "microphone", "unicode": "1F3A4", - "digest": "46da2b94e4dc233f640249103f09ec915aaa812cce90afe68fedb6774a27ad4b" + "digest": "9ef4fc2e40d5391c4bb2d30f34f59662cff7cbb1b04341c9dac210d0e21b44ae" }, { "name": "microphone2", "unicode": "1F399", - "digest": "f9df32cd207808f67a895d3460a215d1ecc42e377907bcd64731c02b697d4f32" + "digest": "8a30464d51f7f101335778444c43270ac0679900f49463e6556682d9db1cb4dc" }, { "name": "studio_microphone", "unicode": "1F399", - "digest": "f9df32cd207808f67a895d3460a215d1ecc42e377907bcd64731c02b697d4f32" + "digest": "8a30464d51f7f101335778444c43270ac0679900f49463e6556682d9db1cb4dc" }, { "name": "microscope", "unicode": "1F52C", - "digest": "79918f5fe0a39f31f270a481f4c6e00ea49fc09d64b1ae78770971293c2b1ed8" + "digest": "4ca4322c6ba99b8c15acdb8b605f84f87398769e504b262b134c1f3868b2692f" }, { "name": "middle_finger", "unicode": "1F595", - "digest": "c6320b236a4a9593aeade511b52dd3114207e947458cb3b818c78737a505fdf6" + "digest": "0c3f1cc0ec7323f6d19508ad22fa90050845f7b5cc83f599ab2cacb89cf5dd0e" }, { "name": "reversed_hand_with_middle_finger_extended", "unicode": "1F595", - "digest": "c6320b236a4a9593aeade511b52dd3114207e947458cb3b818c78737a505fdf6" + "digest": "0c3f1cc0ec7323f6d19508ad22fa90050845f7b5cc83f599ab2cacb89cf5dd0e" }, { "name": "middle_finger_tone1", "unicode": "1F595-1F3FB", - "digest": "93c7aa994856185519d576cb779bdcff3a33f7077eef98e70968125f92f02448" + "digest": "4ebecf1058a3059aaa826eaad39c1a791120f115f65dde6d6ae32fc5561f60f7" }, { "name": "reversed_hand_with_middle_finger_extended_tone1", "unicode": "1F595-1F3FB", - "digest": "93c7aa994856185519d576cb779bdcff3a33f7077eef98e70968125f92f02448" + "digest": "4ebecf1058a3059aaa826eaad39c1a791120f115f65dde6d6ae32fc5561f60f7" }, { "name": "middle_finger_tone2", "unicode": "1F595-1F3FC", - "digest": "a0de802294717b80e08d9d30f5fd64eacb90b5b3b9d7a0c27d6226a22822597f" + "digest": "85ff506a08c38663c2dfa2e3a90584c02a36aa3dda33af47cdb49834bf9baf83" }, { "name": "reversed_hand_with_middle_finger_extended_tone2", "unicode": "1F595-1F3FC", - "digest": "a0de802294717b80e08d9d30f5fd64eacb90b5b3b9d7a0c27d6226a22822597f" + "digest": "85ff506a08c38663c2dfa2e3a90584c02a36aa3dda33af47cdb49834bf9baf83" }, { "name": "middle_finger_tone3", "unicode": "1F595-1F3FD", - "digest": "8bbbab07c838257416bbf8377904362c07019fca9d5abf9fd048ccf6370178da" + "digest": "cac697ff5207bf8a4e091912f3127f4e73c88ef69b5c6561d1d7b12ed60be8f1" }, { "name": "reversed_hand_with_middle_finger_extended_tone3", "unicode": "1F595-1F3FD", - "digest": "8bbbab07c838257416bbf8377904362c07019fca9d5abf9fd048ccf6370178da" + "digest": "cac697ff5207bf8a4e091912f3127f4e73c88ef69b5c6561d1d7b12ed60be8f1" }, { "name": "middle_finger_tone4", "unicode": "1F595-1F3FE", - "digest": "d9eed8db540fdb669c6ae5ef168b77659660589f5ddd9b66062274d335a3ef04" + "digest": "9324a5a4e3986b798ad8c61f31c18fb507ca7a4abfd6e9ae1408b80b185bf8c7" }, { "name": "reversed_hand_with_middle_finger_extended_tone4", "unicode": "1F595-1F3FE", - "digest": "d9eed8db540fdb669c6ae5ef168b77659660589f5ddd9b66062274d335a3ef04" + "digest": "9324a5a4e3986b798ad8c61f31c18fb507ca7a4abfd6e9ae1408b80b185bf8c7" }, { "name": "middle_finger_tone5", "unicode": "1F595-1F3FF", - "digest": "0519c3298040e57db202294476df239edb9b23b44848bab296bc45eda7cf8664" + "digest": "078f917cd4d8be08a880724e9400449980d92740ccbee4a57f5046a9cf7f6575" }, { "name": "reversed_hand_with_middle_finger_extended_tone5", "unicode": "1F595-1F3FF", - "digest": "0519c3298040e57db202294476df239edb9b23b44848bab296bc45eda7cf8664" + "digest": "078f917cd4d8be08a880724e9400449980d92740ccbee4a57f5046a9cf7f6575" }, { "name": "military_medal", "unicode": "1F396", - "digest": "bd1da0004768f404c6bb4db85d4b748f766a77ab3edb74e709d0c0064509a043" + "digest": "5da18351dc14b66cfc070148c83b7c8e67e6b1e3f515ae501133c38ee5c28d3d" }, { "name": "milky_way", "unicode": "1F30C", - "digest": "598b4e641c1081bb03ce38a29f9711fc8616373216a833e4daa14fbe97a358f5" + "digest": "17405ff31d94b13a1fb0adcda204b8adb95ca340bc3980d9ad9f42ba1e366e7d" }, { "name": "minibus", "unicode": "1F690", - "digest": "3d15791ca96349c3abb5bd5d1014b6b33b984db19609f56f5fd1e8d2fc551809" + "digest": "08ccb4b1bf397b7c9aed901e2b5dcdd6cb8ca5c5487ef26775bb3120f7b92524" }, { "name": "minidisc", "unicode": "1F4BD", - "digest": "83c4bfda4e0a80785fa1c3f2bbf3c15aca2bda8ea3727ce78bc4236e1e377a36" + "digest": "bebf82c0b91ef66321e7ae7a0abf322e59b2f7d8e6fbf9a94243210c00229c59" }, { "name": "mobile_phone_off", "unicode": "1F4F4", - "digest": "cfe6dfd766b9e0b4768df25d6e943c9abc0e910ff5e5c7a8a0f425c786bbab8d" + "digest": "6f9d8d6a32fc998f5d8144a5ff7e2ad00de37ad464cd97285e7c72efb09a1feb" }, { "name": "money_mouth", "unicode": "1F911", - "digest": "3ac2f9b5409e1426eef6966938ca04cf78aeffefd43f44b6c86af4af7836e22f" + "digest": "5a43973dadf48a89201b1816fea9972c5cfe501a26fe457b6f7eee0a6362018e" }, { "name": "money_mouth_face", "unicode": "1F911", - "digest": "3ac2f9b5409e1426eef6966938ca04cf78aeffefd43f44b6c86af4af7836e22f" + "digest": "5a43973dadf48a89201b1816fea9972c5cfe501a26fe457b6f7eee0a6362018e" }, { "name": "money_with_wings", "unicode": "1F4B8", - "digest": "f7f1fa502d2f6804169869aeb5ca7f0ea64bc2d6a0204f08875d65da4f8cb332" + "digest": "15fcf0595021374ba091ca00efdb4167770da4d421eab930964108545f4edab9" }, { "name": "moneybag", "unicode": "1F4B0", - "digest": "442db49cda27360d2eb781489c9879730a6094c3267bb0a0a8687d84f8fed078" + "digest": "02d708e2f603b0df6f6c169b5c49b3452e1c02e7d72e96f228b73d0b0a20bff4" }, { "name": "monkey", "unicode": "1F412", - "digest": "3141c971aacbadaba21f970a515e192740212be2a49fa1f5eb0fc4dc576e209f" + "digest": "3588a544d6d9e9995b45d60327a1a42002fa1faa4d48224b140facd249af1c67" }, { "name": "monkey_face", "unicode": "1F435", - "digest": "e2397431d2befe44bf5298fa81d865d80722bf954113bceacc2aa98b84d856e2" + "digest": "9e263ef5ca42bb76d1b1d1e3cbf020bcf05023a6e9f91301d30c9eb406363a2a" }, { "name": "monorail", "unicode": "1F69D", - "digest": "b546153200d6fbe8d65b1b34f62ff4a19b1b6a159eb1b536c5c2ecb56dab0ec9" + "digest": "2c9f185babcb4001fcef2b8dfc4a32126729843084d0076c3e3ccdc845ab23ad" }, { "name": "mood_bubble", @@ -7112,102 +7112,102 @@ { "name": "mortar_board", "unicode": "1F393", - "digest": "cb59edb08f75c374088b65284e4d0f77b9bc9573de3e6a5127f865431011e54c" + "digest": "d7fbe41d4b340d3564e484aec46a22c9613521414b2ba6eece2180db4d23e410" }, { "name": "mosque", "unicode": "1F54C", - "digest": "a08ddb74342dea8f79063db6f98ba03eb08fe99481de8ce9123827ca7f17c7f3" + "digest": "5f3d3de7feac953a70a318113531c2857d760a516c3d8d6f42d2a3b3b67ed196" }, { "name": "motorboat", "unicode": "1F6E5", - "digest": "9dbea67bbe2e95dcc68c049a58f87390a44350b32308342615d75214af3d1cef" + "digest": "81c156643528c5a94a12d6d478e52a019f5a4e3eb58ee365cdd9d2361a7fdb01" }, { "name": "motorcycle", "unicode": "1F3CD", - "digest": "8429fb6dfeb873abdffcc179c32d4f23e91c9e6b27b06cd204fd2e83cc11189e" + "digest": "354aa8157732184ad50eff9330f7a8915309dc9b7893cc308226adb429311a62" }, { "name": "racing_motorcycle", "unicode": "1F3CD", - "digest": "8429fb6dfeb873abdffcc179c32d4f23e91c9e6b27b06cd204fd2e83cc11189e" + "digest": "354aa8157732184ad50eff9330f7a8915309dc9b7893cc308226adb429311a62" }, { "name": "motorway", "unicode": "1F6E3", - "digest": "fc05a36c917637c135b0a60db8afcd58cee2b335070fe3888697f8026c9d11a5" + "digest": "148c3c13c7c4565453d16e504e0d4b8d007e4f2cad1ab56b1b51fefe39162d17" }, { "name": "mount_fuji", "unicode": "1F5FB", - "digest": "22bfffef033637b3c9b2fe7e539c74a659d2a49e594d2b33be894da00654d059" + "digest": "f8093b9dba62b22c6c88f137be88b2fd3971c560714db15ec053cf697a3820bc" }, { "name": "mountain", "unicode": "26F0", - "digest": "486cf4e9d5f3913d138fdb7878fe869b39caa3fca53876365957a89dc8f7edb8" + "digest": "07423804ad79da68f140948d29df193f5d5343b7b2c23758c086697c4d3a50da" }, { "name": "mountain_bicyclist", "unicode": "1F6B5", - "digest": "b547b96951b6837df8ae3be1e846f15e7e2ac06d976e1fe7f1442dcc5d3a0942" + "digest": "91084b6c887cb7e34f3d7ec30656ecb82c36cc987f53a6c83ccb4c6f7950f96a" }, { "name": "mountain_bicyclist_tone1", "unicode": "1F6B5-1F3FB", - "digest": "68ce0d55163c7b89ee1d87b752ece127bb25ca9deb3421b31df549a00ac5f69d" + "digest": "5d57fcfad61bca26c3e8965eb57602a1993a3117ebdda0f24569af730310ab6e" }, { "name": "mountain_bicyclist_tone2", "unicode": "1F6B5-1F3FC", - "digest": "5bfa82180bfb8bc4444cf301688aff02884895574a7ba66b398aaf20bde0f101" + "digest": "c0da7fb85d99aa01a665f64063cd7e2d994f8a16d3f6fbf52df5d471e771a98a" }, { "name": "mountain_bicyclist_tone3", "unicode": "1F6B5-1F3FD", - "digest": "33cb64a792123b81a05080465a0ea1035a2cdfdab01c71f5f725a5f92251c3e8" + "digest": "b099e7ee84eae44ebc99023fa06bdf37ffa0d69767c7c0163a89f7ced2a26765" }, { "name": "mountain_bicyclist_tone4", "unicode": "1F6B5-1F3FE", - "digest": "9c3fa4e65dcb0ad69b963292e77c7a75853ae3c1d18a90670f81ffb65b5d020c" + "digest": "9d09f7b3899ea44e736f237a161ef8d5170dccfa162a872c59532ceaf65ee007" }, { "name": "mountain_bicyclist_tone5", "unicode": "1F6B5-1F3FF", - "digest": "871de9e3fddb49b305e5f91000143878b0288c107a125c4e60acf2b6cf8b7f3f" + "digest": "71e374981d955056748a60c6d1820b45e9688a156b55318b4ea54a3a67ca801c" }, { "name": "mountain_cableway", "unicode": "1F6A0", - "digest": "f248ed5bf864f4a81e365b30d2825d2e6fc15a200c4ccf69e9f797341529f955" + "digest": "e261c3292758b1c0063c5a0d0c7f5c9803306d2265e08677027e1210506ced94" }, { "name": "mountain_railway", "unicode": "1F69E", - "digest": "7dd08745ab56c95c3dfcebcca517ff231cef61b670cedf9d7c53f3244c34e30b" + "digest": "b0987f8f391b3cbc7a56b9b8945ebfca240e01d12f8fd163877ebebe51d6b277" }, { "name": "mountain_snow", "unicode": "1F3D4", - "digest": "9939aade3d4d972ba3af16fcc6cc2454978f5426e4c92838734a44db065ce0ff" + "digest": "49aac2b851aa6f2bd2ca641efa8060f93e89395357f49d211658d46f5a2b0189" }, { "name": "snow_capped_mountain", "unicode": "1F3D4", - "digest": "9939aade3d4d972ba3af16fcc6cc2454978f5426e4c92838734a44db065ce0ff" + "digest": "49aac2b851aa6f2bd2ca641efa8060f93e89395357f49d211658d46f5a2b0189" }, { "name": "mouse", "unicode": "1F42D", - "digest": "fb20b3a82f407a6316bbbac68d58018c3d5b93a9a6ae968f44ace18d1c5698d9" + "digest": "007dd108507b45224f7a1fad3c1de6ecc75f38d71fc142744611eb13555f5eff" }, { "name": "mouse2", "unicode": "1F401", - "digest": "87be4099523ec32440e6d091f1193a8ed90730b9fbecaafed4912585bfe7818c" + "digest": "f3ed37b639b7c16aae49502bd423f9fdeabaf15bc6f0f74063954b189e176b5d" }, { "name": "mouse_one", @@ -7222,132 +7222,132 @@ { "name": "mouse_three_button", "unicode": "1F5B1", - "digest": "6a5629fee01145211cc8f4e8f59c5f1e61affed38c650502213d76c7d8861b01" + "digest": "3724341ac5ad0d01027ef1575db64f1db7619f590ca6ada960d1f2c18dc7fc6a" }, { "name": "three_button_mouse", "unicode": "1F5B1", - "digest": "6a5629fee01145211cc8f4e8f59c5f1e61affed38c650502213d76c7d8861b01" + "digest": "3724341ac5ad0d01027ef1575db64f1db7619f590ca6ada960d1f2c18dc7fc6a" }, { "name": "movie_camera", "unicode": "1F3A5", - "digest": "d6633b89a637b64d617c3032eed74bb82d3fa732dd9975486b2b5841b473808a" + "digest": "f7e285eda35b4431c07951e071643ddc34147cd76640e0d516fbfd11208346e9" }, { "name": "moyai", "unicode": "1F5FF", - "digest": "bf948c26cd98e2f5e48da363f2924a9d7c217232115a00cec372d0d5293402a8" + "digest": "2c1d0662c95928936e6b9ab5a40c6110ff1cea5339f2803c7b63aabc76115afb" }, { "name": "muscle", "unicode": "1F4AA", - "digest": "c85147efb786bdea3e7d53e2edf6b827280cd9fa881661a6102a614bf5b3579f" + "digest": "e4ce52757b2b7982e2516e0e8bf2e2253617cc9f3e6178f1887c61c9039461ba" }, { "name": "muscle_tone1", "unicode": "1F4AA-1F3FB", - "digest": "38d071df2b25031b61f3605b03c34d2e5d3e35d29f3c4aada14be37e19750eb8" + "digest": "4a2fa226a05bb847b62cdd163eb6c2d514d3c2330a727991cf550c0d32b0e818" }, { "name": "muscle_tone2", "unicode": "1F4AA-1F3FC", - "digest": "dcf11b76c8ffb58dc7e4f9ecd32a4c291d9772d51df2853d41081e041e7e0876" + "digest": "a8d5ecce335c782ca5f5e55763c06cfefa1c16c24cd6602237cf125d4ff95e47" }, { "name": "muscle_tone3", "unicode": "1F4AA-1F3FD", - "digest": "a3d5f8f2dbfc28f9713ee657428ea3292c47d0b22f11a51c13594be22b0f5204" + "digest": "070354b443faec3969663b770545fc4cf5ec75148557b2b9d6fc82ab22b43bd1" }, { "name": "muscle_tone4", "unicode": "1F4AA-1F3FE", - "digest": "eb220fc19be58d16cacc6b721e1011078b03256c0245756f251a4c2bcf50586c" + "digest": "8eafcdb6a607aeafa673c257df0d2a1b20f00fc0868d811babcbe784490a0dd3" }, { "name": "muscle_tone5", "unicode": "1F4AA-1F3FF", - "digest": "4e18708cbd61eaad288f913c86ad2d45108dd4484bc35879c5dcdd075eeb09fd" + "digest": "85a1e2b5c89907694240e9c5b9d876a741fa7ba38918c5718273e289cbc40efe" }, { "name": "mushroom", "unicode": "1F344", - "digest": "a2b252cd759244409d9a8066470059948e2c50b8cc86b59821c1c86b5190f640" + "digest": "aaca8cf7c5cfa4487b5fef365a231f98be4bbf041197fc022161bcc8ce6f57c8" }, { "name": "musical_keyboard", "unicode": "1F3B9", - "digest": "dcb3e84d27bfe373e5ea7ede457908de52002f0fd6105e9f3f5525c54d2a43dd" + "digest": "fb0a726728900377d76d94aac9c94dce29107e8e3f1dcb0599d95bce7169b492" }, { "name": "musical_note", "unicode": "1F3B5", - "digest": "76a0f598f8e251a9dab44f2e14f2b7a6fb0c0c351e0f37862c8c99d380f1c261" + "digest": "41288e79b4070bb980281d0e0d1c14d8b144b4aedb2eaadb9f2bebcb4ef892b4" }, { "name": "musical_score", "unicode": "1F3BC", - "digest": "a132c6b35236005b45c830a42fa97b454d3061c14991c6320f34807f10ba6a4a" + "digest": "f0f91b9fa4a2bff7a5a1a11afa6f31cfe7e5fa8b0d6f3cce904b781a28ed0277" }, { "name": "mute", "unicode": "1F507", - "digest": "73a99b7f9e00f92cab78cd304dee4e893a112c3a6f2285c13d44916ea547458e" + "digest": "def277da49d744b55c7cdde269a15aa05315898f615e721ee7e9205d7b8030d6" }, { "name": "nail_care", "unicode": "1F485", - "digest": "62f721d3610d1647dba4b3f53cd4f2bc4180dae298314c2cca2a6a8ab1664525" + "digest": "48b33b1dbbd25b4f34ab2ca07bb99ddaaaa741990142c5623310f76b78c076f9" }, { "name": "nail_care_tone1", "unicode": "1F485-1F3FB", - "digest": "11b82ed2e6b6619c9b74702fdacfb0ddc91310191c8b89f355c7c69a72673f8f" + "digest": "a9ac92a34f407e7dd7c71377e6275e66657f7f42e4b911c540d1a66a02d92ac5" }, { "name": "nail_care_tone2", "unicode": "1F485-1F3FC", - "digest": "5195c76bccb9149d9080347d785dae2cce947bada5b198fae8c23e42f5553154" + "digest": "f295ec85980aaa75818fad619c3d25042146ecbbf361db9e9bb96e7bc202bc73" }, { "name": "nail_care_tone3", "unicode": "1F485-1F3FD", - "digest": "50eab0bf825c5e00db07a3f5ad26b1bb221f54efb5c55549f392b2f5aec09e5a" + "digest": "02ec373052a250977298bae85262177910126cc10de9480f1afa328ac2f65a95" }, { "name": "nail_care_tone4", "unicode": "1F485-1F3FE", - "digest": "d05a9ccfad02191c89e4cbd00aa48fdaf908c0de6681f4a587d500be448e528f" + "digest": "f3d95390ab59caedfda66122bbd0acf3aabedc142fc48352d68900766a7e6f5c" }, { "name": "nail_care_tone5", "unicode": "1F485-1F3FF", - "digest": "62466354dcf6717a8b9e942ca2c5ad15a26aa815c213e3b01faba9a2e302ecdd" + "digest": "009423c97f2aafd24fb8c7c485c58b30bbf9ae6797cc14b80d472b207327b518" }, { "name": "name_badge", "unicode": "1F4DB", - "digest": "0a1cb0f7d489d3356a4d3e01f9faf78449d82d8ec4595c8639a55c3606c97c40" + "digest": "f9f6a4895ff0be8fb2ccc7ad195b94e9650f742f66ead999e90724cfb77af628" }, { "name": "necktie", "unicode": "1F454", - "digest": "029e1140391ef559a9316021c2db94f05653751fdf9d8f366446467a70fee6df" + "digest": "01bb18dc8bfe787daa9613b5d09988cd5a065449ef906099ce3cb308c8a7da68" }, { "name": "negative_squared_cross_mark", "unicode": "274E", - "digest": "0ba0e705fdeac99edd712db31a8846320b9d2cf53c9cb4d4bcfd22ba4e1488ea" + "digest": "1cdaf4abc9adafa089c91c2e33a24e9e647aea0f857e767941a899a16ec53b74" }, { "name": "nerd", "unicode": "1F913", - "digest": "94efd551700aae8909b8dd7a78a54a33e070d24b2e0a10534353645084614e98" + "digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66" }, { "name": "nerd_face", "unicode": "1F913", - "digest": "94efd551700aae8909b8dd7a78a54a33e070d24b2e0a10534353645084614e98" + "digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66" }, { "name": "network", @@ -7362,157 +7362,157 @@ { "name": "neutral_face", "unicode": "1F610", - "digest": "df01da8501e1f588049c8ed66e504e9abcce83f74ce5790f4d3dc547408f77ee" + "digest": "7449430a60619956573e9dc80834045296f2b99853737b6c7794c785ff53d64e" }, { "name": "new", "unicode": "1F195", - "digest": "24e80abd29750d8b297335cdd4751b6250bb820560cf0392a6cc8783d34db63a" + "digest": "e20bc3e9f40726afd0cfb7268d02f1e1a07343364fd08b252d59f38de067bf06" }, { "name": "new_moon", "unicode": "1F311", - "digest": "2d697e431eac53d6e1ea367b5da03c15fc535cd7e8c214f801fe595b768a8e11" + "digest": "dbfc5dcae34b45f15ff767e297cba3a12cb83f3b542db8cfc8dbd9669e0df46c" }, { "name": "new_moon_with_face", "unicode": "1F31A", - "digest": "ea469a4668ded071f35e5898ae229fdb5d02b0730ce233169b83e22f81292baa" + "digest": "c66d347d2222ac8d77d323a07699aff6b168328648db4f885b1ed0e2831fd59b" }, { "name": "newspaper", "unicode": "1F4F0", - "digest": "0aaf6747a43fb60cd15e6e64ca0eccaade331b376c6fe6712fd5e8294e9868cc" + "digest": "c05e986d9cdac11afa30c6a21a72572ddf50fc64e87ae0c4e0ad57ffe70acc5c" }, { "name": "newspaper2", "unicode": "1F5DE", - "digest": "0ca6b5850091f23295c970815a8e64a52e3c3dae492029ecb1e0726c2693f9bf" + "digest": "63db7bcf51effc73e5124392740736383774a4bcfbc1156cf55599504760883d" }, { "name": "rolled_up_newspaper", "unicode": "1F5DE", - "digest": "0ca6b5850091f23295c970815a8e64a52e3c3dae492029ecb1e0726c2693f9bf" + "digest": "63db7bcf51effc73e5124392740736383774a4bcfbc1156cf55599504760883d" }, { "name": "ng", "unicode": "1F196", - "digest": "4994c9b795033ed788e98c4af571a1dffe28c0a1479e3b42dcae21bb08381b5f" + "digest": "34d5a11c70f48ea719e602908534f446b192622e775d4160f0e1ec52c342a35c" }, { "name": "night_with_stars", "unicode": "1F303", - "digest": "56bb4a59a897c1836ee1a49cc99f468891b790b0f8bce203c201c13bb7b8ae9a" + "digest": "39d9c079be80ee6ce1667531be528a2aa7f8bd46c7b6c2a6ee279d9a207c84a4" }, { "name": "nine", "unicode": "0039-20E3", - "digest": "7e3644a98cb6417a351530c9ce6b368e637a22c847a8c04133897dc1c5d7419f" + "digest": "8bb40750eda8506ef877c9a3b8e2039d26f20eef345742f635740574a7e8daa6" }, { "name": "no_bell", "unicode": "1F515", - "digest": "f4fb42836132000101624fecef8b9358736a0fc76beae460e6986aaa479204fd" + "digest": "6542a9a5656c79c153f8c37f12d48f677c89b02ed0989ae37fa5e51ce6895422" }, { "name": "no_bicycles", "unicode": "1F6B3", - "digest": "b3c258bea7d6988640e3348598c03c97632ca00a11cbf0352995b801ff4a296b" + "digest": "af71c183545da2ff4c05609f9d572edb64b63ccba7c6a4b208d271558aa92b0a" }, { "name": "no_entry", "unicode": "26D4", - "digest": "ac807d54092efdc3aea417790a7d0c50b59800c9ea49b37f1aec6d2e453c5f6d" + "digest": "dc0bac1ed9ab8e9af143f0fce5043fe68f7f46bd80856cdec95d20c3999b637d" }, { "name": "no_entry_sign", "unicode": "1F6AB", - "digest": "5a17d677ec1c7595a7970a1cbe0d20909341b30d3ab31471ced590f51fff1ff7" + "digest": "2c1fceef23b62effca68e0e087b8f020125d25b98d61492b1540055d1914fdc3" }, { "name": "no_good", "unicode": "1F645", - "digest": "8ce921e5e13e1203cf43fdc3e7c5ec1fb2a1f9ff79f21539cff542c80af2e5fe" + "digest": "6eb970b104389be5d18657d7c04be5149958c26855c52ea68574af852c5f85c4" }, { "name": "no_good_tone1", "unicode": "1F645-1F3FB", - "digest": "aab4d354aaac06e8348eb354487c6381e475b44651cb2716660904a36c47a1b6" + "digest": "c20a24a1e536240b4dcf90ecb530796de621d7ba1fb9e3fa0f849d048c509c03" }, { "name": "no_good_tone2", "unicode": "1F645-1F3FC", - "digest": "8fb66b1a7b8f72062794281294515d47471a8c59de300b99d656c3412ca19d64" + "digest": "f31a4628c1f2e6a39288fda8eb19c9ec89983e3726e17a09384d9ecc13ef0b4c" }, { "name": "no_good_tone3", "unicode": "1F645-1F3FD", - "digest": "aeecf73fb9dca24b4002db2802fc9b5a483644c49f834c19f143d4e56ec46c1a" + "digest": "959dec1bfdaf37b20a86ab2bcbdbacd3179c87b163042377d966eab47564c0fb" }, { "name": "no_good_tone4", "unicode": "1F645-1F3FE", - "digest": "fadeb23307d5ccabbf08c848cf81c66c05b152aa32b85f86061caf14760f8eb9" + "digest": "efd931f0080adf2e04129c83a8b24fda0ae7a9fa7c4b463686c0b99023620db8" }, { "name": "no_good_tone5", "unicode": "1F645-1F3FF", - "digest": "cf26d5d6463d0febf4e1f08e343308742ffe0811cfc30c459b87d4cc812f5d04" + "digest": "f35df2b26af9baef47c1f8cc97a1b28a58aa7fcb2a13fdac7b2d9189f1e40105" }, { "name": "no_mobile_phones", "unicode": "1F4F5", - "digest": "3b4ead88beca33f1e303d0a45268849be7aaaff7830b761732c7a5afc5a2de3a" + "digest": "a472decd6ac7f9777961c09e00458746b2c04965585e3bee4556be3968e55bcd" }, { "name": "no_mouth", "unicode": "1F636", - "digest": "2af81a3e07a8b7827a1e58f6f5036ccff2f6e7b0027a4f934c9fa34c6a780963" + "digest": "72dda8b1e3ad4b05d9b095f9bd05e95d7ba013906c68914976a4554e8edf5866" }, { "name": "no_pedestrians", "unicode": "1F6B7", - "digest": "9f9ed90bb8f9964fa8cb0048fc092ecc0612a1994c98d19ef0b5a58607888476" + "digest": "062b4a71b338fe09775e465bfba8ac04efbb3640330e8cabe88f3af62b0f4225" }, { "name": "no_smoking", "unicode": "1F6AD", - "digest": "fb90290ff5c917b7307a97c8ba793d20c61042525cf2f7bfd4cd2a7878aeefa5" + "digest": "ae2ebb331f79f6074091c0ee9cd69fce16d5e12a131d18973fc05520097e14ee" }, { "name": "non-potable_water", "unicode": "1F6B1", - "digest": "c4ddca2ab1a97260e9b2c2aa33fb03455c0e8174541c3a9416fc44143a3ee567" + "digest": "32eba0a99b498133c2e4450036f768d3dccaaf5b50adc9ad988757adc777a6a1" }, { "name": "nose", "unicode": "1F443", - "digest": "308e28b15b7f734f6f184ae367789d7cf258656b24861cf8d5935127d810aa3f" + "digest": "9f800e24658ea3cebe1144d5d808cf13a88261f1a7f1f81a10d03b3d9d00e541" }, { "name": "nose_tone1", "unicode": "1F443-1F3FB", - "digest": "392d24b38ac3edc2d7b83945a60bbe9115a6a97658d8af35281a7cbef79449e8" + "digest": "a2d0af22284b1d264eb780943b8360f463996a5c9c9584b8473edf8d442d9173" }, { "name": "nose_tone2", "unicode": "1F443-1F3FC", - "digest": "409a790339c405770492e49fdb0b5ba34087c27e2f9018ecd845ab078e61476a" + "digest": "244dcaa8540024cf521f29f36bd48f933bf82f4833e35e6fa0abf113022038f3" }, { "name": "nose_tone3", "unicode": "1F443-1F3FD", - "digest": "92b52b479a935f31e460257d809c531edad1a6bb4583ad18233b12c4e45202fe" + "digest": "c935b64866f0d49da52035aa09f36ff56d238eb7f5b92205386451056e8ea74f" }, { "name": "nose_tone4", "unicode": "1F443-1F3FE", - "digest": "78ad2e857792e86cded6ba5620f634f7d1f79a92c82c266e48fab9bd73df3688" + "digest": "a87e95fd9319c49e66b6dea0e57319d0ed9921b8d94df037767bf3d5dc7c94f3" }, { "name": "nose_tone5", "unicode": "1F443-1F3FF", - "digest": "dbef6813c1965d3e93f70f33f118f9950130af21c622cea97ea215a36b4fa73f" + "digest": "1e0f9842e0f8ad5805eabd3f35a6038b7a2e49d566a1f5c17271f9cdf467ca60" }, { "name": "note", @@ -7537,12 +7537,12 @@ { "name": "notebook", "unicode": "1F4D3", - "digest": "64bd4a3e7ca7b22fc704c7b7bd4d13540c16bc69b9d8dd76e69e6ad573ab3823" + "digest": "fc679d3728f86073d1607a926885dd8b0261132f5c4a0322f1e46ea9f95c8cb8" }, { "name": "notebook_with_decorative_cover", "unicode": "1F4D4", - "digest": "4b45f28fbde1be5c214a6bc2413abc91db02bccd86f74c21b7f4a4da8b75a46f" + "digest": "d822eda4b49cbfa399b36f134c1a0b8dcfdd27ed89f12c50bc18f6f0a9aa56ef" }, { "name": "notepad", @@ -7567,297 +7567,297 @@ { "name": "notepad_spiral", "unicode": "1F5D2", - "digest": "c181b6c1cc6063ec1848e46cbbf1d8b890c53b59cdc5218311ce06889570e727" + "digest": "c6a8e16aa62474cef13e5659fddb4afc57e3f79635e32e6020edbee2b5b50f18" }, { "name": "spiral_note_pad", "unicode": "1F5D2", - "digest": "c181b6c1cc6063ec1848e46cbbf1d8b890c53b59cdc5218311ce06889570e727" + "digest": "c6a8e16aa62474cef13e5659fddb4afc57e3f79635e32e6020edbee2b5b50f18" }, { "name": "notes", "unicode": "1F3B6", - "digest": "bf3868386e17eac40ac7fbabea027042027ff061daafe406c869cdd8ce94641d" + "digest": "98467e0adc134d45676ef1c6c459e5853a9db50c8a6e91b6aec7d449aa737f48" }, { "name": "nut_and_bolt", "unicode": "1F529", - "digest": "fdb9d7408202fad7a52ff21608042c08c3b0beb195999fff233df36a29dc9e96" + "digest": "a77bd72f29a7302195dcec240174b15586de79e3204258e3fb401a6ea90563b3" }, { "name": "o", "unicode": "2B55", - "digest": "8e119dba4130bd33b3ee5c862fb4fa5a691173911ffee51cb9359fee3398e330" + "digest": "2387e5fd9ae4c2972d40298d32319b8fa55c50dbfc1c04c5c36088213e6951dd" }, { "name": "o2", "unicode": "1F17E", - "digest": "00d751124c25633611055bd61e74fc3f3d1779f0d09e1e707837686f613367b4" + "digest": "6a9ccb0bf394e4d05ffda19327cee18f7b9ed80367fc7f41c93da9bb7efab0bf" }, { "name": "ocean", "unicode": "1F30A", - "digest": "9b1fbfd2a64f417d0c2cb91085b29a12d14e15844bc21798bdee938bb7bf6222" + "digest": "1a9ca9848d4fb75852addfc10bf84eccf7caa5339714b90e3de4cb6f2518465e" }, { "name": "octopus", "unicode": "1F419", - "digest": "3fdfbc02f47ad434bdeb7f3a15cd4e8f8118ee1cd754627e358f1c2f4616f5e3" + "digest": "0fcc65c12f4b29ea75a8c4823d20838a7e6db6978fdcb536943072aa1460bc59" }, { "name": "oden", "unicode": "1F362", - "digest": "afed1c5166943e5803602ffacc67652e3b29ee4222a6c36aba2daf88bd21ad3c" + "digest": "089974cb13a0bef6a245fc73029c5ed5153fd4caae0177b835f779e32200b8aa" }, { "name": "office", "unicode": "1F3E2", - "digest": "dc1836ef152d88fd628df18db770594f5dbc8d7f20d6ce982588b25b78b19c92" + "digest": "3633a2e91036362e273eef4e0cfbdbbb4cb1208afe2cfa110ebef7b78109a66f" }, { "name": "oil", "unicode": "1F6E2", - "digest": "f8b7626cb09e229203105b9c8c7f3fbb38c0650021092fc50115ad517248644a" + "digest": "00b94d33bcc9b9e8a5d4bd6e7f7e2fced9497ce05919edd5e58eafbc011c2caa" }, { "name": "oil_drum", "unicode": "1F6E2", - "digest": "f8b7626cb09e229203105b9c8c7f3fbb38c0650021092fc50115ad517248644a" + "digest": "00b94d33bcc9b9e8a5d4bd6e7f7e2fced9497ce05919edd5e58eafbc011c2caa" }, { "name": "ok", "unicode": "1F197", - "digest": "6b05bbab4a7104541c2f4bce553884d17ae0ad07589b19d6b53b6949c14f2269" + "digest": "5f320f9b96e98a2f17ebe240daff9b9fd2ae0727cd6c8e4633b1744356e89365" }, { "name": "ok_hand", "unicode": "1F44C", - "digest": "9981f32ef200b011a10f6bfa2066c41b6b5e7bcd6c3c21647980b640bc1fa93b" + "digest": "d63002dce3cc3655b67b8765b7c28d370edba0e3758b2329b60e0e61c4d8e78d" }, { "name": "ok_hand_tone1", "unicode": "1F44C-1F3FB", - "digest": "e5933a9b64b03ce0634f15f02ff7b6424530dbdc0e283461e0c9992d0c2ca2ad" + "digest": "ef1508efcf483b09807554fe0e451c2948224f9deb85463e8e0dad6875b54012" }, { "name": "ok_hand_tone2", "unicode": "1F44C-1F3FC", - "digest": "4c04741c9f2c8731da8df3015e9aae00061a01848c2d22aab1e9853c271deed3" + "digest": "1215a101a082fd8e04c5d2f7e3c59d0f480cb0bedd79aeab5d36676bfe760088" }, { "name": "ok_hand_tone3", "unicode": "1F44C-1F3FD", - "digest": "216dc5a72f9e34bbb7b39f680c388bd5b52abf9b41b843342e53e285b7933076" + "digest": "6fe0ed9fb42e86bb2bed4cb37b2acacacda1471fb1ee845ad55e54fb0897fbf4" }, { "name": "ok_hand_tone4", "unicode": "1F44C-1F3FE", - "digest": "7139de7ec9d5a962cf87b9fbbeef3a53aa482bb840ab3b64d8d0da81bdc19886" + "digest": "bfb9041c49d95e901a667264abaf9b398f6c4aa8b52bf5191c122db20c13c020" }, { "name": "ok_hand_tone5", "unicode": "1F44C-1F3FF", - "digest": "e18b0a1bc5d970cc63466bd6da6e9f855db37d1eada3230d19f600c1f5a402a3" + "digest": "1c218dc04d698da2cbdd7bea1ca3f845f9b386e967b7247c52f4b0f6ec8f5320" }, { "name": "ok_woman", "unicode": "1F646", - "digest": "3b2fa732d9c9addb056f136192428e99d805d4cb1c7dab724fd552c7e93197e4" + "digest": "3f8bd4ce2c4497155d697e5a71ebdc9339f65633d07fa9a7903e1bd76cfa4ba1" }, { "name": "ok_woman_tone1", "unicode": "1F646-1F3FB", - "digest": "017aca3797701b043a44f22e67dcad8b531a3ca14e629ae0d2fbc601ed3e49cb" + "digest": "1660cd904ccd2ecdc6f4ba00527f7d4ec8c33f3c6183344616f97badae4c3730" }, { "name": "ok_woman_tone2", "unicode": "1F646-1F3FC", - "digest": "036bed032bc5a616668775cda0d5640c810e2836aa28009c8e8bf2b487259c59" + "digest": "7ba5fddd1e141424fac6778894dfc5af28e125839c58937c69496f99cd2c4002" }, { "name": "ok_woman_tone3", "unicode": "1F646-1F3FD", - "digest": "d9a4414caddda43d1a36828cfbecce5f2b7e5c1b67b4a47991b2ae0a34cf7ab7" + "digest": "1d972b8377c52f598406f59ab1e5be41aaf8f027e1fefba3deda66312ccd6a9b" }, { "name": "ok_woman_tone4", "unicode": "1F646-1F3FE", - "digest": "942e1b9aa495c4c4de0804e4d4348422201299d649e5d65829ba4a308880df1c" + "digest": "a176328d8f53503aa743448968afd21d72ffd3510555526a3fb38d6b30ee7c15" }, { "name": "ok_woman_tone5", "unicode": "1F646-1F3FF", - "digest": "e8d0fb5b999d5d63404493aa505b5af2260c76001023431d5e788773d0a9e2de" + "digest": "13cfc1b589c57e81f768ee07a14b737cafc71407a7eb0956728b2ec4b1df14c4" }, { "name": "older_man", "unicode": "1F474", - "digest": "620f763325827acbeb9d57798ef55d87827d0dfc77b84d942e25bc5057f2cbfe" + "digest": "4c0462b199bf26181c9e4d2d4cb878a32b0294566941212efc67362d0645f948" }, { "name": "older_man_tone1", "unicode": "1F474-1F3FB", - "digest": "e0f35c12362eae503d1c30a345c3a4978196d351d8a1eb9d5f107c60ea4bbf52" + "digest": "99baa083f78cb01166d0a928d0b53682be14be04c29fc17bef14aac1a73a61e6" }, { "name": "older_man_tone2", "unicode": "1F474-1F3FC", - "digest": "671766ce9fa47c3fa009d4f138344c87d73032a1c38e48614c663f8ea5d0f673" + "digest": "5b4ce713e8820ba517fe92c25f3b93e6a6bf3704d1f982c461d5f31fc02b9d3d" }, { "name": "older_man_tone3", "unicode": "1F474-1F3FD", - "digest": "6ff4885ef8c416b8970780a691fef74c8d89421ab11e0aa8c522c33e1c67fbe8" + "digest": "0eff72b3226c3a703c635798ee84129a695c896fa011fe1adbc105312eecc083" }, { "name": "older_man_tone4", "unicode": "1F474-1F3FE", - "digest": "0ae7d4e316dcd4d27a5a6cdaabab88a4f992bd1b75f6ceaeb5b906ed1eb5269c" + "digest": "ad9ba82b0c5d3b171b0639ee4265370dbddff5e0eeb70729db122659bb8c8f84" }, { "name": "older_man_tone5", "unicode": "1F474-1F3FF", - "digest": "abe2757bd5e35f30d2a6daec09637ea5382a46d14d239b77282e9bf874229b57" + "digest": "5eb0a7467cc40e75752e11fd5126b275863dc037557a0d0d3b24b681e00c2386" }, { "name": "older_woman", "unicode": "1F475", - "digest": "3ed599443eed25399aac999fc234c9e97f8fb6ec567e37a553c26e01021b097c" + "digest": "c261fdf3b01e0c7d949e177144531add5895197fbadf1acbba8eb17d18766bf6" }, { "name": "grandma", "unicode": "1F475", - "digest": "3ed599443eed25399aac999fc234c9e97f8fb6ec567e37a553c26e01021b097c" + "digest": "c261fdf3b01e0c7d949e177144531add5895197fbadf1acbba8eb17d18766bf6" }, { "name": "older_woman_tone1", "unicode": "1F475-1F3FB", - "digest": "7421c5dba67cfd1eeabb2fa8faf4aa0d615d23f191cf7d7c0ad9c1fa884edfda" + "digest": "1f2bb9e42270a58194498254da27ac2b7a50edaa771b90ee194ccd6d24660c62" }, { "name": "grandma_tone1", "unicode": "1F475-1F3FB", - "digest": "7421c5dba67cfd1eeabb2fa8faf4aa0d615d23f191cf7d7c0ad9c1fa884edfda" + "digest": "1f2bb9e42270a58194498254da27ac2b7a50edaa771b90ee194ccd6d24660c62" }, { "name": "older_woman_tone2", "unicode": "1F475-1F3FC", - "digest": "65edeef25648ac7f8be535df06af1286441691fa15176e99a6e83fc779aa2cde" + "digest": "2e28198e9b7ac08c55980677ed66655fd899e157f14184958bebd87fcd714940" }, { "name": "grandma_tone2", "unicode": "1F475-1F3FC", - "digest": "65edeef25648ac7f8be535df06af1286441691fa15176e99a6e83fc779aa2cde" + "digest": "2e28198e9b7ac08c55980677ed66655fd899e157f14184958bebd87fcd714940" }, { "name": "older_woman_tone3", "unicode": "1F475-1F3FD", - "digest": "5d27bbcc5796227a9caec1c7612d3f691055655b96f7303e420839463d76c269" + "digest": "c968be0170f7e0c65d4f796337034cfb1daba897884da6fad85635ab5b6edf67" }, { "name": "grandma_tone3", "unicode": "1F475-1F3FD", - "digest": "5d27bbcc5796227a9caec1c7612d3f691055655b96f7303e420839463d76c269" + "digest": "c968be0170f7e0c65d4f796337034cfb1daba897884da6fad85635ab5b6edf67" }, { "name": "older_woman_tone4", "unicode": "1F475-1F3FE", - "digest": "75b858e910175fc0233503d672120fd43ac035ba3fd2052fbb44df39f6e3695c" + "digest": "3596a6fa9a643bf79255afcd29657b03850df8499db9669b92ce013af908af44" }, { "name": "grandma_tone4", "unicode": "1F475-1F3FE", - "digest": "75b858e910175fc0233503d672120fd43ac035ba3fd2052fbb44df39f6e3695c" + "digest": "3596a6fa9a643bf79255afcd29657b03850df8499db9669b92ce013af908af44" }, { "name": "older_woman_tone5", "unicode": "1F475-1F3FF", - "digest": "9da1cf10a605c470877d7f4a840f99344b1ec2e7b1ec7db61e930cde77025e3b" + "digest": "c8998cb3dbd15e22bd1d6dad613d109ce371d9ffca3657e1a8afe5aeb30c1275" }, { "name": "grandma_tone5", "unicode": "1F475-1F3FF", - "digest": "9da1cf10a605c470877d7f4a840f99344b1ec2e7b1ec7db61e930cde77025e3b" + "digest": "c8998cb3dbd15e22bd1d6dad613d109ce371d9ffca3657e1a8afe5aeb30c1275" }, { "name": "om_symbol", "unicode": "1F549", - "digest": "c8c1c9d445b1fc50a627b71bee21fba978e04532e4685ec032a0174f51fc12bb" + "digest": "5ead73bea546ba9ba6da522f7280cc289c75ff5467742bdba31f92d0e1b3f4e6" }, { "name": "on", "unicode": "1F51B", - "digest": "08e1159a68d3334a87ffa75b9e70826cb557d0f73a2c1d08f4c3d60476ecacc8" + "digest": "9cc61a6b31a30c32dab594191bf23f91e341c4105384ab22158a6d43e6364631" }, { "name": "oncoming_automobile", "unicode": "1F698", - "digest": "6bff7f40fe223df6d16c7512532b8aa6f83e8c13e1007b63eb9aabf774c1a322" + "digest": "557c9cacdc3f95215d4f7a6f097a2baa7c007cb9c519492a6717077af4ca6b56" }, { "name": "oncoming_bus", "unicode": "1F68D", - "digest": "127a357fcd96ce4b9ab11c3dba95d8ff811bab193dd8ba38efb7067a44752ce8" + "digest": "059f28ce6bfb337e107db5982cbd2004844450ef20b4a54b9ca3cb738360ab05" }, { "name": "oncoming_police_car", "unicode": "1F694", - "digest": "57cb70e05e70c1f68ab42259f307ed9782c2b9d6e35d2dff2895aa23d7eb6b04" + "digest": "aee79306a0d129cfc1980f58db80391eb46d2d7d5f814bf431414dc7680cab72" }, { "name": "oncoming_taxi", "unicode": "1F696", - "digest": "174967ae4c3d5881d2408c71c020f704e933190af4caef5d2908e9ac382f35ea" + "digest": "84351489fc86d980b8d3eb9ec4e81120fe700b3ac01346daebe2b7aeb9607a55" }, { "name": "one", "unicode": "0031-20E3", - "digest": "113b9d87c3e37c9c54e49cecccbfc40c15fb97fd03a51505df85e48b78702b2b" + "digest": "d5d3fff04e68a114ff6464ee06fc831f3f381713045165f62a88d5e8215c195b" }, { "name": "open_file_folder", "unicode": "1F4C2", - "digest": "def93715203aed464211798d773732895a19389a94a2e7ed43e7f229b2aab7da" + "digest": "96cfc322ee4903ae8cec07604811742245fd7d14f00bb70276d39d29c48bed28" }, { "name": "open_hands", "unicode": "1F450", - "digest": "7c60a37ae11727c998908199b8709e52593b931843aef942f37b306b1edca12a" + "digest": "a6c131da2040b48103cea14f280e728675da50fa448d2b3f3438fcbb5bf5596a" }, { "name": "open_hands_tone1", "unicode": "1F450-1F3FB", - "digest": "09ffa9b3f28fc56a71e4e711bdfc87ce1a56721229377e71f1c00224523f8b9b" + "digest": "867128dff2fa9b860c10c6b792f989f0c057928783696062378f834c0ef89d85" }, { "name": "open_hands_tone2", "unicode": "1F450-1F3FC", - "digest": "21ecaba9f086bcb7eb07c17c2b2621bcd1ca28c57f79032d5e0eba356494cc85" + "digest": "487ff2745b03d49bb3b1d0acd86ba530fd8cc3f467ca3fa504f88f0ef1cbbc01" }, { "name": "open_hands_tone3", "unicode": "1F450-1F3FD", - "digest": "c7dbb8c44f78f7793b202ec215fee42b7e1e555d659fbf402383500217b89656" + "digest": "cb8cddc8b8661f874ac9478289d16cc41406b947bb87f3363df518a588a53e16" }, { "name": "open_hands_tone4", "unicode": "1F450-1F3FE", - "digest": "867451d42492ab2277687447f421f744530b9ea057312326353fec39c94b18fd" + "digest": "17dcc2c07230846a769f3c79ce618a757c88b9b58c95c6c5b2d7f968814d447d" }, { "name": "open_hands_tone5", "unicode": "1F450-1F3FF", - "digest": "56335506cf68e29150cb68d7ebbb4a92aed390018966669a8144d20ae0d6cfe3" + "digest": "36b2493d67c84cea4f3f85a3088c6abcfd35cf99f7aeaeedfafa420ee878e3d2" }, { "name": "open_mouth", "unicode": "1F62E", - "digest": "f05fdf998e8b5c0b00ebd8b5ab17a67f5c0a45275f31a201af74e8ab0c2f7ba9" + "digest": "1906c5100ae0c8326ca5c4f9422976958a38dadd8d77724d68538a25d9623035" }, { "name": "ophiuchus", "unicode": "26CE", - "digest": "98c61bb0c36d60c476d42d5e074297662e8d141dcab7004a5bd63c359eed3b84" + "digest": "6112e2a1656b1cb8bd9a8b0dfa6cbf66d30cae671710a9ef75c821de344aab2b" }, { "name": "optical_disk", @@ -7872,27 +7872,27 @@ { "name": "orange_book", "unicode": "1F4D9", - "digest": "86d150ea3d62183ab7dfe2851cf7f4d1ae769b7ecbb1987b0f463e639e429598" + "digest": "41141b08d2beceded21a94795431603c47fd7d42a3a472a2aa8b2bb25fa87ebf" }, { "name": "orthodox_cross", "unicode": "2626", - "digest": "9c861285ca6d699cd2c72b6df44ec2b1e64138152f19c66e32df1ce770ff2e83" + "digest": "c16372102f0169dd6d32eb2b27a633aaee74e4e0fddcf723c15ad97f9dc6075c" }, { "name": "outbox_tray", "unicode": "1F4E4", - "digest": "b6a6015d5d7d528af485de23ff4518dc35408def1cc49bc6c9b01d880d613985" + "digest": "e47cb481a0ffcb39996f32fd313e19b362a91d8dda15ffca48ac23a3b5bb5baf" }, { "name": "ox", "unicode": "1F402", - "digest": "cbcfe5c8c4d6b939e24e18e610785f171bb9410441e02c2eeb1bceb0a6246daf" + "digest": "d13bc60552190bb9936bf32d681bdc742439b702a09cfc62137ea09a98624aed" }, { "name": "package", "unicode": "1F4E6", - "digest": "4023cffce85384217a73609f457aec013876e689c44bcfff0bcc35f3e4e1ab00" + "digest": "e82bf5accebb65136e897c15607eef635fb79fd7b2d8c8e19a9eb00b6786918c" }, { "name": "page", @@ -7902,17 +7902,17 @@ { "name": "page_facing_up", "unicode": "1F4C4", - "digest": "71a0872bf1b13c58746f9b41655227c75be107ab6083c0dce13cb16444af22e7" + "digest": "3884868bdcb2f29615b09a13a30385cbc5269379094a54b5a7e8a5f4e8ce905a" }, { "name": "page_with_curl", "unicode": "1F4C3", - "digest": "cb4210464faea946c7b07db7067c7fc98920f778cf57721388f5362942ba3029" + "digest": "3d6257670189f841ad1fa45415c34feb2433b2cb35bb435c4ee122ce89b39669" }, { "name": "pager", "unicode": "1F4DF", - "digest": "209dbdc19aa650ecacc0569e17a9123c9a1e39df59c9b4120f3b0888b63cd6f1" + "digest": "e21c756cc1c58ebc1b37ebcd38e22a25b31e2e81306c6f18285d6a7671f9eb12" }, { "name": "pages", @@ -7922,132 +7922,132 @@ { "name": "paintbrush", "unicode": "1F58C", - "digest": "73eb33184f5f495d6c2699fafc1a8680069f82a70fbe519290c3a2ce30d1aee9" + "digest": "fc0da7a25b726b8be9dd6467953e27293d2313a21eeff21424c2a19be614fff2" }, { "name": "lower_left_paintbrush", "unicode": "1F58C", - "digest": "73eb33184f5f495d6c2699fafc1a8680069f82a70fbe519290c3a2ce30d1aee9" + "digest": "fc0da7a25b726b8be9dd6467953e27293d2313a21eeff21424c2a19be614fff2" }, { "name": "palm_tree", "unicode": "1F334", - "digest": "1589ff4b1b87296edc0118e4aa67b3b504ed85a5b8d47e7d0c3e309d0bbf8cd6" + "digest": "90fedafd62fe0abf51325174d0f293ebb9a4794913b9ba93b12f2d0119056df1" }, { "name": "panda_face", "unicode": "1F43C", - "digest": "050ee87892f56ff485f460bc6c3846d98a0ca7083d2cf0b8ab24772b672273f2" + "digest": "56a4b84abe983bd6569be1b81ac5e43071015fd308389a16b92231310ae56a5b" }, { "name": "paperclip", "unicode": "1F4CE", - "digest": "1463607a59345973f009fa53a719e2264b95743560adb99737bef29b1d133a95" + "digest": "d1e2ce94a12b7e8b7a9bba49e47ddc7432ec0288545d3b6817c7a499e806e3f0" }, { "name": "paperclips", "unicode": "1F587", - "digest": "7071e031f4a100c3cb3573fbfa375360043f0276289a0818f2ffaf71b3580040" + "digest": "70cefa0d0777f070e393e9f95c24146fe2dd627f30fa3845baa19310d9291fe2" }, { "name": "linked_paperclips", "unicode": "1F587", - "digest": "7071e031f4a100c3cb3573fbfa375360043f0276289a0818f2ffaf71b3580040" + "digest": "70cefa0d0777f070e393e9f95c24146fe2dd627f30fa3845baa19310d9291fe2" }, { "name": "park", "unicode": "1F3DE", - "digest": "d257f0f1b1a0134573f80ba1a5f522a91c320ee7f93a1cb64877c077e7e19b50" + "digest": "444dce8014e0817ddd756c36a38adfbbf7ae4c6aa509e4cae291828f0716d5e7" }, { "name": "national_park", "unicode": "1F3DE", - "digest": "d257f0f1b1a0134573f80ba1a5f522a91c320ee7f93a1cb64877c077e7e19b50" + "digest": "444dce8014e0817ddd756c36a38adfbbf7ae4c6aa509e4cae291828f0716d5e7" }, { "name": "parking", "unicode": "1F17F", - "digest": "e1d2cfd1c57ea85003ca4df066cbba4e506bf6c4d6c790e27b2f78ad8443fabf" + "digest": "9f1da460a7dd58b26beab8cf701be2691fb812208fbc941c71daa35be1507c2f" }, { "name": "part_alternation_mark", "unicode": "303D", - "digest": "b3cc2e803b255e858417345ba6ba52a1c22f511b483fec11b5d68c4432f759b6" + "digest": "956da19353bb38fd4dfe0ab5360679a9035d566858fb5de62887b85c75fb8eef" }, { "name": "partly_sunny", "unicode": "26C5", - "digest": "484990f5e1a3b14c731e7bd4b0b4a1c10cd5fb54ac7cf2751f40c8bf59d7e2b4" + "digest": "8fb9a6d2caf9e0cce58447762f0dfd6aa0b581b2e83fea6411348e0cbc8cf3c4" }, { "name": "passport_control", "unicode": "1F6C2", - "digest": "224e8ef60d4d6587721727555de324948fb5b6c1cb5cc4b546960983d1ec85c4" + "digest": "d9be6eed2c90e1c89171c42d70a06485fdf86a4c68833371832cc1f6897fadd0" }, { "name": "pause_button", "unicode": "23F8", - "digest": "edd605ffaa39a7905ed0958b7cc69f00f5b271e579198d2df1746ad1b3648272" + "digest": "143221d99e82399ed7824b6c5e185700896492058b65c04e4c668291de78b203" }, { "name": "double_vertical_bar", "unicode": "23F8", - "digest": "edd605ffaa39a7905ed0958b7cc69f00f5b271e579198d2df1746ad1b3648272" + "digest": "143221d99e82399ed7824b6c5e185700896492058b65c04e4c668291de78b203" }, { "name": "peace", "unicode": "262E", - "digest": "e0ee8a5c9fb18d5db6841b21527ed8fd955abdff9ffdb7b2684dca22107015fc" + "digest": "65181429e373c1f0507bbd98425c1bec0c042d648fb285a392460cbce60f44d4" }, { "name": "peace_symbol", "unicode": "262E", - "digest": "e0ee8a5c9fb18d5db6841b21527ed8fd955abdff9ffdb7b2684dca22107015fc" + "digest": "65181429e373c1f0507bbd98425c1bec0c042d648fb285a392460cbce60f44d4" }, { "name": "peach", "unicode": "1F351", - "digest": "a3f4fd5ff02e0a03104ab54456ee1a7521858ee68443856ee10e0972e5b6aaa5" + "digest": "768d1f4f29e1e06aff5abb29043be83087ded16427ce6a2d0f682814e665e311" }, { "name": "pear", "unicode": "1F350", - "digest": "7a7a72568d53677cd1fff4d9e58e63327a742fa16d22a2bef03b4a6fa378d3b3" + "digest": "b7c9cf90bb979649b863d2f4132f1b51f6f8107d42e08fb8b4033fea32844948" }, { "name": "pen_ballpoint", "unicode": "1F58A", - "digest": "6becdc6f622c774bb09b7e7592bba2123ecccc9de32a35f0b18b50d7d54109cb" + "digest": "aacb20b220f26704e10303deeea33be0eec2d3811dcba7795902ca44b6ae9876" }, { "name": "lower_left_ballpoint_pen", "unicode": "1F58A", - "digest": "6becdc6f622c774bb09b7e7592bba2123ecccc9de32a35f0b18b50d7d54109cb" + "digest": "aacb20b220f26704e10303deeea33be0eec2d3811dcba7795902ca44b6ae9876" }, { "name": "pen_fountain", "unicode": "1F58B", - "digest": "8c78cf0c2bd1d5e309d2d3356ff207e3fc76ca18dd6b90762cb62f6afbc95c6a" + "digest": "3619913eab2b6291f518b40481bb3eca0820d68b0a1b3c11fb6a69c62b75a626" }, { "name": "lower_left_fountain_pen", "unicode": "1F58B", - "digest": "8c78cf0c2bd1d5e309d2d3356ff207e3fc76ca18dd6b90762cb62f6afbc95c6a" + "digest": "3619913eab2b6291f518b40481bb3eca0820d68b0a1b3c11fb6a69c62b75a626" }, { "name": "pencil", "unicode": "1F4DD", - "digest": "62b7ee5d9352114d09ee6f2c9a4c5e8b79f775a6c509e82ddfcdd61e13716249" + "digest": "accbc3f1439b7faa4411e502385f78a16c8e71851f71fc13582753291ffb507c" }, { "name": "memo", "unicode": "1F4DD", - "digest": "62b7ee5d9352114d09ee6f2c9a4c5e8b79f775a6c509e82ddfcdd61e13716249" + "digest": "accbc3f1439b7faa4411e502385f78a16c8e71851f71fc13582753291ffb507c" }, { "name": "pencil2", "unicode": "270F", - "digest": "aa2c572772187fee1f9125bb0950f5ce8a61f7dd2647258c40b4077ee5feb498" + "digest": "9ca1b56b5726f472b1f1b23050ed163e213916dac379d22e38e4c8358fe871e0" }, { "name": "pencil3", @@ -8062,7 +8062,7 @@ { "name": "penguin", "unicode": "1F427", - "digest": "095de34b3f6a2521a342c21f5f2551a0092bf47429801c15b7bbf0913924f412" + "digest": "a1800ab931d6dc84a9c89bfab2c815198025c276d952509c55b18dd20bd9d316" }, { "name": "pennant_black", @@ -8087,147 +8087,147 @@ { "name": "pensive", "unicode": "1F614", - "digest": "2d9e7f1eed14dcc86674cec78e992567a40d0f223fc67d722b91eebcd1251269" + "digest": "d237deff9f5ead8a0b281b7e5c6f4b82e98cc30c80c86c22c3fdc6160090b2f2" }, { "name": "performing_arts", "unicode": "1F3AD", - "digest": "a202755bab6427433975589bb8b63e61e5d7f55c6242676d8000e91eedabc55e" + "digest": "d7c7bc9213e308ca26286cbbd8012e656b0f9b00293758faf1bfccc4c5ceabed" }, { "name": "persevere", "unicode": "1F623", - "digest": "686ef3fc70ce8294d02a764ebd75b69f25cca6bff6b92e7905130366d22f6d8a" + "digest": "c361509c9b8663af19a02a1ffff61b1b0d0b4bd75d693ce3d406b0ca1bde1ca0" }, { "name": "person_frowning", "unicode": "1F64D", - "digest": "16e8fbf22c0b4c237d0d45202fa32d1ebd04760a5b6975c9c9b477321ccb0e12" + "digest": "b37be8bd95f21a6860ad3f171b8086125ab37331b382d87bcdb4cd684800546b" }, { "name": "person_frowning_tone1", "unicode": "1F64D-1F3FB", - "digest": "a143b865976ce3cf307db854cfd1ca58c3832df0eee5e9b0ab307cf4f24ba3db" + "digest": "3d5e78a367f9673baed2a86bc11cf04fd44394aadb65291fa51ade8dca318427" }, { "name": "person_frowning_tone2", "unicode": "1F64D-1F3FC", - "digest": "4e7050d8a38019ba2293f66b9930e6a7e35dacf3b3bc9431edb586a0d9ea8054" + "digest": "7456c414c65ad6b6f11855f68a2eedc18113526f86862c4373202397cb1bed2c" }, { "name": "person_frowning_tone3", "unicode": "1F64D-1F3FD", - "digest": "0750015d3ac1b5954d31e36cd59c70b6ed9f4df698082484b7ac59eb0b9964b0" + "digest": "c86cf2d6951f1e6a7c786a74caaf68a777cf00e88023e23849d4383f864ae437" }, { "name": "person_frowning_tone4", "unicode": "1F64D-1F3FE", - "digest": "18d6cc92d0990624218d38d6eeed60bccb371d0fc9f1c889e9476b3b0c44b5e8" + "digest": "944e96ced645ced8db6bb50120c7e37ed46b6960d595cbfe964c81803efa83aa" }, { "name": "person_frowning_tone5", "unicode": "1F64D-1F3FF", - "digest": "4a898199cbaf083d37511f51d8a1d2560b7a20c62a1b09087831da7010fbd093" + "digest": "4bd0ea571be6ef9f0493784ef0d12d5e47bc2d6ac610fb42c450bf3d87fb2948" }, { "name": "person_with_blond_hair", "unicode": "1F471", - "digest": "67d95a0801c65f62db55fa80ab35dec65c239601a44bf5f5902e4645f126770e" + "digest": "a7f94ede2e43308108c2260d83fc10121dda09a67f94a0a840e6d7bba7fd5616" }, { "name": "person_with_blond_hair_tone1", "unicode": "1F471-1F3FB", - "digest": "e79717bfe30a26eafc082a75fa7547d8f2ad3c123fb2d75a95e75f0ce7ecbd0c" + "digest": "00a116357a7878554c83e5bade4bddfa9cfabf76a229efa19cbb58e0d216219c" }, { "name": "person_with_blond_hair_tone2", "unicode": "1F471-1F3FC", - "digest": "c4a1961c292149ab6e1fd54a7894398599bf855de97a05ee4e836a86a400deb3" + "digest": "df509ebe92ed3138b9d5bd4645eff4b13f77f714cf62bb949c59eff1adc00019" }, { "name": "person_with_blond_hair_tone3", "unicode": "1F471-1F3FD", - "digest": "e2707d0cf778bee5b72d861ec76430eb1cf9f9820f066ee6327574d5697f445e" + "digest": "6f328513f440a0c8cd1dc44596a5028fd8f306bdaf57c1e6f3aa94a3aa262b3c" }, { "name": "person_with_blond_hair_tone4", "unicode": "1F471-1F3FE", - "digest": "94da43f0b12ef4a98dabec096ff1184b0a9b5b6ee55824d257e5112cc7e88730" + "digest": "32df1a577815b009696643ad80d063cc97b35d54add6d4e5517fc936f6da9ee8" }, { "name": "person_with_blond_hair_tone5", "unicode": "1F471-1F3FF", - "digest": "9e096a210ea720d32bc6a7005cd77f8b314ccf817fc3060da2e1796de39e9d60" + "digest": "2e270bb39187d8e36a33f4aa4d6045308189595fafc157cf7993e82d7ce93442" }, { "name": "person_with_pouting_face", "unicode": "1F64E", - "digest": "8c3199a422250d2db9a163156191ed2c6697d7f31699e2efe19e05ca26e5d225" + "digest": "57e9a6e5f82121516dc189173f2a63b218f726cd51014e24a18c2bdfeeec3a0b" }, { "name": "person_with_pouting_face_tone1", "unicode": "1F64E-1F3FB", - "digest": "3e1f09bbf607381c992739ea92dd35cbd26b1bbc705a7d21b7c3156f50e9d8b3" + "digest": "d10dadb1ac03fc2e221eff77b4c47935dc0b4fe897af3de30461e7226c3b4bbc" }, { "name": "person_with_pouting_face_tone2", "unicode": "1F64E-1F3FC", - "digest": "b5fc1cf3fdc5ff01105ee2452db90baa6a52c1e42f3795b2836c3e35197ece1f" + "digest": "efface531537ab934b3b96985210a2dac88de812e82e804d6ec12174e536d1cc" }, { "name": "person_with_pouting_face_tone3", "unicode": "1F64E-1F3FD", - "digest": "e8ec2539c458a8283c8c1050634c432b6363f3e64b68ba4c977994782f09b564" + "digest": "7ff26ece237216b949bfa96d16bd12cfd248c6fd3e4ed89aa6c735c09eafaeff" }, { "name": "person_with_pouting_face_tone4", "unicode": "1F64E-1F3FE", - "digest": "5cab7a29699decd45682583446c2bf56ddcd69cd16e14db661b526a4076dfa17" + "digest": "045c04105df41d94ff4942133c7394e42ff35ef76c4ccb711497ab77ae6219f2" }, { "name": "person_with_pouting_face_tone5", "unicode": "1F64E-1F3FF", - "digest": "3caebd3626fd77d849859d1c99a747f80a2b59bfa5c1854494f1ce0485539a94" + "digest": "783ee37f146fcf61d38af5009f5823cf6526fe99ed891979f454016bce9dd4ba" }, { "name": "pick", "unicode": "26CF", - "digest": "24a3e8f592435b97272e6d134ea5503dce3012811659c4aadbad4e45d9fba679" + "digest": "7f0ec5445b4d5c66cf46e2a7332946cce34bd70e9929ac7a119251a7f57f555d" }, { "name": "pig", "unicode": "1F437", - "digest": "50b55fc74e8f6c89c6e04609381c99a660748908f0ef015f5da37089678ad0c3" + "digest": "51362570ab36805c8f67622ee4543e38811f8abb20f732a1af2ffbff2d63d042" }, { "name": "pig2", "unicode": "1F416", - "digest": "e8189fb678608e8b9d69e11d2566f9a4765cbdff99ec8e66df30c7a2dabf742f" + "digest": "67010e255f28061b9d9210bcdab6edc072642ad134122a1d0c7e3a6b1795a45b" }, { "name": "pig_nose", "unicode": "1F43D", - "digest": "7e299cb49a771884f5065c68733a5a1fe354a54cff009127230177f1717af4a5" + "digest": "0b21cac238bf4910939fbea9bed35552378c1b605a3867d7b85c1556dbda22a9" }, { "name": "pill", "unicode": "1F48A", - "digest": "53ae3379cc6721744979122569f157a5a13aa6b48e081a89f17b2d90134efe9e" + "digest": "cb00be361aaba6dbcf8da58bd20b76221dd75031362ecae99496b088ed413a7f" }, { "name": "pineapple", "unicode": "1F34D", - "digest": "ceda8ffa4a41594f28a4e69d03f8a1daeb2ba20740f0b8c56447cae833eea035" + "digest": "621d4d4c52b59e566c2e29ed7845c8bd2d1da0946577527342097808d170dd70" }, { "name": "ping_pong", "unicode": "1F3D3", - "digest": "dd2a84716c93410a285ff759bfbc2dc31a10f90b203c7a657b908e5949e89a39" + "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1" }, { "name": "table_tennis", "unicode": "1F3D3", - "digest": "dd2a84716c93410a285ff759bfbc2dc31a10f90b203c7a657b908e5949e89a39" + "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1" }, { "name": "piracy", @@ -8242,322 +8242,322 @@ { "name": "pisces", "unicode": "2653", - "digest": "75f11b9a094196b54a242420362fa7c0aeba7cfc497b187e1aaaba96d93684a7" + "digest": "453c3915122a4b6b32867056d2447be48675a84469145c88d52f8007fcb0861a" }, { "name": "pizza", "unicode": "1F355", - "digest": "ac94ae1c034f7b854ce2a483e1c219d101a84336f5065342f4824ff32ba705c4" + "digest": "169bc6c1e1d7fdab1b8bf2eab0eeec4f9a7ae08b7b9b38f33b0b0c642e72053a" }, { "name": "place_of_worship", "unicode": "1F6D0", - "digest": "4fabc307b7e35f94288f6d53985485662a4814b11a9a382f0a3873d41b1290d3" + "digest": "daf271d36a38ee8c0f8b9de84c128ab8b25a5b7df8f107308d0353c961f2c644" }, { "name": "worship_symbol", "unicode": "1F6D0", - "digest": "4fabc307b7e35f94288f6d53985485662a4814b11a9a382f0a3873d41b1290d3" + "digest": "daf271d36a38ee8c0f8b9de84c128ab8b25a5b7df8f107308d0353c961f2c644" }, { "name": "play_pause", "unicode": "23EF", - "digest": "d69e8cdec33447283cf65d343b986115e27681d781b721db7894e5c587ca18ad" + "digest": "af1498f34a3d6e0da8bbd26ebaa447e697e2df08c8eb255437cf7905c93f8c42" }, { "name": "point_down", "unicode": "1F447", - "digest": "685f46a643be7f3033896e59a822f87d61ce50db6969bcdbacc743215a96bb7a" + "digest": "4ecdb3f31c16dc38113b8854ec1a7884613b688a185ebdf967eab9a81018f76d" }, { "name": "point_down_tone1", "unicode": "1F447-1F3FB", - "digest": "d3dd2608fe17d5649c960fcf8dbdb68466908d80fa349b7947b457da2a27ebb1" + "digest": "c74a7c94367cddbfa840542dc0924adeb0d108be0c7fde8c25fb95d69115d283" }, { "name": "point_down_tone2", "unicode": "1F447-1F3FC", - "digest": "67ab236a14f6d63abcdb26433a66a183d223186c21ebc9f978fab50165ebe271" + "digest": "dc4bda0726d85418b974addb42738f437fbb9cf16e5815cdbab3859c4ada6cae" }, { "name": "point_down_tone3", "unicode": "1F447-1F3FD", - "digest": "c8a2368f2cedb5bbb5cc0195b97fbf3787747637bf6e77bdc9a4edf4a3f22a04" + "digest": "e460f81a501376d2f0ed1d45e358c5ed03ba049e8f466e4298afb4f3ca6d24dc" }, { "name": "point_down_tone4", "unicode": "1F447-1F3FE", - "digest": "6a92eab3bc8f950fa423e690f54a352887bda92f01e91c62eb3f3a9544c10cd8" + "digest": "4bc91cd771f24e0f897a9d8b18f323fec9a82da0fc2429c4a7e4e6a9d885a0a3" }, { "name": "point_down_tone5", "unicode": "1F447-1F3FF", - "digest": "6ad329f156414f421d6f8cf5e2a68d34b7a41f90d80e8e66b15bcbd3788126c7" + "digest": "7e47c6bc73250f36dc7ae1c1c09e7b41f30647b9d0ff703a53a75cc046b5057d" }, { "name": "point_left", "unicode": "1F448", - "digest": "cb520d6bba4c2b3bd7911315c9efce3261d048ff090437d7e24c9c5a7255043e" + "digest": "b5a7e864a0016afbadb3bec41f51ecf8c4af73cc20462e1a08b357f90bca6879" }, { "name": "point_left_tone1", "unicode": "1F448-1F3FB", - "digest": "81813901bdaa8d261277f79aff9e9a21beb80a5855899941820b25f70786ec21" + "digest": "9f1868272a10a2b738c065be5d30241643324550cfd47baf01c7a09060e66d31" }, { "name": "point_left_tone2", "unicode": "1F448-1F3FC", - "digest": "ecdc3dea0d644290aa7e0dab758c215822482a482ba35d825a33152453593c1e" + "digest": "bf0d58c68178a2c2c01d4a6235a1a66b90073cea170f9f6fe2668b6dd68424f7" }, { "name": "point_left_tone3", "unicode": "1F448-1F3FD", - "digest": "84e73b6a37755016271c255eba164f349dbd2a2badf5d9ac1c6f4cbfcae589f0" + "digest": "34d28c97bc8f9d111d14e328153c4298fc32cf18e39e20aacaec17846645ed90" }, { "name": "point_left_tone4", "unicode": "1F448-1F3FE", - "digest": "d16800499b6c6ede94256796b1de8a8f723879f636849856b3bd8b7a092b5576" + "digest": "c40c8436316915d516c53bb1c98a469528cefd98baa719be7e748c4608cbbcc9" }, { "name": "point_left_tone5", "unicode": "1F448-1F3FF", - "digest": "18b7108066cebf2d4090f29e595a2f01db94bd210f3b1d61dc269ec249a749b9" + "digest": "c410fe32e4ce0ded74845a54b86090e59e5820d457837b16e175b36cc71ecb46" }, { "name": "point_right", "unicode": "1F449", - "digest": "866180bf31e92de32aba336d5b5ce81773a29cdaadada1d93c944cf9ad9783bc" + "digest": "44d9251ab41f2f48c2250c44a47f92b3476a71f13fbbbfb637547db837fd5a49" }, { "name": "point_right_tone1", "unicode": "1F449-1F3FB", - "digest": "ebe2e4bf6bd46a5798b9a845a4ed055911c4fe58dbeacc4d39d6ea63e28e7cc9" + "digest": "9fcce259eb81c0b52ec7796b98a1653194e3a9021a1d338df1dbbab7522fc406" }, { "name": "point_right_tone2", "unicode": "1F449-1F3FC", - "digest": "b638662a67b1c6adde4f5abc789aae010b178404cdd1b71fcc982cdf8307c655" + "digest": "9d00a0b1cfc435674dc56065b3d28d28839196977504cf20581205351d8708f2" }, { "name": "point_right_tone3", "unicode": "1F449-1F3FD", - "digest": "32c6ca2f992416ab2c36672dfbc1c0de8f102c77a13496dd8d63736a7b0261d2" + "digest": "e3026a70630ba73d76892a055a80cac2f78d509faddce737f802d2abefa074ba" }, { "name": "point_right_tone4", "unicode": "1F449-1F3FE", - "digest": "89bd6828e9b82408a3829d49fa43332e2599f7d10bc6e5b14b750ef03267b173" + "digest": "ea508fde90561460361773b4e1b8e80874667b19ac115926206e7c592587cb76" }, { "name": "point_right_tone5", "unicode": "1F449-1F3FF", - "digest": "390525048a12b0efa22de550c800e439b0deaad03f1f31155d179aef093354af" + "digest": "d59cdb2864eb2929941ecd233f8b8afcddc30fbd4594e5f9acf6386ae06ac12c" }, { "name": "point_up", "unicode": "261D", - "digest": "31b5ca1303c1afabe1db322b24f73b23f3568c87a364f61c82f6e0c858c090e9" + "digest": "b69ff4f650989709f2185822d278c7773672bd9eb4a625da80f3038a2b9ce42b" }, { "name": "point_up_2", "unicode": "1F446", - "digest": "55c237054aa347c9847f3f3f577eb755db55dfcf793aa7de0f8f868574d70e8f" + "digest": "e83cd9eff2af5125a25f5a306c3ee3cfea240add683b5c36a86a994a8d8c805c" }, { "name": "point_up_2_tone1", "unicode": "1F446-1F3FB", - "digest": "dc07e7732d973de96ae3b08b14c19e20b6c1aea7f5a30e7198679b750422e914" + "digest": "b02ec3e7e04a83bfb769cffb951cbf32aa78e56fa5a51c097f9326df9e08ed33" }, { "name": "point_up_2_tone2", "unicode": "1F446-1F3FC", - "digest": "af2211fc4a1bd51d1e76f7bc43a6fa87bdd24e4295c52fdbdb01c1ca670a6cd7" + "digest": "32994b85c8b4a1383ca985ebc3382be88866cea1ff1315adfb71fb05e992a232" }, { "name": "point_up_2_tone3", "unicode": "1F446-1F3FD", - "digest": "917701169b3fb3e1b6e14a68e9572b25998ef2e38abac9ad8cf30100f8ea0dac" + "digest": "9e263bcfb82ada34ff85291f36e64e66b86760fb11a4e0c554e801644d417d6d" }, { "name": "point_up_2_tone4", "unicode": "1F446-1F3FE", - "digest": "20843904764c6c3e55792cce0c55c76f72b97788c5229cad655ebf1f2873b439" + "digest": "3edc92130a0851ac7b5236772ce7918d088689221df287098688e1ed5b3ff181" }, { "name": "point_up_2_tone5", "unicode": "1F446-1F3FF", - "digest": "1d0cca546027c717da50f90da65757af46fe7cd4e397da9b8e203446f707208d" + "digest": "cabb3b7da9290840ef59d0c8b22625bdb2e94842f01b0a575ccbc348f3069d77" }, { "name": "point_up_tone1", "unicode": "261D-1F3FB", - "digest": "5ede60379dee23166c6b834d73da8b55268e330f67058843b8a3705dca6ed71a" + "digest": "e496fda349072f8b321ceb7a251175f7244c3076661f5ede48ea75ba1acf8339" }, { "name": "point_up_tone2", "unicode": "261D-1F3FC", - "digest": "c94a15ef848d410aa5d32b8d0e453b59682fde6f39e6705cbb81cf0829833a81" + "digest": "5a8081323f3baa67e6431e21e16a36559b339f5175d586644e34947f738dd07a" }, { "name": "point_up_tone3", "unicode": "261D-1F3FD", - "digest": "d319ce72876d97a3b1d4bc7c0679e546a983f02145d723a0da5ed0b73a51cfe7" + "digest": "07bf0cea812eb226b443334e026e13d1ec23e013478f4af862a3919703107842" }, { "name": "point_up_tone4", "unicode": "261D-1F3FE", - "digest": "9171a27f86f27fd144347a17153fb56e30bd32e67a8f10f8c1f32a40cad4e009" + "digest": "1fbbd71433108143ee157d0fdadd183f7f013bafa96f0dd93b181e1fd5fd4af2" }, { "name": "point_up_tone5", "unicode": "261D-1F3FF", - "digest": "a894f87da4c3d33d5e6e74d003a33ec60c453db6507fe05d22235f807ead27d6" + "digest": "ad068ef32df32f8297955490a9a90590a0f93ed5702a052cd0d8f6484c6cc679" }, { "name": "police_car", "unicode": "1F693", - "digest": "7999869cb75be404fc34942b6f9d8e84fa7e259aa892a1e8e1652a5f02cceea6" + "digest": "0909be1bd615ae331a7cce71e16dee3ca663c721d5170072c593cb7c76f9f661" }, { "name": "poodle", "unicode": "1F429", - "digest": "8a568d8688bf19b440b7c1b49fcfe6672b8f75af0031d89ab6212623430acadb" + "digest": "f1742fdf3fd26a8a5cfeaba57026518dacaad364cbd03344c4000a35af13e47a" }, { "name": "poop", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "shit", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "hankey", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "poo", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "popcorn", "unicode": "1F37F", - "digest": "12264cb16fca9317e3ba8d5924a2c8f15f790e36d2f29e7b12aaaf77e1beb73d" + "digest": "684f1b7ef34ea7ca933aed41569bc6595a19ef0d546a1b7b9e69f8335540b323" }, { "name": "post_office", "unicode": "1F3E3", - "digest": "5e2d896cd646a2eecd5596af9e44ca1fa2745de5cedaf0f6d193b8243201c6cc" + "digest": "54398ee396c1314a7993b1cb1cba264946b5c9d5a7dbb43fd67286854d1d1a0f" }, { "name": "postal_horn", "unicode": "1F4EF", - "digest": "339aa61fa1567a1d159bb8204d15db889fbb6cc1106f6e1991b4a184d1bc1fc7" + "digest": "0ea12f44f3bae9a14bde3b37361b48bd738d2f613bb1b53a9204959b70e643f8" }, { "name": "postbox", "unicode": "1F4EE", - "digest": "ef1a6543fccb9f1009cc3782c51883e51167721a0b49e8ba21e8e6049b216906" + "digest": "bbc424ae8d46de380d7023a43ea064002fd614657d00330d3503275827ac87e2" }, { "name": "potable_water", "unicode": "1F6B0", - "digest": "4a2379835660dfa8b6780d662a10d1effab710f471eb9b5e6ade4772ba7e5aeb" + "digest": "dbe80d9637837377cc2a290da2e895f81a3108cc18b049e3d87212402c1c2098" }, { "name": "pouch", "unicode": "1F45D", - "digest": "cbd47ec1a65f5c642773d8ea2e7e57f7041a2d7ed9df05fbdd7bc8743c6dece6" + "digest": "9f012b90310b4a072b6a8fa2c64def087b5f7ffffaafc36e1856ba943a170351" }, { "name": "poultry_leg", "unicode": "1F357", - "digest": "d416e9464bd58073bd3e32eb06c0da96905609f47b9d667acdc0810e94237584" + "digest": "1445ec4f5e68a19e5a84e5537dca8190d62409070c954d112e6097f1a6b7f054" }, { "name": "pound", "unicode": "1F4B7", - "digest": "1ac491bb8a91613b2b1faaac4e7b4bc794d2abef69ac79de17d54c824c3ef826" + "digest": "eb11b83eb52adb0a15e69a3bc15788a2dc7825dedee81ac3af84963c9dd517b5" }, { "name": "pouting_cat", "unicode": "1F63E", - "digest": "ba28d75401d5bb98773acd35aaf173356bae4d5a5520a226559478138364ebdf" + "digest": "8822abedf3499cf98278d7eeea0764d1100ec25cad71b4b2e877f9346f8c8138" }, { "name": "pray", "unicode": "1F64F", - "digest": "fb0df9c1566014bd2df2a1afd59366b896f20c03ca3516e02e4be44ea556c8ea" + "digest": "735b79dab34ac2cf81fd42fdcd7eb1f13c24655e5e343816d5764896c03edeea" }, { "name": "pray_tone1", "unicode": "1F64F-1F3FB", - "digest": "c6d8cb46e65ad13a92e85f97e018176fd89513f23e899e15d1ad09e3b4009f4b" + "digest": "e8b6103450215e8566797f150978355e297deade4eb47a6371f7a7bc558fed9d" }, { "name": "pray_tone2", "unicode": "1F64F-1F3FC", - "digest": "2cd68cbe1ba3254f173ec8136af79cae64873bd0f20480158c3e6babd5a1a442" + "digest": "ee8baacd95d7e8dbad8a1f2d9a12e36c98f3d518db5d3b117d0a18290815e62b" }, { "name": "pray_tone3", "unicode": "1F64F-1F3FD", - "digest": "d2e81863f74a87b96335fb108e7b206f28ed18185362ab4d42a3b0523801398b" + "digest": "ae8c0caa9aca0a6c44069e76a7535c961d0284cd701812f76bbd2bd79ce2bd53" }, { "name": "pray_tone4", "unicode": "1F64F-1F3FE", - "digest": "ad1b91254b101d872325c325ebd1f2a6257cfe22e83de88e29dd16ffac191979" + "digest": "64f7b3178b8cd6f6a877ed583539eefe068fa87a0dd658fdcd58c8bc809f7e17" }, { "name": "pray_tone5", "unicode": "1F64F-1F3FF", - "digest": "23f40a11321decbdc6a1d274b9ad571041d261d364d13d1063c306e73ad52254" + "digest": "5bc8cdce937ac06779c87021423efcec4f602aa4a39dba90b00de81033005332" }, { "name": "prayer_beads", "unicode": "1F4FF", - "digest": "cb6f8700154f75749cf2642a25c03e255dc18428baf8b57f6bd807c92b83e28d" + "digest": "80177091264430cbcf7c994fbe5ee17319d1a58d933636cc752a54dafcf98a05" }, { "name": "princess", "unicode": "1F478", - "digest": "47b93eb52d757c3c000d9760391ecb942776d883b28050d833fa11612483d8ee" + "digest": "efabd28480a843c735f0868734da2f9ce28133933b02ab07b645498f494f3f80" }, { "name": "princess_tone1", "unicode": "1F478-1F3FB", - "digest": "1e4073c2abdf51a61a1a85a3e063541fe96e9b9ec36ec6f7fb9c98deeb230869" + "digest": "52b88b99ba64f82e8f36e2a1827c85145e4fcd6863478c2345fe9fa9e8901cdf" }, { "name": "princess_tone2", "unicode": "1F478-1F3FC", - "digest": "6a0a5dc447cd887798f908c15972e7a12d28d81f168b92bcb105786ac253bea0" + "digest": "7e44289404693668f20e681fcdc2e516512d54a69c627eedae958f69dfe6eea9" }, { "name": "princess_tone3", "unicode": "1F478-1F3FD", - "digest": "2f08d22fdfc7a7d66fcd87ae716b811f43077f5bb17fef87f5b7e2aa93700d70" + "digest": "96c9a9857348d7a1a8be899c50d55b352b9a9fd5c65e4777bfa199fe7929d41c" }, { "name": "princess_tone4", "unicode": "1F478-1F3FE", - "digest": "02129211bf7bf7ff6de35913b7069aee151532d878b8c4f7e24c012e5b09d4b4" + "digest": "67696f96be60f2a36598072172d2db197d007e6c1ac3acef526a5ce6d59bf3f7" }, { "name": "princess_tone5", "unicode": "1F478-1F3FF", - "digest": "d676f103600b69dbfdb469469a77b9d561ec460ff862befa58ab30ddc909c9f7" + "digest": "007f624e2fad91bb57ce32ecd35213a796d71807f3b12f3f1575bf50e6a50eeb" }, { "name": "printer", "unicode": "1F5A8", - "digest": "c44402c87071f8d31d3997abab53ab9f8f7c11434e747380928814ceb6b0a417" + "digest": "5e5307e3dc7ec4e16c9978fb00934c99c4adefca7d32732a244d1f2de71ce6f8" }, { "name": "prohibited", @@ -8572,57 +8572,57 @@ { "name": "projector", "unicode": "1F4FD", - "digest": "fc361282f367926254c08150b02cb8fda7fa8d2c9c939d9360c78bf19a4f982e" + "digest": "7f8e1fdb89584849a56ee34c62cab808af48b7bd4823467d090af4657a2e0420" }, { "name": "film_projector", "unicode": "1F4FD", - "digest": "fc361282f367926254c08150b02cb8fda7fa8d2c9c939d9360c78bf19a4f982e" + "digest": "7f8e1fdb89584849a56ee34c62cab808af48b7bd4823467d090af4657a2e0420" }, { "name": "punch", "unicode": "1F44A", - "digest": "5759db1d7093744c74b840bbb4761fb025d6633f8fa539bcb35dcf54fc05ceb6" + "digest": "c7e7edf6d64f755db3f02874354f08337b3971aff329476d19ac946e0b421329" }, { "name": "punch_tone1", "unicode": "1F44A-1F3FB", - "digest": "793b3fa2a43c23b2c1e1b48b86ae35e8c4024cd065fac0a0a5ada87cb78d6de3" + "digest": "c9ba508b0c36041047473782acfedab5af40dd7946b33daf4d8d54c726e06a11" }, { "name": "punch_tone2", "unicode": "1F44A-1F3FC", - "digest": "6fc2467e99982ab00b0c352c6f7793d34faf17b16a0312082c9bd1f0709e3938" + "digest": "d53011cd2f3334c7b3fffdfe1e2b8cc1c832c74306e1ac6d03f954a1309d7d0b" }, { "name": "punch_tone3", "unicode": "1F44A-1F3FD", - "digest": "bf747b29952550c5b4d3807b9ed85b5e5d4bcc3265b0e214791f7db547f861fb" + "digest": "f7522347094e0130ed8e304678106574dbd7dd2b6b3aeb4d8a7a0fef880920b2" }, { "name": "punch_tone4", "unicode": "1F44A-1F3FE", - "digest": "3b6c0ccb682552f32d6744c438e3af04a1732c67a74bcafb14c723cf526fed87" + "digest": "3e62bdd426f3e6ff175ce3b8dd6f6d3998d9c1506128defa96b528b455295b47" }, { "name": "punch_tone5", "unicode": "1F44A-1F3FF", - "digest": "945bae1aa3587cd1dc57d1ec4da18c67a59e0e7150dcc8735e5357b4ea1234ac" + "digest": "7d9bff777dc4ec41ac132b1252fa08cf92a398c8dc146c4a5327b45d568982d8" }, { "name": "purple_heart", "unicode": "1F49C", - "digest": "e0eb886e74f22d40d059ff3a089d472af53c6c53de380f428cca140dfd046345" + "digest": "a6bf01de806525942be480e45a4b2879f91df8129b78a1b8734d4f917bcab773" }, { "name": "purse", "unicode": "1F45B", - "digest": "67d82ff9a4d76148b9d98538d4b786f880058a556e650ec3f93e1632aa42aaa7" + "digest": "2b785f36e01875d66cfda2192c8c53606e7224a7c869a4826b62cb61613d60c8" }, { "name": "pushpin", "unicode": "1F4CC", - "digest": "c4de129d5d8744caffeb2f499fcc0bc6b551843938f8166ffecd0de00bda66e3" + "digest": "c3f7d7008be6bab8dc02284d4d759abf7aafbb3dbbe3a53f0f5b2ff685af88f8" }, { "name": "pushpin_black", @@ -8632,202 +8632,202 @@ { "name": "put_litter_in_its_place", "unicode": "1F6AE", - "digest": "b26d3b68bd62d30ecfe75cfaf309a7a0f91e92db0aa18b0b97b97baf0609d4e6" + "digest": "f52a57d6f1bada7b6e6b9a6458597d70cb701c01e1120d8cb1d7ff65e01d405c" }, { "name": "question", "unicode": "2753", - "digest": "258e3169bae177fb0f01ed5f9b933f7f02dd2673e12a316af44a0c3729a78a2c" + "digest": "40050a1fd29bed321fd601d13dc33de5d6084121f1d873b29bde9dc3d823a310" }, { "name": "rabbit", "unicode": "1F430", - "digest": "9817a7454aeda77d28f63eb13c0dc0a6d9e6c9abe3dcf538b4b3477e494cddb6" + "digest": "678ad953a7ab8f618c59051449a67c965d1f04f42dd6f6669adaf3fadebd080c" }, { "name": "rabbit2", "unicode": "1F407", - "digest": "67ba57a31b0768a2118faabdcb088f96f1441e1132397f65b6937d523ff7dabb" + "digest": "19b1f5108292472434cc7a49efac4ea9275779735c7aeb0f15c36021d5998ca0" }, { "name": "race_car", "unicode": "1F3CE", - "digest": "2e9828e3884c79ad7e9e1173d3470790f3f56cfa08ef4e38deff45db0728c66c" + "digest": "46f4814259d3d17ff35c04110e73e5327aee99f4711cd459ca1ee951508da3a6" }, { "name": "racing_car", "unicode": "1F3CE", - "digest": "2e9828e3884c79ad7e9e1173d3470790f3f56cfa08ef4e38deff45db0728c66c" + "digest": "46f4814259d3d17ff35c04110e73e5327aee99f4711cd459ca1ee951508da3a6" }, { "name": "racehorse", "unicode": "1F40E", - "digest": "36aa3c7123ee7e15600657166032b21b8edeb192cf6d3ada39b5c65001f7fc40" + "digest": "a57b7aca35347ada8225eeee06b70cfd040484104963b4df56ea8fec690576b0" }, { "name": "radio", "unicode": "1F4FB", - "digest": "b1403f9a883405b909208f52c9474c2d3923681ea0b02609a6e9dc12460319a5" + "digest": "9245951dd779cdd141089891b15a90d3999a6358acf1fc296aa505100f812108" }, { "name": "radio_button", "unicode": "1F518", - "digest": "9bcdac17b3620331a32f9bb876812231a701eb5a7f696e7d875f877ab92159fc" + "digest": "565bec59198df2592e96564c6e314d3cde33c47b453db1bec6c5d027b5cb4fd9" }, { "name": "radioactive", "unicode": "2622", - "digest": "5ad8e8594617c0153672a76421deb836e05c6098020c33af3f975f8fcfe216e4" + "digest": "0ed6634057824e0cfd10b2533753e3632b0624341a7eac8d9835706480335581" }, { "name": "radioactive_sign", "unicode": "2622", - "digest": "5ad8e8594617c0153672a76421deb836e05c6098020c33af3f975f8fcfe216e4" + "digest": "0ed6634057824e0cfd10b2533753e3632b0624341a7eac8d9835706480335581" }, { "name": "rage", "unicode": "1F621", - "digest": "02ac70551fc51478884c133b29539cae58b463c760db38c0aeec1bdf5b282312" + "digest": "d97ba6bd08eec46dbc7199f530c945b73a87a878e35397b0a3e4f2b45039e89e" }, { "name": "railway_car", "unicode": "1F683", - "digest": "8490e2ecf94c7c1d1e22fea0d80cc18a49648741009e51984f583b17bbd022e2" + "digest": "2cddc08d555e7fc24e312c3d255ed013fbf9cd2974a6918369c32554049ba2be" }, { "name": "railway_track", "unicode": "1F6E4", - "digest": "63ee881cc775d5b2711082b6c96ab44d5204c5d390afd6d8ee97e52aeeaa5e5e" + "digest": "0da351b6d4e75c6beeaef1225e151d9580d4b5c41dfa1cf192715bf3cec981d7" }, { "name": "railroad_track", "unicode": "1F6E4", - "digest": "63ee881cc775d5b2711082b6c96ab44d5204c5d390afd6d8ee97e52aeeaa5e5e" + "digest": "0da351b6d4e75c6beeaef1225e151d9580d4b5c41dfa1cf192715bf3cec981d7" }, { "name": "rainbow", "unicode": "1F308", - "digest": "bbd8ecc8d0737948969a3539d2d202e599404e509f1a21bdbb0a0c41c2540522" + "digest": "a93aceb54e965f35e397e8c8716b1831614933308d026012d5464ee42783ed4d" }, { "name": "raised_hand", "unicode": "270B", - "digest": "4192881a0d613b4fcb19b1c2d8b83aadee6f0b12170721c8dd7b1ccef6540199" + "digest": "5cf11be683aea985d5ba51fbd44722c2327311bfe26b61c3d441c90f5d5a195a" }, { "name": "raised_hand_tone1", "unicode": "270B-1F3FB", - "digest": "df2e046c99dceb9184c50a777b403d72bfb25ff473d6a4e20bb9a731db64ed8d" + "digest": "865afca29b57577fed8fe8c2be57b74254a008c8cf34194680be2759239b5f5d" }, { "name": "raised_hand_tone2", "unicode": "270B-1F3FC", - "digest": "ed179299a1c397cd51cf6067d6795d71a3831d35e1ec9eacbf0286c8992c1e7a" + "digest": "832169a0b626a682a58a3b998f68413657b4962c1fab05f1fdc2668e82727210" }, { "name": "raised_hand_tone3", "unicode": "270B-1F3FD", - "digest": "cacbd0ddef65bc01a41bd921ea159f8cd89050309b10f15780d6199f79434a54" + "digest": "3959a873ad7671de82c615c4ed840b011e67baafb2bab7dd16859608d3e83cb1" }, { "name": "raised_hand_tone4", "unicode": "270B-1F3FE", - "digest": "04c934c7a55b83bcfa7f3880fc1f6aa0f188090c37b9670e6775a512a1cf59e9" + "digest": "db542f65d076ccf3dbfca27cb7c2f135a8bf7a487a81a04873e70172bdfcd579" }, { "name": "raised_hand_tone5", "unicode": "270B-1F3FF", - "digest": "da0c4283b7b19861237c023234c6db28045b8f5a5971acb015733e08e2940e86" + "digest": "88ca884d14baaae48df21d75c22d82fb15bdc395e42026f5ca34cd65e5ae8674" }, { "name": "raised_hands", "unicode": "1F64C", - "digest": "308e475f38558e73bd66e28693d77478caa5bca4360cffaffc2a97b5858c56ba" + "digest": "2ee73466a3f5079e542857fe6f5497e9f87753a81854985ce3356a8d3da1d8b8" }, { "name": "raised_hands_tone1", "unicode": "1F64C-1F3FB", - "digest": "e39b9bc49dccc127e44f543e98961fcf5bcd44d6e216741bcd10ec3667263c84" + "digest": "43e73c60f040a66374b8ec98f3629a90d13ae9f472446ed7676cd5573e824f4b" }, { "name": "raised_hands_tone2", "unicode": "1F64C-1F3FC", - "digest": "f376ab13071ffdc11888ec221ef5b4de546ca0f60bd9ae30bf3da4066c220462" + "digest": "fcc5255bb2b06dc82d6878e74cf34e8ce118c70004a06d39a980683772b98c52" }, { "name": "raised_hands_tone3", "unicode": "1F64C-1F3FD", - "digest": "67694325a43e629c00fa9bd2ff7e19f84f216b2855ae2cf097762dfa7aca25e6" + "digest": "3ee3e0aafef486e766a166935e8147fb75a7329cfebc96dec876cc45e83a8754" }, { "name": "raised_hands_tone4", "unicode": "1F64C-1F3FE", - "digest": "a2254fe75a0770708916a4ddd5db4420221c6ea9db9f74068d14eadfc0f3772c" + "digest": "78a8cbf6b2b85be4d6b18f0ff6a77f197963117955725fb7e57e0441effb928f" }, { "name": "raised_hands_tone5", "unicode": "1F64C-1F3FF", - "digest": "bd7c9897cefb454ccdc46027bf56d6587565bdd345d7d0f081b7b671a53f6c99" + "digest": "2a5ed7334a17172db0cd820a559e7f75df40ec44de6c25d194c76e1b58c634cb" }, { "name": "raising_hand", "unicode": "1F64B", - "digest": "d57178fc77e9fa140682634da35f9ab12a65d9b4c506b7cd8a9697f1b5910bdb" + "digest": "512750b00704f1ccefd3c757743540b785ad7670dbbe4a2c4dca8d93e6701920" }, { "name": "raising_hand_tone1", "unicode": "1F64B-1F3FB", - "digest": "f46b34361ef79743f3187d6860182bbe1ae411031db7fe5c0f7292fa472b9c16" + "digest": "2897722f091c273dd3714cff7423c2475bc3070416c28014ca03322b9ece48bc" }, { "name": "raising_hand_tone2", "unicode": "1F64B-1F3FC", - "digest": "20b85a2ebca150b2020a04b41d34884c78c22f42c251e2b9d23fd3724574143b" + "digest": "59199b334b3845911382c1f29bd7c0d5ef9d2486417345e265b166ead7d3e1c1" }, { "name": "raising_hand_tone3", "unicode": "1F64B-1F3FD", - "digest": "5e0401b528c2b8edff766d39cdcedbe9abebe4c940df7a36ace61f59c08d508a" + "digest": "f95b338d5efcf14ef12f415a2c1bba93df48628ddc94f34f70c31e1b3c2e1d28" }, { "name": "raising_hand_tone4", "unicode": "1F64B-1F3FE", - "digest": "e4f5624264269ad09cde207cd7d4eb0fd46de816880daeec457ac8cd51cc1b7b" + "digest": "951ddbfdb57d5a60551b59b3d0f7ca00a64912f4a101a73afaebd68445cd6cec" }, { "name": "raising_hand_tone5", "unicode": "1F64B-1F3FF", - "digest": "eb34b6c037bee5bbc4222f6aab421aa785f527ebf1b5e971769e5102244d60e1" + "digest": "9370f93704d8f89ca6dc946715eab5e7dba82bf04dd68c00f5c0abb8bc16371e" }, { "name": "ram", "unicode": "1F40F", - "digest": "b71950d7a286a4c4909c5ec7c35211c2a5c20b6bad341bd863c6a85c4bcf9c80" + "digest": "2875ab28e1018b39062aeb0c5ce488c48a98f13e9f2364470a0a700b126604f2" }, { "name": "ramen", "unicode": "1F35C", - "digest": "7dd185b24852b577913edc78647cd53b27d42e225fde29aa2f3aba25c980b5c4" + "digest": "425662a49c4c13577c0de8d45d004e5ba204aaadbaabae62a5c283ecd7a9a2c5" }, { "name": "rat", "unicode": "1F400", - "digest": "7a10d9ba5ee1010d421d9cf73d7966507302a69617a32fe9f1a00d57a31f7bd7" + "digest": "14380d65498c6ce037c02a93bca2b24f25a368d85278d6015b8c9f7cd261f8e2" }, { "name": "record_button", "unicode": "23FA", - "digest": "c2ba672994ad0f99d7fdc449f3fee45a2dca68a58f9fe95825b38465a30ef44e" + "digest": "92be12161ba206bb2e06a39131711c7b17368d55b4aae0b48f0ac5b6b1cde76b" }, { "name": "recycle", "unicode": "267B", - "digest": "74a54ed62a40dfbdcace1f08b085658a77d45c62570273927ad270bf9a8a2f4d" + "digest": "c377e8537367b05b5de9be860a0fcabd7aed2bf4ba146eefc423671a21530369" }, { "name": "red_car", "unicode": "1F697", - "digest": "558730d6418aa5d85b73af58c8041efd12cff906e26ea47c50963f66d33d6eb8" + "digest": "8a99832a195263c0e922af53d52dea37aa3e07032b3c2a1977f8527b4a144b9c" }, { "name": "red_circle", @@ -8837,72 +8837,72 @@ { "name": "registered", "unicode": "00AE", - "digest": "ed924107384461aabb4924c401c6c087ffa047bc2ef735823e7c2be67804707c" + "digest": "9661b1df529ecb752d130820c55c403e5de263748eb02f7fea327818bc282d94" }, { "name": "relaxed", "unicode": "263A", - "digest": "65072f7b9bfaaa92b8a0ed012dffe2cfd2efa3748264aaf450aa31ba6bd44045" + "digest": "2d5aed4fb8504c6d6660ef8d3bfe0cc053dcd6099c2f53748c202dc970c639bc" }, { "name": "relieved", "unicode": "1F60C", - "digest": "1f2c7ae6a9d74a112de89403be6eca3d8155d70395e7fce51032fc961f235c7d" + "digest": "b4ce2ba6c220d887fe5e333c05ed773df9b6df0ac456879fd8f5103ff68604a5" }, { "name": "reminder_ribbon", "unicode": "1F397", - "digest": "e4a2afc7dce40589657f7043ba8acc9638fd4117252278233ea89f84cddad387" + "digest": "c3de2a7c9350b77a0b86c0dcce9dcd9953ea8a97aa1e7aed149755924742f54d" }, { "name": "repeat", "unicode": "1F501", - "digest": "27b6dad9215e58e24c607a39dbf398ecf66ccb692c81e08eb2f5f4912db30522" + "digest": "b9512d508613ed0eb3181eb1030f7f6fd6b994476ecdfa308733c6df975fb99e" }, { "name": "repeat_one", "unicode": "1F502", - "digest": "052d13f2b08eaf70b31252aa78f95d06fbe22c58945c19381b13cbeb1c855651" + "digest": "53409cf24dd4bb0d7b50ae359f15d06b87b7f4a292ed5c3a09652fa421a90bf2" }, { "name": "restroom", "unicode": "1F6BB", - "digest": "b77fbc4247c241362e5ef9e6eb58b1b437aa9d16b65886cec0c55ceb55c1440e" + "digest": "2e7a1bfc9a9d49b0272230a91db7369e24d54bf1de8e683d36b85f1d8c037f77" }, { "name": "revolving_hearts", "unicode": "1F49E", - "digest": "2b8925d3e78df2dba8534252fe60bf03285346f6b3697be7668bd568e6d85931" + "digest": "c43d3197cb4cf06659f643638f6c4e91a2889e0f6531b7d81ea826c2a8b784fc" }, { "name": "rewind", "unicode": "23EA", - "digest": "91a95b26d12ca76111556096f4d96484c9f1d7e1b20ccff5a3291b36e529a6d1" + "digest": "d20c918c1e528ff0947312738501ca9a6fb6ff4016aad07db7a8125d00fd65cd" }, { "name": "ribbon", "unicode": "1F380", - "digest": "9c0296d8c2baa84c99347c431bf79b288d98b5f17b1ce7605ad7ce1da265d5aa" + "digest": "74315fe907f9f0203afe139cd4552aa442eecfa2a64fac12db3e1292fc5a8828" }, { "name": "rice", "unicode": "1F35A", - "digest": "e34849496a79e71ae4700df94f2a54895bf6de758a92edeae33fe78295a3ba21" + "digest": "f544f12606de59d28739798003f14ebd8869856add8e24496ec5dda3e131daf4" }, { "name": "rice_ball", "unicode": "1F359", - "digest": "52df5da8b0edbdeb56d66e0f30ad4549abdd81c064f7269d920dcac66a3df2e4" + "digest": "2cba6f5364cd366859bc8948897b65fc97b225ea7973d9be3b24aba388fed8e8" }, { "name": "rice_cracker", "unicode": "1F358", - "digest": "d55f8f9d807f4619eb243c510938067a7417a64bd9435b05dfeb2a36fdb2b6a0" + "digest": "ac0f805d41d4f322154c1968bd3ce3e9aabcd39d908182e52fd7d28458dbef92" }, { "name": "rice_scene", "unicode": "1F391", - "digest": "482d854d8d30edfc1ecd48a4ce476e6498606321405bf5a0b4ff74489a092af8" + "digest": "b942a06d3da0570aca59bab0af57cd8c16863934f12a38f70339fd0a36f675f5" }, { "name": "right_speaker", @@ -8932,7 +8932,7 @@ { "name": "ring", "unicode": "1F48D", - "digest": "ae2a93e7895b9b89f5a39f01d356ffed988f219ef8b658a56c55285826a4533b" + "digest": "b5322907222797b5e1786209cda88513e76cd397a40f0a7da24847245c95ef9d" }, { "name": "ringing_bell", @@ -8942,47 +8942,47 @@ { "name": "robot", "unicode": "1F916", - "digest": "cc0e363774b86e21a5b2cea7f7af85bca9e92c124ebcd39c6067c125048baa60" + "digest": "4d788e6ec89279588b036fca6b17f5a981291681df8f90306ecf5c039de40848" }, { "name": "robot_face", "unicode": "1F916", - "digest": "cc0e363774b86e21a5b2cea7f7af85bca9e92c124ebcd39c6067c125048baa60" + "digest": "4d788e6ec89279588b036fca6b17f5a981291681df8f90306ecf5c039de40848" }, { "name": "rocket", "unicode": "1F680", - "digest": "65d8bd005ceac41904237b7a8c5f55f16713a55d971522f0bbe63a1d548e515d" + "digest": "b82e68a95aa89a6de344d6e256fef86a848ebc91de560b043b3e1f7fd072d57d" }, { "name": "roller_coaster", "unicode": "1F3A2", - "digest": "907baab1f3d7becf3f8a3b1264642b395bd73b4af49e23058b3abb5c69e9106a" + "digest": "a65e9ace1d7900499777af1225995f17af90a398bb414764c20b6e09a8c23a2c" }, { "name": "rolling_eyes", "unicode": "1F644", - "digest": "f596f203030b6c9bd743848512aa3fc7919447020d35ae5c2bf13ccb16fa2dbe" + "digest": "23dea8100da488a05721a4e82823eb438393b0ea762211c9ecef011d127aa1b7" }, { "name": "face_with_rolling_eyes", "unicode": "1F644", - "digest": "f596f203030b6c9bd743848512aa3fc7919447020d35ae5c2bf13ccb16fa2dbe" + "digest": "23dea8100da488a05721a4e82823eb438393b0ea762211c9ecef011d127aa1b7" }, { "name": "rooster", "unicode": "1F413", - "digest": "6cefdaa45631ed8c9480e15f578c793d95af81b42687164fd7900eee325ccf07" + "digest": "2b90c5cf6fa46da13eb77285443d600afcea0c48bd1d215d60167e7dc510da5d" }, { "name": "rose", "unicode": "1F339", - "digest": "584909a4a2ece625c688f8479a39692bb8e816b692e6eb7dfd40cb045259b1b2" + "digest": "73799e459dba188de4de704605d824242feeb65d587c5bf9109acf528d037146" }, { "name": "rosette", "unicode": "1F3F5", - "digest": "0ce3b85ca05124ab99d57ebc9aa17bb246ee614d2fcda1ef62bf42ac7e616148" + "digest": "2537def4deef422d4e669b28b1a0675259306ab38601019df3ec3482b14e52d5" }, { "name": "rosette_black", @@ -8992,542 +8992,542 @@ { "name": "rotating_light", "unicode": "1F6A8", - "digest": "369e069e0bfecc7413e75f4015e9c1de527a33c7cce3f6c2b4adb60a0d9d338c" + "digest": "91fcdb85a752ae904d335a978c7e7936aed4c75d414b35219b5a74430e51555f" }, { "name": "round_pushpin", "unicode": "1F4CD", - "digest": "1bc5fe5a90a6e56ea00246f1b008a0e0cce0d77c226dc0300bf9a2804b543877" + "digest": "8ffca77bbdc6f1f726daf3abd6eff338a5ad1aa9b09dbbd8782c1e7ef5452f30" }, { "name": "rowboat", "unicode": "1F6A3", - "digest": "c10e09bf8be8b1a8ef3113edd9327126d6a4644f3bc81c7ada2922851e4d1cfb" + "digest": "83715d83a061926d4ad3bb569b21f5d337e3ebd4c9bcdfe493e661c12adc0a16" }, { "name": "rowboat_tone1", "unicode": "1F6A3-1F3FB", - "digest": "a84fc1b30d1a284dcd3899dc4de8f11e7b65c258528eb41c7dbf8f82425fee12" + "digest": "e279ac816442c0876fba1f42c700b80f2fb6de671e1a8a9e9d11b71eed5c58e8" }, { "name": "rowboat_tone2", "unicode": "1F6A3-1F3FC", - "digest": "85f001430a2ad607a15901f7c2dcf8381471f42d6cc0775e76a2ff1f457151c1" + "digest": "6a48eba352ed4971d26498b6c622e5772389c89c5205ed02acde8e995dddcc3b" }, { "name": "rowboat_tone3", "unicode": "1F6A3-1F3FD", - "digest": "adf8b1e45a46a13f3db40c29df0312216558e9d0c615aa46a8e913cee5003a81" + "digest": "875948f6d8354ebd95ce9a66fde30f06a8366dcd89d5ca3e660845f8801e9305" }, { "name": "rowboat_tone4", "unicode": "1F6A3-1F3FE", - "digest": "05482749ec40bdf02e53fc42d316c51f4f3ed643f21e8fc16b81930e4a884bda" + "digest": "8c7ac7346b0020d0ff5e2f4a1efb1b7785eac637f17556663ec33e2335083f0a" }, { "name": "rowboat_tone5", "unicode": "1F6A3-1F3FF", - "digest": "d4bb337d948996d4a23d87f99988f02fc207815b862082ffd2eef5f0c1016aa9" + "digest": "a399dbb647892b22323e0bf17bc36a9b5f1708ebedf9ba525233ee7b9d48339a" }, { "name": "rugby_football", "unicode": "1F3C9", - "digest": "e14aebbded78d4a5e9b4028f79a8ca840d02798c6758cb9e926e992e2a35a4f3" + "digest": "cc6f00ade3e0bbb7899e7bfb138b57216dd66de26d7967d5ffa501f382ed09f4" }, { "name": "runner", "unicode": "1F3C3", - "digest": "58a884f06d37b0ce78197bebcd3f0e102dd90022ebd86ec70a2ef5a5cdf9683b" + "digest": "e9af7b591be60ade2049dbada0f062ba2d3e17f02bec76cbd34ce68854a2a10c" }, { "name": "runner_tone1", "unicode": "1F3C3-1F3FB", - "digest": "65f1633d1517803de23686d2dbcc75a5787874266db4981138ccdbe4badc773c" + "digest": "21091cbb09c558712ecf63548bf28b7995df42bdb85235088799a517800e52f5" }, { "name": "runner_tone2", "unicode": "1F3C3-1F3FC", - "digest": "2bc81f3fb77445cdc75c34806ab0ce912bacfe47f63b5d2011a4f5d370cf7064" + "digest": "1fe3d194f675a46fe67799394192e66c407dd81163363692c5e7da32ddb9af2b" }, { "name": "runner_tone3", "unicode": "1F3C3-1F3FD", - "digest": "beaf5f254cba2991fdd0c38ce2ddd1b4c1110e15b2b7bc026d32f162e295c4ef" + "digest": "8cea1bf4ef3be71f42dc5bae978d5b7a197a3851543225349ef0dda29a370537" }, { "name": "runner_tone4", "unicode": "1F3C3-1F3FE", - "digest": "21d531ba9b3d13747ad636b8f7a6f184c974bf61d9f529975a64f9629263c407" + "digest": "c33f0b8b5a71d295fb6ba322e79446964a8eca9e4573efd591e4273808b088a0" }, { "name": "runner_tone5", "unicode": "1F3C3-1F3FF", - "digest": "b02a5bcc58cc45f8219262ec44c77764172fd8f2624d9122ded4a5a5db04c0ed" + "digest": "9f59e6dd0fdf2f17bceb41f5c355b4e6f3c8bb8cbd8af0992f0b5630ff8892e8" }, { "name": "running_shirt_with_sash", "unicode": "1F3BD", - "digest": "431bed35f4a55175bf99af769e74a81e8650c6ab34af6ecddaa1417ff7e437e6" + "digest": "7542307d3595aca45e8ccae66b6e58b6e92870144b738263d5379ec6dc992b76" }, { "name": "sa", "unicode": "1F202", - "digest": "a47a480631f874e8a2cd69b5d513f90a1e81a96bfa2f6025bf244a82baca3656" + "digest": "6042bcabd1516ef3847d695aba22851c49421244432d256e24eba04e8a223dab" }, { "name": "sagittarius", "unicode": "2650", - "digest": "14871e6681c35e4a63a0b19613f77b3674d00cb78d06975e02ca29e61b5cea8c" + "digest": "a02593e025023f2e82a01c587a8c0bbb1eff88cbcabf535a1558413eb32ed1d5" }, { "name": "sailboat", "unicode": "26F5", - "digest": "6f742dde6c180a174b771aa3942b558e98a3dc1eb212dd31add86c5fa5620865" + "digest": "c95ef4dc939cbdcb757ef3cd90331310e8c0a426add8cc800bae2540148a3195" }, { "name": "sake", "unicode": "1F376", - "digest": "aa1392790c805950779dde7778292c937f8c1aaecb522876171d5ee542ec51f8" + "digest": "0a786075f3d9da48ae91afccf6ae0d097888da9509d354ee1d3cb99afcc88fe4" }, { "name": "sandal", "unicode": "1F461", - "digest": "14f1e9003a6acd90a55f23c48ed87a758fca586f2e0b0edc4dc9d1deef9eb067" + "digest": "03c3077cb4bd900934f9bdf921165b465e5cc9a6bee53e45a091411bceb8892d" }, { "name": "santa", "unicode": "1F385", - "digest": "12feddd84eb49ce30ae68d4f93d66e2c0dd11297a4d1275c9a50d4f35bea83a9" + "digest": "178513e3d815917e59958870f5885b3414b43a16b8056980c863a468dfe00179" }, { "name": "santa_tone1", "unicode": "1F385-1F3FB", - "digest": "a75813770efe27d5b4c80ad892d0c796d88d1a0dbb1bd02d5f68882d7abad479" + "digest": "bf900bbc19bbd329229add9326e28e8197b69d6ddceb69f42162b0200fde5d16" }, { "name": "santa_tone2", "unicode": "1F385-1F3FC", - "digest": "90f8072fdde5f4a275cbd1902d6c94689d453b1bee0336213dc9d6f7e1d038e1" + "digest": "7340f2171adab97198e3eecac8b0d84c4c2a41f84606301a0d10e9fe655c93d1" }, { "name": "santa_tone3", "unicode": "1F385-1F3FD", - "digest": "0973053e7b77d268080126a50b95b45429630e5d49f62210e7b71840794c7dc5" + "digest": "7368ab75454ec28d8f7d6baef6ad69b5278445a9f50753f6624731bffde32054" }, { "name": "santa_tone4", "unicode": "1F385-1F3FE", - "digest": "5cd49c0d199a42846b400b3c1244d448ed6fe5ce993d379817cb2a5f7c0b609b" + "digest": "0ee60188353e0ee7772079c192bebbc6d49e74e63906f840c66da4eb35f4f245" }, { "name": "santa_tone5", "unicode": "1F385-1F3FF", - "digest": "a54c36dfa99b39549fb1d3dd7f0021a7aee28112960172ed466dacc67961c525" + "digest": "e4378a0cc5d21e9b9fe6e35c32d1ebc6fb8c2e1c09554cd096aeaefd3a6eb511" }, { "name": "satellite", "unicode": "1F4E1", - "digest": "3b9797c8161526edce0bd8e9b8563055166f9307761c367ab3e2ad7645b6dee0" + "digest": "c9d63118dcb445856917bb080460ab695cc78e715dcbba30ba18dffa9e906b27" }, { "name": "satellite_orbital", "unicode": "1F6F0", - "digest": "104b135e3736a4bcfd51a42dadb53bf3e00d7f85d77a94bcb86c6704fbfacd01" + "digest": "beb2f50e7f2b010e76bed9daa95d7329a93c783d3ebc4f0b797dd721c5e3d32d" }, { "name": "saxophone", "unicode": "1F3B7", - "digest": "1090da174ce8aa4f7d35025f65d5ac235e09310abde998d2a725ef3a989a2b75" + "digest": "dfd138634f6702a3b89b5a2a50016720eef3f800b0d1d8c9fe097808c9491e96" }, { "name": "scales", "unicode": "2696", - "digest": "b2984caa182b691a33650344708f47c61d6d319fd067760d7594c2ef60c1e27b" + "digest": "2280c026f16c6b92e0daa00bc14e718770f8d231c571ab439bde84d837cf31cc" }, { "name": "school", "unicode": "1F3EB", - "digest": "caf35260dc465a833521e4a0034201978fed41bbf72cd770756b3340c60e8a0c" + "digest": "af198b068a86ccad3daec4c6873e6b4735086c1ecbb3848182e70bae9aa3ee24" }, { "name": "school_satchel", "unicode": "1F392", - "digest": "a89a2cc46d24d57c2d6b95ed7a56ed829ae2f97b9e6201b2d5adc78c2b78518b" + "digest": "f670ae8aea67eb9d8aaa0bf2748c1cc3e503dcc1dbe999133afcdf21af046b24" }, { "name": "scissors", "unicode": "2702", - "digest": "a4e91127ac83acf5ebc64fbeca768cbbf24f2f0a484861c9c8104bee377b97ae" + "digest": "95225be28f05d8b5a6b6e6bf58d973f61f183ad4fef55a558dc1b810796b85c8" }, { "name": "scorpion", "unicode": "1F982", - "digest": "a090a96731bc1171b054b51abec4c9b36faa62708fd51ac48277ccf5e55d9d12" + "digest": "d41119d1ea5daf727c17dbea7dadec1718c72fc9f98ae88252161df5fde0938a" }, { "name": "scorpius", "unicode": "264F", - "digest": "1ad9bc1030a8f58f3f3223bac52c954cc7a0350805a9df7a42a26972c3b74728" + "digest": "a36404b408814c2ecb8fa8b61f5c5432dfcf54cae8c09cc67b8d0fadf7cbdc03" }, { "name": "scream", "unicode": "1F631", - "digest": "75d613786737ee9c0a74da7394b9ae190eacc7182164627ad8205ac64e4cc09a" + "digest": "916e4903a4b694da4b00f190f872a4e100e7736b7a2e6171fa1636f46bf646e6" }, { "name": "scream_cat", "unicode": "1F640", - "digest": "eee04ff27c2c6b57d698cb87b0af8064ba8313ffc13aa090e38cd5aa8c3d2f76" + "digest": "f1d3a6ff538064e7d5e0321bbc33aba44e8da703dc1894ef1403c0cd6d63d781" }, { "name": "scroll", "unicode": "1F4DC", - "digest": "b8205847649e3ce6b946f1d1da972ed015adde3841c62971b8169235f4b41c1f" + "digest": "9b2cb00860bcc2d20017cafb2ed9681b6232dc07273d489d75d53ce29e4ba3ab" }, { "name": "seat", "unicode": "1F4BA", - "digest": "054c4db0bc8939e9dd951a3f73e9ae4b3c31652784f4d304b509c2bd32f98e31" + "digest": "ae68d86fc2a07cae332451b23bd1ceba3f6526a6c56d8c1089777fa4632850e1" }, { "name": "secret", "unicode": "3299", - "digest": "77daef6e5c91d55228781ddec954a7089d1851297ec81daef6e813cd22915b5e" + "digest": "1d0b9adde2657f41421b135962de20820cf4b4eb0204044f9859522ab9d211b0" }, { "name": "see_no_evil", "unicode": "1F648", - "digest": "aa5883fe605aeaa172d16640b8347580f9cb7d85a596da1b13955f27b0b79297" + "digest": "3ff66d2e84b36d071d0a34f8e41cfd620a56b83131474ea50ed7803b635551ed" }, { "name": "seedling", "unicode": "1F331", - "digest": "a75ec929402de1e653fd6bc89e5be2f92fe5fe52f39e4b6c290eae3c59172b56" + "digest": "c0ec5e6d20e1afdc4e78eeddb1301c8b708ad6278e7287a4e4e825417c858e75" }, { "name": "seven", "unicode": "0037-20E3", - "digest": "c6a34020f6bb25871164fad44302a45c5bffced87f51dfbb816c2985ad7f6a1c" + "digest": "ae85172d2c76c44afb4e3b45d277d400abb2dc895244b9abfbd1dac1cd7c53c2" }, { "name": "shamrock", "unicode": "2618", - "digest": "530e6b987ecb9bcbf0d6e0e11bd075e7949873c784da4f9e1e1b47efd37e5058" + "digest": "68ed70c26e04a818439a1742d2da6bc169edd02db86b6e6f8014b651f3235488" }, { "name": "shaved_ice", "unicode": "1F367", - "digest": "fc22c3568f6be56771e83fd0e67b7eb3750041304d5d4979d3ec417f5201230e" + "digest": "54048e77268b7548d03088517bf8558d11324db901ca57f9bec93f1873663a74" }, { "name": "sheep", "unicode": "1F411", - "digest": "3e3656b82784164ca02c5d775db7245260f0119d2c1d35ba552a6dc75ef02544" + "digest": "c867c8e6e51768f1f51f4fe5abd3fbd5c1d69b01a3cb48b5fb94b6e2338a271c" }, { "name": "shell", "unicode": "1F41A", - "digest": "ff2f4f574b61bffd85c63bc2315c80d3cbcaba37a7c15a1f00783d312bd441d4" + "digest": "8983652d33ad6ab91195518cecb5a268a1c0ae603d271f0ddd756ff50058ddb3" }, { "name": "shield", "unicode": "1F6E1", - "digest": "062aec4a325da7b637c5710846c7e7319229be49b7e59f50428442a7ef725d60" + "digest": "763d0a56a62c51c730ccb0fbea38ab597cbf41a85ab968198e6ec35630d50aa5" }, { "name": "shinto_shrine", "unicode": "26E9", - "digest": "9768fe94142a7dc169703d3707b203f285a546455e29fe2bbf185d44f160d6d0" + "digest": "38a6d756c5aa9703510afa5076d75192f7814bbb6632394d4b8253d9ceda7f8c" }, { "name": "ship", "unicode": "1F6A2", - "digest": "f8d5b0c8ec66287b732d9171ac1913be02efb656de11501213a207d8a6c801e1" + "digest": "79c680845892a3e81ec6af2160ee07c29147155943e5daba6c76d04252014c20" }, { "name": "shirt", "unicode": "1F455", - "digest": "e2e72c323f3bfaea02e8cf52201aa144dc56ec0f25ec97d5f04ee6c2ee99104e" + "digest": "46c7253e15d7cac03699ddb1550fbb7565bbe487310f7e218c0583aa69f9d3c5" }, { "name": "shopping_bags", "unicode": "1F6CD", - "digest": "0194ba540c47e4fc6403be2df68f785d56810efc2dc011dfbf700f3778cb704a" + "digest": "95a3f03c675207bb1354270d02a630c204455c47b3edca23c48523a40cf3ea3b" }, { "name": "shower", "unicode": "1F6BF", - "digest": "c945120182392510348de9a957c2b77a4645d118691298a2ad660dafa62a859c" + "digest": "6b3c767c0eb472d4861c6c3cc2735a5e2c09681872ef42a11dc89f3c80b9da01" }, { "name": "signal_strength", "unicode": "1F4F6", - "digest": "7876ed9d602e1be746ca0629f072d85668d1f9715e9135745e803bdf89819a3c" + "digest": "2c6f04ba4ecd2d2d423e19eb52cfbfd253f4db6e0908d91c1af4ea6192597447" }, { "name": "six", "unicode": "0036-20E3", - "digest": "b409f23b73e46393c7a814442816b5880c38ef12a7feb5505e71276c195e8ca9" + "digest": "cede9324261208d0fd5d00fcdfc0df0331944bd9cff4f40b30a582a641526c1c" }, { "name": "six_pointed_star", "unicode": "1F52F", - "digest": "4bc294dcbf4185250873b52b2fb5453fb7d80df912db929add6e4b7efc066363" + "digest": "9203e3b4f08af439ae0bfb6a7b29a02dceb027b6c2dc5463b524dfd314cbff4e" }, { "name": "ski", "unicode": "1F3BF", - "digest": "7ee81a2e2f7ff4e32dbf3d64b034e7542ec0c86d32e25eb125052e674943d75f" + "digest": "80f0ca8660ba373fef823af9e98e148c4ddb1e217eb6d0a0ea2bae2288b57570" }, { "name": "skier", "unicode": "26F7", - "digest": "49df9a4206ae0c7c2dbfc8a8b13fd3e14e6f7e750bd5a8581ab6a1626d4c165e" + "digest": "4fff0aa155367f551a59aed9657b8afa159173882b25db9cd8434293d1eed76d" }, { "name": "skull", "unicode": "1F480", - "digest": "dfd169764b192ac7c6e5101277dd9f1e010e86bdd32ad37e00ed4499fc0a5dd6" + "digest": "cdd2031164281bf2b0083df4479651d96bc16d11e44bac4deaf402a9c0d6f40a" }, { "name": "skeleton", "unicode": "1F480", - "digest": "dfd169764b192ac7c6e5101277dd9f1e010e86bdd32ad37e00ed4499fc0a5dd6" + "digest": "cdd2031164281bf2b0083df4479651d96bc16d11e44bac4deaf402a9c0d6f40a" }, { "name": "skull_crossbones", "unicode": "2620", - "digest": "e2acf0f36b6a6800c1829a1c6551b5d0eb6dcdef4b7f02070cf69570aeab608c" + "digest": "ae764ba21a1fcc4409f4cc9e75a261d70b87548f64158dbd3451374ad5724123" }, { "name": "skull_and_crossbones", "unicode": "2620", - "digest": "e2acf0f36b6a6800c1829a1c6551b5d0eb6dcdef4b7f02070cf69570aeab608c" + "digest": "ae764ba21a1fcc4409f4cc9e75a261d70b87548f64158dbd3451374ad5724123" }, { "name": "sleeping", "unicode": "1F634", - "digest": "4ead95079b1a542eedd0e5a0e93fddb318a002bdaffaa2fe5d8d7f20bf8143ed" + "digest": "1050a011509b56735c9f30a6fccc876256e2a4546dc6052e518151c8aca4b526" }, { "name": "sleeping_accommodation", "unicode": "1F6CC", - "digest": "10ee8cd925a75d7977b7cf004e08b5a8147b509ee4281e879a8b57c4a7c2cb04" + "digest": "2ce42c027d1d0947abc403c359fd668a7bc44f5ead2582e97f3db7dd4e22e5d5" }, { "name": "sleepy", "unicode": "1F62A", - "digest": "dea3b246bb8af1b28e200358e3d5d59c8bba1813f35a7f4a57ec568ef43591db" + "digest": "2ee9bb1f72ef99e0e33095ec2bbf7a58ffea0ff7d40b840f4cdba57be9de74b0" }, { "name": "slight_frown", "unicode": "1F641", - "digest": "3ae82b38b58ffa50eddebd87153428d880ca181f4f4178a9ca3bd813ea15ccbc" + "digest": "d71d564a6c2d366a8e28a78ef4e07d387a77037fe8c99aa0ea1571299dc490c9" }, { "name": "slightly_frowning_face", "unicode": "1F641", - "digest": "3ae82b38b58ffa50eddebd87153428d880ca181f4f4178a9ca3bd813ea15ccbc" + "digest": "d71d564a6c2d366a8e28a78ef4e07d387a77037fe8c99aa0ea1571299dc490c9" }, { "name": "slight_smile", "unicode": "1F642", - "digest": "5eee09f634a4e2031927d008a6530a258a00e611ead0c386dd5b7ebb5e75a306" + "digest": "10f4b66a755f5c78762a330f20d1866e4a22f3f1d495161d758d3bab8d2f36fe" }, { "name": "slightly_smiling_face", "unicode": "1F642", - "digest": "5eee09f634a4e2031927d008a6530a258a00e611ead0c386dd5b7ebb5e75a306" + "digest": "10f4b66a755f5c78762a330f20d1866e4a22f3f1d495161d758d3bab8d2f36fe" }, { "name": "slot_machine", "unicode": "1F3B0", - "digest": "9d516b389299431b608c89d3f02ac68d28cb8df2a780f2048923bbcfbb49f416" + "digest": "914184788f8cd865cd074dca25c22acee31f5498117bd9a6e78cae67e6601652" }, { "name": "small_blue_diamond", "unicode": "1F539", - "digest": "97389e82755dc43015089dee635072357ec347f0117b2d3e9b006c46514948ee" + "digest": "0b56d8e6b5ddf1f49fcc76e45e5fb2ee9f99ae6ffe682c26eaea4d9b7faac36c" }, { "name": "small_orange_diamond", "unicode": "1F538", - "digest": "67442d3b707501b7768f606115688373d13617ecf0b3b03ace0f1a6d38f66ddf" + "digest": "a2235830550e289c1608f2dcf5ede48f5c1a0eff45570699c39708c9677ab950" }, { "name": "small_red_triangle", "unicode": "1F53A", - "digest": "e0a556a3dd5bbf0290ed7c00eb6f6307dc2ea98d1fb3111fd85a7f46242a3638" + "digest": "8c2985c4e9ce42d2f3b35539b879bc36206c5ef749f39fbd1eac51bd2676e1e5" }, { "name": "small_red_triangle_down", "unicode": "1F53B", - "digest": "7a11dcb8a517df220493d471759e4f4bca0db3769e2d942bbf596a88a3e57f72" + "digest": "46bd328df2fbf5d0597596bbf00d2d5f6e0c65bcb8f3fb325df8ba0c25e445b5" }, { "name": "smile", "unicode": "1F604", - "digest": "46a7c3545b0038dfce6825d97544f6665f28512ad05c404d668e32ac599c7ecb" + "digest": "14905c372d5bf7719bd727c9efae31a03291acec79801652a23710c6848c5d14" }, { "name": "smile_cat", "unicode": "1F638", - "digest": "c1db961f0fa261532b842816aca7ea7f6d8b461c7e930a1a1c91f96efd9db515" + "digest": "c35b76d6df100edb4022d762f47abfeb9f5e70886960c1d25908bd5d57ccb47e" }, { "name": "smiley", "unicode": "1F603", - "digest": "deeaaee64ebdd9fc0bcb719db75c3f7e0c33ddbcc97f6cd51f9f84377a4368ce" + "digest": "a89f31eb9d814636852517a7f4eadec59195e2ac2cc9f8d124f1a1cc0f775b4a" }, { "name": "smiley_cat", "unicode": "1F63A", - "digest": "85ad852cb3881c4b754af172fdfc6231af42578033ea9f2981ceae944c41e72f" + "digest": "3e66a113c5e3e73fb94be29084cb27986b6bdb0e78ab44785bf2a35a550e71bf" }, { "name": "smiling_imp", "unicode": "1F608", - "digest": "e777bdf186d89921df106d23bf002967b69afffd7e981b3cbb19f89630a06e87" + "digest": "3e02131d16525938f6facc7e097365dec7e13c8a0049a3be35fc29c80cc291b3" }, { "name": "smirk", "unicode": "1F60F", - "digest": "2e7fddd8bed33ef4b7d8c13320302b87a28203e576ef87bd43716952cf0b5ace" + "digest": "3c180d46f5574d6fca3bb68eb02517da60b7008843cb3e90f2f9620d0c8ee943" }, { "name": "smirk_cat", "unicode": "1F63C", - "digest": "9ca0721f4c18592b4b809ade8f716b95fa30cd31dd87d1e41db29a319becd705" + "digest": "0683c7f73e1f65984e91313607d7cca21d99acd4b2e9932f00e0fffd0ce90742" }, { "name": "smoking", "unicode": "1F6AC", - "digest": "3d14b3f0c57eb7a6a31ff371b0a454986533b79dbbeac78a76e4063478911b8d" + "digest": "baa9cb444bf0fe5c74358f981b19bc9e5c0415ced7f042baf93642282476ea61" }, { "name": "snail", "unicode": "1F40C", - "digest": "57d946c7ec84dfad71bc4f7a042927ec5712aef50c66d21af892b6c8a7faf5e1" + "digest": "5733bf3672ae4b2b3e090fa670aeac70dcbcc04ca5b13abc8c8e53b8b3d4ff33" }, { "name": "snake", "unicode": "1F40D", - "digest": "d084da540162288721364992f3b8059cbf2efd9f5b48f49a196ddbe23a073870" + "digest": "18da2d97c771149ef5454dd23470e900903a62ab93f9e2ce301aad5a8181d773" }, { "name": "snowboarder", "unicode": "1F3C2", - "digest": "de9e1767526de606f4908743af94cc17e89fdb0a2a44167d3d021ef09d033ab9" + "digest": "c6e074139b851aa53b1ba6464d84da14b3da7412fc44c6c196a8469d76915c19" }, { "name": "snowflake", "unicode": "2744", - "digest": "e476863ccd7d7b549c6191fb25c121c6a467b4baef4683b7dc3e0a793c2e5d76" + "digest": "6556c918e181df01ba849e76c43972d5310439971e5d8fc2409d112c05bf0028" }, { "name": "snowman", "unicode": "26C4", - "digest": "792946b8446f2243d11b89d07c73a774be3abd36573f3918640b1ba8714270b5" + "digest": "6137456b2335e88e09c1859615eb22bb636355ef438f7a3949ad2f3d54478dd3" }, { "name": "snowman2", "unicode": "2603", - "digest": "571acabaa4d55782c4529b762423a7e34cb1fb6bb7852cbd013e2e846d8311d1" + "digest": "33ec75c22a13c81fa3c6b24a77ac1a08dc0dbe70b3716cf17b6702014d8a63fe" }, { "name": "sob", "unicode": "1F62D", - "digest": "562f02ab584bcbcf9ba73cf7fa7d7129965266abd28db2c73913b8c42f2f5aca" + "digest": "d1ed4b31861f9f9fd4e9c95a9c17530e2320a1b4cad6ececb1545ce25d65e4ce" }, { "name": "soccer", "unicode": "26BD", - "digest": "5fd0d534659b63dc862c65a80561b255bece0b76708fe8ecbae8e01b08d8cad0" + "digest": "6a3f2e6a9a0b64c3fbf8705995792091daf386a4112dba75507a1f556f662f84" }, { "name": "soon", "unicode": "1F51C", - "digest": "d2a1ab16a4056d80c827ea23f9332bb73235fc841b857cbf545062ff8aeed81d" + "digest": "a49d1bcfbac3e6ccc05b9a9863eff74b0eb8b4d4b22b8b0f7b2787fcba1c73cc" }, { "name": "sos", "unicode": "1F198", - "digest": "fadfe8337e133a6f05d205d0807f288e5c230db04cb09f3547ce0cb73cfcf48a" + "digest": "2fa7e0274383aeed6019eb9177e778d7aab8b88575b078b0ffeb77cd18df14b3" }, { "name": "sound", "unicode": "1F509", - "digest": "c0074b338fd461f1f9d1143b7f9b3781ddb3fd501ea79b2410630433a8e87b83" + "digest": "faaca7b315b2495cbc381468580d25f1d11362441c35bb43d8a914f2ec8202d2" }, { "name": "space_invader", "unicode": "1F47E", - "digest": "d264390004bd28d664dfda0069104be6db32ce477e23a95ac595bac2e29fd4e7" + "digest": "e75379cb5063f9a8861d762ad1886097c1697fbb61f2e4e8f531047955a4a2dd" }, { "name": "spades", "unicode": "2660", - "digest": "d1ad99a4fc20dfea881a9062a9f2109e483dbb5dea3b29e9653cb27ec57b4800" + "digest": "2c4d20f6a4893cfc62498d3f1f8f67577f39ed09f3e6682d8cb9cd8f365d30da" }, { "name": "spaghetti", "unicode": "1F35D", - "digest": "ac63f9ad143e236ce6068098e5330a333ade9cddfb3dd6b1457ea47ce9dcf7e9" + "digest": "6d3451dc0faa1913539edb99261448f51735f269b61193c53dfe63466c0191e8" }, { "name": "sparkle", "unicode": "2747", - "digest": "95b8f4f1bb6080cd1d7bd333c4724dbba43ed196dce72a2bbaab46c4a1bc0e48" + "digest": "7131163cd6c2f879110c86e9f068c33cf580f7c4b619449c41851fe6083402ee" }, { "name": "sparkler", "unicode": "1F387", - "digest": "3a296e4d0081ad1a566e111d218e352e1439bba9fd04e8a1eb9a8e36bd438cb7" + "digest": "88539ed8a13bd66e0c265c0913bd3ec2ddc4d95484323595713beb102221a1f6" }, { "name": "sparkles", "unicode": "2728", - "digest": "5ab280ea10c30e0e0b5a26ef52b8f47ad44a983330f7ef62ac0c0888752bbdb6" + "digest": "cf84d16b1c0a381d5a7ae79031872747c9a6887eab6e92cc4a10a4b8600ef506" }, { "name": "sparkling_heart", "unicode": "1F496", - "digest": "f145dab6b597c07e5a851176fabaf56dd857209645483d1acc1490d12c969113" + "digest": "b80b1ddef83b6528b309a194f6f2faf5acab603daeb9254523efc2b941bcb6d2" }, { "name": "speak_no_evil", "unicode": "1F64A", - "digest": "6eae2d066d39c4ba81e58a8327ed875c68bc9b1297c18dc0f5243e477a81040f" + "digest": "d2d7cfb4d471928a496bdc146890adc8422a68500b68115630b24c125d18e81f" }, { "name": "speaker", "unicode": "1F508", - "digest": "ea59c5a9d994808ff7937c300303e644b5f1ad41097e82f9e73ea6e1c718936c" + "digest": "dbca5f7181728d2ad67ff76fd566ffbdf53e333e7eeed341f54668bd47969413" }, { "name": "speaking_head", "unicode": "1F5E3", - "digest": "d92cfe1200887300b2f05f9576448a2f2a79d0accd51f323a65ce3db0aa5639b" + "digest": "4be1af79b4506c00af4df64663413bcbae195dab0bc63c5011feb8f9663ed544" }, { "name": "speaking_head_in_silhouette", "unicode": "1F5E3", - "digest": "d92cfe1200887300b2f05f9576448a2f2a79d0accd51f323a65ce3db0aa5639b" + "digest": "4be1af79b4506c00af4df64663413bcbae195dab0bc63c5011feb8f9663ed544" }, { "name": "speech_balloon", "unicode": "1F4AC", - "digest": "5dccfda46fc984583bc9eaece66e7e884f2a9eb12a69dbd3493035e3c862edd0" + "digest": "817100d9979456e7d2f253ac22e13b7a2302dc1590566214915b003e403c53ca" }, { "name": "speech_left", "unicode": "1F5E8", - "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed" + "digest": "912797107d574f5665411498b6e349dbdec69846f085b6dc356548c4155e90b0" }, { "name": "left_speech_bubble", "unicode": "1F5E8", - "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed" + "digest": "912797107d574f5665411498b6e349dbdec69846f085b6dc356548c4155e90b0" }, { "name": "speech_right", @@ -9562,122 +9562,122 @@ { "name": "speedboat", "unicode": "1F6A4", - "digest": "553a288ab8eeb3dee7b9d1c92eba38016caef7658beaa828136ba1d6ba8ed08a" + "digest": "a523b2320f0b24be1e9fdbc1ff828e28d8fd9a64d51e5888ab453ef0bc9f0576" }, { "name": "spider", "unicode": "1F577", - "digest": "519f7243b5574102ce3f8953e5480812830a1feb32ae51e8573724c864338481" + "digest": "8411eac0c1b80926fd93cc1d6423e00b05d04c485b79ee232da8f1714e899a37" }, { "name": "spider_web", "unicode": "1F578", - "digest": "42959fae08a2162d6ee8c8706f823c5932f3801bc90da30d2ca9a48c3ff25572" + "digest": "2434bdfbe56dcc4a43699dd59b638af431486b52fb1d6d685451f3b231b2be23" }, { "name": "spy", "unicode": "1F575", - "digest": "eaa570a36d83119d0a596228e74affe84d7355714ff6901d88a89410d26dec2a" + "digest": "99fe3cdeff934726ee5855b0e401bf32570084aaad4eb10df837fd410ca742aa" }, { "name": "sleuth_or_spy", "unicode": "1F575", - "digest": "eaa570a36d83119d0a596228e74affe84d7355714ff6901d88a89410d26dec2a" + "digest": "99fe3cdeff934726ee5855b0e401bf32570084aaad4eb10df837fd410ca742aa" }, { "name": "spy_tone1", "unicode": "1F575-1F3FB", - "digest": "abdc066d4cad6a17047faf7806c45feb43ae1e2056cf500536f08f4173dbfa94" + "digest": "1720a99064061c43c7647b6bd517efa2ee2621b355a644adfb347d62849366a2" }, { "name": "sleuth_or_spy_tone1", "unicode": "1F575-1F3FB", - "digest": "abdc066d4cad6a17047faf7806c45feb43ae1e2056cf500536f08f4173dbfa94" + "digest": "1720a99064061c43c7647b6bd517efa2ee2621b355a644adfb347d62849366a2" }, { "name": "spy_tone2", "unicode": "1F575-1F3FC", - "digest": "72a3313ef12364105e764cc3deabd47eb6bd086f261c435682ae1cd29dc8230b" + "digest": "23ff0026723f2b5a46fbfb55e24c4a4a33af2bd96808b3ea3af76aae99965d68" }, { "name": "sleuth_or_spy_tone2", "unicode": "1F575-1F3FC", - "digest": "72a3313ef12364105e764cc3deabd47eb6bd086f261c435682ae1cd29dc8230b" + "digest": "23ff0026723f2b5a46fbfb55e24c4a4a33af2bd96808b3ea3af76aae99965d68" }, { "name": "spy_tone3", "unicode": "1F575-1F3FD", - "digest": "2a1108d3d2e778f88aa5b3ae36705c877b84d0bf6b421409582ba748aeb2aee7" + "digest": "1d0cb3d54fb61e4763a4f0642ef32094bdd40832be0d42799ce9ba69773616df" }, { "name": "sleuth_or_spy_tone3", "unicode": "1F575-1F3FD", - "digest": "2a1108d3d2e778f88aa5b3ae36705c877b84d0bf6b421409582ba748aeb2aee7" + "digest": "1d0cb3d54fb61e4763a4f0642ef32094bdd40832be0d42799ce9ba69773616df" }, { "name": "spy_tone4", "unicode": "1F575-1F3FE", - "digest": "1d4fe62912384bc0d687bcf4565752caf0ed6146c903a156d1c6ba6ea239b154" + "digest": "e36a4b52df6cb954fab9d9128111f1301c6d46bdeacf51993ffb5bb354cd0ad3" }, { "name": "sleuth_or_spy_tone4", "unicode": "1F575-1F3FE", - "digest": "1d4fe62912384bc0d687bcf4565752caf0ed6146c903a156d1c6ba6ea239b154" + "digest": "e36a4b52df6cb954fab9d9128111f1301c6d46bdeacf51993ffb5bb354cd0ad3" }, { "name": "spy_tone5", "unicode": "1F575-1F3FF", - "digest": "69c1baac73783edb9e2d0c951f922dc7dddac34d0a9c818fee8d1021bc17db0d" + "digest": "ffc6fefd9a537124ebf0a9ddf387414dce1291335026064644f6cf9315591129" }, { "name": "sleuth_or_spy_tone5", "unicode": "1F575-1F3FF", - "digest": "69c1baac73783edb9e2d0c951f922dc7dddac34d0a9c818fee8d1021bc17db0d" + "digest": "ffc6fefd9a537124ebf0a9ddf387414dce1291335026064644f6cf9315591129" }, { "name": "stadium", "unicode": "1F3DF", - "digest": "4356db5d2cdef8c40830638debaf1f50831130c12ae8d8dc3d9a6bd28fdaa1f7" + "digest": "73bf955e767ba1518c9c92b2ba59a2aa1ec4b018652dffd97bcd74832a33789f" }, { "name": "star", "unicode": "2B50", - "digest": "13240b8fada84e7555892996e9f9652503bf9b9a002056c2bae428d543abe2da" + "digest": "d78e5c1b78caed103e100150c10b08a9ca3ee30c243943d6fc3cc08f422122e9" }, { "name": "star2", "unicode": "1F31F", - "digest": "9b56c7548f6a222499d4e848576ea25eab837db72b207ebf8a62a451b35f758f" + "digest": "f91ac4afe3f5d4a52847ae8b4a9704b591e00399aebba553d150d7e34ee939fa" }, { "name": "star_and_crescent", "unicode": "262A", - "digest": "10b8a0771e415aa6610fa62185137aa1836c2bb3e82f1a3f601470e94f784923" + "digest": "1bf3d29e50034f5e7c0dccff0a3a533b74bfa9b489e357b2739a473311f1332a" }, { "name": "star_of_david", "unicode": "2721", - "digest": "5bc4d1038b8316281e01a9c575ded7ede0fc24c7593db5b5d36ca2e188aa5614" + "digest": "28a0bd0eeac9d0835ceb8425d72c2472464e863dd09b76a0ddc1c08cf1986402" }, { "name": "stars", "unicode": "1F320", - "digest": "23605eafc949feead3eca145a7ff5ee3b211a8bfd95621bd35dd05df532b97c6" + "digest": "837d9045316b8fb5e533457eac61241534f641eb78d8cb75f688f80fb8e8a7f0" }, { "name": "station", "unicode": "1F689", - "digest": "c346f12fff64161041af8492550c3541a6304e53f30288224ddd0c6fe08c4d6b" + "digest": "27a163ac0aea4ed247a121cae826eafc475977c68b0d888e9405bea14326ff56" }, { "name": "statue_of_liberty", "unicode": "1F5FD", - "digest": "56fa27ab059a9fd1f53aec47d9108277a3bf04a73186f36297cd1207c832ee31" + "digest": "f5a43599ab3f24ed3a78a745e06e2ac3e33107a292386ad81c67935ee5b22493" }, { "name": "steam_locomotive", "unicode": "1F682", - "digest": "d0ec2eb3d761ab6157e17eab1b8b4dec3a69f9becc4251592cbb67d71825e661" + "digest": "52ad0073f37b978faf3884fb193046f2b0614e1557bbcc9de1b020e42aff2dba" }, { "name": "stereo", @@ -9692,7 +9692,7 @@ { "name": "stew", "unicode": "1F372", - "digest": "12e6e4bf48a7296700e07a053d831dd67b70c308ca9522ca96e933a4d1ef6c5e" + "digest": "c16f61236db314ad8d9f2dd241ec1e15c8d64e5872cce93ec4d0996490dd39df" }, { "name": "stock_chart", @@ -9702,212 +9702,212 @@ { "name": "stop_button", "unicode": "23F9", - "digest": "57310962c7738a7da4f2a62cbd5e0b26d7aec357978267a0d8ca8e6cbd7ffb02" + "digest": "83f9d0da3ad845fef41b4e8336815d30e9c8f042ab2a8340894ade2f428fc98a" }, { "name": "stopwatch", "unicode": "23F1", - "digest": "c8e69c24f9da98dcb41c9c6355922d08a702f12a35667fbc5beb3f659430333d" + "digest": "9b6b9491a24d8ab4f896eb876da7973f028bd5e7c51a3767ba7e61bb6fbb2be0" }, { "name": "straight_ruler", "unicode": "1F4CF", - "digest": "55ff7182a3696461df52e3000708083f803bc8bf0f3c25dacb34175cc104b51d" + "digest": "cee31101767bd3f961363599924dc3790675d05a1285a8396428d2f91771c111" }, { "name": "strawberry", "unicode": "1F353", - "digest": "fd501e1fefb70242ac7c4dc30ad3d8c3ae200b263a832daedaa984906114afaf" + "digest": "5750a15e12f21259286ddbc3a8222a385b3b97a9f368897f42dd000060343174" }, { "name": "stuck_out_tongue", "unicode": "1F61B", - "digest": "1b49956cec511ee382177d95da77c8b6a9214a02c86bf7c6c6fd6cc9df3e9331" + "digest": "92dc42980a6dfdd7204fc874a762d6a0bbf0fdbfb5a7c0698fca04782e99fde6" }, { "name": "stuck_out_tongue_closed_eyes", "unicode": "1F61D", - "digest": "60a4d5d92550c6ad4db901d42c9f6434fe94fa3ddb353b6019a93d374d9485e9" + "digest": "434d25ac24cad7ba699eae876a25d9a99b584449cca50b124bf6aa7f20a83d51" }, { "name": "stuck_out_tongue_winking_eye", "unicode": "1F61C", - "digest": "d9c15ad1c4782a0391a79aeda2745127527385b0b5fc01c8d96c3f3b637a74ae" + "digest": "dbacd6428a2a2933212e6a4dc0c7f302177fb23b963626ccb26f27f91737f03d" }, { "name": "sun_with_face", "unicode": "1F31E", - "digest": "56b14e92f68f8701fdc42763e1f4695ed352845f22bd5d412f827e5cf98dd83b" + "digest": "7256ff5263006c64c03f1eb66e3ddb56d67d785d65dacc37aa886d0cd4be63be" }, { "name": "sunflower", "unicode": "1F33B", - "digest": "817dea222a75bb6492c32b4b144d07f48295d7dd113e21760f90b18277612ebb" + "digest": "27d1161f50f932a6b26c404cf2e8f7083683ed0f2382d62b7472acccaa6eb695" }, { "name": "sunglasses", "unicode": "1F60E", - "digest": "16003cc5256397389889f52e0a5e14daea8d8c72f2ea660b8174529868cba9cd" + "digest": "966684382e5c59e98319e4c0ea7c304c61c2638ad5408faa49ce2c83c4416757" }, { "name": "sunny", "unicode": "2600", - "digest": "f68a774b7d574fc711111e17368b57c40d973d263c7e857544a09051d4592ab9" + "digest": "460fea4cbbdd1595450c1033a2ee5de7fea2e2f147822efa49f7e204812415aa" }, { "name": "sunrise", "unicode": "1F305", - "digest": "ce06a9321bc04605538a59f9fca8536d6209d7ded03120e5d2a0be955bb17ddf" + "digest": "7718a49636b0cdd1862ed67c7a9d6e72f471c2591ff0d912485b1be55d1ea115" }, { "name": "sunrise_over_mountains", "unicode": "1F304", - "digest": "286244ac2bec8c5c41cf8c7c439702fa525c57fab623f7f9bd7687db0adf75b2" + "digest": "743d0701cdbe2a814962363813c3153d3c5e62c3e410349f56d49dbb9581f356" }, { "name": "surfer", "unicode": "1F3C4", - "digest": "d17c7ea185ca5ef5a2950ef126ee14103bf7769acb419a20d08cc023f619e459" + "digest": "bb440775e9213430942015c37db8de58b5a561ee971b2a0f3993fc3f1d2554d4" }, { "name": "surfer_tone1", "unicode": "1F3C4-1F3FB", - "digest": "af66f2f26071b3ba8d7c795139055a58a857212f8cb1f51a507242ad7d2c49c7" + "digest": "a4937b030aca30b68bb644f37cf63c38aebce3c00b57d1c8a0ffe596b57d2f1e" }, { "name": "surfer_tone2", "unicode": "1F3C4-1F3FC", - "digest": "7a34e8b1fdad0a89bbb10333d241583ef018517fdd90f171ad7121de53776a3f" + "digest": "1c2a954a9c5284dedf0327d6f3c954c9fdd3953b848076d298874775ad8bf0a3" }, { "name": "surfer_tone3", "unicode": "1F3C4-1F3FD", - "digest": "b2f4cbd59a0aa93c7ee2bbb14ce55c8306dc25884377982a5f132ce6c074fa1d" + "digest": "418a3408b9ab026124f067c8597b500217e56bc28d9844a29eea5eee6f604ff8" }, { "name": "surfer_tone4", "unicode": "1F3C4-1F3FE", - "digest": "b16a02cfcc3606524cca9408e69c654fb83a162eaec8faae8dfd8ec67fe391c5" + "digest": "530870b9ac9f4d45ff750e264feb90b44fb93ca2852f323987b06f5f12fb5a4d" }, { "name": "surfer_tone5", "unicode": "1F3C4-1F3FF", - "digest": "b9a156e1aa57544b703db4e4a7773e244a3139e82c2c808c2e5a804fb524f512" + "digest": "40e11b1ae652cfd085d083377f1da24160065ed1b67403c6fa4655e6e44169ec" }, { "name": "sushi", "unicode": "1F363", - "digest": "d2709b51ee92997c7fafa1b1517259cb896819c8dc9ba98ae26e1d44ec810d4f" + "digest": "b924c621236ca3284b349b0509ae1043f2fc2c7f6d67615716f9717ada78c992" }, { "name": "suspension_railway", "unicode": "1F69F", - "digest": "48903e103ef00a068b0100b28319b1e41c6a4485cb564f0ca59422ec9d3b259c" + "digest": "cd3d21da79864f0c018b863e82fb0561fff3c5e3c065303cfcb89c3663d638ba" }, { "name": "sweat", "unicode": "1F613", - "digest": "8d684fa882bcbf07f4e91ea02a48cd61f22e7aa206162b8352c26fc19361ed4e" + "digest": "1aa771479aa1ac5eeea4bafbe93ebd85a0f692f6d869034f31e25b689c2e264d" }, { "name": "sweat_drops", "unicode": "1F4A6", - "digest": "fca48e255dff08dab97ef98b75c67f7504a13be8b90afac88b69a7b7e887e445" + "digest": "b575b85415bc9852cf6415d417ebf799167fde03c6819ebcaa24ae1b3dde8dab" }, { "name": "sweat_smile", "unicode": "1F605", - "digest": "0c8156554eec2396b5fee908da46484945db980d2ebc6dee57b4069a86826182" + "digest": "171b0d0845d46c33bedb6d3b39fb1ff366e22ba90685eedabebd91bb2b0680de" }, { "name": "sweet_potato", "unicode": "1F360", - "digest": "3ce74ea9bc14906a3d29a9592c0657aee8f7961d406992752f7580b16ca6bdd0" + "digest": "4b91920f0b87d42763313bc476f4c821a74e4c12dc1c92165a859dddeaaf8844" }, { "name": "swimmer", "unicode": "1F3CA", - "digest": "05f3aa8544e3b15837bb06ae47344633b3e60d64c572dc6638c4cee19d6e5506" + "digest": "2c4ed4a51aad99d9957ae11a219d5164db9748fc3a65002c6085a9f15adfa9e2" }, { "name": "swimmer_tone1", "unicode": "1F3CA-1F3FB", - "digest": "85a266a9131f6a1b37e758305ca43ffb46e3e07b0a465c5faefbdb5e5adeb7a4" + "digest": "48588f129ee4af52ca2e0f4594213391978601087cd607896b2f979ca077284b" }, { "name": "swimmer_tone2", "unicode": "1F3CA-1F3FC", - "digest": "f2afdc4d05a2694e663a420d5ad82bd48c92aedc4137d0fd3725bf08c41bd12a" + "digest": "fff209448524bd1ef4d6decabf6c1ead94c8d3d5b1bfb5e54f20cc8e139232fc" }, { "name": "swimmer_tone3", "unicode": "1F3CA-1F3FD", - "digest": "b87ecc38fb9e8eeeef8b120164d758d3f6a68a407053b03261354fd7f90f43b6" + "digest": "2003932cb2cf4ae9a10b23338bf375a9293fb18c0ecf91bdfae73be6eebb3800" }, { "name": "swimmer_tone4", "unicode": "1F3CA-1F3FE", - "digest": "a08629cf3484953b851b357c6a04891fb97ac15e70c376bbb82af47479835e1c" + "digest": "20b4bff9baa1c694ad98067dde834c56092f023b9664bec382c2e512232bd480" }, { "name": "swimmer_tone5", "unicode": "1F3CA-1F3FF", - "digest": "21d83f66b2ef3e348f9e14ec108b9a90262d9934039ebd573471d2bdcde68974" + "digest": "0ff8eb57c2be8e80a1bc6ba75b8d9ffb9bd8d3be636150c4c03399ec1886f218" }, { "name": "symbols", "unicode": "1F523", - "digest": "f33c3ce58374e23b8957c759016fdb5c56ef7fe812bd4e693ae8ff7574cf6bbf" + "digest": "2a2a79816c4d0751a0d73586eec5e63b410653d3c85cc968906bf1fc03d89b94" }, { "name": "synagogue", "unicode": "1F54D", - "digest": "b13402c3c5793ebf924335a87a9f69befb7a6c152fc2a288261b2c2d49842eb6" + "digest": "98569cdd7c61528963b67b7891dfa46025c5e810cbb22ee18ddb3bd85de2da69" }, { "name": "syringe", "unicode": "1F489", - "digest": "39e5e7530255ccf2ff35ec5c653568c8645a4711170c573117f796ea3438c44a" + "digest": "e1538e645ccc571227c994b71b3d1be2c4d072d8bd9c944a42ff4a11c91a34a6" }, { "name": "taco", "unicode": "1F32E", - "digest": "6b004ce7129e00abcc10278bba1b9c3d5ac71888b99bf353f9878d8e494e3e0d" + "digest": "e1e45aefdb7445faeae75c3831df6a3d6f2590fcdd48a20d847593c246df613b" }, { "name": "tada", "unicode": "1F389", - "digest": "956a180a1f18e3a1252761e5b3713324f63975ee1fe32168b59b60aa4dd8b72b" + "digest": "1d2e6cbb2a3244240bc70209715d2213d1efee2e370cccfbcc046c333ae2d650" }, { "name": "tanabata_tree", "unicode": "1F38B", - "digest": "d074457ba347687bfc8397ec62edee6325c411356216e7d43acd3f60628a0bb8" + "digest": "592f2907ffc1b914390e1a106c15120ff3607e99192158b94d237975647c5540" }, { "name": "tangerine", "unicode": "1F34A", - "digest": "1b46bb690458914220cba18c43d7ae0f6914adfee6dba7cf2bb58ed4e1854ad8" + "digest": "40c9ddcde1b0bcfaeb466629a87825eb8c2037835720cbee5e2fda04be3c8d0a" }, { "name": "taurus", "unicode": "2649", - "digest": "ea87fb3baa32605107d63b60847e4873ad9e21b7e7b652e3721cde777168670d" + "digest": "21cf24cb6410ab6596e2df8b3e242cc07f9dbb247eabc00c590fe184b373d068" }, { "name": "taxi", "unicode": "1F695", - "digest": "f44249c643a96d924e1eb35f67a133f3ca61128e610a880afaa09a73c7bcaf9d" + "digest": "c546cc743831cfbf0c15452767cf2a4faf3775066797e997ae7c1fcbe4eca479" }, { "name": "tea", "unicode": "1F375", - "digest": "56ab8c291de8320c5b339e1cfbe972696e4ea31c592cefa240eda9a3abdf4fa3" + "digest": "00e3f1e389fa58c4fcd8c53ebbf83d25872f4315845ab1984b35410ae65553d9" }, { "name": "telephone", "unicode": "260E", - "digest": "609104588e00039199a2fef3190ee6a7be5fca7cb09b36ffe5a7d800aac69d8d" + "digest": "3a53851e641f8ad938ce3597b1afca2ea63c9314ff81f62563b99937496a13d7" }, { "name": "telephone_black", @@ -9922,7 +9922,7 @@ { "name": "telephone_receiver", "unicode": "1F4DE", - "digest": "e3bf6034de6cf2160893ba4990eba198185a6a3f9cd5767a63b048e41c297640" + "digest": "1614d67f3d8814b0d75f39d55f9149e4d28ef57b343498625e62fcfff8365046" }, { "name": "telephone_white", @@ -9937,52 +9937,52 @@ { "name": "telescope", "unicode": "1F52D", - "digest": "abe0aca5f2c78105b0e9e4c8ee7a40adcd9bb013e7c49d568076459bade73556" + "digest": "4adf40387870276c4f59fb050d441023e8dac784365b6a8c0282fb519780b495" }, { "name": "ten", "unicode": "1F51F", - "digest": "7593aa7ffe7192a2e35c6ccec76522f6243777783c9152c7c03419835ea58c03" + "digest": "c7c9491021740d2c17edddb856f79579b0b943d8dc85a2f48dbaac84f35b8a40" }, { "name": "tennis", "unicode": "1F3BE", - "digest": "0a5fad3f7f35da0f37761e2279c148dbe154fa14c0e2a0749209b8b2b213a388" + "digest": "dc1600b4d8dce3d26259eb0d1c6ab042566565e3c1f2c96112210f1550a716fd" }, { "name": "tent", "unicode": "26FA", - "digest": "7ddf437d8d186e4e3c3e818d137518d590fa06098813c7fe20e1f2a9704feab2" + "digest": "30d9b17ac3219d4970ddf54d7c1a288b0ae50f7f3b82ed232c0b1b19ef585662" }, { "name": "thermometer", "unicode": "1F321", - "digest": "597d1714442698a22187fee4d57a2580322f7206c7d51e4519023824598ec08f" + "digest": "66616babbcaef256d7b652796c760e8e893cb950c073348a408fe70904f80f25" }, { "name": "thermometer_face", "unicode": "1F912", - "digest": "f19c489d89dd2d39770a6c8725a20f3e98f9e5216774af60c0665fd6a03a7687" + "digest": "ac2b5caddd128563711a9dcc7f690cf210f684d5e8b64b09c0431d6902437126" }, { "name": "face_with_thermometer", "unicode": "1F912", - "digest": "f19c489d89dd2d39770a6c8725a20f3e98f9e5216774af60c0665fd6a03a7687" + "digest": "ac2b5caddd128563711a9dcc7f690cf210f684d5e8b64b09c0431d6902437126" }, { "name": "thinking", "unicode": "1F914", - "digest": "f64a9a18dca4c502b46f933838753a818b604a9d0268aa32eda26cbd31abc58c" + "digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3" }, { "name": "thinking_face", "unicode": "1F914", - "digest": "f64a9a18dca4c502b46f933838753a818b604a9d0268aa32eda26cbd31abc58c" + "digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3" }, { "name": "thought_balloon", "unicode": "1F4AD", - "digest": "76c8513191641f0a79e878ccc0d83c4576984609810633f596db2f64cc684b7d" + "digest": "bf59624560c333561d636aedf2c8827089e275895cf434974daaabb3d5cea46e" }, { "name": "thought_left", @@ -10007,7 +10007,7 @@ { "name": "three", "unicode": "0033-20E3", - "digest": "ca0147a8f67cea3bc2516fa8deef4325188359559786c94ff0b27f90eef04b88" + "digest": "d3f85828787799c769655c38a519cad0743ab799ab276c7606e6e6894cc442e6" }, { "name": "thumbs_down_reverse", @@ -10032,192 +10032,192 @@ { "name": "thumbsdown", "unicode": "1F44E", - "digest": "a98f742c9773e0d95c0de5e1c10d1ab373fa761378a205f27d095e85debe69a3" + "digest": "5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61" }, { "name": "-1", "unicode": "1F44E", - "digest": "a98f742c9773e0d95c0de5e1c10d1ab373fa761378a205f27d095e85debe69a3" + "digest": "5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61" }, { "name": "thumbsdown_tone1", "unicode": "1F44E-1F3FB", - "digest": "5d0a7c63d52eafe6267c552168c5557a66622009d565c3cf7b5378c1f6e84bce" + "digest": "3c2853491473fd7ae2d1b5415a425cc390d26a8754446f8736c1360e4cb18ba3" }, { "name": "-1_tone1", "unicode": "1F44E-1F3FB", - "digest": "5d0a7c63d52eafe6267c552168c5557a66622009d565c3cf7b5378c1f6e84bce" + "digest": "3c2853491473fd7ae2d1b5415a425cc390d26a8754446f8736c1360e4cb18ba3" }, { "name": "thumbsdown_tone2", "unicode": "1F44E-1F3FC", - "digest": "ca5c15dc516660b2989a1c717bf3745fdfb6964c7acf3b938285ff6c7caf2ca2" + "digest": "4e0f8f86a06b69e423df8d93f41ec393f12800633acc82c4cb6dff64ca0d8507" }, { "name": "-1_tone2", "unicode": "1F44E-1F3FC", - "digest": "ca5c15dc516660b2989a1c717bf3745fdfb6964c7acf3b938285ff6c7caf2ca2" + "digest": "4e0f8f86a06b69e423df8d93f41ec393f12800633acc82c4cb6dff64ca0d8507" }, { "name": "thumbsdown_tone3", "unicode": "1F44E-1F3FD", - "digest": "05740e3568795270674dac9134198bf75b1b778c11daa71649c88c231859ec16" + "digest": "e08fa35575f59978612d4330bbc35313eca9c4dfa04f4212626abc700819effe" }, { "name": "-1_tone3", "unicode": "1F44E-1F3FD", - "digest": "05740e3568795270674dac9134198bf75b1b778c11daa71649c88c231859ec16" + "digest": "e08fa35575f59978612d4330bbc35313eca9c4dfa04f4212626abc700819effe" }, { "name": "thumbsdown_tone4", "unicode": "1F44E-1F3FE", - "digest": "5ee93bcc2f515806462a7b303064beade2b22a3f43a8162e39fd65d15d772e27" + "digest": "7c6d118d20d5add8ca003e4a53e42685a1f9436b872ed10d79f67ad418fb2a44" }, { "name": "-1_tone4", "unicode": "1F44E-1F3FE", - "digest": "5ee93bcc2f515806462a7b303064beade2b22a3f43a8162e39fd65d15d772e27" + "digest": "7c6d118d20d5add8ca003e4a53e42685a1f9436b872ed10d79f67ad418fb2a44" }, { "name": "thumbsdown_tone5", "unicode": "1F44E-1F3FF", - "digest": "5c9ef8d53cf6f755668ab6dabfbfcdfd4b95fd59db3b3dd60290efefe9c33994" + "digest": "8697c4a4ee4d6669dc2d47aa97699c42012ca59b80818ad6845878b37b4a9c58" }, { "name": "-1_tone5", "unicode": "1F44E-1F3FF", - "digest": "5c9ef8d53cf6f755668ab6dabfbfcdfd4b95fd59db3b3dd60290efefe9c33994" + "digest": "8697c4a4ee4d6669dc2d47aa97699c42012ca59b80818ad6845878b37b4a9c58" }, { "name": "thumbsup", "unicode": "1F44D", - "digest": "28b31df963773ba42a1a089f43cd89d0ce1ab0981e5410f41242e9a125fc1aee" + "digest": "59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61" }, { "name": "+1", "unicode": "1F44D", - "digest": "28b31df963773ba42a1a089f43cd89d0ce1ab0981e5410f41242e9a125fc1aee" + "digest": "59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61" }, { "name": "thumbsup_tone1", "unicode": "1F44D-1F3FB", - "digest": "f6365942738d2128b6959d6672b3d295757dc8240703cb84a2b014ad78d67de3" + "digest": "f57e6c525e8830779ea5026590eec3ca10869dc438a0c779734b617d04f28d21" }, { "name": "+1_tone1", "unicode": "1F44D-1F3FB", - "digest": "f6365942738d2128b6959d6672b3d295757dc8240703cb84a2b014ad78d67de3" + "digest": "f57e6c525e8830779ea5026590eec3ca10869dc438a0c779734b617d04f28d21" }, { "name": "thumbsup_tone2", "unicode": "1F44D-1F3FC", - "digest": "771d30146e4dc947a69057b05d32c765c8457ab02b5342889c5489acf27ef356" + "digest": "980eeeb1d8f5d79dae35c7ff81a576e980aa13a440d07b10e32e98ed34cbf7f1" }, { "name": "+1_tone2", "unicode": "1F44D-1F3FC", - "digest": "771d30146e4dc947a69057b05d32c765c8457ab02b5342889c5489acf27ef356" + "digest": "980eeeb1d8f5d79dae35c7ff81a576e980aa13a440d07b10e32e98ed34cbf7f1" }, { "name": "thumbsup_tone3", "unicode": "1F44D-1F3FD", - "digest": "0bb7bbfb654c6139260e1786e7ffa5a33f31e19410c1d4d15737fdf5dd4c721d" + "digest": "b3881060569e56e1dd75ca7960feab0e58ae51f440458781948d65d461116b4e" }, { "name": "+1_tone3", "unicode": "1F44D-1F3FD", - "digest": "0bb7bbfb654c6139260e1786e7ffa5a33f31e19410c1d4d15737fdf5dd4c721d" + "digest": "b3881060569e56e1dd75ca7960feab0e58ae51f440458781948d65d461116b4e" }, { "name": "thumbsup_tone4", "unicode": "1F44D-1F3FE", - "digest": "df0927c5342f0075fbf4ea83b724e6f70c0466c54769c9ce4a5c2deb602b28aa" + "digest": "86fbe2c95414bce5e38fb5c33da31305d7942fca2c9c79168dcffdbd895e9ad6" }, { "name": "+1_tone4", "unicode": "1F44D-1F3FE", - "digest": "df0927c5342f0075fbf4ea83b724e6f70c0466c54769c9ce4a5c2deb602b28aa" + "digest": "86fbe2c95414bce5e38fb5c33da31305d7942fca2c9c79168dcffdbd895e9ad6" }, { "name": "thumbsup_tone5", "unicode": "1F44D-1F3FF", - "digest": "0683ae08c50aaf186c6406680a60617679c7b4bccd0817f24b15911dbb06866f" + "digest": "49fa63ff725c746a18649df16c8fab69bad88bbb564884df79d1d15f553b7343" }, { "name": "+1_tone5", "unicode": "1F44D-1F3FF", - "digest": "0683ae08c50aaf186c6406680a60617679c7b4bccd0817f24b15911dbb06866f" + "digest": "49fa63ff725c746a18649df16c8fab69bad88bbb564884df79d1d15f553b7343" }, { "name": "thunder_cloud_rain", "unicode": "26C8", - "digest": "dd836f06b41a10d6ed9bcbdae291d2886847ff66dc3ede2427382e469f60674c" + "digest": "dacc20b4f6b68e5834aa1b8391afa5e83b5e6eb28e2d2174d3a68186a770506d" }, { "name": "thunder_cloud_and_rain", "unicode": "26C8", - "digest": "dd836f06b41a10d6ed9bcbdae291d2886847ff66dc3ede2427382e469f60674c" + "digest": "dacc20b4f6b68e5834aa1b8391afa5e83b5e6eb28e2d2174d3a68186a770506d" }, { "name": "ticket", "unicode": "1F3AB", - "digest": "a7654a5529535120da3c377e72cd1f7997bdc2dabf1d44b584f7df7852b158f9" + "digest": "b4326fe7761940216e6c76ee2928110a6b37bf913da9d694e96557e7c7c10420" }, { "name": "tickets", "unicode": "1F39F", - "digest": "ccafcc9583a84e847ff1eaa3d53187c5ab150a7d27c6a19363e59b9bc046b567" + "digest": "fb73358c3697c04fcfde6a1e705b1c3b47635b93b9cadfe31d5657566c7d190a" }, { "name": "admission_tickets", "unicode": "1F39F", - "digest": "ccafcc9583a84e847ff1eaa3d53187c5ab150a7d27c6a19363e59b9bc046b567" + "digest": "fb73358c3697c04fcfde6a1e705b1c3b47635b93b9cadfe31d5657566c7d190a" }, { "name": "tiger", "unicode": "1F42F", - "digest": "9ebe3117f5f1b589ff8164f8d87dcc275923e0db87121d2cee0fdb9b56dfc4ac" + "digest": "e139531e6c930bc46242dc0ed274661229de026b5419d8ea8f99fdb0f8a719ab" }, { "name": "tiger2", "unicode": "1F405", - "digest": "212c95dc60d52420a6320917fe3fdd0683b4edc1a2a2c4a1c60920d1f90f4bc3" + "digest": "f930cc8714198310d9b0edca6baff243ac5a3320f75fadb56fa5acc6fe34ff24" }, { "name": "timer", "unicode": "23F2", - "digest": "c48199312ed42ff53a33bb2791db19e2e2521223cd49d8f758ea95b9b379c5ff" + "digest": "69b33f219523d89d81cbbc070ad7e528711e4b34e124a50acb12a0280a34d0b0" }, { "name": "timer_clock", "unicode": "23F2", - "digest": "c48199312ed42ff53a33bb2791db19e2e2521223cd49d8f758ea95b9b379c5ff" + "digest": "69b33f219523d89d81cbbc070ad7e528711e4b34e124a50acb12a0280a34d0b0" }, { "name": "tired_face", "unicode": "1F62B", - "digest": "ad687a956388ec53ca1e301a0abe2f1e2cfb9f73cd543dd61a21c7335a42e332" + "digest": "775739bc9324517e614878ca0960d793df97775feeb62b14dbfb311a42a21802" }, { "name": "tm", "unicode": "2122", - "digest": "1156c8b0af40b336bbb6534b3302ac63eab009c4cd0476adcf1fc4669f04b647" + "digest": "7d9fafdb72d91860478fc185719f289f359eab2c368a132cb936a269e2ab6a24" }, { "name": "toilet", "unicode": "1F6BD", - "digest": "a4a24529c21e00e0861f4160c771f0e90aae8f6aee7550ad30d3dbb3fabbd4be" + "digest": "0d1b0dd0078f51104e8632a0726e1b3f075561a1ffa8a2546602de15798415d0" }, { "name": "tokyo_tower", "unicode": "1F5FC", - "digest": "6324f154f5f5c722044129e5bca03484aca1439911585e42c1c181ffa30b480c" + "digest": "73eaf6fd59d16396673afef620c6d928857d5cf616e95a40eaf2861686e0956a" }, { "name": "tomato", "unicode": "1F345", - "digest": "41bb6de095b27815eacb74a70aea8f7d4fe1ff947182b112001dd47ae7e45fbb" + "digest": "d092d8ad381d542e59b6a82b4f1ef0d10fc1ed48460952375c6c5c6258cea111" }, { "name": "tone1", @@ -10247,72 +10247,72 @@ { "name": "tongue", "unicode": "1F445", - "digest": "bf9dd7c65a8dc5d77eb013658a0a12a13f7b224a784e65e203d9584bb6b41427" + "digest": "286e9d2583c371431d6fc979dd4ab48981676da26baada51a846657a3654c19b" }, { "name": "tools", "unicode": "1F6E0", - "digest": "9b0a36dfdb475621d326359662b22cbdb80563c4f476aa5e7d7c00cdba605bd9" + "digest": "bf08d60dedc06de73d04dab05703bb8ad81989c72b5035d1a07821e51096f158" }, { "name": "hammer_and_wrench", "unicode": "1F6E0", - "digest": "9b0a36dfdb475621d326359662b22cbdb80563c4f476aa5e7d7c00cdba605bd9" + "digest": "bf08d60dedc06de73d04dab05703bb8ad81989c72b5035d1a07821e51096f158" }, { "name": "top", "unicode": "1F51D", - "digest": "d645030099aeb433307569e8e1c4342c1c411a8fefe50fdca7a3207a1a0db671" + "digest": "c9a9f25b17db014e76b6be54aa07ef89bb18f8adb41b3199d180a559ff1d9ea5" }, { "name": "tophat", "unicode": "1F3A9", - "digest": "1082fb2ee2e98fe65d21081b74ca59b07adef85043e2d36f25cac69db2d31fd3" + "digest": "43a45dfb5d6b57a63a0491f4e3ec780774c0301b53ed39a303a0bd803d16ed71" }, { "name": "track_next", "unicode": "23ED", - "digest": "d5415ed140933f345fea8023a3d8fca30dcfcf7d19d9dc9771fa2cae9df62a3b" + "digest": "88592ef6c720a32aeb752322fb4c794bf5110a72408e21e898630452115c731c" }, { "name": "next_track", "unicode": "23ED", - "digest": "d5415ed140933f345fea8023a3d8fca30dcfcf7d19d9dc9771fa2cae9df62a3b" + "digest": "88592ef6c720a32aeb752322fb4c794bf5110a72408e21e898630452115c731c" }, { "name": "track_previous", "unicode": "23EE", - "digest": "97ff4a59a236e5cf506fa3577b20715b3b0197e0f343a50615b36185d5b835f1" + "digest": "98c1b3d643768d94857fb762f6d26cfb87282b449a67792242e8b7068643ac87" }, { "name": "previous_track", "unicode": "23EE", - "digest": "97ff4a59a236e5cf506fa3577b20715b3b0197e0f343a50615b36185d5b835f1" + "digest": "98c1b3d643768d94857fb762f6d26cfb87282b449a67792242e8b7068643ac87" }, { "name": "trackball", "unicode": "1F5B2", - "digest": "8332503454ce42059d720c285fe2b15eb0562a0a4b234dccb0f3159bb30a91aa" + "digest": "32a819a3129429f797ad434d0c40e263dc236808e34878c599ed2304b43702f5" }, { "name": "tractor", "unicode": "1F69C", - "digest": "a41d304c41a85d966f6a7c301735fdbe2ae41f4471dd7dcd72023046ca2546d0" + "digest": "5e4686290f1a4c9953ae208340b7d276f25b3b2197a43e52469aeb6450e93997" }, { "name": "traffic_light", "unicode": "1F6A5", - "digest": "005f68d028fec8d9ae389cc2b23e1343a82c028eb32820d5e56f5c84eba315d1" + "digest": "d96aacade33d1ad3e0414f8a920513010f36eb7e5889774251c1d91148917ead" }, { "name": "train", "unicode": "1F68B", - "digest": "bf32893b7b9ecd248e8afe840624061746ac6ceb741e3e861ebfa46014f4bed4" + "digest": "7423d17e131df7aadaa350b5d39dcbce3b28de331ff8b6703a3b2d0093963f4b" }, { "name": "train2", "unicode": "1F686", - "digest": "08a9732453a0b4f68dd2d3d3879f04ee538f65897913b5a5157c0585132a374a" + "digest": "06e65d549e771632f3c64287a38ba67236f9800ccb6a23c3b592bc010e24e122" }, { "name": "train_diesel", @@ -10327,7 +10327,7 @@ { "name": "tram", "unicode": "1F68A", - "digest": "5a86d31f7ab677d967fecd75babc900b5169766d0228961912314c4c4d1d64ee" + "digest": "21a7699f1a94f06dcb4d1e896448b98a4205f8efe902a8ac169a5005d11ab100" }, { "name": "triangle_round", @@ -10342,62 +10342,62 @@ { "name": "triangular_flag_on_post", "unicode": "1F6A9", - "digest": "d824c973d84cd62c845d64e546de87b094fda8f9972b6a33acd75e1a5ac19f75" + "digest": "1f5ce3828a42f5b1717bac1521d0502cf7081ad9f15e8ed292c1a65f0d1386da" }, { "name": "triangular_ruler", "unicode": "1F4D0", - "digest": "5576802d8bcb8836f473d9c7641ff666250c23c8476c676b253e577695025959" + "digest": "a0367dcf663ec934f1fc7c88bfaccc02b229a896f60930a66bb02241c933e501" }, { "name": "trident", "unicode": "1F531", - "digest": "70c1e8254da5b0e4552673b487503a20feeb249484d4596836b75de70220be82" + "digest": "ee45920845d3b35c2e45b934cf30ce97bfe2f24c5d72ef1ac6e0842e52b50fc1" }, { "name": "triumph", "unicode": "1F624", - "digest": "b09262121b0d3d9d017ded22d0fbb1acaa6ee8c9d38e9ac34292b390d97408fe" + "digest": "4aa44b8e1682c1269624a359f4b0bf613553683b883d947561ab169d7f85da0f" }, { "name": "trolleybus", "unicode": "1F68E", - "digest": "5af943836cc30c3b79160c70b6488c984fa63c104dce08c436597a93d30ff6f4" + "digest": "f610b4fd1123f06778a8e3bb8f738d5b0079aeb0b0926b6a63268c0dd0ee03ed" }, { "name": "trophy", "unicode": "1F3C6", - "digest": "c249938815042716db2b39cdece6715fabf9e56ed583270c451925e6c91f9191" + "digest": "50cfbedac18bf0fa5dec727643e15ec47f64068944b536e97518ee3be4f08006" }, { "name": "tropical_drink", "unicode": "1F379", - "digest": "352d903e813a27d2a74803322539b50a50aec0ca2ed7ab4a92ec480b1c226cb6" + "digest": "54144fce60d650f426b1edf09e47c70b2762222398c1fe40231881f074603a69" }, { "name": "tropical_fish", "unicode": "1F420", - "digest": "13a104ca9c326238ab8d85b60759629b4efaa836946fbe58d78d779443475f7b" + "digest": "fd92100aaa9328da35e6090388824921b9726b474d1432a926d2cf9c45ad6528" }, { "name": "truck", "unicode": "1F69A", - "digest": "13d381d6b43b42350a1e24c02296904b8fdc38c1bf0939fc7037850127e91f21" + "digest": "0d1571e58e900abc453df0ff683fe7acb5906ecbdd52ab35b7101074359faf18" }, { "name": "trumpet", "unicode": "1F3BA", - "digest": "df7fb48920ac0919ee2d7b30102016479f747a5d4dd25b3e18d9f17121d232d1" + "digest": "cea3614c309f5573f328f4603120dbe930016a35f0dfa400b0d968fe9fff2d55" }, { "name": "tulip", "unicode": "1F337", - "digest": "519a84336464b5dc8db57eecef3e5b8ed82ccfdaa0ed0fa9ef7bcf0e8acea1f8" + "digest": "e744e8dbbdc6b126bd5b15aad56b524191de5a604189f4ab6d96730dfef4d086" }, { "name": "turkey", "unicode": "1F983", - "digest": "e87bff52ad3e301dc62f6832b8a6fcaf99db260a96263e4203a55ce3abda8cf8" + "digest": "bf5daef15716b66636a5fdb6d059420521443c0603e2d56bd7c99c791a7285f4" }, { "name": "turned_ok_hand", @@ -10412,442 +10412,442 @@ { "name": "turtle", "unicode": "1F422", - "digest": "388b3e75b931638a09f65b842d26e2cc87b200ba782dec871f84cddd71aaeaf3" + "digest": "588c35fb42c9502a908e9805517d4cc8c4ba4e74c9beed4035779fea1efe14f8" }, { "name": "tv", "unicode": "1F4FA", - "digest": "dba03be6482d6291599c7393b0f749c0de5c873d45c96a20ccc53b3e104a6a24" + "digest": "1279f3f3955a58dbbf74e248fc914b0bdba9c4c6b6a5176e9d12bf2750ecfeb4" }, { "name": "twisted_rightwards_arrows", "unicode": "1F500", - "digest": "5fcad0247576e10e683f353008749975e9371a4f66c0901a73c3a0c7803c63c7" + "digest": "fed07eebc2cf0d977ca0826bbd80defafbbcf118508444148f47b58949ebe27c" }, { "name": "two", "unicode": "0032-20E3", - "digest": "20ad722532a5073fff8aef0a5e890421da0ae97f0723a8a2cc503c13d24ba597" + "digest": "b346f51f6523b02ebcbd753256804e2f9cc1574c96aa634362bf9401dac2c661" }, { "name": "two_hearts", "unicode": "1F495", - "digest": "160cb11e3ed2ae1b20957d445c6c4b4bd604d067294818dfeeefba4562425eb9" + "digest": "6ded120a59aed790b441ec8fbbdea6f5cbfb4fa48e9e4b224cc29c9fde2d2e4c" }, { "name": "two_men_holding_hands", "unicode": "1F46C", - "digest": "923734704e544f7484fdb424bfe26f51ee07754db712cd151f8fbe955023a1ab" + "digest": "bfcf9e20a67d00262cdf6e85f1acd545dda91f2e370d68bfd41ce02f232a2987" }, { "name": "two_women_holding_hands", "unicode": "1F46D", - "digest": "58a40e7819cab3589ac81bb4fdc485b7196ee355544b54c6b00169028c260130" + "digest": "9d9d2b37a7f8e16fde1468dd8b5645003ea81ae4bf8bcf68471e2381845dd0dd" }, { "name": "u5272", "unicode": "1F239", - "digest": "b7e8ad52629a1f1fca77a5c9a51da87ce2b9a81f6af9bcbe9bec9552d398e9bf" + "digest": "01e6cb8f74ea3c19fdade59c2d13d158b90dc6b4b293421b2014b7478bf20870" }, { "name": "u5408", "unicode": "1F234", - "digest": "f359799d206cff6aae3af26eb8ad153abd38e817d4c70b2e5e5e8cf2f46e645e" + "digest": "084cdbd5436670ea4dc22010e269c1ab7b0432897b8675301e69120374bcdd14" }, { "name": "u55b6", "unicode": "1F23A", - "digest": "c40293bea0f148e76ca5152e830b1b474380fe259180fbf74fece1ccc9afd8a3" + "digest": "c1017023d20d4aae78d59342dd3bfc5282716ea0601d9a8c2476335cbf7a2e12" }, { "name": "u6307", "unicode": "1F22F", - "digest": "45449f7ae29da9e507c19d0f2b22f17f7cbd763f2ec87eb893be5bae49c7f78e" + "digest": "f459b092b974f459db1fb9cc13617a448b2e4f2b4dc46cc316d8c46af6e7d8bd" }, { "name": "u6708", "unicode": "1F237", - "digest": "b897ead8c952013975ce6f381cdb8c584ebe4015311ef87f2a332c8a9e155d75" + "digest": "928815abf5b30f92efe5168de0c7e6cf8c17899a03e358ab42f42667e0a4a04c" }, { "name": "u6709", "unicode": "1F236", - "digest": "8b2f792abc1313a1a58f2fb8b37ad68a964004c962535f7739131257b1331a05" + "digest": "f63a48ee06c892d24acec8b5634c021658d2ebde67a42d8faa86f27804a9f26d" }, { "name": "u6e80", "unicode": "1F235", - "digest": "fd982a56d4c492e63526b427bb948d7f155b0d5c414a68c7177698a71e72269b" + "digest": "489181d90a5e43068459530673a153e4af04fdad8514ec341ff7afbcfd366c3b" }, { "name": "u7121", "unicode": "1F21A", - "digest": "334f87a5254b58503d9f7a8ecc3d971a99839ec9c22c443469d72caca1750a48" + "digest": "9c50fd2ba14221affd2dcd3746322c2137dd75458493f4d385b544eb5bd8d6cd" }, { "name": "u7533", "unicode": "1F238", - "digest": "3c8e743ae9960e43b9fa0cc698018fcb2a52ae34d143f0561298191f9def019c" + "digest": "2b05819b380a2ea47cc5fde8fcce3d53922fd223d6f5bd83d696d44175b69f18" }, { "name": "u7981", "unicode": "1F232", - "digest": "a08bf39be3a54c076de79478c09b79c5c4d221853722870dd6e81abb78a4b64a" + "digest": "adbe12601b22972003ddebcb0bd1532b979aa9c78bfdc147511854b5014eabc0" }, { "name": "u7a7a", "unicode": "1F233", - "digest": "5dfb74a534a6490df989f84eac271c79d52f29313b6d43662dd0ff029794367c" + "digest": "b9ee0ec7bb0b86c3eb73d4dbbb91848c427bf356ae30a263b9b44bd9bd784482" }, { "name": "umbrella", "unicode": "2614", - "digest": "ff1191f6c11b82f5337f78aadb58af50c69abaf676a384b0473bf49004e4018f" + "digest": "0328a2f48b7df47905e2655460e524c0794ef12d3d7c32a049a10892d5662f77" }, { "name": "umbrella2", "unicode": "2602", - "digest": "aa7db9d6ed42dff847a8e5ee48a8eeff7a6e7f30de155a28951407f5aaa3dae2" + "digest": "2f6a58110dc590480a822a3ffa2b5bc86f295e0c994a4a632837d25d4cf9fc58" }, { "name": "unamused", "unicode": "1F612", - "digest": "efbbcaee6f3178afe509d74d13243ec6befe3112620a01e5079171eac4b32417" + "digest": "0d597088e3e7880918d0166e5c69243b18fe64afa31685c39bfdbc71494aa132" }, { "name": "underage", "unicode": "1F51E", - "digest": "ae9a300fa400a57b7216a0a040fb8a5f02236fbceeeceed58bfd953c87ad51fe" + "digest": "b6b194614ca714ac2b1c2c17b75fe5922c7fdadb3d1157ba89ab2a5d03494a67" }, { "name": "unicorn", "unicode": "1F984", - "digest": "1b1e9c209dabe619db76fd346c3fb51b28ace0e4102697fe0973fe2d46aa9f08" + "digest": "f71bb485a7c208e999dd45f2b36d7b7d517898c0627947926b05aa28603804ca" }, { "name": "unicorn_face", "unicode": "1F984", - "digest": "1b1e9c209dabe619db76fd346c3fb51b28ace0e4102697fe0973fe2d46aa9f08" + "digest": "f71bb485a7c208e999dd45f2b36d7b7d517898c0627947926b05aa28603804ca" }, { "name": "unlock", "unicode": "1F513", - "digest": "63dbef0855399254ae01cf4ef0676adebc1432ae1ee260b569c23ae8152deaf8" + "digest": "9554ef3a6a315938b873e77970d9b0212e61f13c6cc36e4f17f87acc930a9a53" }, { "name": "up", "unicode": "1F199", - "digest": "902a3ecbcd73099a28476b49bc9e7b06da6cc002ee584e0501e5b625fb515088" + "digest": "ff2554ccf08c7208b38794c5fa3d9a93a46ff191a49401195d8f740846121906" }, { "name": "upside_down", "unicode": "1F643", - "digest": "763fe2baf07a9b04f96958adf38a43c7dd2bc70d57398f49604307bd835cbb53" + "digest": "5129121f0a28f5b334268c28565de26a5907559568deca11de6ec620b097dfe1" }, { "name": "upside_down_face", "unicode": "1F643", - "digest": "763fe2baf07a9b04f96958adf38a43c7dd2bc70d57398f49604307bd835cbb53" + "digest": "5129121f0a28f5b334268c28565de26a5907559568deca11de6ec620b097dfe1" }, { "name": "urn", "unicode": "26B1", - "digest": "dbfd5b90709d1b812d2fff71a5cfa10f84a4579866c2d7cd0e80759a22b2ba0e" + "digest": "9bebf589eed8dd361f6a03cd1b325078f2cd0e82270ef63a7dd1b6aee08cd1e6" }, { "name": "funeral_urn", "unicode": "26B1", - "digest": "dbfd5b90709d1b812d2fff71a5cfa10f84a4579866c2d7cd0e80759a22b2ba0e" + "digest": "9bebf589eed8dd361f6a03cd1b325078f2cd0e82270ef63a7dd1b6aee08cd1e6" }, { "name": "v", "unicode": "270C", - "digest": "df85ad1a3ff365c3232a010701c9b25cd824d19fa2511422dee60ac231f457e3" + "digest": "9825bf440df289a8edf8ede494e8c778dc63c95f967f4d7bbea3245cf4f558ec" }, { "name": "v_tone1", "unicode": "270C-1F3FB", - "digest": "ce45db8de862b6f37d9208920d7c7c19335fac2cbff59b52be1ccbc01e3249da" + "digest": "76e358250d9ca519b60b8d7b6a32900700d784433dcc609e9442254a410f6e37" }, { "name": "v_tone2", "unicode": "270C-1F3FC", - "digest": "9036c8d793b02b4d2e6a4752b8ec319ec50efd6fcd6feef7b0671a63e5659acc" + "digest": "4081b674be8416136022523fa9f29ec70a0f7e3aa05ca13152606609f3fd003c" }, { "name": "v_tone3", "unicode": "270C-1F3FD", - "digest": "a94b95f7656d62b442c99f2643b96b0c6114683401a94cdda68405c37efecc4c" + "digest": "b6afb3a4c78384280610b953592d378241c75597a82aa6d16c86a993f8d8f3b0" }, { "name": "v_tone4", "unicode": "270C-1F3FE", - "digest": "5c75f74993856f2faeeaee68df7689056e60d30e8c573039db8303167f7d0a80" + "digest": "7ddc3cdd0138da2c8d7f6d8257ffdb8801496043e8a2395f93b0663447ac7fce" }, { "name": "v_tone5", "unicode": "270C-1F3FF", - "digest": "bb899672adb3c11f65983fbf9581de7f0a1bbac86fde146e799cea1126fe241e" + "digest": "a85dc5c589f0d1cf32f8bfa5c82e5c11c40b35439636914686a2f06f7359f539" }, { "name": "vertical_traffic_light", "unicode": "1F6A6", - "digest": "36296e03620f16d35e5cec195cd97f5b358dfdedcd43bc1b3f7988ff7e85ab47" + "digest": "8cfd49a8f96b15a8313ef855f2e234ea3fa58332e68896dea34760740de9f020" }, { "name": "vhs", "unicode": "1F4FC", - "digest": "f4be55f4c23a85e0caacbf569742c117c8fd52c189465a6560cbd2f8873ad74f" + "digest": "3fb1acaf25805cf86f8d40ee2c17cf25da587b7ca93b931167ab43fce041eee8" }, { "name": "vibration_mode", "unicode": "1F4F3", - "digest": "b9b8dfa3160c22f78b7d627cb52636d81ca6230a196cee5e94028e32e06b9a98" + "digest": "c9a8899222f46fe51dd8cee3e59f77c48268f0b7cfae2bcb34a791213acb1755" }, { "name": "video_camera", "unicode": "1F4F9", - "digest": "3bfaa24e5fb00145e3e4dd07ecf569dabbb3f211551e46085ef23cf23002cfc3" + "digest": "62e56f26c286a7964ef1021f0f23fcb4b38cdcfb5b5af569b472340c412c619a" }, { "name": "video_game", "unicode": "1F3AE", - "digest": "4dcbd76030e37d0f7429852991a5f3f126cbdedfc124ecad0ba29d227375f6e2" + "digest": "2787e302aa9e6fd7e9dc382c9bc7f5fbf244ef4940e08a4f9e80d33324f3032e" }, { "name": "violin", "unicode": "1F3BB", - "digest": "8ab7adc6e1e934f9e05009cd0a6d4da3136092c8f11c0606b91914be182206f5" + "digest": "1e69d531ce2b5d5bf1dd9470187dbbe76f479d14428834b6a9e2bf5296dc0ec9" }, { "name": "virgo", "unicode": "264D", - "digest": "aaa19752756d0cac949445de1d2b8bf1f75a071368ae0acf5002f4acdc34826f" + "digest": "0f75e9c228bc467fd0cec0f93f0e087c943bc5fb1d945fb0d4de53d07718388e" }, { "name": "volcano", "unicode": "1F30B", - "digest": "86c17d61d66bfa868c02f1d31daca22f077c096368ef53cd9bfb9914a2f0b273" + "digest": "41c92ef88ca533df342a0ebe59d2b676873bfa944c3988495b8a96060a9b8e16" }, { "name": "volleyball", "unicode": "1F3D0", - "digest": "b505684b13f814fbc08dc8ff652849328f46068276e0a24ae1961e2aff15868f" + "digest": "774a83357f7aee890b4d4383236f0a90946dbd7c86aaabadc5753dcc9b4c9d69" }, { "name": "vs", "unicode": "1F19A", - "digest": "e31bd8b48b88c21d717964d1360a7751684dd1e0b63fdd655f1a9ec10a952dfb" + "digest": "ac943e4c737459c2e1adbac8b71d3fdaebb704dbaf5713012e7a77beb09db1ef" }, { "name": "vulcan", "unicode": "1F596", - "digest": "ca800fce797e652c5f47bf44992e8fbe19554688a36423fdf7c29ca6defae1e0" + "digest": "b4d409a0b019e7b06333cefd15ea46cb54aef5132d86e8ba361c1c3b911fe265" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers", "unicode": "1F596", - "digest": "ca800fce797e652c5f47bf44992e8fbe19554688a36423fdf7c29ca6defae1e0" + "digest": "b4d409a0b019e7b06333cefd15ea46cb54aef5132d86e8ba361c1c3b911fe265" }, { "name": "vulcan_tone1", "unicode": "1F596-1F3FB", - "digest": "84bafdaca43426b053f5caa4e868ca109d99113a28ea9799db09d3c5d5f645c8" + "digest": "cc6072c85031b5081995f98a57f09ab177168318f69a51f3acc63251760499a4" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone1", "unicode": "1F596-1F3FB", - "digest": "84bafdaca43426b053f5caa4e868ca109d99113a28ea9799db09d3c5d5f645c8" + "digest": "cc6072c85031b5081995f98a57f09ab177168318f69a51f3acc63251760499a4" }, { "name": "vulcan_tone2", "unicode": "1F596-1F3FC", - "digest": "e7cedf63ead957ee5c287e4cb0828ba70673e17b604f92b529875c32d094e7e3" + "digest": "858bd5a1ac91dc4d7735f57ba4dd69d39138aa6dac1c80cfc05de30a59a5bc33" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone2", "unicode": "1F596-1F3FC", - "digest": "e7cedf63ead957ee5c287e4cb0828ba70673e17b604f92b529875c32d094e7e3" + "digest": "858bd5a1ac91dc4d7735f57ba4dd69d39138aa6dac1c80cfc05de30a59a5bc33" }, { "name": "vulcan_tone3", "unicode": "1F596-1F3FD", - "digest": "e124fef20f289921553274cf834f6dcc1a012889d30d9874dc5ad01afb8235b8" + "digest": "2f74b6f3eab2a75063591b66f1c7350af0d23153e1427af91de20c48a5f4a54a" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone3", "unicode": "1F596-1F3FD", - "digest": "e124fef20f289921553274cf834f6dcc1a012889d30d9874dc5ad01afb8235b8" + "digest": "2f74b6f3eab2a75063591b66f1c7350af0d23153e1427af91de20c48a5f4a54a" }, { "name": "vulcan_tone4", "unicode": "1F596-1F3FE", - "digest": "ea2115f549e4680467521bbf362b229f4a8f0fdadbfaf231378d801f9b369f08" + "digest": "87cf8b87d3610f742857a9704b658462df32b4924d8f1ddba26f761e738c4e11" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone4", "unicode": "1F596-1F3FE", - "digest": "ea2115f549e4680467521bbf362b229f4a8f0fdadbfaf231378d801f9b369f08" + "digest": "87cf8b87d3610f742857a9704b658462df32b4924d8f1ddba26f761e738c4e11" }, { "name": "vulcan_tone5", "unicode": "1F596-1F3FF", - "digest": "1b322e1252491f35ae02f0b279b6529dad867f2a6b3c2c3e77f981bed07e447d" + "digest": "11e9ff62f2385edeb477dbf66c63734536531def5771daf80b66a3425ac71493" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone5", "unicode": "1F596-1F3FF", - "digest": "1b322e1252491f35ae02f0b279b6529dad867f2a6b3c2c3e77f981bed07e447d" + "digest": "11e9ff62f2385edeb477dbf66c63734536531def5771daf80b66a3425ac71493" }, { "name": "walking", "unicode": "1F6B6", - "digest": "8ec0b2207d4368422261bc58944c17dff2554b2356becfb18f21dd87425cd67b" + "digest": "ae77471fe1e8a734d11711cdb589f64347c35d6ee2fc10f6db16ac550c0557fa" }, { "name": "walking_tone1", "unicode": "1F6B6-1F3FB", - "digest": "9ee2224226326833fb0c9598c737fbd2f6bca1c81f082537e9f22ea1de4ff48e" + "digest": "3de871c234e1340ccf95338df7babd94d175cfcb17a57b5a74d950e0a31f03b1" }, { "name": "walking_tone2", "unicode": "1F6B6-1F3FC", - "digest": "4855d521e937d10d58eeb2bbada493699e31e1098128f81a9e3303bcf3edeb49" + "digest": "620eb7bfb753a331a5822b02bdaf08d8dde7b573efd210287a3d3dfdd84a40b9" }, { "name": "walking_tone3", "unicode": "1F6B6-1F3FD", - "digest": "82669cf7167054a3615add01059f87dbb809edac3889ee171d5994de90448000" + "digest": "ff39545acc2256006128f8c186433c28052b8c9aaec46fe06f25cff02c71f6b8" }, { "name": "walking_tone4", "unicode": "1F6B6-1F3FE", - "digest": "c11f03aa96248272f831f68b93c5b21b2ecbffeb1b4c1c13373bf539ee7db8f8" + "digest": "a9499d142392977a9b9e54fb957952359e9bdffce7ec2f1e8320523d185fb066" }, { "name": "walking_tone5", "unicode": "1F6B6-1F3FF", - "digest": "18238ee121a64211f6bcdbd475cee4ad6debe2bf421daba53d125aa005c26d10" + "digest": "b47a4c48ce40298f842f454fc1abccae70f69725d73ee2c80e4018f4c4065d7d" }, { "name": "waning_crescent_moon", "unicode": "1F318", - "digest": "96ef03ff85247877255a5ca3e8a8bb63f7d41f66531e8db61cbcd863e3ad7355" + "digest": "2ec7896eefcf821e0ea013556a17af59e997503662c07f080d0a84ab13ef4cf1" }, { "name": "waning_gibbous_moon", "unicode": "1F316", - "digest": "994223113ad151e6b42ee317a10dad18f86759a308e61ab88eeb10ab780aae67" + "digest": "ce2f5aca8fccdacaaf174d10da4e493e853e4608cc4d159aa3081d108a8b58d5" }, { "name": "warning", "unicode": "26A0", - "digest": "a702e51efd1a3ab425eada008ccf694f38a71db14bb710edacc2e206d61f5ca3" + "digest": "745f1d203958f42bf37ecb5909cd0819934e300308ba0ff20964c8c203092f90" }, { "name": "wastebasket", "unicode": "1F5D1", - "digest": "afecb31aaf5078298ab9f7c5da29a49ce0cdefe477ee50889be9c0e43ccf1799" + "digest": "221a1b6d9975051038d9d97e18a16556cdf4254a6bca4c29bf1c51f306c79f2a" }, { "name": "watch", "unicode": "231A", - "digest": "410334c87b8552f601f4ea1b7e36582a8b22f11b804d5ab1008d4af2b5a0cbe6" + "digest": "acc0c96751404a789b3085f10425cf34f942185215df459515d2439cde3efc6b" }, { "name": "water_buffalo", "unicode": "1F403", - "digest": "d1becfaea464372c46e5442c6030ea355806ce5864c2435c123a9bb3a2c3c5eb" + "digest": "ba6a840d4f57f8f9f3e9f29b8a030faf02a3a3d912e3e31b067616b2ac48a3d1" }, { "name": "watermelon", "unicode": "1F349", - "digest": "88dd78812520c44080c79fe8cb1825bc713e5155da2ce8c73286333749e7035e" + "digest": "42a3821d2e4dd595c93f5db7a5c70b7af486b8f0ddd3b9d26bc4e743a88e699a" }, { "name": "wave", "unicode": "1F44B", - "digest": "5103c49914ff1a2d76a1ab6db2530ddd9f48b98b708ab15292ceadf28873c939" + "digest": "cddbd764d471604446cbaca91f77f6c4119d1cfc2c856732ca0eaac4593cb736" }, { "name": "wave_tone1", "unicode": "1F44B-1F3FB", - "digest": "ef2d79f377d09dedd1e900b2f4e4a2412bf562cd88484f71c52d465053f8aae9" + "digest": "cf40797437ddf68ec0275f337e6aac4bed81e28da7636d56c9f817ddf8e2b30a" }, { "name": "wave_tone2", "unicode": "1F44B-1F3FC", - "digest": "d323e6e2e9ce035bc11b98226d46ab393dfdf3909d99e7a828b51950e6574656" + "digest": "12c8a3e82c03ee35a734c642be482ba2d9d5948dacf91ec1fda243316dd4a0d0" }, { "name": "wave_tone3", "unicode": "1F44B-1F3FD", - "digest": "8a8a386d53252455c20d6b235c462fd9cb3b20c9c19c67e67b3dece4621b5cf6" + "digest": "ebcaef43e21b475f76de811d4f4d1a67d9393973b57b03876e02164345a2ba4a" }, { "name": "wave_tone4", "unicode": "1F44B-1F3FE", - "digest": "a8281c2ab9cf6e2b3d3cad24707fe412ec2398195530b716a2617477416c0432" + "digest": "7df7b70cf76766836ba146c3d91b6104930c384450cf2688426e60c1c06a1fc8" }, { "name": "wave_tone5", "unicode": "1F44B-1F3FF", - "digest": "5ccbee95bfc180580c8a02b88146110c4d132b8ea618dd6a58f03c1db921d58d" + "digest": "8dfdba6aeff5d7dfd807467d431a137547726b34d021f1a5a0b74e155d270ea7" }, { "name": "wavy_dash", "unicode": "3030", - "digest": "b5b67fc12938801a98ff22b6f7b566c603f58c183737fa740a500724879f0e99" + "digest": "7b1968474f01d12fd09a1f2572282927138d9e9d6a3642de4bf68af80a8c3738" }, { "name": "waxing_crescent_moon", "unicode": "1F312", - "digest": "20446122d170b18f88ea71524f6747d42b97f9d765c52e676e5163fee58ec379" + "digest": "852d7e55a19074d061fa3aa80d6b1e7e87a9280bdf44d94bbdbbe6d59178b1be" }, { "name": "waxing_gibbous_moon", "unicode": "1F314", - "digest": "4324e43d4d45e6333f7379c9feb8efd3093d76f3920d7dc5ad3c615e76104998" + "digest": "a3a1c7cc72521a3f74929789a90e1c35d81ac86e21225c9f844d718d8940e3b3" }, { "name": "wc", "unicode": "1F6BE", - "digest": "cb7c5d35bf11149d12cda2c0897cb6038e043127055bbe2e8e33c9b422d6d8fc" + "digest": "4b95d54e0b53e4b705277917653503b32d6a143c2eaf6c547bc8e01c2dc23659" }, { "name": "weary", "unicode": "1F629", - "digest": "29a291033a1b67eda3710dffae42d63fcfa663e37dab728c236172f3e877fe8f" + "digest": "3528f85540996cd5b562efe5421c495fc1bb414dc797bc20062783ae1b730847" }, { "name": "wedding", "unicode": "1F492", - "digest": "6c7d874f464c9c76b0d767135aa40ced94089b5f71d373098b47488d7f3ef7c4" + "digest": "980f3522cc4c19c3096e668032ea2cd19e7900cdc4b73bbb1c9b4c4d28dc78af" }, { "name": "whale", "unicode": "1F433", - "digest": "94168acda6ba502b64ea50ff4aaafb7e6258d7c6806e91f090c8a3c46edc5b6d" + "digest": "6368fe4bc4a7f68aa2bd5386686a5f1b159feacbec16d59515f2b6e5d01adfbd" }, { "name": "whale2", "unicode": "1F40B", - "digest": "e1cde2308bd510b2449c96e88ffec796856f98b19ceedc1cd7e9ea009dae1417" + "digest": "ccd3edf88167965f2abc18631ffb80e2532f728da35bc0c11144376685da18e8" }, { "name": "wheel_of_dharma", "unicode": "2638", - "digest": "bbd6927697c22a1c3e56fd0c9933d9e00dbf120505fe48d02cb486bcd67a8b2c" + "digest": "4a0a13fcd507b9621686c8090bf340aa8770c064e0e3eb576fbae1229000d6da" }, { "name": "wheelchair", "unicode": "267F", - "digest": "513f759acf528f6a7e39d9de1d171c3faebe645c9cf3bd86b185123016beef95" + "digest": "f5250f2b4b5b4ffe6a6f77d30865c3f5d7173fc91aee547869589b2a96da91c8" }, { "name": "white_check_mark", "unicode": "2705", - "digest": "a0b3bf7c4fb131e7a9fab5169ea4094e2665e02cedaa091f0d6e78609b2f17ed" + "digest": "45eb17bde6e503f22c8579d6e4d507ad6557a15f9eaad14aa716ec9ba1540876" }, { "name": "white_circle", @@ -10857,142 +10857,142 @@ { "name": "white_flower", "unicode": "1F4AE", - "digest": "a3efea4950e09994f5e9d3d16f0728969238302304a6cce90b293c56e9a3e20c" + "digest": "ace093b310eeefdecf4a4bdaf4fbcbb568457b0191ac80778a466ac5f3f4025a" }, { "name": "white_large_square", "unicode": "2B1C", - "digest": "99c4442a65f2e3c568f45aed9e74590206c517a716557f4d741d967c9f42ed40" + "digest": "0db6957ee9ff7325b534b730fc05345a63d4ed9060f0f816807d0dcf004baa3e" }, { "name": "white_medium_small_square", "unicode": "25FD", - "digest": "a1edfeb4e540dcc020ba5dde19f7a18d90966788baa5382a22a0f9038d593f01" + "digest": "d79689981a7b38211c60a025a81e44fd39ac6ea4062e227cae3aab8f51572cd4" }, { "name": "white_medium_square", "unicode": "25FB", - "digest": "794c2339ca71bb6d65ac488fb7b5dc4f0a2412f30890d2c4ece53cdbf52ba78b" + "digest": "6c4ce26d3f69667219f29ea18b04f3e79373024426275f25936e09a683e9a4fc" }, { "name": "white_small_square", "unicode": "25AB", - "digest": "9c4c308070a0c4524993cc36feaa778aad8f0df9f209b82d28b1f3811c441bc4" + "digest": "ae0d35a6bbba4592b89b2f0f1f2d183efb2f93cf2a2136c0c195aab72f0bb1c8" }, { "name": "white_square_button", "unicode": "1F533", - "digest": "f46e18c7250c874d1b4d6117eda741d86a081352e76f3d019dd64af2669fa4bb" + "digest": "797f3d9e44e88e940ffc118e52d0f709eec2ef14b13bdf873ad4b0c96cc0b042" }, { "name": "white_sun_cloud", "unicode": "1F325", - "digest": "d8ce416e6bdb0e59e06e2fceac3177dbe59fefc248fd8c6d76b80d1418141070" + "digest": "0e714038bb0a5b091dd4ad8829c5c72dece493e09da6d56ceadcd0b68e1c0fd5" }, { "name": "white_sun_behind_cloud", "unicode": "1F325", - "digest": "d8ce416e6bdb0e59e06e2fceac3177dbe59fefc248fd8c6d76b80d1418141070" + "digest": "0e714038bb0a5b091dd4ad8829c5c72dece493e09da6d56ceadcd0b68e1c0fd5" }, { "name": "white_sun_rain_cloud", "unicode": "1F326", - "digest": "d2b132518261864ac4a95707eaeea335dd8351ed2b8ef4e2272ced456e309bf1" + "digest": "82fb2a91d43c7c511afed216e12f98e32aef4475e7f3c7ccc0f39732d2f7d5e5" }, { "name": "white_sun_behind_cloud_with_rain", "unicode": "1F326", - "digest": "d2b132518261864ac4a95707eaeea335dd8351ed2b8ef4e2272ced456e309bf1" + "digest": "82fb2a91d43c7c511afed216e12f98e32aef4475e7f3c7ccc0f39732d2f7d5e5" }, { "name": "white_sun_small_cloud", "unicode": "1F324", - "digest": "b86a72f1cdb4d24fd3ab180aae9db012ca51fc01f3786aab596c2e330066b185" + "digest": "0a6164cdadf2413555b7ef47b95f823f5a010f36d2dacfb1a38335a0f59e9601" }, { "name": "white_sun_with_small_cloud", "unicode": "1F324", - "digest": "b86a72f1cdb4d24fd3ab180aae9db012ca51fc01f3786aab596c2e330066b185" + "digest": "0a6164cdadf2413555b7ef47b95f823f5a010f36d2dacfb1a38335a0f59e9601" }, { "name": "wind_blowing_face", "unicode": "1F32C", - "digest": "20bdeb8e39dc637792ac9fbee031c5791889f3126e83556ba51f98809c19763c" + "digest": "e4f63149cbc8829118571f6a93487b96d26665fc15d17d578cca4e5c752cd54f" }, { "name": "wind_chime", "unicode": "1F390", - "digest": "1fc26f33ce13b6a969bb76e914de054ec5d1c7c4cd1dc5ee8fea5f3149f794d8" + "digest": "1b1b212fbd74a9edc62aee7ffab9bcf91d3a9f69bffb2be4b7fd527914c14ced" }, { "name": "wine_glass", "unicode": "1F377", - "digest": "7dfcf9c5195a20fd2745b19e102910392b0fc8f1650b98ab81957807841935e0" + "digest": "d99107d6809386bc5e219aa58ee4930d27b7c3a6d2b10deb9f523df369f766d1" }, { "name": "wink", "unicode": "1F609", - "digest": "404ac6c920414ca35894da1d97b3b2fabe92bd09569274eb5798fbb297129036" + "digest": "56e29994a47335a901d0c98fa141d26faae8f647a860517bd3615fa980921885" }, { "name": "wolf", "unicode": "1F43A", - "digest": "ebadd7766c4a314b4027c32435a2f5727a6283123dfb8834e10251cbfc07ca2f" + "digest": "4a983f5ec8ec0872fcde7890e17605b1229064e5e194b6fca1c4259068d1caed" }, { "name": "woman", "unicode": "1F469", - "digest": "9f0dbb5d1e0db4f008141582dcb6413f5aebaa13e191349c976a435b2bee0956" + "digest": "a06a22a48eeb3aeb885321358fe234e97797ed33be17f52d232ce2830cfbcd97" }, { "name": "woman_tone1", "unicode": "1F469-1F3FB", - "digest": "c1f2a503481fdd96cfbfa7d556500f8e0da0cea1c72ed1078ecbb6962221c22a" + "digest": "c2e4b135c1dac6a0b002569a6ccd9d098f6cb18481c68b5d9115e11241a0978d" }, { "name": "woman_tone2", "unicode": "1F469-1F3FC", - "digest": "bf78b3a8f7424037069f8ac337e154ef185f55026c71a6cf6dbe15eb42ef9813" + "digest": "4848e650051214a53c4cd9f6d3d94158f77f65ecb34f891789de34ee0a713006" }, { "name": "woman_tone3", "unicode": "1F469-1F3FD", - "digest": "4ccd70a2052b932b3395ac0a957c05815327dc8082fd461abcd797411db8ce05" + "digest": "b6f751ad47da019cdfb9d6d78f9610adb92120abf204c30df79a9150b57dbdee" }, { "name": "woman_tone4", "unicode": "1F469-1F3FE", - "digest": "71b5efc4a410102e60048ca05f87587384a6db309f3be94109a4f92ea97072dc" + "digest": "fd27d3a669dc34313fbfe518df7dc2ded3ade5dde695f8d773afe87bf8a8b0d4" }, { "name": "woman_tone5", "unicode": "1F469-1F3FF", - "digest": "91a1cd015731f4db501c276a8236eb0665e4dc7aa1891e2a67b8d3e543fbea9c" + "digest": "9ae9b14dfff40fa60a565d89479727feeba4fd6ffea9acb353a81b14aba751d4" }, { "name": "womans_clothes", "unicode": "1F45A", - "digest": "599332c0b863a40fd0c319e4e0f52ae847326a96d180c288e0466b3ac308a27e" + "digest": "d12a27810780fe5cd8118ed4587e0c4e70dbe9bcd014c6866fe6a8c9c7c55698" }, { "name": "womans_hat", "unicode": "1F452", - "digest": "231ff55c3fa56d8fb5731fe41f547e67ffacfdde82286f45d4ca65a2d2821239" + "digest": "52a0255b3483085bd125d39b74516ab6a81003964f44995c2fac821e7ff93086" }, { "name": "womens", "unicode": "1F6BA", - "digest": "f971429456b543804412490af2e27e0b14d0d536a156db898bce67b136e1b563" + "digest": "7e38964006f8b28dfa2b3e9b2b16553bb50c18a63455f556b0bff35ee172137e" }, { "name": "worried", "unicode": "1F61F", - "digest": "e017f636e79b9301f3a06471a5f3513ba7dbb9b97938de1140c1df4c32fd8844" + "digest": "5a073985e1344bc34201ef94a491f7f2b946f5828c9fdbc57eeb2dcd87ac3a6b" }, { "name": "wrench", "unicode": "1F527", - "digest": "c9ded4f7f496bad8691677226310bbd31bb485722ea479bc7a68a2b4ef9d55d9" + "digest": "81aae53bc892035b905bf3ec5b442a8ecc95027c5fa9eb51b7c3e7d8fad3f3f4" }, { "name": "writing_hand", @@ -11007,76 +11007,76 @@ { "name": "writing_hand_tone1", "unicode": "270D-1F3FB", - "digest": "38e64e6dca4847a12aef8a117c113b2025d841501c4bc8188c57d0c8a4f1e34d" + "digest": "2c7e2108e1990490b681343c1b01b4183d4f18fbdef792f113b2f87595e0dad0" }, { "name": "writing_hand_tone2", "unicode": "270D-1F3FC", - "digest": "2b2d0ac2701ae707c31d9c85feb2e3700e11398701e2b0519338897817d53baf" + "digest": "87ec8d44f472d301adbcbd50d8c852b609e46584057f59cc1527401db363c1bf" }, { "name": "writing_hand_tone3", "unicode": "270D-1F3FD", - "digest": "85d67f90ff8bd2e7157f28fd857e6730b660a7eb82eb5350f57671f728ce725b" + "digest": "4a48ddef91f7264e8fa9cca223554db22b3a2e3153e94b88d146644ea6dd661e" }, { "name": "writing_hand_tone4", "unicode": "270D-1F3FE", - "digest": "056c05c201b3d0972433f00910967ad7334e37726e2956fee053ec2e1a9153c7" + "digest": "e5254564a1f91e42ee59f359d8cd26f52abdc04dca8f3b37cb2f140cb7f71390" }, { "name": "writing_hand_tone5", "unicode": "270D-1F3FF", - "digest": "95c59157d301ee08990e4302fd9bdd7953e1d1abed09636d0837d84e44f53ba6" + "digest": "61299bf86d83d323ca3e6052c535ae66c6f7b3d9866a37db0464223b8bc28523" }, { "name": "x", "unicode": "274C", - "digest": "1d256b0015b9cbdeaa4558f9241782c89d86c79a42e507621f7949c56a90b6c0" + "digest": "3e5a7918e31ddefdf1ce73972365e2f0bfd2917d6a450c1a278c108349c9425d" }, { "name": "yellow_heart", "unicode": "1F49B", - "digest": "e869a80266b4379a8d82988fef25e187632bfb076ae619f576e416906cd688a7" + "digest": "a1098f2f04c29754cc9974324508386787d4d803b57cf691d42de414cb2679d6" }, { "name": "yen", "unicode": "1F4B4", - "digest": "8f3d801c687e585e4497123c5c91a8b0c558578deec6a8c1591b25e64a3a8992" + "digest": "944daaeb3f6369c807c0e63b106cee1360040f7800a70c0d942a992f25a55da7" }, { "name": "yin_yang", "unicode": "262F", - "digest": "e8ea4c686518ad6165e15ed67b529f2f1e20d648aa2ecb7e9bff5a6067dd3fea" + "digest": "5ee8d13dacf41306a09237bfcff6abeef110331b40eb7d6e80600628c1327545" }, { "name": "yum", "unicode": "1F60B", - "digest": "d9c97bbf6bdb6e39977437680f0b37c9335306c51e01114056ae1d4c9c85b0e0" + "digest": "31a89088c21bd7a74a3a26d731a907d1bc49436300a9f9c55248703cf7ef44c7" }, { "name": "zap", "unicode": "26A1", - "digest": "37588734c7fe330ae35e6ee99e7cf4183e8fe1bc01f6bbbc6293b21076a338cb" + "digest": "9f8144ae6f866129aea41bbf694b0c858ef9352a139969e57cd8db73385f52c3" }, { "name": "zero", "unicode": "0030-20E3", - "digest": "519c927db8264d5379ab2c6a18656ea6dd1ceb2afc92eb48563bf86af4697571" + "digest": "1b27b5c904defadbdd28ace67a6be5c277ff043297db7cd9f672bbf84e37fa1a" }, { "name": "zipper_mouth", "unicode": "1F910", - "digest": "8396249161b6d865861b56aabd17cae2c821b0d814f4249bf8cab0bb21fa8ee9" + "digest": "81bee5aa1202dfd5a4c7badb71ec0e44b8f75c2cbef94e6fd35c593d8770ae43" }, { "name": "zipper_mouth_face", "unicode": "1F910", - "digest": "8396249161b6d865861b56aabd17cae2c821b0d814f4249bf8cab0bb21fa8ee9" + "digest": "81bee5aa1202dfd5a4c7badb71ec0e44b8f75c2cbef94e6fd35c593d8770ae43" }, { "name": "zzz", "unicode": "1F4A4", - "digest": "f07c56d2d55c0a886c26a8e3d49a9adeab54cc1a0c0354ea8d3bf23aaed3176d" + "digest": "b3313d0c44a59fa9d4ce9f7eb4d07ff71dfc8bb01798154250f27cdcf3c693b5" } ]
\ No newline at end of file diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index c4fa1838b5a..2efe7e3adf3 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -56,9 +56,9 @@ module API not_found!('Award Emoji') unless can_read_awardable? - award = awardable.award_emoji.new(name: params[:name], user: current_user) + award = awardable.create_award_emoji(params[:name], current_user) - if award.save + if award.persisted? present award, with: Entities::AwardEmoji else not_found!("Award Emoji #{award.errors.messages}") diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 8cc4368b5c2..8e03c08f47b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -54,10 +54,19 @@ module API class BasicProjectDetails < Grape::Entity expose :id + expose :http_url_to_repo, :web_url expose :name, :name_with_namespace expose :path, :path_with_namespace end + class SharedGroup < Grape::Entity + expose :group_id + expose :group_name do |group_link, options| + group_link.group.name + end + expose :group_access, as: :group_access_level + end + class Project < Grape::Entity expose :id, :description, :default_branch, :tag_list expose :public?, as: :public @@ -77,6 +86,9 @@ module API expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds + expose :shared_with_groups do |project, options| + SharedGroup.represent(project.project_group_links.all, options) + end end class ProjectMember < UserBasic @@ -93,6 +105,7 @@ module API class GroupDetail < Group expose :projects, using: Entities::Project + expose :shared_projects, using: Entities::Project end class GroupMember < UserBasic @@ -187,7 +200,6 @@ module API expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :label_names, as: :labels - expose :description expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone expose :merge_when_build_succeeds @@ -196,6 +208,8 @@ module API merge_request.subscribed?(options[:current_user]) end expose :user_notes_count + expose :should_remove_source_branch?, as: :should_remove_source_branch + expose :force_remove_source_branch?, as: :force_remove_source_branch end class MergeRequestChanges < MergeRequest @@ -240,9 +254,9 @@ module API class CommitNote < Grape::Entity expose :note - expose(:path) { |note| note.diff_file_path if note.legacy_diff_note? } - expose(:line) { |note| note.diff_new_line if note.legacy_diff_note? } - expose(:line_type) { |note| note.diff_line_type if note.legacy_diff_note? } + expose(:path) { |note| note.diff_file.try(:file_path) if note.diff_note? } + expose(:line) { |note| note.diff_line.try(:new_line) if note.diff_note? } + expose(:line_type) { |note| note.diff_line.try(:type) if note.diff_note? } expose :author, using: Entities::UserBasic expose :created_at end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index b32503e8516..959b700de78 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -13,6 +13,7 @@ module API # action - git action (git-upload-pack or git-receive-pack) # ref - branch name # forced_push - forced_push + # protocol - Git access protocol being used, e.g. HTTP or SSH # helpers do @@ -46,11 +47,13 @@ module API User.find_by(id: params[:user_id]) end + protocol = params[:protocol] + access = if wiki? - Gitlab::GitAccessWiki.new(actor, project) + Gitlab::GitAccessWiki.new(actor, project, protocol) else - Gitlab::GitAccess.new(actor, project) + Gitlab::GitAccess.new(actor, project, protocol) end access_status = access.check(params[:action], params[:changes]) @@ -60,7 +63,12 @@ module API if access_status.status # Return the repository full path so that gitlab-shell has it when # handling ssh commands - response[:repository_path] = project.repository.path_to_repo + response[:repository_path] = + if wiki? + project.wiki.repository.path_to_repo + else + project.repository.path_to_repo + end end response diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 0e94efd4acd..4fcdf8968c9 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -233,8 +233,8 @@ module API render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable? - if params[:sha] && merge_request.source_sha != params[:sha] - render_api_error!("SHA does not match HEAD of source branch: #{merge_request.source_sha}", 409) + if params[:sha] && merge_request.diff_head_sha != params[:sha] + render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409) end merge_params = { diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 0cc1edd65c8..6d2a6f3946c 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -25,7 +25,11 @@ module API @projects = current_user.authorized_projects @projects = filter_projects(@projects) @projects = paginate @projects - present @projects, with: Entities::ProjectWithAccess, user: current_user + if params[:simple] + present @projects, with: Entities::BasicProjectDetails, user: current_user + else + present @projects, with: Entities::ProjectWithAccess, user: current_user + end end # Get an owned projects list for authenticated user diff --git a/lib/banzai.rb b/lib/banzai.rb index 093382261ae..9ebe379f454 100644 --- a/lib/banzai.rb +++ b/lib/banzai.rb @@ -3,6 +3,10 @@ module Banzai Renderer.render(text, context) end + def self.cache_collection_render(texts_and_contexts) + Renderer.cache_collection_render(texts_and_contexts) + end + def self.render_result(text, context = {}) Renderer.render_result(text, context) end diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb new file mode 100644 index 00000000000..d2c4b1e4d76 --- /dev/null +++ b/lib/banzai/filter/blockquote_fence_filter.rb @@ -0,0 +1,71 @@ +module Banzai + module Filter + class BlockquoteFenceFilter < HTML::Pipeline::TextFilter + REGEX = %r{ + (?<code> + # Code blocks: + # ``` + # Anything, including `>>>` blocks which are ignored by this filter + # ``` + + ^``` + .+? + \n```$ + ) + | + (?<html> + # HTML block: + # <tag> + # Anything, including `>>>` blocks which are ignored by this filter + # </tag> + + ^<[^>]+?>\n + .+? + \n<\/[^>]+?>$ + ) + | + (?: + # Blockquote: + # >>> + # Anything, including code and HTML blocks + # >>> + + ^>>>\n + (?<quote> + (?: + # Any character that doesn't introduce a code or HTML block + (?! + ^``` + | + ^<[^>]+?>\n + ) + . + | + # A code block + \g<code> + | + # An HTML block + \g<html> + )+? + ) + \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 d25de900674..ae7d31cf191 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/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index e4d3f87d0aa..e258dc8e2bf 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/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 536b478979f..91f0159f9a1 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -19,14 +19,22 @@ module Banzai language = node.attr('class') code = node.text + css_classes = "code highlight" + + lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText + formatter = Rouge::Formatters::HTML.new + begin - highlighted = block_code(code, language) + code = formatter.format(lexer.lex(code)) + + css_classes << " js-syntax-highlight #{lexer.tag}" rescue # Gracefully handle syntax highlighter bugs/errors to ensure # users can still access an issue/comment/etc. - highlighted = "<pre>#{code}</pre>" end + highlighted = %(<pre class="#{css_classes}"><code>#{code}</code></pre>) + # Extracted to a method to measure it replace_parent_pre_element(node, highlighted) end @@ -40,8 +48,7 @@ module Banzai # Override Rouge::Plugins::Redcarpet#rouge_formatter def rouge_formatter(lexer) - Rouge::Formatters::HTMLGitlab.new( - cssclass: "code highlight js-syntax-highlight #{lexer.tag}") + Rouge::Formatters::HTML.new end end end diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index 5b0a6d8541b..e1ca7f4d24b 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -112,7 +112,7 @@ module Banzai data = data_attribute(project: project.id, author: author.try(:id)) text = link_text || User.reference_prefix + 'all' - link_tag(url, data, text) + link_tag(url, data, text, 'All Project and Group Members') end def link_to_namespace(namespace, link_text: nil) @@ -128,7 +128,7 @@ module Banzai data = data_attribute(group: namespace.id) text = link_text || Group.reference_prefix + group - link_tag(url, data, text) + link_tag(url, data, text, namespace.name) end def link_to_user(user, namespace, link_text: nil) @@ -136,11 +136,11 @@ module Banzai data = data_attribute(user: namespace.owner_id) text = link_text || User.reference_prefix + user - link_tag(url, data, text) + link_tag(url, data, text, namespace.owner_name) end - def link_tag(url, data, text) - %(<a href="#{url}" #{data} class="#{link_class}">#{escape_once(text)}</a>) + def link_tag(url, data, text, title) + %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{escape_once(text)}</a>) end end end diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index f0e4f28bf12..9aef807c152 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -31,17 +31,15 @@ module Banzai redacted = redact_documents(documents) objects.each_with_index do |object, index| - object.__send__("#{attribute}_html=", redacted.fetch(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 - - objects end # Renders the attribute of every given object. def render_objects(objects, attribute) - objects.map do |object| - render_attribute(object, attribute) - end + render_attributes(objects, attribute) end # Redacts the list of documents. @@ -50,9 +48,7 @@ module Banzai def redact_documents(documents) redactor = Redactor.new(project, user) - redactor.redact(documents).map do |document| - document.to_html.html_safe - end + redactor.redact(documents) end # Returns a Banzai context for the given object and attribute. @@ -66,16 +62,21 @@ module Banzai context end - # Renders the attribute of an object. + # Renders the attributes of a set of objects. # - # Returns a `Nokogiri::HTML::Document`. - def render_attribute(object, attribute) - context = context_for(object, attribute) + # 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) - html = Banzai.render(string, context) + string = object.__send__(attribute) - Banzai::Pipeline[:relative_link].to_document(html, context) + { 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 diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb index 50dc978b452..6cf219661d3 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/redactor.rb b/lib/banzai/redactor.rb index ffd267d5e9a..0df3a72d1c4 100644 --- a/lib/banzai/redactor.rb +++ b/lib/banzai/redactor.rb @@ -19,29 +19,36 @@ module Banzai # # Returns the documents passed as the first argument. def redact(documents) - nodes = documents.flat_map do |document| - Querying.css(document, 'a.gfm[data-reference-type]') - end - - redact_nodes(nodes) + all_document_nodes = document_nodes(documents) - documents + redact_document_nodes(all_document_nodes) end - # Redacts the given nodes + # Redacts the given node documents # - # nodes - An Array of HTML nodes to redact. - def redact_nodes(nodes) - visible = nodes_visible_to_user(nodes) + # 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 = [] - nodes.each do |node| - unless visible.include?(node) + 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. @@ -65,5 +72,11 @@ module Banzai 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 3d7b9c4a024..6cf218aaa0d 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 a12b0d19560..863f5725d3b 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 6718acdef7e..910687a7b6a 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/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 54b46e5d23f..ffc1814b29d 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -48,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 dec20d8659b..927f9dad20b 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -20,11 +20,19 @@ module Gitlab if Database.postgresql? options = options.merge({ algorithm: :concurrently }) + disable_statement_timeout end add_index(table_name, column_name, options) end + # Long-running migrations may take more than the timeout allowed by + # the database. Disable the session's statement timeout to ensure + # migrations don't get killed prematurely. (PostgreSQL only) + def disable_statement_timeout + ActiveRecord::Base.connection.execute('SET statement_timeout TO 0') if Database.postgresql? + end + # Updates the value of a column in batches. # # This method updates the table in batches of 5% of the total row count. @@ -133,6 +141,8 @@ module Gitlab 'in the body of your migration class' end + disable_statement_timeout + transaction do add_column(table, column, type, default: nil) diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb new file mode 100644 index 00000000000..8406ca4269c --- /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 d2e85cabf72..7e01f7b61fb 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -1,47 +1,87 @@ 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?, 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 + @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a end - def too_large? - diff.too_large? + def collapsed_by_default? + diff.diff.bytesize > 10240 # 10 KB 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 +93,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 +111,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 9429b3ff88d..649a265a02c 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 789c14518b0..28ad637fda4 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 + (?<del_ins> + - + \g<del_ins>? + \+ + ) + + # 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} " - - offset = 0 - local_edit_indexes = [] - while index = joined_line_prefixes.index(" -+ ", offset) - local_edit_indexes << index - offset = index + 1 + # 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(/[^ +-]/, ' ') + + 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 03730b435ad..c6189d660c2 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 00000000000..576a761423e --- /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 74f9b3c050a..b069afdd28c 100644 --- a/lib/gitlab/diff/parallel_diff.rb +++ b/lib/gitlab/diff/parallel_diff.rb @@ -8,111 +8,96 @@ 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 - } - } - 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 - } + 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 } - 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) + lines end end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb new file mode 100644 index 00000000000..989fff8918e --- /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 + JSON.generate(self.to_h) + 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 00000000000..4d04f867268 --- /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/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 047c77c6fc2..97701b0cd42 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 97ef9851d71..1c671a7487b 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 00000000000..b63213ae208 --- /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/git/hook.rb b/lib/gitlab/git/hook.rb index 420c6883c45..9b681e636c7 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" @@ -34,7 +35,8 @@ module Gitlab vars = { 'GL_ID' => gl_id, - 'PWD' => repo_path + 'PWD' => repo_path, + 'GL_PROTOCOL' => GL_PROTOCOL } options = { @@ -68,13 +70,10 @@ module Gitlab end def call_update_hook(gl_id, oldrev, newrev, ref) - status = nil - Dir.chdir(repo_path) do - status = 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 - - [status, nil] end def retrieve_error_message(stderr, stdout) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index d2a0e316cbe..7679c7e4bb8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -3,11 +3,12 @@ module Gitlab DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } - attr_reader :actor, :project + attr_reader :actor, :project, :protocol - def initialize(actor, project) + def initialize(actor, project, protocol) @actor = actor @project = project + @protocol = protocol end def user @@ -49,6 +50,8 @@ module Gitlab 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 @@ -164,6 +167,10 @@ module Gitlab Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev) end + def protocol_allowed? + Gitlab::ProtocolAccess.allowed?(protocol) + end + private def protected_branch_action(oldrev, newrev, branch_name) diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb index a15fc84b418..7d2d545b84e 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 043f10d96a9..084e514492c 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -78,10 +78,21 @@ module Gitlab 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 730978d502b..3932fcb1eda 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) diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index 498b00cb658..a4ea2210abd 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 3f76ec97977..46d40f75be6 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) - - # Issues && Comments - issues = client.issues(project_identifier) - - issues.each do |issue| - body = @formatter.author_line(issue["author"]["name"]) - body += issue["description"] - - comments = client.issue_comments(project_identifier, issue["id"]) - - if comments.any? - body += @formatter.comments_header + ActiveRecord::Base.no_touching do + project_identifier = CGI.escape(project.import_source) + + # Issues && Comments + issues = client.issues(project_identifier) + + issues.each do |issue| + body = @formatter.author_line(issue["author"]["name"]) + body += issue["description"] + + comments = client.issue_comments(project_identifier, issue["id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"]) + end + + project.issues.create!( + iid: issue["iid"], + description: body, + title: issue["title"], + state: issue["state"], + updated_at: issue["updated_at"], + author_id: gl_user_id(project, issue["author"]["id"]) + ) end - - comments.each do |comment| - body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"]) - end - - project.issues.create!( - description: body, - title: issue["title"], - state: issue["state"], - author_id: gl_user_id(project, issue["author"]["id"]) - ) end true diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb index 2122339d2db..3caf9036459 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 41296415e35..9360afedfcb 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -1,7 +1,7 @@ module Gitlab class Highlight - def self.highlight(blob_name, blob_content, repository: nil, nowrap: true, plain: false) - new(blob_name, blob_content, nowrap: nowrap, repository: repository). + def self.highlight(blob_name, blob_content, repository: nil, plain: false) + new(blob_name, blob_content, repository: repository). highlight(blob_content, continue: false, plain: plain) end @@ -13,30 +13,34 @@ module Gitlab highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe) end - attr_reader :lexer - def initialize(blob_name, blob_content, repository: nil, nowrap: true) + def initialize(blob_name, blob_content, repository: nil) + @formatter = Rouge::Formatters::HTMLGitlab.new + @repository = repository @blob_name = blob_name @blob_content = blob_content - @repository = repository - @formatter = rouge_formatter(nowrap: nowrap) - - @lexer = custom_language || begin - Rouge::Lexer.guess(filename: blob_name, source: blob_content).new - rescue Rouge::Lexer::AmbiguousGuess => e - e.alternatives.sort_by(&:tag).first - end end def highlight(text, continue: true, plain: false) if plain - @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe + hl_lexer = Rouge::Lexers::PlainText + continue = false else - @formatter.format(@lexer.lex(text, continue: continue)).html_safe + hl_lexer = self.lexer end + + @formatter.format(hl_lexer.lex(text, continue: continue)).html_safe rescue @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe end + def lexer + @lexer ||= custom_language || begin + Rouge::Lexer.guess(filename: @blob_name, source: @blob_content).new + rescue Rouge::Guesser::Ambiguous => e + e.alternatives.sort_by(&:tag).first + end + end + private def custom_language @@ -46,16 +50,5 @@ module Gitlab Rouge::Lexer.find_fancy(language_name) end - - def rouge_formatter(options = {}) - options = options.reverse_merge( - nowrap: true, - cssclass: 'code highlight', - lineanchors: true, - lineanchorsid: 'LC' - ) - - Rouge::Formatters::HTMLGitlab.new(options) - end end end diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 588647e5adb..bab2ea73c4f 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -3,6 +3,7 @@ module Gitlab extend self VERSION = '0.1.1' + FILENAME_LIMIT = 50 def export_path(relative_path:) File.join(storage_path, relative_path) @@ -28,6 +29,12 @@ module Gitlab 'VERSION' end + def export_filename(project:) + basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.namespace.path}_#{project.path}" + + "#{basename[0..FILENAME_LIMIT]}_export.tar.gz" + end + def version VERSION end diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 78664f076eb..2249904145c 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -28,7 +28,8 @@ 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 diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 0ac6ff01e3b..051110c23cf 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -12,7 +12,10 @@ module Gitlab json = IO.read(@path) @tree_hash = ActiveSupport::JSON.decode(json) @project_members = @tree_hash.delete('project_members') - create_relations + + ActiveRecord::Base.no_touching do + create_relations + end rescue => e @shared.error(e) false @@ -69,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 + return if tree_hash[relation_key].blank? + tree_hash[relation_key].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/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 9824df3f274..6ba25a31641 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -87,7 +87,7 @@ module Gitlab project_id = @relation_hash.delete('project_id') # project_id may not be part of the export, but we always need to populate it if required. - @relation_hash['project_id'] = project_id if relation_class.column_names.include?('project_id') + @relation_hash['project_id'] = project_id @relation_hash['gl_project_id'] = project_id if @relation_hash['gl_project_id'] @relation_hash['target_project_id'] = project_id if @relation_hash['target_project_id'] @relation_hash['source_project_id'] = -1 if @relation_hash['source_project_id'] @@ -111,7 +111,7 @@ module Gitlab end def imported_object - imported_object = relation_class.new(@relation_hash) + imported_object = relation_class.new(parsed_relation_hash) yield(imported_object) if block_given? imported_object.importing = true if imported_object.respond_to?(:importing) imported_object @@ -125,6 +125,10 @@ module Gitlab def admin_user? @user.is_admin? end + + def parsed_relation_hash + @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + end end end end diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb index f38229c6c59..6130c124dd1 100644 --- a/lib/gitlab/import_export/saver.rb +++ b/lib/gitlab/import_export/saver.rb @@ -7,7 +7,8 @@ module Gitlab new(*args).save end - def initialize(shared:) + def initialize(project:, shared:) + @project = project @shared = shared end @@ -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/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb index 8e345e8ae4a..aaed2184f44 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/protocol_access.rb b/lib/gitlab/protocol_access.rb new file mode 100644 index 00000000000..21aefc884be --- /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/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb index 4831c46c4be..104280f520a 100644 --- a/lib/gitlab/sidekiq_middleware/memory_killer.rb +++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb @@ -29,11 +29,11 @@ module Gitlab "in #{GRACE_TIME} seconds" sleep(GRACE_TIME) - Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}" + Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}" Process.kill('SIGTERM', Process.pid) Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\ - "#{SHUTDOWN_SIGNAL} to PID #{Process.pid}" + "#{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}" sleep(SHUTDOWN_WAIT) Sidekiq.logger.warn "sending #{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}" diff --git a/lib/gitlab/timeless.rb b/lib/gitlab/timeless.rb new file mode 100644 index 00000000000..b290c716f97 --- /dev/null +++ b/lib/gitlab/timeless.rb @@ -0,0 +1,16 @@ +module Gitlab + module Timeless + def self.timeless(model, &block) + original_record_timestamps = model.record_timestamps + model.record_timestamps = false + + if block.arity.abs == 1 + block.call(model) + else + block.call + end + ensure + model.record_timestamps = original_record_timestamps + end + end +end diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 7d02fe3c971..19dad699edf 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -4,10 +4,20 @@ module Gitlab regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git']) content.gsub(regexp) { |url| new(url).masked_url } + rescue Addressable::URI::InvalidURIError + content.gsub(regexp, '') + end + + def self.valid?(url) + Addressable::URI.parse(url.strip) + + true + rescue Addressable::URI::InvalidURIError + false end def initialize(url, credentials: nil) - @url = Addressable::URI.parse(url) + @url = Addressable::URI.parse(url.strip) @credentials = credentials end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index ef1241f8600..6aeb49c0219 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -38,12 +38,10 @@ module Gitlab end def send_git_diff(repository, diff_refs) - from, to = diff_refs - params = { 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => from.sha, - 'ShaTo' => to.sha + 'ShaFrom' => diff_refs.start_sha, + 'ShaTo' => diff_refs.head_sha } [ @@ -52,11 +50,11 @@ module Gitlab ] end - def send_git_patch(repository, from, to) + def send_git_patch(repository, diff_refs) params = { 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => from, - 'ShaTo' => to + 'ShaFrom' => diff_refs.start_sha, + 'ShaTo' => diff_refs.head_sha } [ @@ -65,6 +63,18 @@ module Gitlab ] 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/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index 8c309efc7b8..f818dc78d34 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 <tt>Rouge::Formatter::HTMLGitlab</tt> instance. # - # [+nowrap+] If set to True, don't wrap the output at all, not - # even inside a <tt><pre></tt> tag (default: false). - # [+cssclass+] CSS class for the wrapping <tt><div></tt> tag - # (default: 'highlight'). - # [+linenos+] If set to 'table', output line numbers as a table - # with two cells, one containing the line numbers, - # the other the whole code. This is copy paste friendly, - # but may cause alignment problems with some browsers - # or fonts. If set to 'inline', the line numbers will - # be integrated in the <tt><pre></tt> tag that contains - # the code (default: nil). # [+linenostart+] The line number for the first line (default: 1). - # [+lineanchors+] If set to true the formatter will wrap each output - # line in an anchor tag with a name of L-linenumber. - # This allows easy linking to certain lines - # (default: false). - # [+lineanchorsid+] If lineanchors is true the name of the anchors can - # be changed with lineanchorsid to e.g. foo-linenumber - # (default: 'L'). - # [+anchorlinenos+] If set to true, will wrap line numbers in <tt><a></tt> - # tags. Used in combination with linenos and lineanchors - # (default: false). - # [+inline_theme+] Inline CSS styles for the <pre> tag (default: false). - def initialize( - nowrap: false, - cssclass: 'highlight', - linenos: nil, - linenostart: 1, - lineanchors: false, - lineanchorsid: 'L', - anchorlinenos: false, - inline_theme: nil - ) - @nowrap = nowrap - @cssclass = cssclass - @linenos = linenos + def initialize(linenostart: 1) @linenostart = linenostart - @lineanchors = lineanchors - @lineanchorsid = lineanchorsid - @anchorlinenos = anchorlinenos - @inline_theme = Theme.find(inline_theme).new if inline_theme.is_a?(String) - end - - def render(tokens) - case @linenos - when 'table' - render_tableized(tokens) - when 'inline' - render_untableized(tokens) - else - render_untableized(tokens) - end - end - - alias_method :format, :render - - private - - def render_untableized(tokens) - data = process_tokens(tokens) - - html = '' - html << "<pre class=\"#{@cssclass}\"><code>" unless @nowrap - html << wrap_lines(data[:code]) - html << "</code></pre>\n" unless @nowrap - html - end - - def render_tableized(tokens) - data = process_tokens(tokens) - - html = '' - html << "<div class=\"#{@cssclass}\">" unless @nowrap - html << '<table><tbody>' - html << "<td class=\"linenos\"><pre>" - html << wrap_linenos(data[:numbers]) - html << '</pre></td>' - html << "<td class=\"lines\"><pre><code>" - html << wrap_lines(data[:code]) - html << '</code></pre></td>' - html << '</tbody></table>' - html << '</div>' unless @nowrap - html - end - - def process_tokens(tokens) - rendered = [] - current_line = '' - - tokens.each do |tok, val| - # In the case of multi-line values (e.g. comments), we need to apply - # styling to each line since span elements are inline. - val.lines.each do |line| - stripped = line.chomp - current_line << span(tok, stripped) - - if line.end_with?("\n") - rendered << current_line - current_line = '' - end - end - end - - # Add leftover text - rendered << current_line if current_line.present? - - num_lines = rendered.size - numbers = (@linenostart..num_lines + @linenostart - 1).to_a - - { numbers: numbers, code: rendered } + @line_number = linenostart end - def wrap_linenos(numbers) - if @anchorlinenos - numbers.map! do |number| - "<a href=\"##{@lineanchorsid}#{number}\">#{number}</a>" - end - end - numbers.join("\n") - end - - def wrap_lines(lines) - if @lineanchors - lines = lines.each_with_index.map do |line, index| - number = index + @linenostart - - if @linenos == 'inline' - "<a name=\"L#{number}\"></a>" \ - "<span class=\"linenos\">#{number}</span>" \ - "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \ - '</span>' - else - "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \ - '</span>' - end - end - lines.join("\n") - else - if @linenos == 'inline' - lines = lines.each_with_index.map do |line, index| - number = index + @linenostart - "<span class=\"linenos\">#{number}</span>#{line}" - end - lines.join("\n") - else - lines.join("\n") - end - end - end + def stream(tokens, &b) + is_first = true + token_lines(tokens) do |line| + yield "\n" unless is_first + is_first = false - def span(tok, val) - # http://stackoverflow.com/a/1600584/2587286 - val = CGI.escapeHTML(val) + yield %(<span id="LC#{@line_number}" class="line">) + line.each { |token, value| yield span(token, value) } + yield %(</span>) - if tok.shortname.empty? - val - else - if @inline_theme - rules = @inline_theme.style_for(tok).rendered_rules - "<span style=\"#{rules.to_a.join(';')}\"#{val}</span>" - else - "<span class=\"#{tok.shortname}\">#{val}</span>" - end + @line_number += 1 end end end diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index d521de28e8a..4a4892a2e07 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -49,7 +49,12 @@ server { proxy_http_version 1.1; - proxy_set_header Host $http_host; + ## By overwriting Host and clearing X-Forwarded-Host we ensure that + ## internal HTTP redirects generated by GitLab always send users to + ## YOUR_SERVER_FQDN. + proxy_set_header Host YOUR_SERVER_FQDN; + proxy_set_header X-Forwarded-Host ""; + proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index bf014b56cf6..0b93d7f292f 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -93,7 +93,12 @@ server { proxy_http_version 1.1; - proxy_set_header Host $http_host; + ## By overwriting Host and clearing X-Forwarded-Host we ensure that + ## internal HTTP redirects generated by GitLab always send users to + ## YOUR_SERVER_FQDN. + proxy_set_header Host YOUR_SERVER_FQDN; + proxy_set_header X-Forwarded-Host ""; + proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Ssl on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake index 030ee8bafcb..e930ace1041 100644 --- a/lib/tasks/gemojione.rake +++ b/lib/tasks/gemojione.rake @@ -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 diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 4cb8b8da150..8eaacef2024 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/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb deleted file mode 100644 index a3a3309e15e..00000000000 --- 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).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 '#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 -end diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index 1f7fd517342..267d511c2db 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: 'workflow/protected_branches/protected_branches1', 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,8 +57,7 @@ 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 diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 6e3db10e451..3001d32e719 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, + go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html') + + expect(response).to be_success + end + + 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: TestEnv::BRANCH_SHA['binary-encoding'], - format: "html") + id: commit.id) - expect(response).to be_success + 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 - def go(id:) - get :show, + 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 4018dac95a2..4058d5e2453 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/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index eff74e12869..210085e3b1a 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, @@ -103,12 +103,12 @@ describe Projects::MergeRequestsController do id: merge_request.iid, format: :patch) - expect(response.headers['Gitlab-Workhorse-Send-Data']).to start_with("git-format-patch:") + 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, @@ -140,7 +140,7 @@ describe Projects::MergeRequestsController do 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) } @@ -168,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, @@ -212,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 @@ -229,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 @@ -266,7 +266,7 @@ describe Projects::MergeRequestsController do 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 @@ -290,96 +290,210 @@ describe Projects::MergeRequestsController do 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 + def go(extra_params = {}) + params = { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid + } + + get :diffs, params.merge(extra_params) end - context 'as html' do - it 'renders the diff template' do - go + context 'with default params' do + context 'as html' do + before { go(format: 'html') } - expect(response).to render_template('diffs') + it 'renders the diff template' do + expect(response).to render_template('diffs') + end end - end - context 'as json' do - it 'renders the diffs template to a string' do - go format: 'json' + context 'as json' do + before { go(format: 'json') } - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(JSON.parse(response.body)).to have_key('html') + 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 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) } + context 'with forked projects with submodules' do + render_views - 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 + 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) } - it 'renders' do - go format: 'json' + 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 - expect(response).to be_success - expect(response.body).to have_content('Subproject commit') + it 'renders' do + expect(response).to be_success + expect(response.body).to have_content('Subproject commit') + end 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 'with ignore_whitespace_change' do + context 'as html' do + before { go(format: 'html', w: 1) } - context 'as html' do - it 'renders the diff template' do - go + it 'renders the diff template' do + expect(response).to render_template('diffs') + end + end + + context 'as json' do + before { go(format: 'json', w: 1) } - expect(response).to render_template('diffs') + 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 'as json' do - it 'renders the diffs template to a string' do - go format: 'json' + context 'with view' do + before { go(view: 'parallel') } - expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(JSON.parse(response.body)).to have_key('html') + it 'saves the preferred diff view in a cookie' do + expect(response.cookies['diff_view']).to eq('parallel') end end end - describe 'GET diffs with view' do - def go(extra_params = {}) + 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: merge_request.iid + project_id: project.to_param } - get :diffs, params.merge(extra_params) + get :diff_for_path, params.merge(extra_params) end - it 'saves the preferred diff view in a cookie' do - go view: 'parallel' + context 'when an ID param is passed' do + let(:existing_path) { 'files/ruby/popen.rb' } - expect(response.cookies['diff_view']).to eq('parallel') + 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/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb index 5a8bba28594..936320a3709 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,43 +10,51 @@ 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).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).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).to have_http_status(302) @@ -55,43 +65,51 @@ 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).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).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).to have_http_status(302) diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 696cf276e57..83e38095feb 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 00000000000..b5e96d18b8f --- /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 5c8ddbebf0d..b682ced75ac 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 7fc20cd5555..866e663f026 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -23,6 +23,10 @@ FactoryGirl.define do action { Todo::BUILD_FAILED } end + trait :approval_required do + action { Todo::APPROVAL_REQUIRED } + end + trait :done do state :done 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 00000000000..16baf7e9516 --- /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_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb new file mode 100644 index 00000000000..5b1c0460274 --- /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_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 1cb709c1de3..767504df251 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/compare_spec.rb b/spec/features/compare_spec.rb new file mode 100644 index 00000000000..c62556948e0 --- /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/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb new file mode 100644 index 00000000000..78bc888f2a6 --- /dev/null +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -0,0 +1,207 @@ +require 'spec_helper' + +feature 'Expand and collapse diffs', js: true, feature: true do + include WaitForAjax + + before do + login_as :admin + project = create(:project) + branch = 'expand-collapse-diffs' + + # 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 '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/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 00000000000..37c433cc09a --- /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_spec.rb b/spec/features/groups_spec.rb index 891df65216d..2d8b59472e8 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -1,14 +1,26 @@ require 'spec_helper' feature 'Group', feature: true do + before do + login_as(:admin) + end + + describe 'creating a group with space in group path' do + it 'renders new group form with validation errors' do + visit new_group_path + fill_in 'Group path', with: 'space group' + + click_button 'Create group' + + expect(current_path).to eq(groups_path) + expect(page).to have_content("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.'.") + end + end + describe 'description' do let(:group) { create(:group) } let(:path) { group_path(group) } - before do - login_as(:admin) - end - it 'parses Markdown' do group.update_attribute(:description, 'This is **my** group') visit path diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 8c6b669ce78..1e2306d7f59 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/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 006a06b8235..4b9b5394b61 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] @@ -196,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) @@ -287,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/login_spec.rb b/spec/features/login_spec.rb index 72b5ff231f7..58753ff21f6 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/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index b4d2201c729..f676200ecf3 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 00000000000..c9a0059645d --- /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 c5e6412d7bf..96f7b8c9932 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 65e9185ec24..80e8b8fc642 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 737efcef45d..0b38c413f44 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_spec.rb b/spec/features/pipelines_spec.rb index 98703ef3ac4..e7ee0aaea3c 100644 --- a/spec/features/pipelines_spec.rb +++ b/spec/features/pipelines_spec.rb @@ -123,7 +123,7 @@ describe "Pipelines" do 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) 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 b8c06c383fb..fca40f68b01 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -19,12 +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_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 9d66f76ef58..bc3bf53fe9d 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -42,6 +42,23 @@ feature 'project import', feature: true, js: true do 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 + def wiki_exists? wiki = ProjectWiki.new(project) File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty? diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 6a39c302f55..98ba93b4036 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 4d5d656f00c..ff9b6007806 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/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 00000000000..9564347e733 --- /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/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 00000000000..0e54c4fdf20 --- /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/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb new file mode 100644 index 00000000000..d94dee0c797 --- /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 85923f0a19d..d0a301038c4 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) diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index 14613754f74..9335f5bf120 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do + before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) } + def register_u2f_device(u2f_device = nil) u2f_device ||= FakeU2fDevice.new(page) u2f_device.respond_to_u2f_registration @@ -208,21 +210,52 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: expect(page.body).to match('Authentication via U2F device failed') end end - end - describe "when two-factor authentication is disabled" do - let(:user) { create(:user) } + describe "when more than one device has been registered by the same user" do + it "allows logging in with either device" do + # Register first device + user = login_as(:user) + user.update_attribute(:otp_required_for_login, true) + visit profile_two_factor_auth_path + expect(page).to have_content("Your U2F device needs to be set up.") + first_device = register_u2f_device + + # Register second device + visit profile_two_factor_auth_path + expect(page).to have_content("Your U2F device needs to be set up.") + second_device = register_u2f_device + logout + + # Authenticate as both devices + [first_device, second_device].each do |device| + login_as(user) + device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + click_on "Authenticate via U2F Device" - before do - login_as(user) - user.update_attribute(:otp_required_for_login, true) - visit profile_account_path - click_on 'Manage Two-Factor Authentication' - register_u2f_device + expect(page.body).to match('Signed in successfully') + + logout + end + end end - it "deletes u2f registrations" do - expect { click_on "Disable" }.to change { U2fRegistration.count }.from(1).to(0) + describe "when two-factor authentication is disabled" do + let(:user) { create(:user) } + + before do + user = login_as(:user) + user.update_attribute(:otp_required_for_login, true) + visit profile_account_path + click_on 'Manage Two-Factor Authentication' + expect(page).to have_content("Your U2F device needs to be set up.") + register_u2f_device + end + + it "deletes u2f registrations" do + expect { click_on "Disable" }.to change { U2fRegistration.count }.by(-1) + end end end end diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 1bd354815e4..8db897b1646 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 diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md new file mode 100644 index 00000000000..2652a842c0e --- /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: + +<pre> +# Code +>>> +# Code +</pre> + +Double `>>>` inside HTML: + +<pre> +# Code +>>> +# Code +>>> +# Code +</pre> + +Blockquote outside HTML: + +> Quote + +HTML inside blockquote: + +> Quote +> +> <pre> +> # Code +> </pre> +> +> Quote + +Single `>>>` inside HTML inside blockquote: + +> Quote +> +> <pre> +> # Code +> >>> +> # Code +> </pre> +> +> Quote + +Double `>>>` inside HTML inside blockquote: + +> Quote +> +> <pre> +> # Code +> >>> +> # Code +> >>> +> # Code +> </pre> +> +> Quote diff --git a/spec/fixtures/blockquote_fence_before.md b/spec/fixtures/blockquote_fence_before.md new file mode 100644 index 00000000000..d52eec72896 --- /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: + +<pre> +# Code +>>> +# Code +</pre> + +Double `>>>` inside HTML: + +<pre> +# Code +>>> +# Code +>>> +# Code +</pre> + +Blockquote outside HTML: + +>>> +Quote +>>> + +HTML inside blockquote: + +>>> +Quote + +<pre> +# Code +</pre> + +Quote +>>> + +Single `>>>` inside HTML inside blockquote: + +>>> +Quote + +<pre> +# Code +>>> +# Code +</pre> + +Quote +>>> + +Double `>>>` inside HTML inside blockquote: + +>>> +Quote + +<pre> +# Code +>>> +# Code +>>> +# Code +</pre> + +Quote +>>> diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml index a8b7907d4ba..37066c8e930 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 <span id="LC6" class="line"></span> :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 <span id="LC6" class="line"></span> :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 <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> :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 <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> :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 <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> :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 <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> :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: | - -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span> + -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :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: | - +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span> + +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :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 <span id="LC10" class="line"> <span class="k">end</span></span> :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 <span id="LC10" class="line"> <span class="k">end</span></span> :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 <span id="LC11" class="line"></span> :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 <span id="LC11" class="line"></span> :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 <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> :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 <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> :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: | - -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}</span></span> + -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13 - :right: - :type: old - :number: - :text: '' - :line_code: -- :left: - :type: old - :number: 14 - :text: | - -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span> - :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: | +<span id="LC13" class="line"></span> :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: | + -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span> + :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: | +<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span> :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: | - +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span></span> + +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :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: | +<span id="LC16" class="line"> <span class="p">}</span></span> :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: | +<span id="LC17" class="line"></span> :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: | +<span id="LC18" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span> :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: | +<span id="LC19" class="line"> <span class="ss">chdir: </span><span class="n">path</span></span> :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: | +<span id="LC20" class="line"> <span class="p">}</span></span> :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 <span id="LC21" class="line"></span> :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 <span id="LC21" class="line"></span> :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 <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :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 <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :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 <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :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 <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :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 <span id="LC25" class="line"></span> :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 <span id="LC25" class="line"></span> :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 - <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> + <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :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 - <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> + <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :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 <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> :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 <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> :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: | +<span id="LC28" class="line"></span> :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 <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> :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 <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> :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 <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> :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 <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> :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 <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> :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 <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> :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/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 6d1c02db297..bd0108f9938 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -16,19 +16,19 @@ describe BlobHelper do describe '#highlight' do it 'should return plaintext for unknown lexer context' do - result = helper.highlight(blob_name, no_context_content, nowrap: true) - expect(result).to eq('<span id="LC1" class="line">:type "assem"))</span>') + result = helper.highlight(blob_name, no_context_content) + expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line">:type "assem"))</span></code></pre>]) end it 'should highlight single block' do - expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> -<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span>] + expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> +<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>] - expect(helper.highlight(blob_name, blob_content, nowrap: true)).to eq(expected) + expect(helper.highlight(blob_name, blob_content)).to eq(expected) end it 'should highlight multi-line comments' do - result = helper.highlight(blob_name, multiline_content, nowrap: true) + result = helper.highlight(blob_name, multiline_content) html = Nokogiri::HTML(result) lines = html.search('.s') expect(lines.count).to eq(3) @@ -41,33 +41,19 @@ describe BlobHelper do let(:blob_name) { 'test.diff' } let(:blob_content) { "+aaa\n+bbb\n- ccc\n ddd\n"} let(:expected) do - %q(<span id="LC1" class="line"><span class="gi">+aaa</span></span> + %q(<pre class="code highlight"><code><span id="LC1" class="line"><span class="gi">+aaa</span></span> <span id="LC2" class="line"><span class="gi">+bbb</span></span> <span id="LC3" class="line"><span class="gd">- ccc</span></span> -<span id="LC4" class="line"> ddd</span>) +<span id="LC4" class="line"> ddd</span></code></pre>) end it 'should highlight each line properly' do - result = helper.highlight(blob_name, blob_content, nowrap: true) + result = helper.highlight(blob_name, blob_content) expect(result).to eq(expected) end end end - describe "#highlighter" do - it 'should highlight continued blocks' do - # Both lines have LC1 as ID since formatter doesn't support continue at the moment - expected = [ - '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>', - '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span>' - ] - - highlighter = helper.highlighter(blob_name, blob_content, nowrap: true) - result = split_content.map{ |content| highlighter.highlight(content) } - expect(result).to eq(expected) - end - end - describe "#sanitize_svg" do let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') } let(:data) { open(input_svg_path).read } diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 52764f41e0d..4b134a48410 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 @@ -31,26 +31,11 @@ describe DiffHelper do 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 + it 'should return hard limit for a diff' 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) - end end describe 'unfold_bottom_class' do @@ -59,7 +44,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 c0d2be98e85..6b5e3d93d48 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -57,7 +57,7 @@ describe EventsHelper do expected = '<pre class="code highlight js-syntax-highlight ruby">' \ "<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \ " <span class=\"s1\">\'hello world\'</span>\n" \ - "<span class=\"k\">end</span>" \ + "<span class=\"k\">end</span>\n" \ '</code></pre>' expect(helper.event_note(input)).to eq(expected) end diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb index 7b2155e9a4e..f75fdb739f6 100644 --- a/spec/helpers/members_helper_spec.rb +++ b/spec/helpers/members_helper_spec.rb @@ -57,72 +57,6 @@ describe MembersHelper do end end - describe '#can_see_request_access_button?' do - let(:user) { create(:user) } - let(:group) { create(:group, :public) } - let(:project) { create(:project, :public, group: group) } - - before do - allow(helper).to receive(:current_user).and_return(user) - end - - context 'source is a group' do - context 'current_user is not a member' do - it 'returns true' do - expect(helper.can_see_request_access_button?(group)).to be_truthy - end - end - - context 'current_user is a member' do - it 'returns false' do - group.add_owner(user) - - expect(helper.can_see_request_access_button?(group)).to be_falsy - end - end - - context 'current_user is a requester' do - it 'returns true' do - group.request_access(user) - - expect(helper.can_see_request_access_button?(group)).to be_truthy - end - end - end - - context 'source is a project' do - context 'current_user is not a member' do - it 'returns true' do - expect(helper.can_see_request_access_button?(project)).to be_truthy - end - end - - context 'current_user is a group member' do - it 'returns false' do - group.add_owner(user) - - expect(helper.can_see_request_access_button?(project)).to be_falsy - end - end - - context 'current_user is a group requester' do - it 'returns false' do - group.request_access(user) - - expect(helper.can_see_request_access_button?(project)).to be_falsy - end - end - - context 'current_user is a member' do - it 'returns false' do - project.team << [user, :master] - - expect(helper.can_see_request_access_button?(project)).to be_falsy - end - end - end - end - describe '#remove_member_message' do let(:requester) { build(:user) } let(:project) { create(:project) } diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml index 470cabeafbb..06c2ab1e823 100644 --- a/spec/javascripts/fixtures/issues_show.html.haml +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -1,7 +1,7 @@ :css .hidden { display: none !important; } -.flash-container +.flash-container.flash-container-page .flash-alert .flash-notice diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee index f0d26fb5446..0244119fa0e 100644 --- a/spec/javascripts/project_title_spec.js.coffee +++ b/spec/javascripts/project_title_spec.js.coffee @@ -22,7 +22,7 @@ describe 'Project Title', -> @projects_data = fixture.load('projects.json')[0] spyOn(jQuery, 'ajax').and.callFake (req) => - expect(req.url).toBe('/api/v3/projects.json') + expect(req.url).toBe('/api/v3/projects.json?simple=true') d = $.Deferred() d.resolve @projects_data d.promise() diff --git a/spec/javascripts/u2f/authenticate_spec.coffee b/spec/javascripts/u2f/authenticate_spec.coffee index e8a2892d678..8ffeda11704 100644 --- a/spec/javascripts/u2f/authenticate_spec.coffee +++ b/spec/javascripts/u2f/authenticate_spec.coffee @@ -5,13 +5,12 @@ #= require ./mock_u2f_device describe 'U2FAuthenticate', -> - U2FUtil.enableTestMode() fixture.load('u2f/authenticate') beforeEach -> @u2fDevice = new MockU2FDevice @container = $("#js-authenticate-u2f") - @component = new U2FAuthenticate(@container, {}, "token") + @component = new U2FAuthenticate(@container, {sign_requests: []}, "token") @component.start() it 'allows authenticating via a U2F device', -> diff --git a/spec/javascripts/u2f/register_spec.js.coffee b/spec/javascripts/u2f/register_spec.js.coffee index 0858abeca1a..87dc769792b 100644 --- a/spec/javascripts/u2f/register_spec.js.coffee +++ b/spec/javascripts/u2f/register_spec.js.coffee @@ -5,7 +5,6 @@ #= require ./mock_u2f_device describe 'U2FRegister', -> - U2FUtil.enableTestMode() fixture.load('u2f/register') beforeEach -> diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb new file mode 100644 index 00000000000..2799249ae3e --- /dev/null +++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +describe Banzai::Filter::BlockquoteFenceFilter, lib: true do + include FilterSpecHelper + + it 'converts blockquote fences to blockquote lines' do + content = File.read(Rails.root.join('spec/fixtures/blockquote_fence_before.md')) + expected = File.read(Rails.root.join('spec/fixtures/blockquote_fence_after.md')) + + output = filter(content) + + expect(output).to eq(expected) + end +end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index f1064a701d8..9276a154007 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -93,8 +93,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end it 'links with adjacent text' do - doc = reference_filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) + doc = reference_filter("Label (#{reference}).") + expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\)\.)) end it 'ignores invalid label names' do @@ -104,6 +104,55 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end + context 'String-based single-word references that begin with a digit' do + let(:label) { create(:label, name: '2fa', project: project) } + let(:reference) { "#{Label.reference_prefix}#{label.name}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + expect(doc.text).to eq 'See 2fa' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}).") + expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\)\.)) + end + + it 'ignores invalid label names' do + exp = act = "Label #{Label.reference_prefix}#{label.id}#{label.name.reverse}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'String-based single-word references with special characters' do + let(:label) { create(:label, name: '?g.fm&', project: project) } + let(:reference) { "#{Label.reference_prefix}#{label.name}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + expect(doc.text).to eq 'See ?g.fm&' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}).") + expect(doc.to_html).to match(%r(\(<a.+><span.+>\?g\.fm&</span></a>\)\.)) + end + + it 'ignores invalid label names' do + act = "Label #{Label.reference_prefix}#{label.name.reverse}" + exp = "Label #{Label.reference_prefix}&mf.g?" + + expect(reference_filter(act).to_html).to eq exp + end + end + context 'String-based multi-word references in quotes' do let(:label) { create(:label, name: 'gfm references', project: project) } let(:reference) { label.to_reference(format: :name) } @@ -128,6 +177,95 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end + context 'String-based multi-word references that begin with a digit' do + let(:label) { create(:label, name: '2 factor authentication', project: project) } + let(:reference) { label.to_reference(format: :name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + expect(doc.text).to eq 'See 2 factor authentication' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) + end + + it 'ignores invalid label names' do + exp = act = "Label #{Label.reference_prefix}#{label.id}#{label.name.reverse}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'String-based multi-word references with special characters in quotes' do + let(:label) { create(:label, name: 'g.fm & references?', project: project) } + let(:reference) { label.to_reference(format: :name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + expect(doc.text).to eq 'See g.fm & references?' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(<a.+><span.+>g\.fm & references\?</span></a>\.\))) + end + + it 'ignores invalid label names' do + act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") + exp = %(Label #{Label.reference_prefix}"?secnerefer & mf.g\") + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'consecutive references' do + let(:bug) { create(:label, name: 'bug', project: project) } + let(:feature_proposal) { create(:label, name: 'feature proposal', project: project) } + let(:technical_debt) { create(:label, name: 'technical debt', project: project) } + + let(:bug_reference) { "#{Label.reference_prefix}#{bug.name}" } + let(:feature_proposal_reference) { feature_proposal.to_reference(format: :name) } + let(:technical_debt_reference) { technical_debt.to_reference(format: :name) } + + context 'separated with a comma' do + let(:references) { "#{bug_reference}, #{feature_proposal_reference}, #{technical_debt_reference}" } + + it 'links to valid references' do + doc = reference_filter("See #{references}") + + expect(doc.css('a').map { |a| a.attr('href') }).to match_array([ + urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name), + urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name), + urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name) + ]) + expect(doc.text).to eq 'See bug, feature proposal, technical debt' + end + end + + context 'separated with a space' do + let(:references) { "#{bug_reference} #{feature_proposal_reference} #{technical_debt_reference}" } + + it 'links to valid references' do + doc = reference_filter("See #{references}") + + expect(doc.css('a').map { |a| a.attr('href') }).to match_array([ + urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name), + urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name), + urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name) + ]) + expect(doc.text).to eq 'See bug feature proposal technical debt' + end + end + end + describe 'edge cases' do it 'gracefully handles non-references matching the pattern' do exp = act = '(format nil "~0f" 3.0) ; 3.0' diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 407617f3307..b1370bca833 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -3,15 +3,35 @@ require 'spec_helper' describe Banzai::Filter::SyntaxHighlightFilter, lib: true do include FilterSpecHelper - it 'highlights valid code blocks' do - result = filter('<pre><code>def fun end</code>') - expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n") + context "when no language is specified" do + it "highlights as plaintext" do + result = filter('<pre><code>def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>def fun end</code></pre>') + end end - it 'passes through invalid code blocks' do - allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError) + context "when a valid language is specified" do + it "highlights as that language" do + result = filter('<pre><code class="ruby">def fun end</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>') + end + end + + context "when an invalid language is specified" do + it "highlights as plaintext" do + result = filter('<pre><code class="gnuplot">This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>This is a test</code></pre>') + end + end + + context "when Rouge formatting fails" do + before do + allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError) + end - result = filter('<pre><code>This is a test</code></pre>') - expect(result.to_html).to eq('<pre>This is a test</pre>') + it "highlights as plaintext" do + result = filter('<pre><code class="ruby">This is a test</code></pre>') + expect(result.to_html).to eq('<pre class="code highlight"><code>This is a test</code></pre>') + end end end diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 44256b32bdc..bcdb95250ca 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -17,6 +17,7 @@ describe Banzai::ObjectRenderer do and_call_original expect(object).to receive(:note_html=).with('<p>hello</p>') + expect(object).to receive(:user_visible_reference_count=).with(0) renderer.render([object], :note) end @@ -25,9 +26,10 @@ describe Banzai::ObjectRenderer do describe '#render_objects' do it 'renders an Array of objects' do object = double(:object, note: 'hello') + renderer = described_class.new(project, user) - expect(renderer).to receive(:render_attribute).with(object, :note). + expect(renderer).to receive(:render_attributes).with([object], :note). and_call_original rendered = renderer.render_objects([object], :note) @@ -38,7 +40,7 @@ describe Banzai::ObjectRenderer do end describe '#redact_documents' do - it 'redacts a set of documents and returns them as an Array of Strings' do + it 'redacts a set of documents and returns them as an Array of Hashes' do doc = Nokogiri::HTML.fragment('<p>hello</p>') renderer = described_class.new(project, user) @@ -48,7 +50,9 @@ describe Banzai::ObjectRenderer do redacted = renderer.redact_documents([doc]) - expect(redacted).to eq(['<p>hello</p>']) + expect(redacted.count).to eq(1) + expect(redacted.first[:visible_reference_count]).to eq(0) + expect(redacted.first[:document].to_html).to eq('<p>hello</p>') end end @@ -85,14 +89,36 @@ describe Banzai::ObjectRenderer do end end - describe '#render_attribute' do - it 'renders the attribute of an object' do - object = double(:doc, note: 'hello') + describe '#render_attributes' do + it 'renders the attribute of a list of objects' do + objects = [double(:doc, note: 'hello'), double(:doc, note: 'bye')] renderer = described_class.new(project, user, pipeline: :note) - doc = renderer.render_attribute(object, :note) - expect(doc).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) - expect(doc.to_html).to eq('<p>hello</p>') + expect(Banzai).to receive(:cache_collection_render). + with([ + { text: 'hello', context: renderer.context_for(objects[0], :note) }, + { text: 'bye', context: renderer.context_for(objects[1], :note) } + ]). + and_call_original + + docs = renderer.render_attributes(objects, :note) + + expect(docs[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) + expect(docs[0].to_html).to eq('<p>hello</p>') + + expect(docs[1]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) + expect(docs[1].to_html).to eq('<p>bye</p>') + end + + it 'returns when no objects to render' do + objects = [] + renderer = described_class.new(project, user, pipeline: :note) + + expect(Banzai).to receive(:cache_collection_render). + with([]). + and_call_original + + expect(renderer.render_attributes(objects, :note)).to eq([]) end end diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb index 488f465bcda..254657a881d 100644 --- a/spec/lib/banzai/redactor_spec.rb +++ b/spec/lib/banzai/redactor_spec.rb @@ -15,11 +15,31 @@ describe Banzai::Redactor do expect(redactor).to receive(:nodes_visible_to_user).and_return([]) - expect(redactor.redact([doc1, doc2])).to eq([doc1, doc2]) + redacted_data = redactor.redact([doc1, doc2]) + expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) + expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0]) expect(doc1.to_html).to eq('foo') expect(doc2.to_html).to eq('bar') end + + it 'does not redact an Array of documents' do + doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>' + doc1 = Nokogiri::HTML.fragment(doc1_html) + + doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>' + doc2 = Nokogiri::HTML.fragment(doc2_html) + + nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] } + expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten) + + redacted_data = redactor.redact([doc1, doc2]) + + expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2]) + expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1]) + expect(doc1.to_html).to eq(doc1_html) + expect(doc2.to_html).to eq(doc2_html) + end end describe '#redact_nodes' do @@ -31,7 +51,7 @@ describe Banzai::Redactor do with([node]). and_return(Set.new) - redactor.redact_nodes([node]) + redactor.redact_document_nodes([{ document: doc, nodes: [node] }]) expect(doc.to_html).to eq('foo') end diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index 543b4786d84..ac9c66e2663 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -234,4 +234,79 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do to eq([project]) end end + + describe '#collection_objects_for_ids' do + context 'with RequestStore disabled' do + it 'queries the collection directly' do + collection = User.all + + expect(collection).to receive(:where).twice.and_call_original + + 2.times do + expect(subject.collection_objects_for_ids(collection, [user.id])). + to eq([user]) + end + end + end + + context 'with RequestStore enabled' do + before do + cache = Hash.new { |hash, key| hash[key] = {} } + + allow(RequestStore).to receive(:active?).and_return(true) + allow(subject).to receive(:collection_cache).and_return(cache) + end + + it 'queries the collection on the first call' do + expect(subject.collection_objects_for_ids(User, [user.id])). + to eq([user]) + end + + it 'does not query previously queried objects' do + collection = User.all + + expect(collection).to receive(:where).once.and_call_original + + 2.times do + expect(subject.collection_objects_for_ids(collection, [user.id])). + to eq([user]) + end + end + + it 'casts String based IDs to Fixnums before querying objects' do + 2.times do + expect(subject.collection_objects_for_ids(User, [user.id.to_s])). + to eq([user]) + end + end + + it 'queries any additional objects after the first call' do + other_user = create(:user) + + expect(subject.collection_objects_for_ids(User, [user.id])). + to eq([user]) + + expect(subject.collection_objects_for_ids(User, [user.id, other_user.id])). + to eq([user, other_user]) + end + + it 'caches objects on a per collection class basis' do + expect(subject.collection_objects_for_ids(User, [user.id])). + to eq([user]) + + expect(subject.collection_objects_for_ids(Project, [project.id])). + to eq([project]) + end + end + end + + describe '#collection_cache_key' do + it 'returns the cache key for a Class' do + expect(subject.collection_cache_key(Project)).to eq(Project) + end + + it 'returns the cache key for an ActiveRecord::Relation' do + expect(subject.collection_cache_key(Project.all)).to eq(Project) + end + end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index c9602bcca22..d629bd252e2 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -31,7 +31,7 @@ module Ci }) end - describe :only do + describe 'only' do it "does not return builds if only has another branch" do config = YAML.dump({ before_script: ["pwd"], @@ -187,7 +187,7 @@ module Ci end end - describe :except do + describe 'except' do it "returns builds if except has another branch" do config = YAML.dump({ before_script: ["pwd"], diff --git a/spec/lib/gitlab/build_data_builder_spec.rb b/spec/lib/gitlab/build_data_builder_spec.rb index 38be9448794..23ae5cfacc4 100644 --- a/spec/lib/gitlab/build_data_builder_spec.rb +++ b/spec/lib/gitlab/build_data_builder_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'Gitlab::BuildDataBuilder' do let(:build) { create(:ci_build) } - describe :build do + describe '.build' do let(:data) do Gitlab::BuildDataBuilder.build(build) end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 9096ad101b0..4ec3f19e03f 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -13,6 +13,10 @@ describe Gitlab::Database::MigrationHelpers, lib: true do context 'outside a transaction' do before do expect(model).to receive(:transaction_open?).and_return(false) + + unless Gitlab::Database.postgresql? + allow_any_instance_of(Gitlab::Database::MigrationHelpers).to receive(:disable_statement_timeout) + end end context 'using PostgreSQL' do diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index a0cbef6e6a4..0460dcf4658 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -6,16 +6,16 @@ describe Gitlab::Diff::File, lib: true do let(:project) { create(:project) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.diffs.first } - let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } - describe :diff_lines do + describe '#diff_lines' do let(:diff_lines) { diff_file.diff_lines } it { expect(diff_lines.size).to eq(30) } it { expect(diff_lines.first).to be_kind_of(Gitlab::Diff::Line) } end - describe :mode_changed? do + describe '#mode_changed?' do it { expect(diff_file.mode_changed?).to be_falsey } end diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index d19bf4ac84b..88e4115c453 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -6,11 +6,11 @@ describe Gitlab::Diff::Highlight, lib: true do let(:project) { create(:project) } let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.diffs.first } - let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } describe '#highlight' do context "with a diff file" do - let(:subject) { Gitlab::Diff::Highlight.new(diff_file).highlight } + let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight } it 'should return Gitlab::Diff::Line elements' do expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line) @@ -28,20 +28,20 @@ describe Gitlab::Diff::Highlight, lib: true do end it 'highlights and marks removed lines' do - code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n} expect(subject[4].text).to eq(code) end it 'highlights and marks added lines' do - code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} + code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n} expect(subject[5].text).to eq(code) end end context "with diff lines" do - let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines).highlight } + let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight } it 'should return Gitlab::Diff::Line elements' do expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line) diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb index 95a993d26cf..8ca3f73509e 100644 --- a/spec/lib/gitlab/diff/inline_diff_spec.rb +++ b/spec/lib/gitlab/diff/inline_diff_spec.rb @@ -3,14 +3,19 @@ require 'spec_helper' describe Gitlab::Diff::InlineDiff, lib: true do describe '.for_lines' do let(:diff) do - <<eos - class Test -- def initialize(test = true) -+ def initialize(test = false) - @test = test - end - end -eos + <<-EOF.strip_heredoc + class Test + - def initialize(test = true) + + def initialize(test = false) + @test = test + - if true + - @foo = "bar" + + unless false + + @foo = "baz" + end + end + end + EOF end let(:subject) { described_class.for_lines(diff.lines) } @@ -20,8 +25,11 @@ eos expect(subject[1]).to eq([25..27]) expect(subject[2]).to eq([25..28]) expect(subject[3]).to be_nil - expect(subject[4]).to be_nil - expect(subject[5]).to be_nil + expect(subject[4]).to eq([5..10]) + expect(subject[5]).to eq([17..17]) + expect(subject[6]).to eq([5..15]) + expect(subject[7]).to eq([17..17]) + expect(subject[8]).to be_nil end end diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb new file mode 100644 index 00000000000..4e50e03bb7e --- /dev/null +++ b/spec/lib/gitlab/diff/line_mapper_spec.rb @@ -0,0 +1,137 @@ +require 'spec_helper' + +describe Gitlab::Diff::LineMapper, lib: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:repository) { project.repository } + let(:commit) { project.commit(sample_commit.id) } + let(:diffs) { commit.diffs } + let(:diff) { diffs.first } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } + subject { described_class.new(diff_file) } + + describe '#old_to_new' do + context "with a diff file" do + let(:mapping) do + { + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + 9 => nil, + # nil => 9, + 10 => 10, + 11 => 11, + 12 => 12, + 13 => nil, + 14 => nil, + # nil => 15, + # nil => 16, + # nil => 17, + # nil => 18, + # nil => 19, + # nil => 20, + 15 => 21, + 16 => 22, + 17 => 23, + 18 => 24, + 19 => 25, + 20 => 26, + 21 => 27, + # nil => 28, + 22 => 29, + 23 => 30, + 24 => 31, + 25 => 32, + 26 => 33, + 27 => 34, + 28 => 35, + 29 => 36, + 30 => 37 + } + end + + it 'returns the new line number for the old line number' do + mapping.each do |old_line, new_line| + expect(subject.old_to_new(old_line)).to eq(new_line) + end + end + end + + context "without a diff file" do + let(:diff_file) { nil } + + it "returns the same line number" do + expect(subject.old_to_new(100)).to eq(100) + end + end + end + + describe '#new_to_old' do + context "with a diff file" do + let(:mapping) do + { + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + # nil => 9, + 9 => nil, + 10 => 10, + 11 => 11, + 12 => 12, + # nil => 13, + # nil => 14, + 13 => nil, + 14 => nil, + 15 => nil, + 16 => nil, + 17 => nil, + 18 => nil, + 19 => nil, + 20 => nil, + 21 => 15, + 22 => 16, + 23 => 17, + 24 => 18, + 25 => 19, + 26 => 20, + 27 => 21, + 28 => nil, + 29 => 22, + 30 => 23, + 31 => 24, + 32 => 25, + 33 => 26, + 34 => 27, + 35 => 28, + 36 => 29, + 37 => 30 + } + end + + it 'returns the old line number for the new line number' do + mapping.each do |new_line, old_line| + expect(subject.new_to_old(new_line)).to eq(old_line) + end + end + end + + context "without a diff file" do + let(:diff_file) { nil } + + it "returns the same line number" do + expect(subject.new_to_old(100)).to eq(100) + end + end + end +end diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index 1c5bbc47120..5f76b70c6f5 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -8,8 +8,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do let(:commit) { project.commit(sample_commit.id) } 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: commit.diff_refs, repository: repository) } subject { described_class.new(diff_file) } let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") } diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index cdff063a9ed..c3359627652 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Diff::Parser, lib: true do let(:diff) { commit.diffs.first } let(:parser) { Gitlab::Diff::Parser.new } - describe :parse do + describe '#parse' do let(:diff) do <<eos --- a/files/ruby/popen.rb diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb new file mode 100644 index 00000000000..cf28628cb96 --- /dev/null +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -0,0 +1,341 @@ +require 'spec_helper' + +describe Gitlab::Diff::Position, lib: true do + include RepoHelpers + + let(:project) { create(:project) } + + describe "position for an added file" do + let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } + + subject do + described_class.new( + old_path: "files/images/wm.svg", + new_path: "files/images/wm.svg", + old_line: nil, + new_line: 5, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.new_file).to be true + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq("+ <desc>Created with Sketch.</desc>") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for a changed file" do + let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } + + describe "position for an added line" do + subject do + described_class.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq("+ vars = {") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 15) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for an unchanged line" do + subject do + described_class.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: 16, + new_line: 22, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.unchanged?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq(" unless File.directory?(path)") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for a removed line" do + subject do + described_class.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: 14, + new_line: nil, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.removed?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.text).to eq("- options = { chdir: path }") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + end + + describe "position for a renamed file" do + let(:commit) { project.commit("6907208d755b60ebeacb2e9dfea74c92c3449a1f") } + + describe "position for an added line" do + subject do + described_class.new( + old_path: "files/js/commit.js.coffee", + new_path: "files/js/commit.coffee", + old_line: nil, + new_line: 4, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq("+ new CommitFile(@)") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 5) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for an unchanged line" do + subject do + described_class.new( + old_path: "files/js/commit.js.coffee", + new_path: "files/js/commit.coffee", + old_line: 3, + new_line: 3, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.unchanged?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq(" $('.files .diff-file').each ->") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for a removed line" do + subject do + described_class.new( + old_path: "files/js/commit.js.coffee", + new_path: "files/js/commit.coffee", + old_line: 4, + new_line: nil, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.removed?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.text).to eq("- new CommitFile(this)") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + end + + describe "position for a deleted file" do + let(:commit) { project.commit("8634272bfad4cf321067c3e94d64d5a253f8321d") } + + subject do + described_class.new( + old_path: "LICENSE", + new_path: "LICENSE", + old_line: 3, + new_line: nil, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.deleted_file).to be true + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.removed?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.text).to eq("-Copyright (c) 2014 gitlabhq") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end +end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb new file mode 100644 index 00000000000..08312e60f4a --- /dev/null +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -0,0 +1,1758 @@ +require 'spec_helper' + +describe Gitlab::Diff::PositionTracer, lib: true do + # Douwe's diary New York City, 2016-06-28 + # -------------------------------------------------------------------------- + # + # Dear diary, + # + # Ideally, we would have a test for every single diff scenario that can + # occur and that the PositionTracer should correctly trace a position + # through, across the following variables: + # + # - Old diff file type: created, changed, renamed, deleted, unchanged (5) + # - Old diff line type: added, removed, unchanged (3) + # - New diff file type: created, changed, renamed, deleted, unchanged (5) + # - New diff line type: added, removed, unchanged (3) + # - Old-to-new diff line change: kept, moved, undone (3) + # + # This adds up to 5 * 3 * 5 * 3 * 3 = 675 different potential scenarios, + # and 675 different tests to cover them all. In reality, it would be fewer, + # since one cannot have a removed line in a created file diff, for example, + # but for the sake of this diary entry, let's be pessimistic. + # + # Writing these tests is a manual and time consuming process, as every test + # requires the manual construction or finding of a combination of diffs that + # create the exact diff scenario we are looking for, and can take between + # 1 and 10 minutes, depending on the farfetchedness of the scenario and + # complexity of creating it. + # + # This means that writing tests to cover all of these scenarios would end up + # taking between 11 and 112 hours in total, which I do not believe is the + # best use of my time. + # + # A better course of action would be to think of scenarios that are likely + # to occur, but also potentially tricky to trace correctly, and only cover + # those, with a few more obvious scenarios thrown in to cover our bases. + # + # Unfortunately, I only came to the above realization once I was about + # 1/5th of the way through the process of writing ALL THE SPECS, having + # already wasted about 3 hours trying to be thorough. + # + # I did find 2 bugs while writing those though, so that's good. + # + # In any case, all of this means that the tests below will be extremely + # (excessively, unjustifiably) thorough for scenarios where "the file was + # created in the old diff" and then drop off to comparitively lackluster + # testing of other scenarios. + # + # I did still try to cover most of the obvious and potentially tricky + # scenarios, though. + + include RepoHelpers + + let(:project) { create(:project) } + let(:current_user) { project.owner } + let(:repository) { project.repository } + let(:file_name) { "test-file" } + let(:new_file_name) { "#{file_name}-new" } + let(:second_file_name) { "#{file_name}-2" } + let(:branch_name) { "position-tracer-test" } + + let(:old_diff_refs) { raise NotImplementedError } + let(:new_diff_refs) { raise NotImplementedError } + let(:old_position) { raise NotImplementedError } + + let(:position_tracer) { described_class.new(repository: project.repository, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) } + subject { position_tracer.trace(old_position) } + + def diff_refs(base_commit, head_commit) + Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) + end + + def position(attrs = {}) + attrs.reverse_merge!( + diff_refs: old_diff_refs + ) + Gitlab::Diff::Position.new(attrs) + end + + def expect_new_position(attrs, new_position = subject) + if attrs.nil? + expect(new_position).to be_nil + else + expect(new_position).not_to be_nil + + expect(new_position.diff_refs).to eq(new_diff_refs) + + attrs.each do |attr, value| + expect(new_position.send(attr)).to eq(value) + end + end + end + + def create_branch(new_name, branch_name) + CreateBranchService.new(project, current_user).execute(new_name, branch_name) + end + + def create_file(branch_name, file_name, content) + Files::CreateService.new( + project, + current_user, + source_branch: branch_name, + target_branch: branch_name, + commit_message: "Create file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def update_file(branch_name, file_name, content) + Files::UpdateService.new( + project, + current_user, + source_branch: branch_name, + target_branch: branch_name, + commit_message: "Update file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def delete_file(branch_name, file_name) + Files::DeleteService.new( + project, + current_user, + source_branch: branch_name, + target_branch: branch_name, + commit_message: "Delete file", + file_path: file_name + ).execute + project.commit(branch_name) + end + + let(:initial_commit) do + create_branch(branch_name, "master")[:branch].name + project.commit(branch_name) + end + + describe "#trace" do + describe "diff scenarios" do + let(:create_file_commit) do + initial_commit + + create_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + B + C + CONTENT + ) + end + + let(:create_second_file_commit) do + create_file_commit + + create_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + E + CONTENT + ) + end + + let(:update_line_commit) do + create_second_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + CONTENT + ) + end + + let(:update_second_file_line_commit) do + update_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + EE + CONTENT + ) + end + + let(:move_line_commit) do + update_second_file_line_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + BB + A + C + CONTENT + ) + end + + let(:add_second_file_line_commit) do + move_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + EE + F + CONTENT + ) + end + + let(:move_second_file_line_commit) do + add_second_file_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + F + EE + CONTENT + ) + end + + let(:delete_line_commit) do + move_second_file_line_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + BB + A + CONTENT + ) + end + + let(:delete_second_file_line_commit) do + delete_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + F + CONTENT + ) + end + + let(:delete_file_commit) do + delete_second_file_line_commit + + delete_file(branch_name, file_name) + end + + let(:rename_file_commit) do + delete_file_commit + + create_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + BB + A + CONTENT + ) + end + + let(:update_line_again_commit) do + rename_file_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + BB + AA + CONTENT + ) + end + + let(:move_line_again_commit) do + update_line_again_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + AA + BB + CONTENT + ) + end + + let(:delete_line_again_commit) do + move_line_again_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + AA + CONTENT + ) + end + + context "when the file was created in the old diff" do + context "when the file is created in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + B + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 + BB + # 2 + A + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: 1 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is changed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: 1 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 - C + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is renamed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 2 A + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: old_position.new_line, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 - A + # 2 + AA + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: old_position.new_line, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, move_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 + AA + # 1 2 BB + # 2 - A + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: 1, + new_line: 2 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 - A + # 2 + AA + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 - BB + # 2 - A + # 1 + AA + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is deleted in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # 1 - BB + # 2 - A + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + # 3 - C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 - A + # 2 - BB + # 3 - C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is unchanged in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 B + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + end + + context "when the file was changed in the old diff" do + context "when the file is created in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + A + # 2 + B + # 3 + C + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + + context "when the position pointed at a deleted line in the old diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when the position pointed at an unchanged line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 1) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 2) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2, new_line: 2) } + + # old diff: + # 1 1 BB + # 2 2 A + # 3 - C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + A + # 2 + B + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is changed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 - C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: 1, + new_line: 1 + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: 2, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + + context "when the position pointed at a deleted line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: old_position.old_line, + new_line: nil + ) + end + end + end + end + end + end + end + + describe "typical use scenarios" do + let(:second_branch_name) { "#{branch_name}-2" } + + def expect_positions(old_attrs, new_attrs) + old_positions = old_attrs.map do |old_attrs| + position(old_attrs) + end + + new_positions = old_positions.map do |old_position| + position_tracer.trace(old_position) + end + + new_positions.zip(new_attrs).each do |new_position, new_attrs| + expect_new_position(new_attrs, new_position) + end + end + + let(:create_file_commit) do + initial_commit + + create_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + B + C + D + E + F + CONTENT + ) + end + + let(:second_create_file_commit) do + create_file_commit + + create_branch(second_branch_name, branch_name) + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + B + C + D + E + F + CONTENT + ) + end + + let(:update_file_commit) do + second_create_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + C + DD + E + F + G + CONTENT + ) + end + + let(:update_file_again_commit) do + update_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + describe "simple push of new commit" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C + { old_path: file_name, old_line: 4 }, # - D + { new_path: file_name, new_line: 3 }, # + DD + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F + { new_path: file_name, new_line: 6 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, + { old_path: file_name, old_line: 4, new_line: 4 }, + nil, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, + { old_path: file_name, old_line: 6 }, + { new_path: file_name, new_line: 7 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "force push to overwrite last commit" do + let(:second_create_file_commit) do + create_file_commit + + create_branch(second_branch_name, branch_name) + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C + { old_path: file_name, old_line: 4 }, # - D + { new_path: file_name, new_line: 3 }, # + DD + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F + { new_path: file_name, new_line: 6 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, + { old_path: file_name, old_line: 4, new_line: 4 }, + nil, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, + { old_path: file_name, old_line: 6 }, + { new_path: file_name, new_line: 7 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "force push to delete last commit" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + nil, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, + { old_path: file_name, old_line: 4 }, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, + nil, + { new_path: file_name, new_line: 6 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "rebase on top of target branch" do + let(:second_update_file_commit) do + update_file_commit + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + C + DD + E + F + G + CONTENT + ) + end + + let(:update_file_again_commit) do + second_update_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:overwrite_update_file_again_commit) do + update_file_again_commit + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 + Z + # 2 + Z + # 3 + Z + # 1 4 A + # 2 - B + # 5 + BB + # 3 6 C + # 4 7 D + # 5 8 E + # 6 - F + # 9 + FF + # 0 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 5 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 9 }, # + FF + { new_path: file_name, new_line: 10 }, # + G + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "merge of target branch" do + let(:merge_commit) do + update_file_again_commit + + committer = repository.user_to_committer(current_user) + + options = { + message: "Merge branches", + author: committer, + committer: committer + } + + repository.merge(current_user, second_create_file_commit.sha, branch_name, options) + project.commit(branch_name) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 + Z + # 2 + Z + # 3 + Z + # 1 4 A + # 2 - B + # 5 + BB + # 3 6 C + # 4 7 D + # 5 8 E + # 6 - F + # 9 + FF + # 0 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 5 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 9 }, # + FF + { new_path: file_name, new_line: 10 }, # + G + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "changing target branch" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 1 A + # 2 + BB + # 2 3 C + # 3 - DD + # 4 + D + # 4 5 E + # 5 - F + # 6 + FF + # 7 G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + nil, + { new_path: file_name, new_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 }, + { new_path: file_name, new_line: 4 }, + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 }, + { old_path: file_name, old_line: 5 }, + { new_path: file_name, new_line: 6 }, + { new_path: file_name, new_line: 7 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + end +end diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb new file mode 100644 index 00000000000..a15aa173fbd --- /dev/null +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' +require 'fileutils' + +describe Gitlab::Git::Hook, lib: true do + describe "#trigger" do + let(:project) { create(:project) } + let(:user) { create(:user) } + + def create_hook(name) + FileUtils.mkdir_p(File.join(project.repository.path, 'hooks')) + File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f| + f.write('exit 0') + end + end + + def create_failing_hook(name) + FileUtils.mkdir_p(File.join(project.repository.path, 'hooks')) + File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f| + f.write(<<-HOOK) + echo 'regular message from the hook' + echo 'error message from the hook' 1>&2 + exit 1 + HOOK + end + end + + ['pre-receive', 'post-receive', 'update'].each do |hook_name| + + context "when triggering a #{hook_name} hook" do + context "when the hook is successful" do + it "returns success with no errors" do + create_hook(hook_name) + hook = Gitlab::Git::Hook.new(hook_name, project.repository.path) + blank = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' + + status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + expect(status).to be true + expect(errors).to be_blank + end + end + + context "when the hook is unsuccessful" do + it "returns failure with errors" do + create_failing_hook(hook_name) + hook = Gitlab::Git::Hook.new(hook_name, project.repository.path) + blank = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' + + status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + expect(status).to be false + expect(errors).to eq("error message from the hook\n") + end + end + end + end + + context "when the hook doesn't exist" do + it "returns success with no errors" do + hook = Gitlab::Git::Hook.new('unknown_hook', project.repository.path) + blank = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' + + status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + expect(status).to be true + expect(errors).to be_nil + end + end + end +end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 9b7986fa12d..c79ba11f782 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do - let(:access) { Gitlab::GitAccess.new(actor, project) } + let(:access) { Gitlab::GitAccess.new(actor, project, 'web') } let(:project) { create(:project) } let(:user) { create(:user) } let(:actor) { user } @@ -67,6 +67,43 @@ describe Gitlab::GitAccess, lib: true do end end + describe '#check with single protocols allowed' do + def disable_protocol(protocol) + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, protocol) + end + + context 'ssh disabled' do + before do + disable_protocol('ssh') + @acc = Gitlab::GitAccess.new(actor, project, 'ssh') + end + + it 'blocks ssh git push' do + expect(@acc.check('git-receive-pack').allowed?).to be_falsey + end + + it 'blocks ssh git pull' do + expect(@acc.check('git-upload-pack').allowed?).to be_falsey + end + end + + context 'http disabled' do + before do + disable_protocol('http') + @acc = Gitlab::GitAccess.new(actor, project, 'http') + end + + it 'blocks http push' do + expect(@acc.check('git-receive-pack').allowed?).to be_falsey + end + + it 'blocks http git pull' do + expect(@acc.check('git-upload-pack').allowed?).to be_falsey + end + end + end + describe 'download_access_check' do describe 'master permissions' do before { project.team << [user, :master] } diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 77ecfce6f17..4244b807d41 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do - let(:access) { Gitlab::GitAccessWiki.new(user, project) } + let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') } let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb index 3cb634ba010..fc9d5204148 100644 --- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb @@ -2,17 +2,18 @@ require 'spec_helper' describe Gitlab::GithubImport::BranchFormatter, lib: true do let(:project) { create(:project) } + let(:commit) { create(:commit, project: project) } let(:repo) { double } let(:raw) do { ref: 'feature', repo: repo, - sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b' + sha: commit.id } end describe '#exists?' do - it 'returns true when branch exists' do + it 'returns true when both branch, and commit exists' do branch = described_class.new(project, double(raw)) expect(branch.exists?).to eq true @@ -23,6 +24,12 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do expect(branch.exists?).to eq false end + + it 'returns false when commit does not exist' do + branch = described_class.new(project, double(raw.merge(sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'))) + + expect(branch.exists?).to eq false + end end describe '#name' do @@ -33,7 +40,7 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do end it 'returns formatted ref when branch does not exist' do - branch = described_class.new(project, double(raw.merge(ref: 'removed-branch'))) + branch = described_class.new(project, double(raw.merge(ref: 'removed-branch', sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'))) expect(branch.name).to eq 'removed-branch-2e5d3239' end @@ -51,18 +58,18 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do it 'returns raw sha' do branch = described_class.new(project, double(raw)) - expect(branch.sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b' + expect(branch.sha).to eq commit.id end end describe '#valid?' do - it 'returns true when repository exists' do + it 'returns true when raw repo is present' do branch = described_class.new(project, double(raw)) expect(branch.valid?).to eq true end - it 'returns false when repository does not exist' do + it 'returns false when raw repo is blank' do branch = described_class.new(project, double(raw.merge(repo: nil))) expect(branch.valid?).to eq false diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index 3b023a35446..613c47d55f1 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -61,4 +61,11 @@ describe Gitlab::GithubImport::Client, lib: true do expect(client.api.api_endpoint).to eq 'https://github.company.com/' end end + + it 'does not raise error when rate limit is disabled' do + stub_request(:get, /api.github.com/) + allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound) + + expect { client.issues }.not_to raise_error + end end diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index 120f59e6e71..79931ecd134 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -2,11 +2,13 @@ require 'spec_helper' describe Gitlab::GithubImport::PullRequestFormatter, lib: true do let(:project) { create(:project) } + let(:source_sha) { create(:commit, project: project).id } + let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } let(:repository) { double(id: 1, fork: false) } let(:source_repo) { repository } - let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') } + let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: source_sha) } let(:target_repo) { repository } - let(:target_branch) { double(ref: 'master', repo: target_repo, sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7') } + let(:target_branch) { double(ref: 'master', repo: target_repo, sha: target_sha) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } @@ -41,10 +43,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, source_branch: 'feature', - head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + target_branch_sha: target_sha, state: 'opened', milestone: nil, author_id: project.creator_id, @@ -68,10 +70,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, source_branch: 'feature', - head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + target_branch_sha: target_sha, state: 'closed', milestone: nil, author_id: project.creator_id, @@ -95,10 +97,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, source_branch: 'feature', - head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + target_branch_sha: target_sha, state: 'merged', milestone: nil, author_id: project.creator_id, diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb new file mode 100644 index 00000000000..d3f1deb3837 --- /dev/null +++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Gitlab::GitlabImport::Importer, lib: true do + include ImportSpecHelper + + describe '#execute' do + before do + stub_omniauth_provider('gitlab') + stub_request('issues', [ + { + 'id' => 2579857, + 'iid' => 3, + 'title' => 'Issue', + 'description' => 'Lorem ipsum', + 'state' => 'opened', + 'author' => { + 'id' => 283999, + 'name' => 'John Doe' + } + } + ]) + stub_request('issues/2579857/notes', []) + end + + it 'persists issues' do + project = create(:empty_project, import_source: 'asd/vim') + project.build_import_data(credentials: { password: 'password' }) + + subject = described_class.new(project) + subject.execute + + expected_attributes = { + iid: 3, + title: 'Issue', + description: "*Created by: John Doe*\n\nLorem ipsum", + state: 'opened', + author_id: project.creator_id + } + + expect(project.issues.first).to have_attributes(expected_attributes) + end + + def stub_request(path, body) + url = "https://gitlab.com/api/v3/projects/asd%2Fvim/#{path}?page=1&per_page=100" + + WebMock.stub_request(:get, url). + to_return( + headers: { 'Content-Type' => 'application/json' }, + body: body + ) + end + end +end diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb new file mode 100644 index 00000000000..f5c064303ad --- /dev/null +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Gitlab::Graphs::Commits, lib: true do + let!(:project) { create(:project, :public, :empty_repo) } + + let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) } + let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)} + + let!(:commit2) { create(:commit, git_commit: RepoHelpers.another_sample_commit, project: project, committed_date: Time.now) } + + describe '#commit_per_day' do + context 'when range is only commits from today' do + subject { described_class.new([commit2, commit1]).commit_per_day } + it { is_expected.to eq 2 } + end + end + + context 'when range is only commits from today' do + subject { described_class.new([commit2, commit1]) } + describe '#commit_per_day' do + it { expect(subject.commit_per_day).to eq 2 } + end + + describe '#duration' do + it { expect(subject.duration).to eq 0 } + end + end + + context 'with commits from yesterday and today' do + subject { described_class.new([commit2, commit1_yesterday]) } + describe '#commit_per_day' do + it { expect(subject.commit_per_day).to eq 1 } + end + + describe '#duration' do + it { expect(subject.duration).to eq 1 } + end + end +end diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb new file mode 100644 index 00000000000..d6409a29550 --- /dev/null +++ b/spec/lib/gitlab/import_export/import_export_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::ImportExport, services: true do + describe 'export filename' do + let(:project) { create(:project, :public, path: 'project-path') } + + it 'contains the project path' do + expect(described_class.export_filename(project: project)).to include(project.path) + end + + it 'contains the namespace path' do + expect(described_class.export_filename(project: project)).to include(project.namespace.path) + end + + it 'does not go over a certain length' do + project.path = 'a' * 100 + + expect(described_class.export_filename(project: project).length).to be < 70 + end + end +end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 0b30e8c9b04..4113d829c3c 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -26,6 +26,7 @@ "deleted_at": null, "due_date": null, "moved_to_id": null, + "test_ee_field": "test", "notes": [ { "id": 351, @@ -4208,7 +4209,18 @@ "name": "User 4" }, "events": [ - + { + "id": 529, + "target_type": "Note", + "target_id": 2521, + "title": "test levels", + "data": null, + "project_id": 4, + "created_at": "2016-07-07T14:35:12.128Z", + "updated_at": "2016-07-07T14:35:12.128Z", + "action": 6, + "author_id": 1 + } ] }, { diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index a72aaa44e82..877be300262 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -24,11 +24,35 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Ci::Pipeline.first.notes).not_to be_empty end - it 'restores the correct event' do + it 'restores the correct event with symbolised data' do restored_project_json expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty end + + it 'preserves updated_at on issues' do + restored_project_json + + issue = Issue.where(description: 'Aliquam enim illo et possimus.').first + + expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC') + end + + context 'event at forth level of the tree' do + let(:event) { Event.where(title: 'test levels').first } + + before do + restored_project_json + end + + it 'restores the event' do + expect(event).not_to be_nil + end + + it 'event belongs to note, belongs to merge request, belongs to a project' do + expect(event.note.noteable.project).not_to be_nil + end + end end end end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index a75eaa4d51f..1424de9e60b 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -125,7 +125,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ci_pipeline = create(:ci_pipeline, project: project, - sha: merge_request.last_commit.id, + sha: merge_request.diff_head_sha, ref: merge_request.source_branch, statuses: [commit_status]) diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index f5b66b8156f..acd5394382c 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::LDAP::Access, lib: true do let(:access) { Gitlab::LDAP::Access.new user } let(:user) { create(:omniauth_user) } - describe :allowed? do + describe '#allowed?' do subject { access.allowed? } context 'when the user cannot be found' do diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 03199a2523e..949f6e2b19a 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::LDAP::User, lib: true do OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info_upper_case) end - describe :changed? do + describe '#changed?' do it "marks existing ldap user as changed" do create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') expect(ldap_user.changed?).to be_truthy diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb index d824dc54438..d986c6fac43 100644 --- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb @@ -13,6 +13,61 @@ describe Gitlab::Metrics::Subscribers::RailsCache do subscriber.cache_read(event) end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + context 'with hit event' do + let(:event) { double(:event, duration: 15.2, payload: { hit: true }) } + + it 'increments the cache_read_hit count' do + expect(transaction).to receive(:increment). + with(:cache_read_hit_count, 1) + expect(transaction).to receive(:increment). + with(any_args).at_least(1) # Other calls + + subscriber.cache_read(event) + end + + context 'when super operation is fetch' do + let(:event) { double(:event, duration: 15.2, payload: { hit: true, super_operation: :fetch }) } + + it 'does not increment cache read miss' do + expect(transaction).not_to receive(:increment). + with(:cache_read_hit_count, 1) + + subscriber.cache_read(event) + end + end + end + + context 'with miss event' do + let(:event) { double(:event, duration: 15.2, payload: { hit: false }) } + + it 'increments the cache_read_miss count' do + expect(transaction).to receive(:increment). + with(:cache_read_miss_count, 1) + expect(transaction).to receive(:increment). + with(any_args).at_least(1) # Other calls + + subscriber.cache_read(event) + end + + context 'when super operation is fetch' do + let(:event) { double(:event, duration: 15.2, payload: { hit: false, super_operation: :fetch }) } + + it 'does not increment cache read miss' do + expect(transaction).not_to receive(:increment). + with(:cache_read_miss_count, 1) + + subscriber.cache_read(event) + end + end + end + end end describe '#cache_write' do @@ -42,6 +97,54 @@ describe Gitlab::Metrics::Subscribers::RailsCache do end end + describe '#cache_fetch_hit' do + context 'without a transaction' do + it 'returns' do + expect(transaction).not_to receive(:increment) + + subscriber.cache_fetch_hit(event) + end + end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + it 'increments the cache_read_hit count' do + expect(transaction).to receive(:increment). + with(:cache_read_hit_count, 1) + + subscriber.cache_fetch_hit(event) + end + end + end + + describe '#cache_generate' do + context 'without a transaction' do + it 'returns' do + expect(transaction).not_to receive(:increment) + + subscriber.cache_generate(event) + end + end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + it 'increments the cache_fetch_miss count' do + expect(transaction).to receive(:increment). + with(:cache_read_miss_count, 1) + + subscriber.cache_generate(event) + end + end + end + describe '#increment' do context 'without a transaction' do it 'returns' do diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb index e848d88182f..3d6bcdfd873 100644 --- a/spec/lib/gitlab/note_data_builder_spec.rb +++ b/spec/lib/gitlab/note_data_builder_spec.rb @@ -27,7 +27,7 @@ describe 'Gitlab::NoteDataBuilder', lib: true do end describe 'When asking for a note on commit diff' do - let(:note) { create(:note_on_commit_diff, project: project) } + let(:note) { create(:diff_note_on_commit, project: project) } it 'returns the note and commit-specific data' do expect(data).to have_key(:commit) @@ -90,7 +90,7 @@ describe 'Gitlab::NoteDataBuilder', lib: true do end let(:note) do - create(:note_on_merge_request_diff, noteable: merge_request, + create(:diff_note_on_merge_request, noteable: merge_request, project: project) end diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index bf11472407a..a826b24419a 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -43,9 +43,9 @@ describe Gitlab::UrlBuilder, lib: true do end end - context 'on a CommitDiff' do + context 'on a Commit Diff' do it 'returns a proper URL' do - note = build_stubbed(:note_on_commit_diff) + note = build_stubbed(:diff_note_on_commit) url = described_class.build(note) @@ -75,10 +75,10 @@ describe Gitlab::UrlBuilder, lib: true do end end - context 'on a MergeRequestDiff' do + context 'on a MergeRequest Diff' do it 'returns a proper URL' do merge_request = create(:merge_request, iid: 42) - note = build_stubbed(:note_on_merge_request_diff, noteable: merge_request) + note = build_stubbed(:diff_note_on_merge_request, noteable: merge_request) url = described_class.build(note) diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 59024d3290b..2cb74629da8 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -45,6 +45,12 @@ describe Gitlab::UrlSanitizer, lib: true do expect(filtered_content).to include("user@server:project.git") end + + it 'returns an empty string for invalid URLs' do + filtered_content = sanitize_url('ssh://') + + expect(filtered_content).to include("repository '' not found") + end end describe '#sanitized_url' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index aa382f930d7..0a9b10bebea 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -948,7 +948,7 @@ describe Notify do let(:commits) { Commit.decorate(compare.commits, nil) } let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } let(:send_from_committer_email) { false } - let(:diff_refs) { [project.merge_base_commit(sample_image_commit.id, sample_commit.id), project.commit(sample_commit.id)] } + let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, diff_refs: diff_refs, send_from_committer_email: send_from_committer_email) } @@ -1049,7 +1049,7 @@ describe Notify do let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } let(:commits) { Commit.decorate(compare.commits, nil) } let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } - let(:diff_refs) { [project.merge_base_commit(sample_commit.parent_id, sample_commit.id), project.commit(sample_commit.id)] } + let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 34507cf5083..4e5481f9154 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -15,7 +15,7 @@ describe Ci::Pipeline, models: true do it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :short_sha } - describe :valid_commit_sha do + describe '#valid_commit_sha' do context 'commit.sha can not start with 00000000' do before do pipeline.sha = '0' * 40 @@ -26,7 +26,7 @@ describe Ci::Pipeline, models: true do end end - describe :short_sha do + describe '#short_sha' do subject { pipeline.short_sha } it 'has 8 items' do @@ -35,10 +35,10 @@ describe Ci::Pipeline, models: true do it { expect(pipeline.sha).to start_with(subject) } end - describe :create_next_builds do + describe '#create_next_builds' do end - describe :retried do + describe '#retried' do subject { pipeline.retried } before do @@ -51,7 +51,7 @@ describe Ci::Pipeline, models: true do end end - describe :create_builds do + describe '#create_builds' do let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project, ref: 'master', tag: false } def create_builds(trigger_request = nil) diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 98f60087cf5..4e7833c3162 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -9,7 +9,7 @@ describe Ci::Variable, models: true do subject.value = secret_value end - describe :value do + describe '#value' do it 'stores the encrypted value' do expect(subject.encrypted_value).not_to be_nil end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 96397d7c8a9..05f22c7a9eb 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -24,14 +24,14 @@ describe CommitStatus, models: true do it { is_expected.to respond_to :running? } it { is_expected.to respond_to :pending? } - describe :author do + describe '#author' do subject { commit_status.author } before { commit_status.author = User.new } it { is_expected.to eq(commit_status.user) } end - describe :started? do + describe '#started?' do subject { commit_status.started? } context 'without started_at' do @@ -57,7 +57,7 @@ describe CommitStatus, models: true do end end - describe :active? do + describe '#active?' do subject { commit_status.active? } %w(pending running).each do |state| @@ -77,7 +77,7 @@ describe CommitStatus, models: true do end end - describe :complete? do + describe '#complete?' do subject { commit_status.complete? } %w(success failed canceled).each do |state| @@ -97,7 +97,7 @@ describe CommitStatus, models: true do end end - describe :duration do + describe '#duration' do subject { commit_status.duration } it { is_expected.to eq(120.0) } @@ -122,7 +122,7 @@ describe CommitStatus, models: true do end end - describe :latest do + describe '.latest' do subject { CommitStatus.latest.order(:id) } before do @@ -138,7 +138,7 @@ describe CommitStatus, models: true do end end - describe :running_or_pending do + describe '.running_or_pending' do subject { CommitStatus.running_or_pending.order(:id) } before do diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 0344dae8b5d..5e652660e2c 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -7,7 +7,7 @@ describe Mentionable do nil end - describe :references do + describe 'references' do let(:project) { create(:project) } it 'excludes JIRA references' do diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb new file mode 100644 index 00000000000..af8e890ca95 --- /dev/null +++ b/spec/models/diff_note_spec.rb @@ -0,0 +1,191 @@ +require 'spec_helper' + +describe DiffNote, models: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:commit) { project.commit(sample_commit.id) } + + let(:path) { "files/ruby/popen.rb" } + + let!(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + old_line: nil, + new_line: 14, + diff_refs: merge_request.diff_refs + ) + end + + let!(:new_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + old_line: 16, + new_line: 22, + diff_refs: merge_request.diff_refs + ) + end + + subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + + describe "#position=" do + context "when provided a string" do + it "sets the position" do + subject.position = new_position.to_json + + expect(subject.position).to eq(new_position) + end + end + + context "when provided a hash" do + it "sets the position" do + subject.position = new_position.to_h + + expect(subject.position).to eq(new_position) + end + end + + context "when provided a position object" do + it "sets the position" do + subject.position = new_position + + expect(subject.position).to eq(new_position) + end + end + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file + + expect(diff_file.old_path).to eq(position.old_path) + expect(diff_file.new_path).to eq(position.new_path) + expect(diff_file.diff_refs).to eq(position.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(position.new_line) + expect(diff_line.text).to eq("+ vars = {") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(position.file_path, position.new_line, 15) + + expect(subject.line_code).to eq(line_code) + end + end + + describe "#for_line?" do + context "when provided the correct diff line" do + it "returns true" do + expect(subject.for_line?(subject.diff_line)).to be true + end + end + + context "when provided a different diff line" do + it "returns false" do + some_line = subject.diff_file.diff_lines.first + + expect(subject.for_line?(some_line)).to be false + end + end + end + + describe "#active?" do + context "when noteable is a commit" do + subject { create(:diff_note_on_commit, project: project, position: position) } + + it "returns true" do + expect(subject.active?).to be true + end + end + + context "when noteable is a merge request" do + context "when the merge request's diff refs match that of the diff note" do + it "returns true" do + expect(subject.active?).to be true + end + end + + context "when the merge request's diff refs don't match that of the diff note" do + before do + allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs) + end + + it "returns false" do + expect(subject.active?).to be false + end + end + end + end + + describe "creation" do + describe "updating of position" do + context "when noteable is a commit" do + let(:diff_note) { create(:diff_note_on_commit, project: project, position: position) } + + it "doesn't use the DiffPositionUpdateService" do + expect(Notes::DiffPositionUpdateService).not_to receive(:new) + + diff_note + end + + it "doesn't update the position" do + diff_note + + expect(diff_note.original_position).to eq(position) + expect(diff_note.position).to eq(position) + end + end + + context "when noteable is a merge request" do + let(:diff_note) { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + + context "when the note is active" do + it "doesn't use the DiffPositionUpdateService" do + expect(Notes::DiffPositionUpdateService).not_to receive(:new) + + diff_note + end + + it "doesn't update the position" do + diff_note + + expect(diff_note.original_position).to eq(position) + expect(diff_note.position).to eq(position) + end + end + + context "when the note is outdated" do + before do + allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs) + end + + it "uses the DiffPositionUpdateService" do + service = instance_double("Notes::DiffPositionUpdateService") + expect(Notes::DiffPositionUpdateService).to receive(:new).with( + project, + nil, + old_diff_refs: position.diff_refs, + new_diff_refs: commit.diff_refs, + paths: [path] + ).and_return(service) + expect(service).to receive(:execute) + + diff_note + end + end + end + end + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 166a1dc4ddb..b5d0d79e14e 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -46,6 +46,22 @@ describe Event, models: true do it { expect(@event.author).to eq(@user) } end + describe '#note?' do + subject { Event.new(project: target.project, target: target) } + + context 'issue note event' do + let(:target) { create(:note_on_issue) } + + it { is_expected.to be_note } + end + + context 'merge request diff note event' do + let(:target) { create(:legacy_diff_note_on_merge_request) } + + it { is_expected.to be_note } + end + end + describe '#visible_to_user?' do let(:project) { create(:empty_project, :public) } let(:non_member) { create(:user) } @@ -89,7 +105,7 @@ describe Event, models: true do end end - context 'note event' do + context 'issue note event' do context 'on non confidential issues' do let(:target) { note_on_issue } @@ -112,6 +128,20 @@ describe Event, models: true do it { expect(event.visible_to_user?(admin)).to eq true } end end + + context 'merge request diff note event' do + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) } + let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } + let(:target) { note_on_merge_request } + + it { expect(event.visible_to_user?(non_member)).to eq true } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(guest)).to eq true } + it { expect(event.visible_to_user?(admin)).to eq true } + end end describe '.limit_recent' do diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index fa1a0d4e0c7..f94987dcaff 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -18,7 +18,7 @@ describe ForkedProjectLink, "add link on fork" do end end -describe :forked_from_project do +describe '#forked?' do let(:forked_project_link) { build(:forked_project_link) } let(:project_from) { create(:project) } let(:project_to) { create(:project, forked_project_link: forked_project_link) } diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index c4e781dd1dc..615cfe3142b 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -4,33 +4,33 @@ describe GenericCommitStatus, models: true do let(:pipeline) { FactoryGirl.create :ci_pipeline } let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline } - describe :context do + describe '#context' do subject { generic_commit_status.context } before { generic_commit_status.context = 'my_context' } it { is_expected.to eq(generic_commit_status.name) } end - describe :tags do + describe '#tags' do subject { generic_commit_status.tags } it { is_expected.to eq([:external]) } end - describe :set_default_values do + describe 'set_default_values' do before do generic_commit_status.context = nil generic_commit_status.stage = nil generic_commit_status.save end - describe :context do + describe '#context' do subject { generic_commit_status.context } it { is_expected.not_to be_nil } end - describe :stage do + describe '#stage' do subject { generic_commit_status.stage } it { is_expected.not_to be_nil } diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index 197c99cd007..ae77ec5b348 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -14,7 +14,7 @@ describe GlobalMilestone, models: true do let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } - describe :build_collection do + describe '.build_collection' do before do milestones = [ @@ -42,7 +42,7 @@ describe GlobalMilestone, models: true do end end - describe :initialize do + describe '#initialize' do before do milestones = [ @@ -63,7 +63,7 @@ describe GlobalMilestone, models: true do end end - describe :safe_title do + describe '#safe_title' do let(:milestone) { create(:milestone, title: "git / test", project: project1) } it 'should strip out slashes and spaces' do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index a878ff1b227..266c46213a6 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -97,22 +97,22 @@ describe Group, models: true do end end - describe :users do + describe '#users' do it { expect(group.users).to eq(group.owners) } end - describe :human_name do + describe '#human_name' do it { expect(group.human_name).to eq(group.name) } end - describe :add_users do + describe '#add_user' do let(:user) { create(:user) } before { group.add_user(user, GroupMember::MASTER) } it { expect(group.group_members.masters.map(&:user)).to include(user) } end - describe :add_users do + describe '#add_users' do let(:user) { create(:user) } before { group.add_users([user.id], GroupMember::GUEST) } @@ -124,7 +124,7 @@ describe Group, models: true do end end - describe :avatar_type do + describe '#avatar_type' do let(:user) { create(:user) } before { group.add_user(user, GroupMember::MASTER) } diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index dad2628651b..f37f44a608e 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -32,21 +32,20 @@ describe Label, models: true do it 'should validate title' do expect(label).not_to allow_value('G,ITLAB').for(:title) - expect(label).not_to allow_value('G?ITLAB').for(:title) - expect(label).not_to allow_value('G&ITLAB').for(:title) expect(label).not_to allow_value('').for(:title) expect(label).to allow_value('GITLAB').for(:title) expect(label).to allow_value('gitlab').for(:title) + expect(label).to allow_value('G?ITLAB').for(:title) + expect(label).to allow_value('G&ITLAB').for(:title) expect(label).to allow_value("customer's request").for(:title) end end - describe "#title" do - let(:label) { create(:label, title: "<b>test</b>") } - - it "sanitizes title" do - expect(label.title).to eq("test") + describe '#title' do + it 'sanitizes title' do + label = described_class.new(title: '<b>foo & bar?</b>') + expect(label.title).to eq('foo & bar?') end end diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb index b2d06853886..d23fc06c3ad 100644 --- a/spec/models/legacy_diff_note_spec.rb +++ b/spec/models/legacy_diff_note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe LegacyDiffNote, models: true do describe "Commit diff line notes" do - let!(:note) { create(:note_on_commit_diff, note: "+1 from me") } + let!(:note) { create(:legacy_diff_note_on_commit, note: "+1 from me") } let!(:commit) { note.noteable } it "should save a valid note" do @@ -16,25 +16,25 @@ describe LegacyDiffNote, models: true do end describe '#active?' do - it 'is always true when the note has no associated diff' do - note = build(:note_on_merge_request_diff) + it 'is always true when the note has no associated diff line' do + note = build(:legacy_diff_note_on_merge_request) - expect(note).to receive(:diff).and_return(nil) + expect(note).to receive(:diff_line).and_return(nil) expect(note).to be_active end it 'is never true when the note has no noteable associated' do - note = build(:note_on_merge_request_diff) + note = build(:legacy_diff_note_on_merge_request) - expect(note).to receive(:diff).and_return(double) + expect(note).to receive(:diff_line).and_return(double) expect(note).to receive(:noteable).and_return(nil) expect(note).not_to be_active end it 'returns the memoized value if defined' do - note = build(:note_on_merge_request_diff) + note = build(:legacy_diff_note_on_merge_request) note.instance_variable_set(:@active, 'foo') expect(note).not_to receive(:find_noteable_diff) @@ -45,9 +45,9 @@ describe LegacyDiffNote, models: true do context 'for a merge request noteable' do it 'is false when noteable has no matching diff' do merge = build_stubbed(:merge_request, :simple) - note = build(:note_on_merge_request_diff, noteable: merge) + note = build(:legacy_diff_note_on_merge_request, noteable: merge) - allow(note).to receive(:diff).and_return(double) + allow(note).to receive(:diff_line).and_return(double) expect(note).to receive(:find_noteable_diff).and_return(nil) expect(note).not_to be_active @@ -63,9 +63,9 @@ describe LegacyDiffNote, models: true do code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos) # We're persisting in order to trigger the set_diff callback - note = create(:note_on_merge_request_diff, noteable: merge, - line_code: code, - project: merge.source_project) + note = create(:legacy_diff_note_on_merge_request, noteable: merge, + line_code: code, + project: merge.source_project) # Make sure we don't get a false positive from a guard clause expect(note).to receive(:find_noteable_diff).and_call_original diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 4c103462433..ba622dfb9be 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -101,7 +101,7 @@ describe ProjectMember, models: true do end end - describe :add_users_into_projects do + describe '.add_users_into_projects' do before do @project_1 = create :project @project_2 = create :project @@ -123,7 +123,7 @@ describe ProjectMember, models: true do it { expect(@project_2.users).to include(@user_2) } end - describe :truncate_teams do + describe '.truncate_teams' do before do @project_1 = create :project @project_2 = create :project diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb new file mode 100644 index 00000000000..9a637c94fbe --- /dev/null +++ b/spec/models/merge_request_diff_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe MergeRequestDiff, models: true do + describe '#diffs' do + let(:mr) { create(:merge_request, :with_diffs) } + let(:mr_diff) { mr.merge_request_diff } + + context 'when the :ignore_whitespace_change option is set' do + it 'creates a new compare object instead of loading from the DB' do + expect(mr_diff).not_to receive(:load_diffs) + expect(Gitlab::Git::Compare).to receive(:new).and_call_original + + mr_diff.diffs(ignore_whitespace_change: true) + end + end + + context 'when the raw diffs are empty' do + before { mr_diff.update_attributes(st_diffs: '') } + + it 'returns an empty DiffCollection' do + expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.diffs).to be_empty + end + end + + context 'when the raw diffs exist' do + it 'returns the diffs' do + expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.diffs).not_to be_empty + end + + context 'when the :paths option is set' do + let(:diffs) { mr_diff.diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) } + + it 'only returns diffs that match the (old path, new path) given' do + expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb') + end + + it 'uses the diffs from the DB' do + expect(mr_diff).to receive(:load_diffs) + + diffs + end + end + end + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index ceb4d64698f..c8ad7ab3e7f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe MergeRequest, models: true do + include RepoHelpers + subject { create(:merge_request) } describe 'associations' do @@ -62,7 +64,7 @@ describe MergeRequest, models: true do end end - describe '#target_sha' do + describe '#target_branch_sha' do context 'when the target branch does not exist anymore' do let(:project) { create(:project) } @@ -73,32 +75,32 @@ describe MergeRequest, models: true do end it 'returns nil' do - expect(subject.target_sha).to be_nil + expect(subject.target_branch_sha).to be_nil end end end - describe '#source_sha' do + describe '#source_branch_sha' do let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) } context 'with diffs' do subject { create(:merge_request, :with_diffs) } it 'returns the sha of the source branch last commit' do - expect(subject.source_sha).to eq(last_branch_commit.sha) + expect(subject.source_branch_sha).to eq(last_branch_commit.sha) end end context 'without diffs' do subject { create(:merge_request, :without_diffs) } it 'returns the sha of the source branch last commit' do - expect(subject.source_sha).to eq(last_branch_commit.sha) + expect(subject.source_branch_sha).to eq(last_branch_commit.sha) end end context 'when the merge request is being created' do subject { build(:merge_request, source_branch: nil, compare_commits: []) } it 'returns nil' do - expect(subject.source_sha).to be_nil + expect(subject.source_branch_sha).to be_nil end end end @@ -114,6 +116,31 @@ describe MergeRequest, models: true do end end + describe '#diffs' do + let(:merge_request) { build(:merge_request) } + let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } } + + context 'when there are MR diffs' do + it 'delegates to the MR diffs' do + merge_request.merge_request_diff = MergeRequestDiff.new + + expect(merge_request.merge_request_diff).to receive(:diffs).with(options) + + merge_request.diffs(options) + end + end + + context 'when there are no MR diffs' do + it 'delegates to the compare object' do + merge_request.compare = double(:compare) + + expect(merge_request.compare).to receive(:diffs).with(options) + + merge_request.diffs(options) + end + end + end + describe "#mr_and_commit_notes" do let!(:merge_request) { create(:merge_request) } @@ -252,12 +279,14 @@ describe MergeRequest, models: true do end it "can be removed if the last commit is the head of the source branch" do - allow(subject.source_project).to receive(:commit).and_return(subject.last_commit) + allow(subject.source_project).to receive(:commit).and_return(subject.diff_head_commit) expect(subject.can_remove_source_branch?(user)).to be_truthy end it "cannot be removed if the last commit is not also the head of the source branch" do + subject.source_branch = "lfs" + expect(subject.can_remove_source_branch?(user)).to be_falsey end end @@ -363,7 +392,7 @@ describe MergeRequest, models: true do and_return(2) subject.diverged_commits_count - allow(subject).to receive(:source_sha).and_return('123abc') + allow(subject).to receive(:source_branch_sha).and_return('123abc') subject.diverged_commits_count end @@ -373,7 +402,7 @@ describe MergeRequest, models: true do and_return(2) subject.diverged_commits_count - allow(subject).to receive(:target_sha).and_return('123abc') + allow(subject).to receive(:target_branch_sha).and_return('123abc') subject.diverged_commits_count end end @@ -392,11 +421,10 @@ describe MergeRequest, models: true do describe '#pipeline' do describe 'when the source project exists' do - it 'returns the latest commit' do - commit = double(:commit, id: '123abc') + it 'returns the latest pipeline' do pipeline = double(:ci_pipeline, ref: 'master') - allow(subject).to receive(:last_commit).and_return(commit) + allow(subject).to receive(:diff_head_sha).and_return('123abc') expect(subject.source_project).to receive(:pipeline). with('123abc', 'master'). @@ -464,7 +492,7 @@ describe MergeRequest, models: true do context 'when it is not broken and has no conflicts' do it 'is marked as mergeable' do allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?) { true } + allow(project.repository).to receive(:can_be_merged?).and_return(true) expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') end @@ -481,7 +509,7 @@ describe MergeRequest, models: true do context 'when it has conflicts' do before do allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?) { false } + allow(project.repository).to receive(:can_be_merged?).and_return(false) end it 'becomes unmergeable' do @@ -608,4 +636,42 @@ describe MergeRequest, models: true do end end end + + describe "#reload_diff" do + let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) } + + let(:commit) { subject.project.commit(sample_commit.id) } + + it "reloads the diff content" do + expect(subject.merge_request_diff).to receive(:reload_content) + + subject.reload_diff + end + + it "updates diff note positions" do + old_diff_refs = subject.diff_refs + + merge_request_diff = subject.merge_request_diff + + # Update merge_request_diff so that #diff_refs will return commit.diff_refs + allow(merge_request_diff).to receive(:reload_content) do + merge_request_diff.base_commit_sha = commit.parent_id + merge_request_diff.start_commit_sha = commit.parent_id + merge_request_diff.head_commit_sha = commit.sha + end + + expect(Notes::DiffPositionUpdateService).to receive(:new).with( + subject.project, + nil, + old_diff_refs: old_diff_refs, + new_diff_refs: commit.diff_refs, + paths: note.position.paths + ).and_call_original + expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note) + + expect_any_instance_of(DiffNote).to receive(:save).once + + subject.reload_diff + end + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 1e18c788b50..d661dc0e59a 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -70,7 +70,7 @@ describe Milestone, models: true do end end - describe :expired? do + describe '#expired?' do context "expired" do before do allow(milestone).to receive(:due_date).and_return(Date.today.prev_year) @@ -88,7 +88,7 @@ describe Milestone, models: true do end end - describe :percent_complete do + describe '#percent_complete' do before do allow(milestone).to receive_messages( closed_items_count: 3, @@ -111,11 +111,11 @@ describe Milestone, models: true do it { expect(milestone.is_empty?(user)).to be_falsey } end - describe :can_be_closed? do + describe '#can_be_closed?' do it { expect(milestone.can_be_closed?).to be_truthy } end - describe :total_items_count do + describe '#total_items_count' do before do create :closed_issue, milestone: milestone create :merge_request, milestone: milestone @@ -126,7 +126,7 @@ describe Milestone, models: true do end end - describe :can_be_closed? do + describe '#can_be_closed?' do before do milestone = create :milestone create :closed_issue, milestone: milestone diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 5f68cd2b066..a162da0208e 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -18,11 +18,11 @@ describe Namespace, models: true do it { is_expected.to respond_to(:to_param) } end - describe :to_param do + describe '#to_param' do it { expect(namespace.to_param).to eq(namespace.path) } end - describe :human_name do + describe '#human_name' do it { expect(namespace.human_name).to eq(namespace.owner_name) } end @@ -54,7 +54,7 @@ describe Namespace, models: true do end end - describe :move_dir do + describe '#move_dir' do before do @namespace = create :namespace @project = create :project, namespace: @namespace @@ -98,7 +98,7 @@ describe Namespace, models: true do end end - describe :find_by_path_or_name do + describe '.find_by_path_or_name' do before do @namespace = create(:namespace, name: 'WoW', path: 'woW') end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 6549791f675..7d0697dab42 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -226,6 +226,20 @@ describe Note, models: true do it "returns false" do expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy end + + it "returns false if user visible reference count set" do + note.user_visible_reference_count = 1 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_falsy + end + + it "returns true if ref count is 0" do + note.user_visible_reference_count = 0 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end end describe 'clear_blank_line_code!' do diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index df336a6effe..d58673413c8 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -38,4 +38,21 @@ RSpec.describe NotificationSetting, type: :model do end end end + + describe '#for_projects' do + let(:user) { create(:user) } + + before do + 1.upto(4) do |i| + setting = create(:notification_setting, user: user) + + setting.project.update_attributes(pending_delete: true) if i.even? + end + end + + it 'excludes projects pending delete' do + expect(user.notification_settings.for_projects).to all(have_attributes(project: an_instance_of(Project))) + expect(user.notification_settings.for_projects.map(&:project)).to all(have_attributes(pending_delete: false)) + end + end end diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb index e12258c0874..2142c7c13ef 100644 --- a/spec/models/project_security_spec.rb +++ b/spec/models/project_security_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Project, models: true do - describe :authorization do + describe 'authorization' do before do @p1 = create(:project) diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 60364df2015..0866e1532dd 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -57,7 +57,7 @@ describe BuildkiteService, models: true do ) end - describe :webhook_url do + describe '#webhook_url' do it 'returns the webhook url' do expect(@service.webhook_url).to eq( 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' @@ -65,7 +65,7 @@ describe BuildkiteService, models: true do end end - describe :commit_status_path do + describe '#commit_status_path' do it 'returns the correct status page' do expect(@service.commit_status_path('2ab7834c')).to eq( 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c' @@ -73,7 +73,7 @@ describe BuildkiteService, models: true do end end - describe :build_page do + describe '#build_page' do it 'returns the correct build page' do expect(@service.build_page('2ab7834c', nil)).to eq( 'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c' diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a8c777d1e3e..e842c58dd82 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -129,6 +129,36 @@ describe Project, models: true do expect(project2.errors[:repository_storage].first).to match(/is not included in the list/) end end + + it 'does not allow an invalid URI as import_url' do + project2 = build(:project, import_url: 'invalid://') + + expect(project2).not_to be_valid + end + + it 'does allow a valid URI as import_url' do + project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git') + + expect(project2).to be_valid + end + + it 'does not allow to introduce an empty URI' do + project2 = build(:project, import_url: '') + + expect(project2).not_to be_valid + end + + it 'does not produce import data on an empty URI' do + project2 = build(:project, import_url: '') + + expect(project2.import_data).to be_nil + end + + it 'does not produce import data on an invalid URI' do + project2 = build(:project, import_url: 'test://') + + expect(project2.import_data).to be_nil + end end describe 'default_scope' do @@ -284,7 +314,7 @@ describe Project, models: true do end end - describe :update_merge_requests do + describe '#update_merge_requests' do let(:project) { create(:project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:key) { create(:key, user_id: project.owner.id) } @@ -300,7 +330,7 @@ describe Project, models: true do it 'should update merge request commits with new one if pushed to source branch' do project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user) merge_request.reload - expect(merge_request.last_commit.id).to eq(commit_id) + expect(merge_request.diff_head_sha).to eq(commit_id) end end @@ -333,7 +363,7 @@ describe Project, models: true do end end - describe :to_param do + describe '#to_param' do context 'with namespace' do before do @group = create :group, name: 'gitlab' @@ -344,7 +374,7 @@ describe Project, models: true do end end - describe :repository do + describe '#repository' do let(:project) { create(:project) } it 'should return valid repo' do @@ -352,7 +382,7 @@ describe Project, models: true do end end - describe :default_issues_tracker? do + describe '#default_issues_tracker?' do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } @@ -365,7 +395,7 @@ describe Project, models: true do end end - describe :external_issue_tracker do + describe '#external_issue_tracker' do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } @@ -406,7 +436,7 @@ describe Project, models: true do end end - describe :cache_has_external_issue_tracker do + describe '#cache_has_external_issue_tracker' do let(:project) { create(:project) } it 'stores true if there is any external_issue_tracker' do @@ -428,7 +458,7 @@ describe Project, models: true do end end - describe :open_branches do + describe '#open_branches' do let(:project) { create(:project) } before do @@ -437,6 +467,14 @@ describe Project, models: true do it { expect(project.open_branches.map(&:name)).to include('feature') } it { expect(project.open_branches.map(&:name)).not_to include('master') } + + it "includes branches matching a protected branch wildcard" do + expect(project.open_branches.map(&:name)).to include('feature') + + create(:protected_branch, name: 'feat*', project: project) + + expect(Project.find(project.id).open_branches.map(&:name)).to include('feature') + end end describe '#star_count' do @@ -497,7 +535,7 @@ describe Project, models: true do end end - describe :avatar_type do + describe '#avatar_type' do let(:project) { create(:project) } it 'should be true if avatar is image' do @@ -511,7 +549,7 @@ describe Project, models: true do end end - describe :avatar_url do + describe '#avatar_url' do subject { project.avatar_url } let(:project) { create(:project) } @@ -548,7 +586,7 @@ describe Project, models: true do end end - describe :pipeline do + describe '#pipeline' do let(:project) { create :project } let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' } @@ -568,7 +606,7 @@ describe Project, models: true do end end - describe :builds_enabled do + describe '#builds_enabled' do let(:project) { create :project } before { project.builds_enabled = true } @@ -670,7 +708,7 @@ describe Project, models: true do end end - describe :any_runners do + describe '#any_runners' do let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) } let(:specific_runner) { create(:ci_runner) } let(:shared_runner) { create(:ci_runner, :shared) } @@ -937,15 +975,67 @@ describe Project, models: true do describe '#protected_branch?' do let(:project) { create(:empty_project) } - it 'returns true when a branch is a protected branch' do + it 'returns true when the branch matches a protected branch via direct match' do project.protected_branches.create!(name: 'foo') expect(project.protected_branch?('foo')).to eq(true) end - it 'returns false when a branch is not a protected branch' do + it 'returns true when the branch matches a protected branch via wildcard match' do + project.protected_branches.create!(name: 'production/*') + + expect(project.protected_branch?('production/some-branch')).to eq(true) + end + + it 'returns false when the branch does not match a protected branch via direct match' do expect(project.protected_branch?('foo')).to eq(false) end + + it 'returns false when the branch does not match a protected branch via wildcard match' do + project.protected_branches.create!(name: 'production/*') + + expect(project.protected_branch?('staging/some-branch')).to eq(false) + end + end + + describe "#developers_can_push_to_protected_branch?" do + let(:project) { create(:empty_project) } + + context "when the branch matches a protected branch via direct match" do + it "returns true if 'Developers can Push' is turned on" do + create(:protected_branch, name: "production", project: project, developers_can_push: true) + + expect(project.developers_can_push_to_protected_branch?('production')).to be true + end + + it "returns false if 'Developers can Push' is turned off" do + create(:protected_branch, name: "production", project: project, developers_can_push: false) + + expect(project.developers_can_push_to_protected_branch?('production')).to be false + end + end + + context "when the branch matches a protected branch via wilcard match" do + it "returns true if 'Developers can Push' is turned on" do + create(:protected_branch, name: "production/*", project: project, developers_can_push: true) + + expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true + end + + it "returns false if 'Developers can Push' is turned off" do + create(:protected_branch, name: "production/*", project: project, developers_can_push: false) + + expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false + end + end + + context "when the branch does not match a protected branch" do + it "returns false" do + create(:protected_branch, name: "production/*", project: project, developers_can_push: true) + + expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false + end + end end describe '#container_registry_path_with_namespace' do diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index b523834c6e9..8bf0d24a128 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe ProtectedBranch, models: true do + subject { build_stubbed(:protected_branch) } + describe 'Associations' do it { is_expected.to belong_to(:project) } end @@ -12,4 +14,127 @@ describe ProtectedBranch, models: true do it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:name) } end + + describe "#matches?" do + context "when the protected branch setting is not a wildcard" do + let(:protected_branch) { build(:protected_branch, name: "production/some-branch") } + + it "returns true for branch names that are an exact match" do + expect(protected_branch.matches?("production/some-branch")).to be true + end + + it "returns false for branch names that are not an exact match" do + expect(protected_branch.matches?("staging/some-branch")).to be false + end + end + + context "when the protected branch name contains wildcard(s)" do + context "when there is a single '*'" do + let(:protected_branch) { build(:protected_branch, name: "production/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("production/some-branch")).to be true + expect(protected_branch.matches?("production/")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("staging/some-branch")).to be false + expect(protected_branch.matches?("production")).to be false + end + end + + context "when the wildcard contains regex symbols other than a '*'" do + let(:protected_branch) { build(:protected_branch, name: "pro.duc.tion/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("pro.duc.tion/some-branch")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("production/some-branch")).to be false + expect(protected_branch.matches?("proXducYtion/some-branch")).to be false + end + end + + context "when there are '*'s at either end" do + let(:protected_branch) { build(:protected_branch, name: "*/production/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("gitlab/production/some-branch")).to be true + expect(protected_branch.matches?("/production/some-branch")).to be true + expect(protected_branch.matches?("gitlab/production/")).to be true + expect(protected_branch.matches?("/production/")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("gitlabproductionsome-branch")).to be false + expect(protected_branch.matches?("production/some-branch")).to be false + expect(protected_branch.matches?("gitlab/production")).to be false + expect(protected_branch.matches?("production")).to be false + end + end + + context "when there are arbitrarily placed '*'s" do + let(:protected_branch) { build(:protected_branch, name: "pro*duction/*/gitlab/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("production/some-branch/gitlab/second-branch")).to be true + expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/second-branch")).to be true + expect(protected_branch.matches?("proXYZduction/gitlab/gitlab/gitlab")).to be true + expect(protected_branch.matches?("proXYZduction//gitlab/")).to be true + expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/")).to be true + expect(protected_branch.matches?("proXYZduction//gitlab/some-branch")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("production/some-branch/not-gitlab/second-branch")).to be false + expect(protected_branch.matches?("prodXYZuction/some-branch/gitlab/second-branch")).to be false + expect(protected_branch.matches?("proXYZduction/gitlab/some-branch/gitlab")).to be false + expect(protected_branch.matches?("proXYZduction/gitlab//")).to be false + expect(protected_branch.matches?("proXYZduction/gitlab/")).to be false + expect(protected_branch.matches?("proXYZduction//some-branch/gitlab")).to be false + end + end + end + end + + describe "#matching" do + context "for direct matches" do + it "returns a list of protected branches matching the given branch name" do + production = create(:protected_branch, name: "production") + staging = create(:protected_branch, name: "staging") + + expect(ProtectedBranch.matching("production")).to include(production) + expect(ProtectedBranch.matching("production")).not_to include(staging) + end + + it "accepts a list of protected branches to search from, so as to avoid a DB call" do + production = build(:protected_branch, name: "production") + staging = build(:protected_branch, name: "staging") + + expect(ProtectedBranch.matching("production")).to be_empty + expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).to include(production) + expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).not_to include(staging) + end + end + + context "for wildcard matches" do + it "returns a list of protected branches matching the given branch name" do + production = create(:protected_branch, name: "production/*") + staging = create(:protected_branch, name: "staging/*") + + expect(ProtectedBranch.matching("production/some-branch")).to include(production) + expect(ProtectedBranch.matching("production/some-branch")).not_to include(staging) + end + + it "accepts a list of protected branches to search from, so as to avoid a DB call" do + production = build(:protected_branch, name: "production/*") + staging = build(:protected_branch, name: "staging/*") + + expect(ProtectedBranch.matching("production/some-branch")).to be_empty + expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).to include(production) + expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).not_to include(staging) + end + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 7975fc64e59..b39b958450c 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -12,11 +12,11 @@ describe Repository, models: true do end let(:merge_commit) do source_sha = repository.find_branch('feature').target - merge_commit_id = repository.merge(user, source_sha, 'master', commit_options) - repository.commit(merge_commit_id) + merge_commit_sha = repository.merge(user, source_sha, 'master', commit_options) + repository.commit(merge_commit_sha) end - describe :branch_names_contains do + describe '#branch_names_contains' do subject { repository.branch_names_contains(sample_commit.id) } it { is_expected.to include('master') } @@ -24,7 +24,7 @@ describe Repository, models: true do it { is_expected.not_to include('fix') } end - describe :tag_names_contains do + describe '#tag_names_contains' do subject { repository.tag_names_contains(sample_commit.id) } it { is_expected.to include('v1.1.0') } @@ -72,13 +72,13 @@ describe Repository, models: true do end end - describe :last_commit_for_path do + describe '#last_commit_for_path' do subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } end - describe :find_commits_by_message do + describe '#find_commits_by_message' do subject { repository.find_commits_by_message('submodule').map{ |k| k.id } } it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } @@ -87,7 +87,7 @@ describe Repository, models: true do it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') } end - describe :blob_at do + describe '#blob_at' do context 'blank sha' do subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') } @@ -95,7 +95,7 @@ describe Repository, models: true do end end - describe :merged_to_root_ref? do + describe '#merged_to_root_ref?' do context 'merged branch' do subject { repository.merged_to_root_ref?('improve/awesome') } @@ -103,7 +103,7 @@ describe Repository, models: true do end end - describe :can_be_merged? do + describe '#can_be_merged?' do context 'mergeable branches' do subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') } @@ -305,7 +305,7 @@ describe Repository, models: true do end end - describe :add_branch do + describe '#add_branch' do context 'when pre hooks were successful' do it 'should run without errors' do hook = double(trigger: [true, nil]) @@ -349,7 +349,7 @@ describe Repository, models: true do end end - describe :rm_branch do + describe '#rm_branch' do context 'when pre hooks were successful' do it 'should run without errors' do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) @@ -386,7 +386,7 @@ describe Repository, models: true do end end - describe :commit_with_hooks do + describe '#commit_with_hooks' do context 'when pre hooks were successful' do before do expect_any_instance_of(GitHooksService).to receive(:execute). diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 96bbbec9ea1..67b3783d514 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -22,11 +22,11 @@ describe Service, models: true do @testable = @service.can_test? end - describe :can_test do + describe '#can_test?' do it { expect(@testable).to eq(true) } end - describe :test do + describe '#test' do let(:data) { 'test' } it 'test runs execute' do @@ -45,7 +45,7 @@ describe Service, models: true do @testable = @service.can_test? end - describe :can_test do + describe '#can_test?' do it { expect(@testable).to eq(true) } end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 328254ed56b..ff39f187759 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -427,7 +427,7 @@ describe User, models: true do end end - describe :not_in_project do + describe '.not_in_project' do before do User.delete_all @user = create :user @@ -446,6 +446,7 @@ describe User, models: true do it { expect(user.can_create_group?).to be_truthy } it { expect(user.can_create_project?).to be_truthy } it { expect(user.first_name).to eq('John') } + it { expect(user.external).to be_falsey } end describe 'with defaults' do @@ -468,6 +469,26 @@ describe User, models: true do expect(user.theme_id).to eq(1) end end + + context 'when current_application_settings.user_default_external is true' do + before do + stub_application_setting(user_default_external: true) + end + + it "creates external user by default" do + user = build(:user) + + expect(user.external).to be_truthy + end + + describe 'with default overrides' do + it "creates a non-external user" do + user = build(:user, external: false) + + expect(user.external).to be_falsey + end + end + end end describe '.find_by_any_email' do @@ -577,7 +598,7 @@ describe User, models: true do end end - describe :avatar_type do + describe '#avatar_type' do let(:user) { create(:user) } it "should be true if avatar is image" do @@ -591,7 +612,7 @@ describe User, models: true do end end - describe :requires_ldap_check? do + describe '#requires_ldap_check?' do let(:user) { User.new } it 'is false when LDAP is disabled' do @@ -630,7 +651,7 @@ describe User, models: true do end context 'ldap synchronized user' do - describe :ldap_user? do + describe '#ldap_user?' do it 'is true if provider name starts with ldap' do user = create(:omniauth_user, provider: 'ldapmain') expect(user.ldap_user?).to be_truthy @@ -647,7 +668,7 @@ describe User, models: true do end end - describe :ldap_identity do + describe '#ldap_identity' do it 'returns ldap identity' do user = create :omniauth_user expect(user.ldap_identity.provider).not_to be_empty @@ -804,7 +825,7 @@ describe User, models: true do end end - describe :can_be_removed? do + describe '#can_be_removed?' do subject { create(:user) } context 'no owned groups' do diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 72a6d45f47d..2b74dd4bbb0 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -135,6 +135,22 @@ describe API::API, api: true do expect(response).to have_http_status(401) end + + it "normalizes +1 as thumbsup award" do + post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1' + + expect(issue.award_emoji.last.name).to eq("thumbsup") + end + + context 'when the emoji already has been awarded' do + it 'returns a 404 status code' do + post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' + post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' + + expect(response).to have_http_status(404) + expect(json_response["message"]).to match("has already been taken") + end + end end end @@ -147,6 +163,22 @@ describe API::API, api: true do expect(response).to have_http_status(201) expect(json_response['user']['username']).to eq(user.username) end + + it "normalizes +1 as thumbsup award" do + post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1' + + expect(note.award_emoji.last.name).to eq("thumbsup") + end + + context 'when the emoji already has been awarded' do + it 'returns a 404 status code' do + post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' + post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' + + expect(response).to have_http_status(404) + expect(json_response["message"]).to match("has already been taken") + end + end end describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 04141a45031..c2c94040ece 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -49,10 +49,25 @@ describe API::API, api: true do describe "GET /groups/:id" do context "when authenticated as user" do - it "should return one of user1's groups" do + it "returns one of user1's groups" do + project = create(:project, namespace: group2, path: 'Foo') + create(:project_group_link, project: project, group: group1) + get api("/groups/#{group1.id}", user1) + expect(response).to have_http_status(200) - json_response['name'] == group1.name + expect(json_response['id']).to eq(group1.id) + expect(json_response['name']).to eq(group1.name) + expect(json_response['path']).to eq(group1.path) + expect(json_response['description']).to eq(group1.description) + expect(json_response['visibility_level']).to eq(group1.visibility_level) + expect(json_response['avatar_url']).to eq(group1.avatar_url) + expect(json_response['web_url']).to eq(group1.web_url) + expect(json_response['projects']).to be_an Array + expect(json_response['projects'].length).to eq(2) + expect(json_response['shared_projects']).to be_an Array + expect(json_response['shared_projects'].length).to eq(1) + expect(json_response['shared_projects'][0]['id']).to eq(project.id) end it "should not return a non existing group" do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index fcea45f19ba..f6f85d6e95e 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -56,13 +56,21 @@ describe API::API, api: true do context "git push with project.wiki" do it 'responds with success' do - project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki') - project_wiki.team << [user, :developer] + push(key, project.wiki) - push(key, project_wiki) + expect(response).to have_http_status(200) + expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) + end + end + + context "git pull with project.wiki" do + it 'responds with success' do + pull(key, project.wiki) expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) end end @@ -207,26 +215,86 @@ describe API::API, api: true do expect(json_response["status"]).to be_falsey end end + + context 'ssh access has been disabled' do + before do + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, 'http') + end + + it 'rejects the SSH push' do + push(key, project) + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over SSH is not allowed' + end + + it 'rejects the SSH pull' do + pull(key, project) + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over SSH is not allowed' + end + end + + context 'http access has been disabled' do + before do + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, 'ssh') + end + + it 'rejects the HTTP push' do + push(key, project, 'http') + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over HTTP is not allowed' + end + + it 'rejects the HTTP pull' do + pull(key, project, 'http') + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over HTTP is not allowed' + end + end + + context 'web actions are always allowed' do + it 'allows WEB push' do + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, 'ssh') + project.team << [user, :developer] + push(key, project, 'web') + + expect(response.status).to eq(200) + expect(json_response['status']).to be_truthy + end + end end - def pull(key, project) + def pull(key, project, protocol = 'ssh') post( api("/internal/allowed"), key_id: key.id, project: project.path_with_namespace, action: 'git-upload-pack', - secret_token: secret_token + secret_token: secret_token, + protocol: protocol ) end - def push(key, project) + def push(key, project, protocol = 'ssh') post( api("/internal/allowed"), changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, project: project.path_with_namespace, action: 'git-receive-pack', - secret_token: secret_token + secret_token: secret_token, + protocol: protocol ) end @@ -237,7 +305,8 @@ describe API::API, api: true do key_id: key.id, project: project.path_with_namespace, action: 'git-upload-archive', - secret_token: secret_token + secret_token: secret_token, + protocol: 'ssh' ) end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 2cf130df328..6adccb4ebae 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -482,12 +482,16 @@ describe API::API, api: true do expect(response).to have_http_status(400) end - it 'should return 400 on invalid label names' do + it 'should allow special label names' do post api("/projects/#{project.id}/issues", user), title: 'new issue', - labels: 'label, ?' - expect(response).to have_http_status(400) - expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) + labels: 'label, label?, label&foo, ?, &' + expect(response.status).to eq(201) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' end it 'should return 400 if title is too long' do @@ -557,12 +561,17 @@ describe API::API, api: true do expect(response).to have_http_status(404) end - it 'should return 400 on invalid label names' do + it 'should allow special label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title', - labels: 'label, ?' - expect(response).to have_http_status(400) - expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) + labels: 'label, label?, label&foo, ?, &' + + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' end context 'confidential issues' do @@ -627,21 +636,18 @@ describe API::API, api: true do expect(json_response['labels']).to include 'bar' end - it 'should return 400 on invalid label names' do - put api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: 'label, ?' - expect(response).to have_http_status(400) - expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) - end - it 'should allow special label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), - labels: 'label:foo, label-bar,label_bar,label/bar' - expect(response).to have_http_status(200) + labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' + expect(response.status).to eq(200) expect(json_response['labels']).to include 'label:foo' expect(json_response['labels']).to include 'label-bar' expect(json_response['labels']).to include 'label_bar' expect(json_response['labels']).to include 'label/bar' + expect(json_response['labels']).to include 'label?bar' + expect(json_response['labels']).to include 'label&bar' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' end it 'should return 400 if title is too long' do diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 0404cf31ff7..63636b4a1b6 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -35,10 +35,10 @@ describe API::API, api: true do it 'should return created label when only required params' do post api("/projects/#{project.id}/labels", user), - name: 'Foo', + name: 'Foo & Bar', color: '#FFAABB' - expect(response).to have_http_status(201) - expect(json_response['name']).to eq('Foo') + expect(response.status).to eq(201) + expect(json_response['name']).to eq('Foo & Bar') expect(json_response['color']).to eq('#FFAABB') expect(json_response['description']).to be_nil end @@ -71,7 +71,7 @@ describe API::API, api: true do it 'should return 400 for invalid name' do post api("/projects/#{project.id}/labels", user), - name: '?', + name: ',', color: '#FFAABB' expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) @@ -167,7 +167,7 @@ describe API::API, api: true do it 'should return 400 for invalid name' do put api("/projects/#{project.id}/labels", user), name: 'label1', - new_name: '?', + new_name: ',', color: '#FFFFFF' expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 61e897edf87..651b91e9f68 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -138,6 +138,8 @@ describe API::API, api: true do expect(json_response['work_in_progress']).to be_falsy expect(json_response['merge_when_build_succeeds']).to be_falsy expect(json_response['merge_status']).to eq('can_be_merged') + expect(json_response['should_close_merge_request']).to be_falsy + expect(json_response['force_close_merge_request']).to be_falsy end it "should return merge_request" do @@ -147,6 +149,8 @@ describe API::API, api: true do expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['work_in_progress']).to eq(false) expect(json_response['merge_status']).to eq('can_be_merged') + expect(json_response['should_close_merge_request']).to be_falsy + expect(json_response['force_close_merge_request']).to be_falsy end it 'should return merge_request by iid' do @@ -243,17 +247,19 @@ describe API::API, api: true do expect(response).to have_http_status(400) end - it 'should return 400 on invalid label names' do + it 'should allow special label names' do post api("/projects/#{project.id}/merge_requests", user), title: 'Test merge_request', source_branch: 'markdown', target_branch: 'master', author: user, - labels: 'label, ?' - expect(response).to have_http_status(400) - expect(json_response['message']['labels']['?']['title']).to eq( - ['is invalid'] - ) + labels: 'label, label?, label&foo, ?, &' + expect(response.status).to eq(201) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' end context 'with existing MR' do @@ -437,14 +443,14 @@ describe API::API, api: true do end it "returns 409 if the SHA parameter doesn't match" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha.succ + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha.reverse expect(response).to have_http_status(409) expect(json_response['message']).to start_with('SHA does not match HEAD of source branch') end it "succeeds if the SHA parameter matches" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha expect(response).to have_http_status(200) end @@ -492,13 +498,17 @@ describe API::API, api: true do expect(json_response['target_branch']).to eq('wiki') end - it 'should return 400 on invalid label names' do + it 'should allow special label names' do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: 'new issue', - labels: 'label, ?' - expect(response).to have_http_status(400) - expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) + labels: 'label, label?, label&foo, ?, &' + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label' + expect(json_response['labels']).to include 'label?' + expect(json_response['labels']).to include 'label&foo' + expect(json_response['labels']).to include '?' + expect(json_response['labels']).to include '&' end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 611dd2a2a88..152cd802839 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -81,6 +81,18 @@ describe API::API, api: true do expect(json_response.first.keys).not_to include('open_issues_count') end + context 'GET /projects?simple=true' do + it 'returns a simplified version of all the projects' do + expected_keys = ["id", "http_url_to_repo", "web_url", "name", "name_with_namespace", "path", "path_with_namespace"] + + get api('/projects?simple=true', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first.keys).to match_array expected_keys + end + end + context 'and using search' do it 'should return searched project' do get api('/projects', user), { search: project.name } @@ -392,11 +404,47 @@ describe API::API, api: true do before { project } before { project_member } - it 'should return a project by id' do + it 'returns a project by id' do + group = create(:group) + link = create(:project_group_link, project: project, group: group) + get api("/projects/#{project.id}", user) + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(project.id) + expect(json_response['description']).to eq(project.description) + expect(json_response['default_branch']).to eq(project.default_branch) + expect(json_response['tag_list']).to be_an Array + expect(json_response['public']).to be_falsey + expect(json_response['archived']).to be_falsey + expect(json_response['visibility_level']).to be_present + expect(json_response['ssh_url_to_repo']).to be_present + expect(json_response['http_url_to_repo']).to be_present + expect(json_response['web_url']).to be_present + expect(json_response['owner']).to be_a Hash + expect(json_response['owner']).to be_a Hash expect(json_response['name']).to eq(project.name) - expect(json_response['owner']['username']).to eq(user.username) + expect(json_response['path']).to be_present + expect(json_response['issues_enabled']).to be_present + expect(json_response['merge_requests_enabled']).to be_present + expect(json_response['wiki_enabled']).to be_present + expect(json_response['builds_enabled']).to be_present + expect(json_response['snippets_enabled']).to be_present + expect(json_response['container_registry_enabled']).to be_present + expect(json_response['created_at']).to be_present + expect(json_response['last_activity_at']).to be_present + expect(json_response['shared_runners_enabled']).to be_present + expect(json_response['creator_id']).to be_present + expect(json_response['namespace']).to be_present + expect(json_response['avatar_url']).to be_nil + expect(json_response['star_count']).to be_present + expect(json_response['forks_count']).to be_present + expect(json_response['public_builds']).to be_present + expect(json_response['shared_with_groups']).to be_an Array + expect(json_response['shared_with_groups'].length).to eq(1) + expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) + expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) end it 'should return a project by path name' do diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 8a8e131c57b..2c755919456 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -98,7 +98,7 @@ describe SnippetsController, "routing" do end # help GET /help(.:format) help#index -# help_page GET /help/:category/:file(.:format) help#show {:category=>/.*/, :file=>/[^\/\.]+/} +# help_page GET /help/*path(.:format) help#show # help_shortcuts GET /help/shortcuts(.:format) help#shortcuts # help_ui GET /help/ui(.:format) help#ui describe HelpController, "routing" do @@ -109,23 +109,19 @@ describe HelpController, "routing" do it 'to #show' do path = '/help/markdown/markdown.md' expect(get(path)).to route_to('help#show', - category: 'markdown', - file: 'markdown', + path: 'markdown/markdown', format: 'md') path = '/help/workflow/protected_branches/protected_branches1.png' expect(get(path)).to route_to('help#show', - category: 'workflow/protected_branches', - file: 'protected_branches1', + path: 'workflow/protected_branches/protected_branches1', format: 'png') - end - - it 'to #shortcuts' do - expect(get('/help/shortcuts')).to route_to('help#shortcuts') - end - - it 'to #ui' do - expect(get('/help/ui')).to route_to('help#ui') + path = '/help/shortcuts' + expect(get(path)).to route_to('help#show', + path: 'shortcuts') + path = '/help/ui' + expect(get(path)).to route_to('help#show', + path: 'ui') end end diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb index ae4b7aca820..b72e0bd3dbe 100644 --- a/spec/services/ci/create_trigger_request_service_spec.rb +++ b/spec/services/ci/create_trigger_request_service_spec.rb @@ -9,7 +9,7 @@ describe Ci::CreateTriggerRequestService, services: true do stub_ci_pipeline_to_return_yaml_file end - describe :execute do + describe '#execute' do context 'valid params' do subject { service.execute(project, trigger, 'master') } diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb index 476a888e394..3a3e3efe709 100644 --- a/spec/services/ci/image_for_build_service_spec.rb +++ b/spec/services/ci/image_for_build_service_spec.rb @@ -8,7 +8,7 @@ module Ci let(:commit) { project.ensure_pipeline(commit_sha, 'master') } let(:build) { FactoryGirl.create(:ci_build, pipeline: commit) } - describe :execute do + describe '#execute' do before { build } context 'branch name' do diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index f28f2f1438d..026d0ca6534 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -13,7 +13,7 @@ module Ci specific_runner.assign_to(project) end - describe :execute do + describe '#execute' do context 'runner follow tag list' do it "picks build with the same tag" do pending_build.tag_list = ["linux"] diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb index 309213bd44c..4d09bc5fb12 100644 --- a/spec/services/create_commit_builds_service_spec.rb +++ b/spec/services/create_commit_builds_service_spec.rb @@ -9,7 +9,7 @@ describe CreateCommitBuildsService, services: true do stub_ci_pipeline_to_return_yaml_file end - describe :execute do + describe '#execute' do context 'valid params' do let(:pipeline) do service.execute(project, user, diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index f6dc9d4008f..789836f71bb 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -4,7 +4,7 @@ describe EventCreateService, services: true do let(:service) { EventCreateService.new } describe 'Issues' do - describe :open_issue do + describe '#open_issue' do let(:issue) { create(:issue) } it { expect(service.open_issue(issue, issue.author)).to be_truthy } @@ -14,7 +14,7 @@ describe EventCreateService, services: true do end end - describe :close_issue do + describe '#close_issue' do let(:issue) { create(:issue) } it { expect(service.close_issue(issue, issue.author)).to be_truthy } @@ -24,7 +24,7 @@ describe EventCreateService, services: true do end end - describe :reopen_issue do + describe '#reopen_issue' do let(:issue) { create(:issue) } it { expect(service.reopen_issue(issue, issue.author)).to be_truthy } @@ -36,7 +36,7 @@ describe EventCreateService, services: true do end describe 'Merge Requests' do - describe :open_mr do + describe '#open_mr' do let(:merge_request) { create(:merge_request) } it { expect(service.open_mr(merge_request, merge_request.author)).to be_truthy } @@ -46,7 +46,7 @@ describe EventCreateService, services: true do end end - describe :close_mr do + describe '#close_mr' do let(:merge_request) { create(:merge_request) } it { expect(service.close_mr(merge_request, merge_request.author)).to be_truthy } @@ -56,7 +56,7 @@ describe EventCreateService, services: true do end end - describe :merge_mr do + describe '#merge_mr' do let(:merge_request) { create(:merge_request) } it { expect(service.merge_mr(merge_request, merge_request.author)).to be_truthy } @@ -66,7 +66,7 @@ describe EventCreateService, services: true do end end - describe :reopen_mr do + describe '#reopen_mr' do let(:merge_request) { create(:merge_request) } it { expect(service.reopen_mr(merge_request, merge_request.author)).to be_truthy } @@ -80,7 +80,7 @@ describe EventCreateService, services: true do describe 'Milestone' do let(:user) { create :user } - describe :open_milestone do + describe '#open_milestone' do let(:milestone) { create(:milestone) } it { expect(service.open_milestone(milestone, user)).to be_truthy } @@ -90,7 +90,7 @@ describe EventCreateService, services: true do end end - describe :close_mr do + describe '#close_mr' do let(:milestone) { create(:milestone) } it { expect(service.close_milestone(milestone, user)).to be_truthy } @@ -100,7 +100,7 @@ describe EventCreateService, services: true do end end - describe :destroy_mr do + describe '#destroy_mr' do let(:milestone) { create(:milestone) } it { expect(service.destroy_milestone(milestone, user)).to be_truthy } diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 62b25709a5d..67a919ba8ee 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -12,7 +12,7 @@ describe Issues::CloseService, services: true do project.team << [user2, :developer] end - describe :execute do + describe '#execute' do context "valid params" do before do perform_enqueued_jobs do diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 8443a00e70c..c1db4f3284b 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -12,7 +12,7 @@ describe MergeRequests::CloseService, services: true do project.team << [user2, :developer] end - describe :execute do + describe '#execute' do context 'valid params' do let(:service) { MergeRequests::CloseService.new(project, user, {}) } diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index e433f49872d..d0b55d2d509 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -5,7 +5,7 @@ describe MergeRequests::CreateService, services: true do let(:user) { create(:user) } let(:assignee) { create(:user) } - describe :execute do + describe '#execute' do context 'valid params' do let(:opts) do { diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 2f72cd60071..f5bf3c1e367 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -11,7 +11,7 @@ describe MergeRequests::MergeService, services: true do project.team << [user2, :developer] end - describe :execute do + describe '#execute' do context 'valid params' do let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 7d5cb876063..06f56d85aa8 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -5,7 +5,7 @@ describe MergeRequests::RefreshService, services: true do let(:user) { create(:user) } let(:service) { MergeRequests::RefreshService } - describe :execute do + describe '#execute' do before do @user = create(:user) group = create(:group) diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index ac0221998f5..88c9c640514 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -11,7 +11,7 @@ describe MergeRequests::ReopenService, services: true do project.team << [user2, :developer] end - describe :execute do + describe '#execute' do context 'valid params' do let(:service) { MergeRequests::ReopenService.new(project, user, {}) } diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index 1cd6eb2ab38..5d400299be0 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -9,7 +9,7 @@ describe Milestones::CloseService, services: true do project.team << [user, :master] end - describe :execute do + describe '#execute' do before do Milestones::CloseService.new(project, user, {}).execute(milestone) end diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb index c793026e300..6d29edb449a 100644 --- a/spec/services/milestones/create_service_spec.rb +++ b/spec/services/milestones/create_service_spec.rb @@ -4,7 +4,7 @@ describe Milestones::CreateService, services: true do let(:project) { create(:empty_project) } let(:user) { create(:user) } - describe :execute do + describe '#execute' do context "valid params" do before do project.team << [user, :master] diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 35f576874b8..32753e84b31 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -5,7 +5,7 @@ describe Notes::CreateService, services: true do let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } - describe :execute do + describe '#execute' do context "valid params" do before do project.team << [user, :master] diff --git a/spec/services/notes/diff_position_update_service_spec.rb b/spec/services/notes/diff_position_update_service_spec.rb new file mode 100644 index 00000000000..110efb54fa0 --- /dev/null +++ b/spec/services/notes/diff_position_update_service_spec.rb @@ -0,0 +1,175 @@ +require 'spec_helper' + +describe Notes::DiffPositionUpdateService, services: true do + let(:project) { create(:project) } + let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") } + let(:modify_commit) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e") } + let(:edit_commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } + + let(:path) { "files/ruby/popen.rb" } + + let(:old_diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: create_commit.parent_id, + head_sha: modify_commit.sha + ) + end + + let(:new_diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: create_commit.parent_id, + head_sha: edit_commit.sha + ) + end + + subject do + described_class.new( + project, + nil, + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs, + paths: [path] + ) + end + + # old diff: + # 1 + require 'fileutils' + # 2 + require 'open3' + # 3 + + # 4 + module Popen + # 5 + extend self + # 6 + + # 7 + def popen(cmd, path=nil) + # 8 + unless cmd.is_a?(Array) + # 9 + raise "System commands must be given as an array of strings" + # 10 + end + # 11 + + # 12 + path ||= Dir.pwd + # 13 + vars = { "PWD" => path } + # 14 + options = { chdir: path } + # 15 + + # 16 + unless File.directory?(path) + # 17 + FileUtils.mkdir_p(path) + # 18 + end + # 19 + + # 20 + @cmd_output = "" + # 21 + @cmd_status = 0 + # 22 + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # 23 + @cmd_output << stdout.read + # 24 + @cmd_output << stderr.read + # 25 + @cmd_status = wait_thr.value.exitstatus + # 26 + end + # 27 + + # 28 + return @cmd_output, @cmd_status + # 29 + end + # 30 + end + # + # new diff: + # 1 + require 'fileutils' + # 2 + require 'open3' + # 3 + + # 4 + module Popen + # 5 + extend self + # 6 + + # 7 + def popen(cmd, path=nil) + # 8 + unless cmd.is_a?(Array) + # 9 + raise RuntimeError, "System commands must be given as an array of strings" + # 10 + end + # 11 + + # 12 + path ||= Dir.pwd + # 13 + + # 14 + vars = { + # 15 + "PWD" => path + # 16 + } + # 17 + + # 18 + options = { + # 19 + chdir: path + # 20 + } + # 21 + + # 22 + unless File.directory?(path) + # 23 + FileUtils.mkdir_p(path) + # 24 + end + # 25 + + # 26 + @cmd_output = "" + # 27 + @cmd_status = 0 + # 28 + + # 29 + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # 30 + @cmd_output << stdout.read + # 31 + @cmd_output << stderr.read + # 32 + @cmd_status = wait_thr.value.exitstatus + # 33 + end + # 34 + + # 35 + return @cmd_output, @cmd_status + # 36 + end + # 37 + end + # + # old->new diff: + # .. .. @@ -6,12 +6,18 @@ module Popen + # 6 6 + # 7 7 def popen(cmd, path=nil) + # 8 8 unless cmd.is_a?(Array) + # 9 - raise "System commands must be given as an array of strings" + # 9 + raise RuntimeError, "System commands must be given as an array of strings" + # 10 10 end + # 11 11 + # 12 12 path ||= Dir.pwd + # 13 - vars = { "PWD" => path } + # 14 - options = { chdir: path } + # 13 + + # 14 + vars = { + # 15 + "PWD" => path + # 16 + } + # 17 + + # 18 + options = { + # 19 + chdir: path + # 20 + } + # 15 21 + # 16 22 unless File.directory?(path) + # 17 23 FileUtils.mkdir_p(path) + # 18 24 end + # 19 25 + # 20 26 @cmd_output = "" + # 21 27 @cmd_status = 0 + # 28 + + # 22 29 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # 23 30 @cmd_output << stdout.read + # 24 31 @cmd_output << stderr.read + # .. .. + + describe "#execute" do + let(:note) { create(:diff_note_on_merge_request, project: project, position: old_position) } + + let(:old_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + old_line: nil, + new_line: line, + diff_refs: old_diff_refs + ) + end + + context "when the diff line is the same" do + let(:line) { 16 } + + it "updates the position" do + subject.execute(note) + + expect(note.original_position).to eq(old_position) + expect(note.position).not_to eq(old_position) + expect(note.position.new_line).to eq(22) + end + end + + context "when the diff line has changed" do + let(:line) { 9 } + + it "doesn't update the position" do + subject.execute(note) + + expect(note.original_position).to eq(old_position) + expect(note.position).to eq(old_position) + end + end + end +end diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb index d4c50f824c1..e33a611929b 100644 --- a/spec/services/notes/post_process_service_spec.rb +++ b/spec/services/notes/post_process_service_spec.rb @@ -5,7 +5,7 @@ describe Notes::PostProcessService, services: true do let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } - describe :execute do + describe '#execute' do before do project.team << [user, :master] note_opts = { diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 54719cbb8d8..9fc93f325f7 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -50,7 +50,7 @@ describe NotificationService, services: true do update_custom_notification(:new_note, @u_custom_global) end - describe :new_note do + describe '#new_note' do it do add_users_with_subscription(note.project, issue) @@ -293,6 +293,30 @@ describe NotificationService, services: true do end end end + + context "merge request diff note" do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request, source_project: project, assignee: user) } + let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) } + + before do + build_team(note.project) + project.team << [merge_request.author, :master] + project.team << [merge_request.assignee, :master] + end + + describe '#new_note' do + it "records sent notifications" do + # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note + expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original + + notification.new_note(note) + + expect(SentNotification.last.position).to eq(note.position) + end + end + end end describe 'Issues' do diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index bd4dc6a0f79..ad0d58672b3 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -12,18 +12,28 @@ describe Projects::HousekeepingService do it 'enqueues a sidekiq job' do expect(subject).to receive(:try_obtain_lease).and_return(true) - expect(GitlabShellOneShotWorker).to receive(:perform_async).with(:gc, project.repository_storage_path, project.path_with_namespace) + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id) subject.execute - expect(project.pushes_since_gc).to eq(0) + expect(project.reload.pushes_since_gc).to eq(0) end - it 'does not enqueue a job when no lease can be obtained' do - expect(subject).to receive(:try_obtain_lease).and_return(false) - expect(GitlabShellOneShotWorker).not_to receive(:perform_async) + context 'when no lease can be obtained' do + before(:each) do + expect(subject).to receive(:try_obtain_lease).and_return(false) + end - expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) - expect(project.pushes_since_gc).to eq(0) + it 'does not enqueue a job' do + expect(GitGarbageCollectWorker).not_to receive(:perform_async) + + expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) + end + + it 'does not reset pushes_since_gc' do + expect do + expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) + end.not_to change { project.pushes_since_gc }.from(3) + end end end @@ -39,10 +49,24 @@ describe Projects::HousekeepingService do end describe 'increment!' do + let(:lease_key) { "project_housekeeping:increment!:#{project.id}" } + it 'increments the pushes_since_gc counter' do - expect(project.pushes_since_gc).to eq(0) - subject.increment! - expect(project.pushes_since_gc).to eq(1) + lease = double(:lease, try_obtain: true) + expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease) + + expect do + subject.increment! + end.to change { project.pushes_since_gc }.from(0).to(1) + end + + it 'does not increment when no lease can be obtained' do + lease = double(:lease, try_obtain: false) + expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease) + + expect do + subject.increment! + end.not_to change { project.pushes_since_gc } end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 85dd30bf48c..43693441450 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -213,7 +213,7 @@ describe SystemNoteService, services: true do create(:merge_request, source_project: project, target_project: project) end - subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.last_commit) } + subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.diff_head_commit) } it_behaves_like 'a system note' diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb index f034f251ba4..4f47e89b4b5 100644 --- a/spec/services/test_hook_service_spec.rb +++ b/spec/services/test_hook_service_spec.rb @@ -5,7 +5,7 @@ describe TestHookService, services: true do let(:project) { create :project } let(:hook) { create :project_hook, project: project } - describe :execute do + describe '#execute' do it "should execute successfully" do stub_request(:post, hook.url).to_return(status: 200) expect(TestHookService.new.execute(hook, user)).to be_truthy diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index b4522536724..34d8ea9090e 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -35,8 +35,11 @@ describe TodoService, services: true do should_not_create_any_todo { service.new_issue(unassigned_issue, author) } end - it 'does not create a todo if assignee is the current user' do - should_not_create_any_todo { service.new_issue(unassigned_issue, john_doe) } + it 'creates a todo if assignee is the current user' do + unassigned_issue.update_attribute(:assignee, john_doe) + service.new_issue(unassigned_issue, john_doe) + + should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED) end it 'creates a todo for each valid mentioned user' do @@ -44,7 +47,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: guest, target: issue, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) + should_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) end @@ -57,7 +60,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) - should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end context 'when a private group is mentioned' do @@ -87,7 +90,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: guest, target: issue, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) + should_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) end @@ -105,7 +108,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) - should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) + should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end context 'issues with a task list' do @@ -156,10 +159,11 @@ describe TodoService, services: true do should_not_create_any_todo { service.reassigned_issue(issue, author) } end - it 'does not create a todo if new assignee is the current user' do + it 'creates a todo if new assignee is the current user' do unassigned_issue.update_attribute(:assignee, john_doe) + service.reassigned_issue(unassigned_issue, john_doe) - should_not_create_any_todo { service.reassigned_issue(unassigned_issue, john_doe) } + should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED) end end @@ -250,7 +254,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) - should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) + should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) end @@ -262,7 +266,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) - should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) + should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) end it 'creates a todo for each valid mentioned user when leaving a note on commit' do @@ -270,7 +274,7 @@ describe TodoService, services: true do should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) - should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) + should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) end @@ -312,7 +316,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) end @@ -325,7 +329,7 @@ describe TodoService, services: true do should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) end @@ -382,10 +386,11 @@ describe TodoService, services: true do should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) } end - it 'does not create a todo if new assignee is the current user' do + it 'creates a todo if new assignee is the current user' do mr_assigned.update_attribute(:assignee, john_doe) + service.reassigned_merge_request(mr_assigned, john_doe) - should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, john_doe) } + should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED) end end @@ -435,6 +440,24 @@ describe TodoService, services: true do should_create_todo(user: author, target: mr_unassigned, action: Todo::MARKED) end end + + describe '#new_note' do + let(:mention) { john_doe.to_reference } + let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") } + let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") } + + it 'creates a todo for mentioned user on new diff note' do + service.new_note(diff_note_on_merge_request, author) + + should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request) + end + + it 'creates a todo for mentioned user on legacy diff note' do + service.new_note(legacy_diff_note_on_merge_request, author) + + should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) + end + end end it 'updates cached counts when a todo is created' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 606da1b7605..3638dcbb2d3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -33,7 +33,6 @@ RSpec.configure do |config| config.include LoginHelpers, type: :request config.include StubConfiguration config.include EmailHelpers - config.include RelativeUrl, type: feature config.include TestEnv config.include ActiveJob::TestHelper config.include StubGitlabCalls diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb index 9b5c3065eed..b57a3493aff 100644 --- a/spec/support/capybara_helpers.rb +++ b/spec/support/capybara_helpers.rb @@ -27,6 +27,14 @@ module CapybaraHelpers end end end + + # Refresh the page. Calling `visit current_url` doesn't seem to work consistently. + # + def refresh + url = current_url + visit 'about:blank' + visit url + end end RSpec.configure do |config| diff --git a/spec/support/fake_u2f_device.rb b/spec/support/fake_u2f_device.rb index 553fe9f1fbc..f550e9a0160 100644 --- a/spec/support/fake_u2f_device.rb +++ b/spec/support/fake_u2f_device.rb @@ -18,8 +18,8 @@ class FakeU2fDevice def respond_to_u2f_authentication app_id = @page.evaluate_script('gon.u2f.app_id') - challenges = @page.evaluate_script('gon.u2f.challenges') - json_response = u2f_device(app_id).sign_response(challenges[0]) + challenge = @page.evaluate_script('gon.u2f.challenge') + json_response = u2f_device(app_id).sign_response(challenge) @page.execute_script(" u2f.sign = function(appId, challenges, signRequests, callback) { diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index ffdf2bb0a8a..e5f76afbfc0 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -37,6 +37,40 @@ module LoginHelpers Thread.current[:current_user] = user end + def login_via(provider, user, uid) + mock_auth_hash(provider, uid, user.email) + visit new_user_session_path + click_link provider + end + + def mock_auth_hash(provider, uid, email) + # The mock_auth configuration allows you to set per-provider (or default) + # authentication hashes to return during integration testing. + OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({ + provider: provider, + uid: uid, + info: { + name: 'mockuser', + email: email, + image: 'mock_user_thumbnail_url' + }, + credentials: { + token: 'mock_token', + secret: 'mock_secret' + }, + extra: { + raw_info: { + info: { + name: 'mockuser', + email: email, + image: 'mock_user_thumbnail_url' + } + } + } + }) + Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml] + end + # Requires Javascript driver. def logout find(".header-user-dropdown-toggle").click diff --git a/spec/support/omni_auth.rb b/spec/support/omni_auth.rb new file mode 100644 index 00000000000..0b1af4052ff --- /dev/null +++ b/spec/support/omni_auth.rb @@ -0,0 +1 @@ +OmniAuth.config.test_mode = true diff --git a/spec/support/relative_url.rb b/spec/support/relative_url.rb deleted file mode 100644 index 72e3ccce75b..00000000000 --- a/spec/support/relative_url.rb +++ /dev/null @@ -1,8 +0,0 @@ -# Fix route helpers in tests (e.g. root_path, ...) -module RelativeUrl - extend ActiveSupport::Concern - - included do - default_url_options[:script_name] = Rails.application.config.relative_url_root - end -end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 9f9ef20f99b..bb6c84262f6 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -5,19 +5,20 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { - 'empty-branch' => '7efb185', - 'flatten-dir' => 'e56497b', - 'feature' => '0b4bc9a', - 'feature_conflict' => 'bb5206f', - 'fix' => '48f0be4', - 'improve/awesome' => '5937ac0', - 'markdown' => '0ed8c6c', - 'lfs' => 'be93687', - 'master' => '5937ac0', - "'test'" => 'e56497b', - 'orphaned-branch' => '45127a9', - 'binary-encoding' => '7b1cf43', - 'gitattributes' => '5a62481', + 'empty-branch' => '7efb185', + 'flatten-dir' => 'e56497b', + 'feature' => '0b4bc9a', + 'feature_conflict' => 'bb5206f', + 'fix' => '48f0be4', + 'improve/awesome' => '5937ac0', + 'markdown' => '0ed8c6c', + 'lfs' => 'be93687', + 'master' => '5937ac0', + "'test'" => 'e56497b', + 'orphaned-branch' => '45127a9', + 'binary-encoding' => '7b1cf43', + 'gitattributes' => '5a62481', + 'expand-collapse-diffs' => '4842455' } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily @@ -63,7 +64,7 @@ module TestEnv end def disable_pre_receive - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) end # Clean /tmp/tests diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb new file mode 100644 index 00000000000..c9f5aae0815 --- /dev/null +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe GitGarbageCollectWorker do + let(:project) { create(:project) } + let(:shell) { Gitlab::Shell.new } + + subject { GitGarbageCollectWorker.new } + + before do + allow(subject).to receive(:gitlab_shell).and_return(shell) + end + + describe "#perform" do + it "runs `git gc`" do + expect(shell).to receive(:gc).with( + project.repository_storage_path, + project.path_with_namespace). + and_return(true) + expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original + expect_any_instance_of(Repository).to receive(:branch_names).and_call_original + expect_any_instance_of(Repository).to receive(:branch_count).and_call_original + expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original + + subject.perform(project.id) + end + end +end diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore index f6b286cea98..e5df7b9150e 100644 --- a/vendor/gitignore/Android.gitignore +++ b/vendor/gitignore/Android.gitignore @@ -35,6 +35,7 @@ captures/ # Intellij *.iml .idea/workspace.xml +.idea/libraries # Keystore files *.jks diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index 4581ef2eeef..259148fa18f 100644 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore @@ -1,3 +1,6 @@ +# Prerequisites +*.d + # Compiled Object files *.slo *.lo diff --git a/vendor/gitignore/C.gitignore b/vendor/gitignore/C.gitignore index f805e810e5c..7a065c709c7 100644 --- a/vendor/gitignore/C.gitignore +++ b/vendor/gitignore/C.gitignore @@ -1,3 +1,6 @@ +# Prerequisites +*.d + # Object files *.o *.ko diff --git a/vendor/gitignore/Gradle.gitignore b/vendor/gitignore/Gradle.gitignore index 77617a15c38..a1fc39c070f 100644 --- a/vendor/gitignore/Gradle.gitignore +++ b/vendor/gitignore/Gradle.gitignore @@ -1,5 +1,5 @@ .gradle -build/ +/build/ # Ignore Gradle GUI config gradle-app.setting diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE index b8a103ac9b1..0e259d42c99 100644 --- a/vendor/gitignore/LICENSE +++ b/vendor/gitignore/LICENSE @@ -1,19 +1,121 @@ -Copyright (c) 2016 GitHub, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index 5148e527a7e..aea5294de9d 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -7,6 +7,7 @@ npm-debug.log* pids *.pid *.seed +*.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore index 4123a577c47..3cb097c9d5e 100644 --- a/vendor/gitignore/TeX.gitignore +++ b/vendor/gitignore/TeX.gitignore @@ -152,6 +152,9 @@ pythontex-files-*/ # todonotes *.tdo +# easy-todo +*.lod + # xindy *.xdy diff --git a/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml new file mode 100644 index 00000000000..1678a47f9ac --- /dev/null +++ b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml @@ -0,0 +1,102 @@ +--- +# Build JAVA applications using Apache Maven (http://maven.apache.org) +# For docker image tags see https://hub.docker.com/_/maven/ +# +# For general lifecycle information see https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html +# +# This template will build and test your projects as well as create the documentation. +# +# * Caches downloaded dependencies and plugins between invocation. +# * Does only verify merge requests but deploy built artifacts of the +# master branch. +# * Shows how to use multiple jobs in test stage for verifying functionality +# with multiple JDKs. +# * Uses site:stage to collect the documentation for multi-module projects. +# * Publishes the documentation for `master` branch. + +variables: + # This will supress any download for dependencies and plugins or upload messages which would clutter the console log. + # `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work. + MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true" + # As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used + # when running from the command line. + # `installAtEnd` and `deployAtEnd`are only effective with recent version of the corresponding plugins. + MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true" + +# Cache downloaded dependencies and plugins between builds. +cache: + paths: + - /root/.m2/repository/ + +# This will only validate and compile stuff and run e.g. maven-enforcer-plugin. +# Because some enforcer rules might check dependency convergence and class duplications +# we use `test-compile` here instead of `validate`, so the correct classpath is picked up. +.validate: &validate + stage: build + script: + - 'mvn $MAVEN_CLI_OPTS test-compile' + +# For merge requests do not `deploy` but only run `verify`. +# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html +.verify: &verify + stage: test + script: + - 'mvn $MAVEN_CLI_OPTS verify site site:stage' + except: + - master + +# Validate merge requests using JDK7 +validate:jdk7: + <<: *validate + image: maven:3.3.9-jdk-7 + +# Validate merge requests using JDK8 +validate:jdk8: + <<: *validate + image: maven:3.3.9-jdk-8 + +# Verify merge requests using JDK7 +verify:jdk7: + <<: *verify + image: maven:3.3.9-jdk-7 + +# Verify merge requests using JDK8 +verify:jdk8: + <<: *verify + image: maven:3.3.9-jdk-8 + + +# For `master` branch run `mvn deploy` automatically. +# Here you need to decide whether you want to use JDK7 or 8. +# To get this working you need to define a volume while configuring your gitlab-ci-multi-runner. +# Mount your `settings.xml` as `/root/.m2/settings.xml` which holds your secrets. +# See https://maven.apache.org/settings.html +deploy:jdk8: + # Use stage test here, so the pages job may later pickup the created site. + stage: test + script: + - 'mvn $MAVEN_CLI_OPTS deploy site site:stage' + only: + - master + # Archive up the built documentation site. + artifacts: + paths: + - target/staging + image: maven:3.3.9-jdk-8 + + +pages: + image: busybox:latest + stage: deploy + script: + # Because Maven appends the artifactId automatically to the staging path if you did define a parent pom, + # you might need to use `mv target/staging/YOUR_ARTIFACT_ID public` instead. + - mv target/staging public + dependencies: + - deploy:jdk8 + artifacts: + paths: + - public + only: + - master + diff --git a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml index 7fcc0b436b5..7fcc0b436b5 100644 --- a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml index 791afdd23f1..791afdd23f1 100644 --- a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml index 249a168aa33..249a168aa33 100644 --- a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml index dd3ef149668..dd3ef149668 100644 --- a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml index b468d79bcad..b468d79bcad 100644 --- a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml index 45df6975259..45df6975259 100644 --- a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml index f5b40f2b9f1..f5b40f2b9f1 100644 --- a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml index 36918fc005a..36918fc005a 100644 --- a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml index c5c44a5d86c..c5c44a5d86c 100644 --- a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml index 50e8b7ccd46..50e8b7ccd46 100644 --- a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml index 9f4cc0574d6..9f4cc0574d6 100644 --- a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml index b469b316ba5..b469b316ba5 100644 --- a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml index 4762ec9acfd..4762ec9acfd 100644 --- a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml index c5f3154f587..c5f3154f587 100644 --- a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml index 78f3e39949f..2a761bbd127 100644 --- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -10,12 +10,19 @@ services: - redis:latest - postgres:latest +# Cache gems in between builds +cache: + paths: + - vendor/ruby + # This is a basic example for a gem or script which doesn't use # services such as redis or postgres before_script: - - gem install bundler # Bundler is not installed with the image - - bundle install -j $(nproc) # Install dependencies + - ruby -v # Print out ruby version for debugging + - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image + - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby +# Optional - Delete if not using `rubocop` rubocop: script: - rubocop @@ -26,5 +33,5 @@ rspec: rails: script: - - rake db:migrate - - rspec spec + - bundle exec rake db:migrate + - bundle exec rake test diff --git a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml new file mode 100644 index 00000000000..ae3f7405ea3 --- /dev/null +++ b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml @@ -0,0 +1,23 @@ +# Unofficial language image. Look for the different tagged releases at: +# https://hub.docker.com/r/scorpil/rust/tags/ +image: "scorpil/rust:stable" + +# Optional: Pick zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +#services: +# - mysql:latest +# - redis:latest +# - postgres:latest + +# Optional: Install a C compiler, cmake and git into the container. +# You will often need this when you (or any of your dependencies) depends on C code. +#before_script: +#- apt-get update -yqq +#- apt-get install -yqq --no-install-recommends build-essential + +# Use cargo to test the project +test:cargo: + script: + - rustc --version && cargo --version # Print version info for debugging + - cargo test --verbose --jobs 1 --release # Don't paralize to make errors more readable diff --git a/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml b/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml new file mode 100644 index 00000000000..443ba42e38c --- /dev/null +++ b/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml @@ -0,0 +1,22 @@ +# Official Java image. Look for the different tagged releases at +# https://hub.docker.com/r/library/java/tags/ . A Java image is not required +# but an image with a JVM speeds up the build a bit. +image: java:8 + +before_script: + # Enable the usage of sources over https + - apt-get update -yqq + - apt-get install apt-transport-https -yqq + # Add keyserver for SBT + - echo "deb http://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list + - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 642AC823 + # Install SBT + - apt-get update -yqq + - apt-get install sbt -yqq + # Log the sbt version + - sbt sbt-version + +test: + script: + # Execute your project's tests + - sbt clean test |