From 461c5585e6969303f693cb66d47a53702cf124f9 Mon Sep 17 00:00:00 2001 From: Thomas Wood Date: Mon, 9 May 2016 14:35:05 +0100 Subject: API Docs: Add missing project option shared_runners_enabled Introduced into the API in e80e3f53 [ci skip] --- doc/api/projects.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/api/projects.md b/doc/api/projects.md index de1faadebf5..114ae8c2472 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -424,6 +424,7 @@ Parameters: - `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -447,6 +448,7 @@ Parameters: - `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -472,6 +474,7 @@ Parameters: - `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `public_builds` (optional) -- cgit v1.2.1 From e921fc90b830002f66e6de24165559154a08da6f Mon Sep 17 00:00:00 2001 From: Eric K Idema Date: Tue, 10 May 2016 19:15:26 -0400 Subject: Fix documentation for Github integration authorization callback url. There are two callbacks that could be used with Github integration: * /import/github/callback (used by project import) * /users/auth/github/callback (used by OmniAuth) Github's documentation suggests that authorization callback url should be set to the longest common path. https://developer.github.com/v3/oauth/#redirect-urls Configuring according to the previous documentation resulted in a redirect_uri_mismatch error from Github when logging in via OmniAuth. --- doc/integration/github.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 "\'s GitLab" or "\'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). -- cgit v1.2.1 From 09e0c1faec52876196c69b47a39fae59be437dcc Mon Sep 17 00:00:00 2001 From: Caesar Schinas Date: Sat, 14 May 2016 16:32:52 +0000 Subject: Update README.md - SSH password can be changed with `ssh-keygen -p` --- doc/ssh/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index a1198e5878f..e011330bffb 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -18,8 +18,7 @@ cat ~/.ssh/id_rsa.pub If you see a long string starting with `ssh-rsa`, you can skip the `ssh-keygen` step. Note: It is a best practice to use a password for an SSH key, but it is not -required and you can skip creating a password by pressing enter. Note that -the password you choose here can't be altered or retrieved. +required and you can skip creating a password by pressing enter. To generate a new SSH key, use the following command: ```bash -- cgit v1.2.1 From 382e8cfef60d7bf1a96946ee902b25bc01d174ca Mon Sep 17 00:00:00 2001 From: Jonas Weber Date: Fri, 20 May 2016 22:23:08 +0200 Subject: Infinity Bug in Commit Statistics fixes #1548 --- lib/gitlab/graphs/commits.rb | 2 +- spec/lib/gitlab/graphs/commits_spec.rb | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 spec/lib/gitlab/graphs/commits_spec.rb 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/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 -- cgit v1.2.1 From 1209391f9b5c9cd4c00d8986eba44c7c60e92c16 Mon Sep 17 00:00:00 2001 From: Caesar Schinas Date: Wed, 25 May 2016 03:42:22 +0000 Subject: Add note on changing password of SSH key --- doc/ssh/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index e011330bffb..d6a0979f6ec 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -17,9 +17,6 @@ cat ~/.ssh/id_rsa.pub If you see a long string starting with `ssh-rsa`, you can skip the `ssh-keygen` step. -Note: It is a best practice to use a password for an SSH key, but it is not -required and you can skip creating a password by pressing enter. - To generate a new SSH key, use the following command: ```bash ssh-keygen -t rsa -C "$your_email" @@ -29,6 +26,12 @@ pair and for a password. When prompted for the location and filename, just press enter to use the default. If you use a different name, the key will not be used automatically. +Note: It is a best practice to use a password for an SSH key, but it is not +required and you can skip creating a password by pressing enter. + +If you want to change the password of your key later, you can use the following +command: `ssh-keygen -p ` + Use the command below to show your public key: **Windows Command Line:** -- cgit v1.2.1 From 900a488f99a6050ebd01f00292d5dae98b11ad3a Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 27 May 2016 14:33:43 -0500 Subject: Fix erroneous YAML spacing in LDAP configuration --- doc/administration/auth/ldap.md | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) 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 -- cgit v1.2.1 From af9a38f764ddd86e1179a2a95250755766ba5a66 Mon Sep 17 00:00:00 2001 From: Philipp Kraus Date: Wed, 1 Jun 2016 18:33:42 +0000 Subject: Fixed Typo in README.md --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index a3481f58c6c..01805914b66 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -713,7 +713,7 @@ deploy: It's possible to overwrite globally defined `before_script` and `after_script`: ```yaml -before_script +before_script: - global before script job: -- cgit v1.2.1 From 0e222f02d8b8de24577b754eea4539b29621719f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 17 Jun 2016 15:09:39 +0200 Subject: fixing URL validation for import_url on projects --- app/models/project.rb | 4 +-- app/validators/addressable_url_validator.rb | 49 +++++++++++++++++++++++++++++ spec/models/project_spec.rb | 5 +++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 app/validators/addressable_url_validator.rb diff --git a/app/models/project.rb b/app/models/project.rb index 0bb815e64e7..a3f78349e98 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -153,9 +153,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: :external_import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb new file mode 100644 index 00000000000..4e1a01a1bff --- /dev/null +++ b/app/validators/addressable_url_validator.rb @@ -0,0 +1,49 @@ +# UrlValidator +# +# Custom validator for URLs. +# +# By default, only URLs for the HTTP(S) protocols will be considered valid. +# Provide a `:protocols` option to configure accepted protocols. +# +# Example: +# +# class User < ActiveRecord::Base +# validates :personal_url, url: true +# +# validates :ftp_url, url: { protocols: %w(ftp) } +# +# validates :git_url, url: { protocols: %w(http https ssh git) } +# end +# +class AddressableUrlValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + unless valid_url?(value) + record.errors.add(attribute, "must be a valid URL") + end + end + + private + + def default_options + @default_options ||= { protocols: %w(http https ssh git) } + end + + def valid_url?(value) + return false unless value + + value.strip! + + valid_uri?(value) && valid_protocol?(value) + rescue Addressable::URI::InvalidURIError + false + end + + def valid_uri?(value) + Addressable::URI.parse(strip).is_a?(Addressable::URI) + end + + def valid_protocol?(value) + options = default_options.merge(self.options) + value =~ /\A#{URI.regexp(options[:protocols])}\z/ + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index fedab1f913b..c99fd7c633e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -63,6 +63,11 @@ describe Project, models: true do expect(project2).not_to be_valid expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/) end + + it 'should not allow an invalid URI as import_url' do + project2 = build(:project) + expect(project2).to be_valid + end end describe 'default_scope' do -- cgit v1.2.1 From a5abec905fc69a9999887ea11335f032b4dfa957 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 20 Jun 2016 11:34:34 +0200 Subject: fix addressable url validator --- app/validators/addressable_url_validator.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index 4e1a01a1bff..7aab66548e2 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -1,18 +1,18 @@ -# UrlValidator +# AddressableUrlValidator # -# Custom validator for URLs. +# Custom validator for URLs. This is a # -# By default, only URLs for the HTTP(S) protocols will be considered valid. +# 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, url: true +# validates :personal_url, addressable_url: true # -# validates :ftp_url, url: { protocols: %w(ftp) } +# validates :ftp_url, addressable_url: { protocols: %w(ftp) } # -# validates :git_url, url: { protocols: %w(http https ssh git) } +# validates :git_url, addressable_url: { protocols: %w(http https ssh git) } # end # class AddressableUrlValidator < ActiveModel::EachValidator @@ -39,7 +39,7 @@ class AddressableUrlValidator < ActiveModel::EachValidator end def valid_uri?(value) - Addressable::URI.parse(strip).is_a?(Addressable::URI) + Addressable::URI.parse(value).is_a?(Addressable::URI) end def valid_protocol?(value) -- cgit v1.2.1 From 896e09d055979cdfe6e20a8b5939c9a263f7e48a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 20 Jun 2016 15:31:03 +0200 Subject: started working on a migration for projects that have current import_url issues --- app/validators/addressable_url_validator.rb | 14 +++--- ...20160620110927_fix_no_validatable_import_url.rb | 52 ++++++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20160620110927_fix_no_validatable_import_url.rb diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index 7aab66548e2..585dc182e27 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -1,6 +1,6 @@ # AddressableUrlValidator # -# Custom validator for URLs. This is a +# Custom validator for URLs. This is a # # By default, only URLs for http, https, ssh, and git protocols will be considered valid. # Provide a `:protocols` option to configure accepted protocols. @@ -22,12 +22,6 @@ class AddressableUrlValidator < ActiveModel::EachValidator end end - private - - def default_options - @default_options ||= { protocols: %w(http https ssh git) } - end - def valid_url?(value) return false unless value @@ -38,6 +32,12 @@ class AddressableUrlValidator < ActiveModel::EachValidator false end + private + + def default_options + @default_options ||= { protocols: %w(http https ssh git) } + end + def valid_uri?(value) Addressable::URI.parse(value).is_a?(Addressable::URI) end diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb new file mode 100644 index 00000000000..e56a8a0c853 --- /dev/null +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -0,0 +1,52 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class FixNoValidatableImportUrl < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + class SqlBatches + + attr_reader :results, :query + + def initialize(batch_size: 100, query:) + @offset = 0 + @batch_size = batch_size + @query = query + @results = [] + end + + def next + @results = ActiveRecord::Base.connection.execute(batched_sql) + @offset += @batch_size + @results.any? + end + + private + + def batched_sql + "#{@query} OFFSET #{@offset} LIMIT #{@batch_size}" + end + end + + def up + invalid_import_url_project_ids.each { |project_id| cleanup_import_url(project_id) } + end + + def invalid_import_url_project_ids + ids = [] + batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL") + + while batches.nexts + ids += batches.results.map { |result| invalid_url?(result[:import_url]) ? result[:id] : nil } + end + + ids.compact + end + + def invalid_url?(url) + AddressableUrlValidator.new({ attributes: 1 }).valid_url?(url) + end + + def cleanup_import_url(project_id) + execute("UPDATE projects SET mirror = false, import_url = NULL WHERE id = #{project_id}") + end +end -- cgit v1.2.1 From 6d763831d00027600e4da9807e6be3afb47abd4b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 20 Jun 2016 17:20:53 +0200 Subject: fixed a few MySQL issues and added changelog --- CHANGELOG | 1 + app/validators/addressable_url_validator.rb | 6 +-- ...20160620110927_fix_no_validatable_import_url.rb | 53 +++++++++++++++++----- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 44e6a194745..5b84d136d2d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.9.0 (unreleased) + - Set import_url validation to be more strict - Fix error when CI job variables key specified but not defined - Fix pipeline status when there are no builds in pipeline - Fix Error 500 when using closes_issues API with an external issue tracker diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index 585dc182e27..ee6a5a11850 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -22,6 +22,8 @@ class AddressableUrlValidator < ActiveModel::EachValidator end end + private + def valid_url?(value) return false unless value @@ -32,8 +34,6 @@ class AddressableUrlValidator < ActiveModel::EachValidator false end - private - def default_options @default_options ||= { protocols: %w(http https ssh git) } end @@ -44,6 +44,6 @@ class AddressableUrlValidator < ActiveModel::EachValidator def valid_protocol?(value) options = default_options.merge(self.options) - value =~ /\A#{URI.regexp(options[:protocols])}\z/ + !!(value =~ /\A#{URI.regexp(options[:protocols])}\z/) end end diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb index e56a8a0c853..9cb84faaec1 100644 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -1,5 +1,9 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. +# Updates project records containing invalid URLs using the AddressableUrlValidator. +# This is optimized assuming the number of invalid records is low, but +# we still need to loop through all the projects with an +import_url+ +# so we use batching for the latter. +# +# This migration is non-reversible as we would have to keep the old data. class FixNoValidatableImportUrl < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers @@ -14,8 +18,8 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration @results = [] end - def next - @results = ActiveRecord::Base.connection.execute(batched_sql) + def next? + @results = ActiveRecord::Base.connection.exec_query(batched_sql) @offset += @batch_size @results.any? end @@ -23,11 +27,36 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration private def batched_sql - "#{@query} OFFSET #{@offset} LIMIT #{@batch_size}" + "#{@query} LIMIT #{@batch_size} OFFSET #{@offset}" + end + end + + # AddressableValidator - Snapshot of AddressableUrlValidator + module AddressableUrlValidatorSnap + extend self + + def valid_url?(value) + return false unless value + + value.strip! + + valid_uri?(value) && valid_protocol?(value) + rescue Addressable::URI::InvalidURIError + false + end + + def valid_uri?(value) + Addressable::URI.parse(value).is_a?(Addressable::URI) + end + + def valid_protocol?(value) + !!(value =~ /\A#{URI.regexp(%w(http https ssh git))}\z/) end end def up + say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.') + invalid_import_url_project_ids.each { |project_id| cleanup_import_url(project_id) } end @@ -35,18 +64,20 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration ids = [] batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL") - while batches.nexts - ids += batches.results.map { |result| invalid_url?(result[:import_url]) ? result[:id] : nil } + while batches.next? + batches.results.each do |result| + ids << result['id'] unless valid_url?(result['import_url']) + end end - ids.compact + ids end - def invalid_url?(url) - AddressableUrlValidator.new({ attributes: 1 }).valid_url?(url) + def valid_url?(url) + AddressableUrlValidatorSnap.valid_url?(url) end def cleanup_import_url(project_id) - execute("UPDATE projects SET mirror = false, import_url = NULL WHERE id = #{project_id}") + execute("UPDATE projects SET import_url = NULL WHERE id = #{project_id}") end end -- cgit v1.2.1 From 4273e07e009b63dfd69b824c244826e7e62ac057 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 20 Jun 2016 17:25:51 +0200 Subject: fix comment --- app/validators/addressable_url_validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index ee6a5a11850..64e8581e0d3 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -1,6 +1,6 @@ # AddressableUrlValidator # -# Custom validator for URLs. This is a +# Custom validator for URLs. This is a stricter version of UrlValidator. # # By default, only URLs for http, https, ssh, and git protocols will be considered valid. # Provide a `:protocols` option to configure accepted protocols. -- cgit v1.2.1 From 77d17719fd47d6a71de28bd97c8083cd19cfef1f Mon Sep 17 00:00:00 2001 From: Yatish Mehta Date: Sat, 4 Jun 2016 11:15:08 -0400 Subject: Fix comment for project argument in commit_range.rb --- app/models/commit_range.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) -- cgit v1.2.1 From c91298d554a2535e0a579e6255fd5640d171e6cf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 21 Jun 2016 11:43:32 +0200 Subject: Use generic type validator in new ci configuration --- lib/gitlab/ci/config/node/configurable.rb | 2 +- lib/gitlab/ci/config/node/validators.rb | 9 ++++++--- spec/lib/gitlab/ci/config/node/global_spec.rb | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 374ff71d0f5..e691ab0c5cf 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -19,7 +19,7 @@ module Gitlab included do validations do - validates :config, hash: true + validates :config, type: Hash end end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index dc9cdb9a220..a76f041c953 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -13,10 +13,13 @@ module Gitlab end end - class HashValidator < ActiveModel::EachValidator + class TypeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - unless value.is_a?(Hash) - record.errors.add(attribute, 'should be a configuration entry hash') + type = options[:with] + raise unless type.is_a?(Class) + + unless value.is_a?(type) + record.errors.add(attribute, "should be a #{type.name}") end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index fddd53a2b57..fc9257d89dd 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -106,5 +106,11 @@ describe Gitlab::Ci::Config::Node::Global do expect(global).not_to be_valid end end + + describe '#errors' do + it 'returns error about invalid type' do + expect(global.errors.first).to match /should be a hash/ + end + end end end -- cgit v1.2.1 From 8b550db33e27d95424de31acf20835cc731684a5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 21 Jun 2016 11:47:05 +0200 Subject: Add image configuration entry to new ci config --- lib/gitlab/ci/config/node/image.rb | 22 +++++++++++++ spec/lib/gitlab/ci/config/node/image_spec.rb | 46 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 lib/gitlab/ci/config/node/image.rb create mode 100644 spec/lib/gitlab/ci/config/node/image_spec.rb diff --git a/lib/gitlab/ci/config/node/image.rb b/lib/gitlab/ci/config/node/image.rb new file mode 100644 index 00000000000..ff8dd8308ad --- /dev/null +++ b/lib/gitlab/ci/config/node/image.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a Docker image. + # + class Image < Entry + include Validatable + + validations do + validates :config, type: String + end + + def value + @config + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/image_spec.rb b/spec/lib/gitlab/ci/config/node/image_spec.rb new file mode 100644 index 00000000000..0b0821ca558 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/image_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Image do + let(:entry) { described_class.new(config) } + + describe 'validation' do + context 'when entry config value is correct' do + let(:config) { 'ruby:2.2' } + + describe '#value' do + it 'returns image string' do + expect(entry.value).to eq 'ruby:2.2' + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { ['ruby:2.2'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'Image config should be a string' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + end +end -- cgit v1.2.1 From cd6a2afbbb508e96f67c1f192cee2c1d379b1749 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 21 Jun 2016 12:10:13 +0200 Subject: Move CI image configuration entry to new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 20 +++++++++---------- lib/gitlab/ci/config.rb | 2 +- lib/gitlab/ci/config/node/global.rb | 3 +++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/global_spec.rb | 28 +++++++++++++++++++-------- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index c52d4d63382..f4ef449c84c 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -14,7 +14,7 @@ module Ci ALLOWED_CACHE_KEYS = [:key, :untracked, :paths] ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in] - attr_reader :after_script, :image, :services, :path, :cache + attr_reader :after_script, :services, :path, :cache def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) @@ -22,8 +22,11 @@ module Ci @path = path - initial_parsing + unless @ci_config.valid? + raise ValidationError, @ci_config.errors.first + end + initial_parsing validate! rescue Gitlab::Ci::Config::Loader::FormatError => e raise ValidationError, e.message @@ -60,6 +63,9 @@ module Ci private def initial_parsing + @before_script = @ci_config.before_script + @image = @ci_config.image + @after_script = @config[:after_script] @image = @config[:image] @services = @config[:services] @@ -87,7 +93,7 @@ module Ci { stage_idx: stages.index(job[:stage]), stage: job[:stage], - commands: [job[:before_script] || [@ci_config.before_script], job[:script]].flatten.compact.join("\n"), + commands: [job[:before_script] || [@before_script], job[:script]].flatten.compact.join("\n"), tag_list: job[:tags] || [], name: name, only: job[:only], @@ -107,10 +113,6 @@ module Ci end def validate! - unless @ci_config.valid? - raise ValidationError, @ci_config.errors.first - end - validate_global! @jobs.each do |name, job| @@ -125,10 +127,6 @@ module Ci raise ValidationError, "after_script should be an array of strings" end - unless @image.nil? || @image.is_a?(String) - raise ValidationError, "image should be a string" - end - unless @services.nil? || validate_array_of_strings(@services) raise ValidationError, "services should be an array of strings" end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index adfd097736e..d02902a110a 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -7,7 +7,7 @@ module Gitlab ## # Temporary delegations that should be removed after refactoring # - delegate :before_script, to: :@global + delegate :before_script, :image, to: :@global def initialize(config) @config = Loader.new(config).load! diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 044603423d5..fa5f75beb9f 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -11,6 +11,9 @@ module Gitlab allow_node :before_script, Script, description: 'Script that will be executed before each job.' + + allow_node :image, Image, + description: 'Docker image that will be used to execute jobs.' 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 200ca6aeeea..4c7070ad058 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -979,7 +979,7 @@ EOT config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Image config should be a string") end it "returns errors if job name is blank" do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index fc9257d89dd..ae911d81c49 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -21,7 +21,8 @@ describe Gitlab::Ci::Config::Node::Global do context 'when hash is valid' do let(:hash) do - { before_script: ['ls', 'pwd'] } + { before_script: ['ls', 'pwd'], + image: 'ruby:2.2' } end describe '#process!' do @@ -32,17 +33,21 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 1 + expect(global.nodes.count).to eq 2 end it 'creates node object using valid class' do expect(global.nodes.first) .to be_an_instance_of Gitlab::Ci::Config::Node::Script + expect(global.nodes.second) + .to be_an_instance_of Gitlab::Ci::Config::Node::Image end it 'sets correct description for nodes' do expect(global.nodes.first.description) .to eq 'Script that will be executed before each job.' + expect(global.nodes.second.description) + .to eq 'Docker image that will be used to execute jobs.' end end @@ -51,19 +56,26 @@ describe Gitlab::Ci::Config::Node::Global do expect(global).not_to be_leaf end end + context 'when not processed' do + describe '#before_script' do + it 'returns nil' do + expect(global.before_script).to be nil + end + end + end - describe '#before_script' do - context 'when processed' do - before { global.process! } + context 'when processed' do + before { global.process! } + describe '#before_script' do it 'returns correct script' do expect(global.before_script).to eq "ls\npwd" end end - context 'when not processed' do - it 'returns nil' do - expect(global.before_script).to be nil + describe '#image' do + it 'returns valid image' do + expect(global.image).to eq 'ruby:2.2' end end end -- cgit v1.2.1 From fc00c545b27ab2f4bf713ae246c197f809dd4c11 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 21 Jun 2016 12:40:52 +0200 Subject: Handle CI services config in new CI config classes --- lib/ci/gitlab_ci_yaml_processor.rb | 8 ++--- lib/gitlab/ci/config.rb | 2 +- lib/gitlab/ci/config/node/global.rb | 3 ++ lib/gitlab/ci/config/node/services.rb | 22 +++++++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 +-- spec/lib/gitlab/ci/config/node/global_spec.rb | 12 +++++-- spec/lib/gitlab/ci/config/node/services_spec.rb | 42 +++++++++++++++++++++++++ 7 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 lib/gitlab/ci/config/node/services.rb create mode 100644 spec/lib/gitlab/ci/config/node/services_spec.rb diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index f4ef449c84c..2cb46448e76 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -14,7 +14,7 @@ module Ci ALLOWED_CACHE_KEYS = [:key, :untracked, :paths] ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in] - attr_reader :after_script, :services, :path, :cache + attr_reader :after_script, :path, :cache def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) @@ -68,7 +68,7 @@ module Ci @after_script = @config[:after_script] @image = @config[:image] - @services = @config[:services] + @services = @ci_config.services @stages = @config[:stages] || @config[:types] @variables = @config[:variables] || {} @cache = @config[:cache] @@ -127,10 +127,6 @@ module Ci raise ValidationError, "after_script should be an array of strings" end - unless @services.nil? || validate_array_of_strings(@services) - raise ValidationError, "services should be an array of strings" - end - unless @stages.nil? || validate_array_of_strings(@stages) raise ValidationError, "stages should be an array of strings" end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index d02902a110a..fb93d36c48f 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -7,7 +7,7 @@ module Gitlab ## # Temporary delegations that should be removed after refactoring # - delegate :before_script, :image, to: :@global + delegate :before_script, :image, :services, to: :@global def initialize(config) @config = Loader.new(config).load! diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index fa5f75beb9f..03ed7808d2f 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -14,6 +14,9 @@ module Gitlab allow_node :image, Image, description: 'Docker image that will be used to execute jobs.' + + allow_node :services, Services, + description: 'Docker images that will be linked to the container.' end end end diff --git a/lib/gitlab/ci/config/node/services.rb b/lib/gitlab/ci/config/node/services.rb new file mode 100644 index 00000000000..d9898d9a4a1 --- /dev/null +++ b/lib/gitlab/ci/config/node/services.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a configuration of Docker services. + # + class Services < Entry + include Validatable + + validations do + validates :config, array_of_strings: true + end + + def value + @config + end + end + end + 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 4c7070ad058..bed174e2495 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1007,14 +1007,14 @@ EOT config = YAML.dump({ services: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Services config should be an array of strings") end it "returns errors if services parameter is not an array of strings" do config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Services config should be an array of strings") end it "returns errors if job services parameter is not an array" do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index ae911d81c49..7c8ddffc086 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -22,7 +22,8 @@ describe Gitlab::Ci::Config::Node::Global do context 'when hash is valid' do let(:hash) do { before_script: ['ls', 'pwd'], - image: 'ruby:2.2' } + image: 'ruby:2.2', + services: ['postgres:9.1', 'mysql:5.5'] } end describe '#process!' do @@ -33,7 +34,7 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 2 + expect(global.nodes.count).to eq 3 end it 'creates node object using valid class' do @@ -56,6 +57,7 @@ describe Gitlab::Ci::Config::Node::Global do expect(global).not_to be_leaf end end + context 'when not processed' do describe '#before_script' do it 'returns nil' do @@ -78,6 +80,12 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.image).to eq 'ruby:2.2' end end + + describe '#services' do + it 'returns array of services' do + expect(global.services).to eq ['postgres:9.1', 'mysql:5.5'] + end + end end end diff --git a/spec/lib/gitlab/ci/config/node/services_spec.rb b/spec/lib/gitlab/ci/config/node/services_spec.rb new file mode 100644 index 00000000000..bda4b976cb9 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/services_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Services do + let(:entry) { described_class.new(config) } + + describe '#process!' do + before { entry.process! } + + context 'when entry config value is correct' do + let(:config) { ['postgres:9.1', 'mysql:5.5'] } + + describe '#value' do + it 'returns array of services as is' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { 'ls' } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'Services config should be an array of strings' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + end +end -- cgit v1.2.1 From d399128955756fe7a4651d6595ae31406055dfb8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 21 Jun 2016 13:02:14 +0200 Subject: Handle after script CI config in new classes This also makes Script to return an array of commands instead of concatented command, which is our current direction. --- lib/ci/gitlab_ci_yaml_processor.rb | 13 ++++--------- lib/gitlab/ci/config.rb | 2 +- lib/gitlab/ci/config/node/global.rb | 3 +++ lib/gitlab/ci/config/node/script.rb | 7 +------ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/factory_spec.rb | 4 ++-- spec/lib/gitlab/ci/config/node/global_spec.rb | 13 ++++++++++--- spec/lib/gitlab/ci/config/node/script_spec.rb | 4 ++-- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 2cb46448e76..c0f2a258836 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -14,7 +14,7 @@ module Ci ALLOWED_CACHE_KEYS = [:key, :untracked, :paths] ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in] - attr_reader :after_script, :path, :cache + attr_reader :path, :cache def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) @@ -65,10 +65,9 @@ module Ci def initial_parsing @before_script = @ci_config.before_script @image = @ci_config.image - - @after_script = @config[:after_script] - @image = @config[:image] + @after_script = @ci_config.after_script @services = @ci_config.services + @stages = @config[:stages] || @config[:types] @variables = @config[:variables] || {} @cache = @config[:cache] @@ -93,7 +92,7 @@ module Ci { stage_idx: stages.index(job[:stage]), stage: job[:stage], - commands: [job[:before_script] || [@before_script], job[:script]].flatten.compact.join("\n"), + commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"), tag_list: job[:tags] || [], name: name, only: job[:only], @@ -123,10 +122,6 @@ module Ci end def validate_global! - unless @after_script.nil? || validate_array_of_strings(@after_script) - raise ValidationError, "after_script should be an array of strings" - end - unless @stages.nil? || validate_array_of_strings(@stages) raise ValidationError, "stages should be an array of strings" end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index fb93d36c48f..8475e47d2b0 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -7,7 +7,7 @@ module Gitlab ## # Temporary delegations that should be removed after refactoring # - delegate :before_script, :image, :services, to: :@global + delegate :before_script, :image, :services, :after_script, to: :@global def initialize(config) @config = Loader.new(config).load! diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 03ed7808d2f..7b8d6d63a08 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -17,6 +17,9 @@ module Gitlab allow_node :services, Services, description: 'Docker images that will be linked to the container.' + + allow_node :after_script, Script, + description: 'Script that will be executed after each job.' end end end diff --git a/lib/gitlab/ci/config/node/script.rb b/lib/gitlab/ci/config/node/script.rb index c044f5c5e71..7bbd6291c2d 100644 --- a/lib/gitlab/ci/config/node/script.rb +++ b/lib/gitlab/ci/config/node/script.rb @@ -5,11 +5,6 @@ module Gitlab ## # Entry that represents a script. # - # Each element in the value array is a command that will be executed - # by GitLab Runner. Currently we concatenate these commands with - # new line character as a separator, what is compatible with - # implementation in Runner. - # class Script < Entry include Validatable @@ -18,7 +13,7 @@ module Gitlab end def value - @config.join("\n") + @config 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 bed174e2495..35309eec597 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -965,7 +965,7 @@ EOT config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "after_script should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "After script config should be an array of strings") end it "returns errors if job after_script parameter is not an array of strings" do diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index 01a707a6bd4..10462db7699 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::Ci::Config::Node::Factory do .with(value: ['ls', 'pwd']) .create! - expect(entry.value).to eq "ls\npwd" + expect(entry.value).to eq ['ls', 'pwd'] end context 'when setting description' do @@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::Node::Factory do .with(description: 'test description') .create! - expect(entry.value).to eq "ls\npwd" + expect(entry.value).to eq ['ls', 'pwd'] expect(entry.description).to eq 'test description' end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 7c8ddffc086..84ab1d49d0b 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -23,7 +23,8 @@ describe Gitlab::Ci::Config::Node::Global do let(:hash) do { before_script: ['ls', 'pwd'], image: 'ruby:2.2', - services: ['postgres:9.1', 'mysql:5.5'] } + services: ['postgres:9.1', 'mysql:5.5'], + after_script: ['make clean'] } end describe '#process!' do @@ -34,7 +35,7 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 3 + expect(global.nodes.count).to eq 4 end it 'creates node object using valid class' do @@ -71,7 +72,7 @@ describe Gitlab::Ci::Config::Node::Global do describe '#before_script' do it 'returns correct script' do - expect(global.before_script).to eq "ls\npwd" + expect(global.before_script).to eq ['ls', 'pwd'] end end @@ -86,6 +87,12 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.services).to eq ['postgres:9.1', 'mysql:5.5'] end end + + describe '#after_script' do + it 'returns after script' do + expect(global.after_script).to eq ['make clean'] + end + end end end diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb index 6af6aa15eef..abd43aa1ee9 100644 --- a/spec/lib/gitlab/ci/config/node/script_spec.rb +++ b/spec/lib/gitlab/ci/config/node/script_spec.rb @@ -10,8 +10,8 @@ describe Gitlab::Ci::Config::Node::Script do let(:config) { ['ls', 'pwd'] } describe '#value' do - it 'returns concatenated command' do - expect(entry.value).to eq "ls\npwd" + it 'returns array of strings' do + expect(entry.value).to eq config end end -- cgit v1.2.1 From 97ec24f0b07d78dee1fa079479abc3b8ddf2e844 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 21 Jun 2016 13:12:58 +0200 Subject: Return CI entry config as value by default --- lib/gitlab/ci/config/node/entry.rb | 5 +++-- lib/gitlab/ci/config/node/image.rb | 4 ---- lib/gitlab/ci/config/node/script.rb | 4 ---- lib/gitlab/ci/config/node/services.rb | 4 ---- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index f044ef965e9..91f3fd0e236 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -9,7 +9,8 @@ module Gitlab class InvalidError < StandardError; end attr_reader :config - attr_accessor :key, :description + attr_accessor :description + attr_writer :key def initialize(config) @config = config @@ -48,7 +49,7 @@ module Gitlab end def value - raise NotImplementedError + @config end def self.nodes diff --git a/lib/gitlab/ci/config/node/image.rb b/lib/gitlab/ci/config/node/image.rb index ff8dd8308ad..5d3c7c5eab0 100644 --- a/lib/gitlab/ci/config/node/image.rb +++ b/lib/gitlab/ci/config/node/image.rb @@ -11,10 +11,6 @@ module Gitlab validations do validates :config, type: String end - - def value - @config - end end end end diff --git a/lib/gitlab/ci/config/node/script.rb b/lib/gitlab/ci/config/node/script.rb index 7bbd6291c2d..39328f0fade 100644 --- a/lib/gitlab/ci/config/node/script.rb +++ b/lib/gitlab/ci/config/node/script.rb @@ -11,10 +11,6 @@ module Gitlab validations do validates :config, array_of_strings: true end - - def value - @config - end end end end diff --git a/lib/gitlab/ci/config/node/services.rb b/lib/gitlab/ci/config/node/services.rb index d9898d9a4a1..481e2b66adc 100644 --- a/lib/gitlab/ci/config/node/services.rb +++ b/lib/gitlab/ci/config/node/services.rb @@ -11,10 +11,6 @@ module Gitlab validations do validates :config, array_of_strings: true end - - def value - @config - end end end end -- cgit v1.2.1 From acde2e30b62ca7adaae2a7afd75f09f7a56be58e Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 22 Jun 2016 00:17:44 +0100 Subject: Added 100% width to open dropdown menus on mobile --- app/assets/stylesheets/framework/dropdowns.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 00111dfa706..2ecac17845a 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -22,6 +22,9 @@ .open { .dropdown-menu { display: block; + @media (max-width: $screen-xs-max) { + width: 100%; + } } .dropdown-menu-toggle { -- cgit v1.2.1 From 04ecfca386b70800d3876d3f11ff7451e95d9087 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 22 Jun 2016 10:44:33 +0200 Subject: Rename CI config null node entry to undefined node --- lib/gitlab/ci/config/node/configurable.rb | 2 +- lib/gitlab/ci/config/node/factory.rb | 6 ++---- lib/gitlab/ci/config/node/null.rb | 27 ------------------------ lib/gitlab/ci/config/node/undefined.rb | 22 +++++++++++++++++++ spec/lib/gitlab/ci/config/node/factory_spec.rb | 6 +++--- spec/lib/gitlab/ci/config/node/null_spec.rb | 23 -------------------- spec/lib/gitlab/ci/config/node/undefined_spec.rb | 23 ++++++++++++++++++++ 7 files changed, 51 insertions(+), 58 deletions(-) delete mode 100644 lib/gitlab/ci/config/node/null.rb create mode 100644 lib/gitlab/ci/config/node/undefined.rb delete mode 100644 spec/lib/gitlab/ci/config/node/null_spec.rb create mode 100644 spec/lib/gitlab/ci/config/node/undefined_spec.rb diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index e691ab0c5cf..f26924fab26 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -27,7 +27,7 @@ module Gitlab def create_node(key, factory) factory.with(value: @config[key], key: key) - factory.nullify! unless @config.has_key?(key) + factory.undefine! unless @config.has_key?(key) factory.create! end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 025ae40ef94..dbf027b6d8e 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -5,8 +5,6 @@ module Gitlab ## # Factory class responsible for fabricating node entry objects. # - # It uses Fluent Interface pattern to set all necessary attributes. - # class Factory class InvalidFactory < StandardError; end @@ -20,8 +18,8 @@ module Gitlab self end - def nullify! - @entry_class = Node::Null + def undefine! + @entry_class = Node::Undefined self end diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb deleted file mode 100644 index 4f590f6bec8..00000000000 --- a/lib/gitlab/ci/config/node/null.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This class represents a configuration entry that is not being used - # in configuration file. - # - # This implements Null Object pattern. - # - class Null < Entry - def value - nil - end - - def validate! - nil - end - - def method_missing(*) - nil - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb new file mode 100644 index 00000000000..a812d803f3e --- /dev/null +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # This class represents a configuration entry that is not defined + # in configuration file. + # + # This implements a Null Object pattern. + # + # It can be initialized using a default value of entry that is not + # present in configuration. + # + class Undefined < Entry + def method_missing(*) + nil + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index 10462db7699..8e13e243d4d 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -45,14 +45,14 @@ describe Gitlab::Ci::Config::Node::Factory do end end - context 'when creating a null entry' do + context 'when creating undefined entry' do it 'creates a null entry' do entry = factory .with(value: nil) - .nullify! + .undefine! .create! - expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Null + expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined end end end diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb deleted file mode 100644 index 36101c62462..00000000000 --- a/spec/lib/gitlab/ci/config/node/null_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::Null do - let(:entry) { described_class.new(nil) } - - describe '#leaf?' do - it 'is leaf node' do - expect(entry).to be_leaf - end - end - - describe '#any_method' do - it 'responds with nil' do - expect(entry.any_method).to be nil - end - end - - describe '#value' do - it 'returns nil' do - expect(entry.value).to be nil - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb new file mode 100644 index 00000000000..2d01a8a6ec8 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Undefined do + let(:entry) { described_class.new('some value') } + + describe '#leaf?' do + it 'is leaf node' do + expect(entry).to be_leaf + end + end + + describe '#any_method' do + it 'responds with nil' do + expect(entry.any_method).to be nil + end + end + + describe '#value' do + it 'returns configured value' do + expect(entry.value).to eq 'some value' + end + end +end -- cgit v1.2.1 From 05ce8a118743a5d896b6b8cc99b40af214ac8cd1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 22 Jun 2016 11:22:53 +0200 Subject: Handle CI environment variables in a new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 6 +-- lib/gitlab/ci/config.rb | 3 +- lib/gitlab/ci/config/node/global.rb | 3 ++ lib/gitlab/ci/config/node/validators.rb | 10 ++++ lib/gitlab/ci/config/node/variables.rb | 22 ++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 +- spec/lib/gitlab/ci/config/node/global_spec.rb | 9 +++- spec/lib/gitlab/ci/config/node/variables_spec.rb | 67 ++++++++++++++++++++++++ 8 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 lib/gitlab/ci/config/node/variables.rb create mode 100644 spec/lib/gitlab/ci/config/node/variables_spec.rb diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index c0f2a258836..436b0127c3d 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -67,9 +67,9 @@ module Ci @image = @ci_config.image @after_script = @ci_config.after_script @services = @ci_config.services + @variables = @ci_config.variables @stages = @config[:stages] || @config[:types] - @variables = @config[:variables] || {} @cache = @config[:cache] @jobs = {} @@ -126,10 +126,6 @@ module Ci raise ValidationError, "stages should be an array of strings" end - unless @variables.nil? || validate_variables(@variables) - raise ValidationError, "variables should be a map of key-value strings" - end - validate_global_cache! if @cache end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 8475e47d2b0..1bea9c21f6a 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -7,7 +7,8 @@ module Gitlab ## # Temporary delegations that should be removed after refactoring # - delegate :before_script, :image, :services, :after_script, to: :@global + delegate :before_script, :image, :services, :after_script, :variables, + to: :@global def initialize(config) @config = Loader.new(config).load! diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 7b8d6d63a08..3bb6f98ed0f 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -20,6 +20,9 @@ module Gitlab allow_node :after_script, Script, description: 'Script that will be executed after each job.' + + allow_node :variables, Variables, + description: 'Environment variables that will be used.' end end end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index a76f041c953..56f7661daf0 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -23,6 +23,16 @@ module Gitlab end end end + + class VariablesValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_variables(value) + record.errors.add(attribute, 'should be a hash of key value pairs') + end + end + end end end end diff --git a/lib/gitlab/ci/config/node/variables.rb b/lib/gitlab/ci/config/node/variables.rb new file mode 100644 index 00000000000..5decd777bdb --- /dev/null +++ b/lib/gitlab/ci/config/node/variables.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents environment variables. + # + class Variables < Entry + include Validatable + + validations do + validates :value, variables: true + end + + def value + @config || {} + end + end + end + 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 35309eec597..97a08758c04 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1098,14 +1098,14 @@ EOT config = YAML.dump({ variables: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables value should be a hash of key value pairs") end it "returns errors if variables is not a map of key-value strings" do config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables value should be a hash of key value pairs") end it "returns errors if job when is not on_success, on_failure or always" do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 84ab1d49d0b..ef4b669c403 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -24,6 +24,7 @@ describe Gitlab::Ci::Config::Node::Global do { before_script: ['ls', 'pwd'], image: 'ruby:2.2', services: ['postgres:9.1', 'mysql:5.5'], + variables: { VAR: 'value' }, after_script: ['make clean'] } end @@ -35,7 +36,7 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 4 + expect(global.nodes.count).to eq 5 end it 'creates node object using valid class' do @@ -93,6 +94,12 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.after_script).to eq ['make clean'] end end + + describe '#variables' do + it 'returns variables' do + expect(global.variables).to eq(VAR: 'value') + end + end end end diff --git a/spec/lib/gitlab/ci/config/node/variables_spec.rb b/spec/lib/gitlab/ci/config/node/variables_spec.rb new file mode 100644 index 00000000000..67df70992b4 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/variables_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Variables do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end + + describe '#value' do + it 'returns hash with key value strings' do + expect(entry.value).to eq config + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { [ :VAR, 'test' ] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include /should be a hash of key value pairs/ + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + + ## + # See #18775 + # + context 'when entry value is not defined' do + let(:config) { nil } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#values' do + it 'returns an empty hash' do + expect(entry.value).to eq({}) + end + end + end + end +end -- cgit v1.2.1 From bc2348f2e4365099e2a99df3d8e2a55fe7d138f4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 22 Jun 2016 14:26:33 +0200 Subject: Return default config value when entry is undefined --- lib/gitlab/ci/config/node/configurable.rb | 6 +-- lib/gitlab/ci/config/node/entry.rb | 3 ++ lib/gitlab/ci/config/node/factory.rb | 9 +++-- lib/gitlab/ci/config/node/global.rb | 10 ++--- lib/gitlab/ci/config/node/undefined.rb | 18 +++++---- lib/gitlab/ci/config/node/variables.rb | 6 ++- .../lib/gitlab/ci/config/node/configurable_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/undefined_spec.rb | 25 ++++++++---- spec/lib/gitlab/ci/config_spec.rb | 46 +++++++++++----------- 9 files changed, 74 insertions(+), 51 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index f26924fab26..25b5c2c9e21 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -33,12 +33,12 @@ module Gitlab class_methods do def nodes - Hash[@allowed_nodes.map { |key, factory| [key, factory.dup] }] + Hash[@nodes.map { |key, factory| [key, factory.dup] }] end private - def allow_node(symbol, entry_class, metadata) + def node(symbol, entry_class, metadata) factory = Node::Factory.new(entry_class) .with(description: metadata[:description]) @@ -47,7 +47,7 @@ module Gitlab @nodes[symbol].try(:value) end - (@allowed_nodes ||= {}).merge!(symbol => factory) + (@nodes ||= {}).merge!(symbol => factory) end end end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 91f3fd0e236..444a276d5c9 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -52,6 +52,9 @@ module Gitlab @config end + def self.default + end + def self.nodes {} end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index dbf027b6d8e..2271c386df6 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -8,8 +8,8 @@ module Gitlab class Factory class InvalidFactory < StandardError; end - def initialize(entry_class) - @entry_class = entry_class + def initialize(node) + @node = node @attributes = {} end @@ -19,14 +19,15 @@ module Gitlab end def undefine! - @entry_class = Node::Undefined + @attributes[:value] = @node.dup + @node = Node::Undefined self end def create! raise InvalidFactory unless @attributes.has_key?(:value) - @entry_class.new(@attributes[:value]).tap do |entry| + @node.new(@attributes[:value]).tap do |entry| entry.description = @attributes[:description] entry.key = @attributes[:key] end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 3bb6f98ed0f..b5d177c5285 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -9,19 +9,19 @@ module Gitlab class Global < Entry include Configurable - allow_node :before_script, Script, + node :before_script, Script, description: 'Script that will be executed before each job.' - allow_node :image, Image, + node :image, Image, description: 'Docker image that will be used to execute jobs.' - allow_node :services, Services, + node :services, Services, description: 'Docker images that will be linked to the container.' - allow_node :after_script, Script, + node :after_script, Script, description: 'Script that will be executed after each job.' - allow_node :variables, Variables, + node :variables, Variables, description: 'Environment variables that will be used.' end end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index a812d803f3e..e8a69b810e0 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -3,17 +3,21 @@ module Gitlab class Config module Node ## - # This class represents a configuration entry that is not defined - # in configuration file. + # This class represents an undefined entry node. # - # This implements a Null Object pattern. + # It takes original entry class as configuration and returns default + # value of original entry as self value. # - # It can be initialized using a default value of entry that is not - # present in configuration. # class Undefined < Entry - def method_missing(*) - nil + include Validatable + + validations do + validates :config, type: Class + end + + def value + @config.default end end end diff --git a/lib/gitlab/ci/config/node/variables.rb b/lib/gitlab/ci/config/node/variables.rb index 5decd777bdb..fd3ce8715a9 100644 --- a/lib/gitlab/ci/config/node/variables.rb +++ b/lib/gitlab/ci/config/node/variables.rb @@ -13,7 +13,11 @@ module Gitlab end def value - @config || {} + @config || self.class.default + end + + def self.default + {} end end end diff --git a/spec/lib/gitlab/ci/config/node/configurable_spec.rb b/spec/lib/gitlab/ci/config/node/configurable_spec.rb index 9bbda6e7396..2ac436cb4b2 100644 --- a/spec/lib/gitlab/ci/config/node/configurable_spec.rb +++ b/spec/lib/gitlab/ci/config/node/configurable_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Ci::Config::Node::Configurable do describe 'configured nodes' do before do node.class_eval do - allow_node :object, Object, description: 'test object' + node :object, Object, description: 'test object' end end diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb index 2d01a8a6ec8..5ded0504a3f 100644 --- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -1,23 +1,34 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Undefined do - let(:entry) { described_class.new('some value') } + let(:undefined) { described_class.new(entry) } + let(:entry) { Class.new } describe '#leaf?' do it 'is leaf node' do - expect(entry).to be_leaf + expect(undefined).to be_leaf end end - describe '#any_method' do - it 'responds with nil' do - expect(entry.any_method).to be nil + describe '#valid?' do + it 'is always valid' do + expect(undefined).to be_valid + end + end + + describe '#errors' do + it 'is does not contain errors' do + expect(undefined.errors).to be_empty end end describe '#value' do - it 'returns configured value' do - expect(entry.value).to eq 'some value' + before do + allow(entry).to receive(:default).and_return('some value') + end + + it 'returns default value for entry that is undefined' do + expect(undefined.value).to eq 'some value' end end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 2a5d132db7b..bc5a5e43103 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -40,38 +40,38 @@ describe Gitlab::Ci::Config do end end end + end - context 'when config is invalid' do - context 'when yml is incorrect' do - let(:yml) { '// invalid' } + context 'when config is invalid' do + context 'when yml is incorrect' do + let(:yml) { '// invalid' } - describe '.new' do - it 'raises error' do - expect { config }.to raise_error( - Gitlab::Ci::Config::Loader::FormatError, - /Invalid configuration format/ - ) - end + describe '.new' do + it 'raises error' do + expect { config }.to raise_error( + Gitlab::Ci::Config::Loader::FormatError, + /Invalid configuration format/ + ) end end + end - context 'when config logic is incorrect' do - let(:yml) { 'before_script: "ls"' } + context 'when config logic is incorrect' do + let(:yml) { 'before_script: "ls"' } - describe '#valid?' do - it 'is not valid' do - expect(config).not_to be_valid - end + describe '#valid?' do + it 'is not valid' do + expect(config).not_to be_valid + end - it 'has errors' do - expect(config.errors).not_to be_empty - end + it 'has errors' do + expect(config.errors).not_to be_empty end + end - describe '#errors' do - it 'returns an array of strings' do - expect(config.errors).to all(be_an_instance_of(String)) - end + describe '#errors' do + it 'returns an array of strings' do + expect(config.errors).to all(be_an_instance_of(String)) end end end -- cgit v1.2.1 From 48d76ecec8ec0387db4fbbda3f065c424c3ea51a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 22 Jun 2016 16:39:16 +0200 Subject: another fix and fixed spec --- app/models/project.rb | 2 ++ spec/models/project_spec.rb | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index ceebfcd733b..64c1722ccf0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -447,6 +447,8 @@ class Project < ActiveRecord::Base import_url = Gitlab::UrlSanitizer.new(value) create_or_update_import_data(credentials: import_url.credentials) super(import_url.sanitized_url) + rescue Addressable::URI::InvalidURIError + errors.add(:import_url, 'must be a valid URL.') end def import_url diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 897b6898f54..5859691a3c4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -65,7 +65,14 @@ describe Project, models: true do end it 'should not allow an invalid URI as import_url' do - project2 = build(:project) + project2 = build(:project, import_url: 'invalid://') + + expect(project2).not_to be_valid + end + + it 'should 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 end -- cgit v1.2.1 From 1e1bf322896fc515157b943a35e41632e26cda07 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 22 Jun 2016 16:48:57 +0200 Subject: fix chnagelog --- CHANGELOG | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bd6c922d30f..4cbb1a91077 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,11 +1,12 @@ Please view this file on the master branch, on stable branches it's out of date. -v 8.9.0 (unreleased) - - Set import_url validation to be more strict v 8.10.0 (unreleased) - Wrap code blocks on Activies and Todos page. !4783 (winniehell) - Fix MR-auto-close text added to description. !4836 +v 8.9.1 (unreleased) + - Set import_url validation to be more strict + v 8.9.0 - Fix builds API response not including commit data - Fix error when CI job variables key specified but not defined -- cgit v1.2.1 From 2240807c1aaa7d7df313dde9775e3ec99f7ad1b3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 23 Jun 2016 10:07:42 +0200 Subject: Assume that unspecified CI config is undefined We assume that when someone adds a key for the configuration entry, but does not provide a valid value, which causes entry to be `nil`, then entry should be considered as the undefined one. We also assume this is semantically correct, this is also backwards compatible with legacy CI config processor. See issue #18775 for more details. --- lib/gitlab/ci/config/node/configurable.rb | 1 - lib/gitlab/ci/config/node/factory.rb | 18 +-- lib/gitlab/ci/config/node/variables.rb | 6 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 8 +- spec/lib/gitlab/ci/config/node/factory_spec.rb | 5 +- spec/lib/gitlab/ci/config/node/global_spec.rb | 145 +++++++++++++++-------- spec/lib/gitlab/ci/config/node/services_spec.rb | 4 +- spec/lib/gitlab/ci/config/node/variables_spec.rb | 19 --- 8 files changed, 112 insertions(+), 94 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 25b5c2c9e21..0fb9092dafa 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -27,7 +27,6 @@ module Gitlab def create_node(key, factory) factory.with(value: @config[key], key: key) - factory.undefine! unless @config.has_key?(key) factory.create! end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 2271c386df6..647b0c82a79 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -18,16 +18,20 @@ module Gitlab self end - def undefine! - @attributes[:value] = @node.dup - @node = Node::Undefined - self - end - def create! raise InvalidFactory unless @attributes.has_key?(:value) - @node.new(@attributes[:value]).tap do |entry| + ## + # We assume unspecified entry is undefined. + # See issue #18775. + # + if @attributes[:value].nil? + node, value = Node::Undefined, @node + else + node, value = @node, @attributes[:value] + end + + node.new(value).tap do |entry| entry.description = @attributes[:description] entry.key = @attributes[:key] end diff --git a/lib/gitlab/ci/config/node/variables.rb b/lib/gitlab/ci/config/node/variables.rb index fd3ce8715a9..5f813f81f55 100644 --- a/lib/gitlab/ci/config/node/variables.rb +++ b/lib/gitlab/ci/config/node/variables.rb @@ -9,11 +9,7 @@ module Gitlab include Validatable validations do - validates :value, variables: true - end - - def value - @config || self.class.default + validates :config, variables: true end def self.default diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 97a08758c04..eb20f5f4c06 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -551,8 +551,8 @@ module Ci config_processor = GitlabCiYamlProcessor.new(config, path) ## - # TODO, in next version of CI configuration processor this - # should be invalid configuration, see #18775 and #15060 + # When variables config is empty, we asumme this is a correct, + # see issue #18775 # expect(config_processor.job_variables(:rspec)) .to be_an_instance_of(Array).and be_empty @@ -1098,14 +1098,14 @@ EOT config = YAML.dump({ variables: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables value should be a hash of key value pairs") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables config should be a hash of key value pairs") end it "returns errors if variables is not a map of key-value strings" do config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables value should be a hash of key value pairs") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables config should be a hash of key value pairs") end it "returns errors if job when is not on_success, on_failure or always" do diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index 8e13e243d4d..dd5f6e62b3e 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -45,11 +45,10 @@ describe Gitlab::Ci::Config::Node::Factory do end end - context 'when creating undefined entry' do - it 'creates a null entry' do + context 'when creating entry with nil value' do + it 'creates an undefined entry' do entry = factory .with(value: nil) - .undefine! .create! expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index ef4b669c403..36a5b8041f0 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -20,84 +20,125 @@ describe Gitlab::Ci::Config::Node::Global do end context 'when hash is valid' do - let(:hash) do - { before_script: ['ls', 'pwd'], - image: 'ruby:2.2', - services: ['postgres:9.1', 'mysql:5.5'], - variables: { VAR: 'value' }, - after_script: ['make clean'] } - end + context 'when all entries defined' do + let(:hash) do + { before_script: ['ls', 'pwd'], + image: 'ruby:2.2', + services: ['postgres:9.1', 'mysql:5.5'], + variables: { VAR: 'value' }, + after_script: ['make clean'] } + end - describe '#process!' do - before { global.process! } + describe '#process!' do + before { global.process! } - it 'creates nodes hash' do - expect(global.nodes).to be_an Array - end + it 'creates nodes hash' do + expect(global.nodes).to be_an Array + end - it 'creates node object for each entry' do - expect(global.nodes.count).to eq 5 - end + it 'creates node object for each entry' do + expect(global.nodes.count).to eq 5 + end - it 'creates node object using valid class' do - expect(global.nodes.first) - .to be_an_instance_of Gitlab::Ci::Config::Node::Script - expect(global.nodes.second) - .to be_an_instance_of Gitlab::Ci::Config::Node::Image + it 'creates node object using valid class' do + expect(global.nodes.first) + .to be_an_instance_of Gitlab::Ci::Config::Node::Script + expect(global.nodes.second) + .to be_an_instance_of Gitlab::Ci::Config::Node::Image + end + + it 'sets correct description for nodes' do + expect(global.nodes.first.description) + .to eq 'Script that will be executed before each job.' + expect(global.nodes.second.description) + .to eq 'Docker image that will be used to execute jobs.' + end end - it 'sets correct description for nodes' do - expect(global.nodes.first.description) - .to eq 'Script that will be executed before each job.' - expect(global.nodes.second.description) - .to eq 'Docker image that will be used to execute jobs.' + describe '#leaf?' do + it 'is not leaf' do + expect(global).not_to be_leaf + end end - end - describe '#leaf?' do - it 'is not leaf' do - expect(global).not_to be_leaf + context 'when not processed' do + describe '#before_script' do + it 'returns nil' do + expect(global.before_script).to be nil + end + end end - end - context 'when not processed' do - describe '#before_script' do - it 'returns nil' do - expect(global.before_script).to be nil + context 'when processed' do + before { global.process! } + + describe '#before_script' do + it 'returns correct script' do + expect(global.before_script).to eq ['ls', 'pwd'] + end + end + + describe '#image' do + it 'returns valid image' do + expect(global.image).to eq 'ruby:2.2' + end + end + + describe '#services' do + it 'returns array of services' do + expect(global.services).to eq ['postgres:9.1', 'mysql:5.5'] + end + end + + describe '#after_script' do + it 'returns after script' do + expect(global.after_script).to eq ['make clean'] + end + end + + describe '#variables' do + it 'returns variables' do + expect(global.variables).to eq(VAR: 'value') + end end end end - context 'when processed' do + context 'when most of entires not defined' do + let(:hash) { { rspec: {} } } before { global.process! } - describe '#before_script' do - it 'returns correct script' do - expect(global.before_script).to eq ['ls', 'pwd'] + describe '#nodes' do + it 'instantizes all nodes' do + expect(global.nodes.count).to eq 5 end - end - describe '#image' do - it 'returns valid image' do - expect(global.image).to eq 'ruby:2.2' + it 'contains undefined nodes' do + expect(global.nodes.last) + .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined end end - describe '#services' do - it 'returns array of services' do - expect(global.services).to eq ['postgres:9.1', 'mysql:5.5'] + describe '#variables' do + it 'returns default value for variables' do + expect(global.variables).to eq({}) end end + end - describe '#after_script' do - it 'returns after script' do - expect(global.after_script).to eq ['make clean'] - end - end + ## + # When nodes are specified but not defined, we assume that + # configuration is valid, and we asume that entry is simply undefined, + # despite the fact, that key is present. See issue #18775 for more + # details. + # + context 'when entires specified but not defined' do + let(:hash) { { variables: nil } } + before { global.process! } describe '#variables' do - it 'returns variables' do - expect(global.variables).to eq(VAR: 'value') + it 'undefined entry returns a default value' do + expect(global.variables).to eq({}) end end end diff --git a/spec/lib/gitlab/ci/config/node/services_spec.rb b/spec/lib/gitlab/ci/config/node/services_spec.rb index bda4b976cb9..e38f6f6923f 100644 --- a/spec/lib/gitlab/ci/config/node/services_spec.rb +++ b/spec/lib/gitlab/ci/config/node/services_spec.rb @@ -3,9 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Services do let(:entry) { described_class.new(config) } - describe '#process!' do - before { entry.process! } - + describe 'validations' do context 'when entry config value is correct' do let(:config) { ['postgres:9.1', 'mysql:5.5'] } diff --git a/spec/lib/gitlab/ci/config/node/variables_spec.rb b/spec/lib/gitlab/ci/config/node/variables_spec.rb index 67df70992b4..4b6d971ec71 100644 --- a/spec/lib/gitlab/ci/config/node/variables_spec.rb +++ b/spec/lib/gitlab/ci/config/node/variables_spec.rb @@ -44,24 +44,5 @@ describe Gitlab::Ci::Config::Node::Variables do end end end - - ## - # See #18775 - # - context 'when entry value is not defined' do - let(:config) { nil } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - - describe '#values' do - it 'returns an empty hash' do - expect(entry.value).to eq({}) - end - end - end end end -- cgit v1.2.1 From 29b96d92c163d71fe5a0fdf37d6a3c57c51141cd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 23 Jun 2016 13:51:07 +0200 Subject: Move CI stages configuration to new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 25 +++++-------- lib/gitlab/ci/config.rb | 2 +- lib/gitlab/ci/config/node/configurable.rb | 12 +++++-- lib/gitlab/ci/config/node/entry.rb | 4 +++ lib/gitlab/ci/config/node/factory.rb | 2 +- lib/gitlab/ci/config/node/global.rb | 10 ++++++ lib/gitlab/ci/config/node/stages.rb | 22 ++++++++++++ lib/gitlab/ci/config/node/undefined.rb | 4 +++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 8 ++--- spec/lib/gitlab/ci/config/node/global_spec.rb | 35 ++++++++++++++++-- spec/lib/gitlab/ci/config/node/stages_spec.rb | 46 ++++++++++++++++++++++++ spec/lib/gitlab/ci/config/node/undefined_spec.rb | 6 ++++ 12 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 lib/gitlab/ci/config/node/stages.rb create mode 100644 spec/lib/gitlab/ci/config/node/stages_spec.rb diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 436b0127c3d..f0c3eae661e 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -4,7 +4,6 @@ module Ci include Gitlab::Ci::Config::Node::LegacyValidationHelpers - DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, @@ -46,7 +45,7 @@ module Ci end def stages - @stages || DEFAULT_STAGES + @stages end def global_variables @@ -68,8 +67,8 @@ module Ci @after_script = @ci_config.after_script @services = @ci_config.services @variables = @ci_config.variables + @stages = @ci_config.stages - @stages = @config[:stages] || @config[:types] @cache = @config[:cache] @jobs = {} @@ -90,7 +89,7 @@ module Ci def build_job(name, job) { - stage_idx: stages.index(job[:stage]), + stage_idx: @stages.index(job[:stage]), stage: job[:stage], commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"), tag_list: job[:tags] || [], @@ -112,7 +111,7 @@ module Ci end def validate! - validate_global! + validate_global_cache! if @cache @jobs.each do |name, job| validate_job!(name, job) @@ -121,14 +120,6 @@ module Ci true end - def validate_global! - unless @stages.nil? || validate_array_of_strings(@stages) - raise ValidationError, "stages should be an array of strings" - end - - validate_global_cache! if @cache - end - def validate_global_cache! @cache.keys.each do |key| unless ALLOWED_CACHE_KEYS.include? key @@ -225,8 +216,8 @@ module Ci end def validate_job_stage!(name, job) - unless job[:stage].is_a?(String) && job[:stage].in?(stages) - raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}" + unless job[:stage].is_a?(String) && job[:stage].in?(@stages) + raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}" end end @@ -290,12 +281,12 @@ module Ci raise ValidationError, "#{name} job: dependencies parameter should be an array of strings" end - stage_index = stages.index(job[:stage]) + stage_index = @stages.index(job[:stage]) job[:dependencies].each do |dependency| raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym] - unless stages.index(@jobs[dependency.to_sym][:stage]) < stage_index + unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages" end end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 1bea9c21f6a..61a2d2069a3 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -8,7 +8,7 @@ module Gitlab # Temporary delegations that should be removed after refactoring # delegate :before_script, :image, :services, :after_script, :variables, - to: :@global + :stages, to: :@global def initialize(config) @config = Loader.new(config).load! diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 0fb9092dafa..61e4f1cee2c 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -38,14 +38,20 @@ module Gitlab private def node(symbol, entry_class, metadata) - factory = Node::Factory.new(entry_class) - .with(description: metadata[:description]) + define_method("#{symbol}_defined?") do + @nodes[symbol].try(:defined?) + end - define_method(symbol) do + define_method("#{symbol}_value") do raise Entry::InvalidError unless valid? @nodes[symbol].try(:value) end + alias_method symbol.to_sym, "#{symbol}_value".to_sym + + factory = Node::Factory.new(entry_class) + .with(description: metadata[:description]) + (@nodes ||= {}).merge!(symbol => factory) end end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 444a276d5c9..e6f738b1795 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -52,6 +52,10 @@ module Gitlab @config end + def defined? + true + end + def self.default end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 647b0c82a79..39b5784af25 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -22,7 +22,7 @@ module Gitlab raise InvalidFactory unless @attributes.has_key?(:value) ## - # We assume unspecified entry is undefined. + # We assume that unspecified entry is undefined. # See issue #18775. # if @attributes[:value].nil? diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index b5d177c5285..88f9bb3f43e 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -23,6 +23,16 @@ module Gitlab node :variables, Variables, description: 'Environment variables that will be used.' + + node :stages, Stages, + description: 'Configuration of stages for this pipeline.' + + node :types, Stages, + description: 'Stages for this pipeline (deprecated key).' + + def stages + stages_defined? ? stages_value : types_value + end end end end diff --git a/lib/gitlab/ci/config/node/stages.rb b/lib/gitlab/ci/config/node/stages.rb new file mode 100644 index 00000000000..88d88252bce --- /dev/null +++ b/lib/gitlab/ci/config/node/stages.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a configuration for pipeline stages. + # + class Stages < Entry + include Validatable + + validations do + validates :config, array_of_strings: true + end + + def self.default + %w(build test deploy) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index e8a69b810e0..699605e1e3a 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -19,6 +19,10 @@ module Gitlab def value @config.default end + + def defined? + false + end 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 eb20f5f4c06..3f732d2ca2a 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1081,17 +1081,17 @@ EOT end it "returns errors if stages is not an array" do - config = YAML.dump({ types: "test", rspec: { script: "test" } }) + config = YAML.dump({ stages: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Stages config should be an array of strings") end it "returns errors if stages is not an array of strings" do - config = YAML.dump({ types: [true, "test"], rspec: { script: "test" } }) + config = YAML.dump({ stages: [true, "test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Stages config should be an array of strings") end it "returns errors if variables is not a map" do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 36a5b8041f0..6aef6b913cf 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -26,7 +26,8 @@ describe Gitlab::Ci::Config::Node::Global do image: 'ruby:2.2', services: ['postgres:9.1', 'mysql:5.5'], variables: { VAR: 'value' }, - after_script: ['make clean'] } + after_script: ['make clean'], + stages: ['build', 'pages'] } end describe '#process!' do @@ -37,7 +38,7 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 5 + expect(global.nodes.count).to eq 7 end it 'creates node object using valid class' do @@ -101,6 +102,22 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.variables).to eq(VAR: 'value') end end + + describe '#stages' do + context 'when stages key defined' do + it 'returns array of stages' do + expect(global.stages).to eq %w[build pages] + end + end + + context 'when deprecated types key defined' do + let(:hash) { { types: ['test', 'deploy'] } } + + it 'returns array of types as stages' do + expect(global.stages).to eq %w[test deploy] + end + end + end end end @@ -110,7 +127,7 @@ describe Gitlab::Ci::Config::Node::Global do describe '#nodes' do it 'instantizes all nodes' do - expect(global.nodes.count).to eq 5 + expect(global.nodes.count).to eq 7 end it 'contains undefined nodes' do @@ -124,6 +141,12 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.variables).to eq({}) end end + + describe '#stages' do + it 'returns an array of default stages' do + expect(global.stages).to eq %w[build test deploy] + end + end end ## @@ -188,4 +211,10 @@ describe Gitlab::Ci::Config::Node::Global do end end end + + describe '#defined?' do + it 'is concrete entry that is defined' do + expect(global.defined?).to be true + end + end end diff --git a/spec/lib/gitlab/ci/config/node/stages_spec.rb b/spec/lib/gitlab/ci/config/node/stages_spec.rb new file mode 100644 index 00000000000..dbf2eb8993d --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/stages_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Stages do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) { [:stage1, :stage2] } + + describe '#value' do + it 'returns array of stages' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { { test: true } } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'Stages config should be an array of strings' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + end + + describe '.default' do + it 'returns default stages' do + expect(described_class.default).to eq %w[build test deploy] + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb index 5ded0504a3f..4318dfe6e53 100644 --- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -31,4 +31,10 @@ describe Gitlab::Ci::Config::Node::Undefined do expect(undefined.value).to eq 'some value' end end + + describe '#undefined?' do + it 'is not a concrete entry that is defined' do + expect(undefined.defined?).to be false + end + end end -- cgit v1.2.1 From 1f320edb7721bcc86d26add7ba3fcbd185a1ca06 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 23 Jun 2016 13:51:34 +0200 Subject: Minor refactorings in new CI configuration classes --- lib/ci/gitlab_ci_yaml_processor.rb | 5 +++++ lib/gitlab/ci/config/node/configurable.rb | 2 +- lib/gitlab/ci/config/node/entry.rb | 3 +-- lib/gitlab/ci/config/node/validator.rb | 2 +- spec/lib/gitlab/ci/config/node/validator_spec.rb | 4 ++-- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index f0c3eae661e..e4710195687 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -91,6 +91,11 @@ module Ci { stage_idx: @stages.index(job[:stage]), stage: job[:stage], + ## + # Refactoring note: + # - before script behaves differently than after script + # - after script returns an array of commands + # - before script should be a concatenated command commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"), tag_list: job[:tags] || [], name: name, diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 61e4f1cee2c..590cf3d7b70 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -52,7 +52,7 @@ module Gitlab factory = Node::Factory.new(entry_class) .with(description: metadata[:description]) - (@nodes ||= {}).merge!(symbol => factory) + (@nodes ||= {}).merge!(symbol.to_sym => factory) end end end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index e6f738b1795..08d8020f8e7 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -44,8 +44,7 @@ module Gitlab end def errors - @validator.full_errors + - nodes.map(&:errors).flatten + @validator.messages + nodes.flat_map(&:errors) end def value diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index 02edc9219c3..5f62d68710d 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -11,7 +11,7 @@ module Gitlab @node = node end - def full_errors + def messages errors.full_messages.map do |error| "#{@node.key} #{error}".humanize end diff --git a/spec/lib/gitlab/ci/config/node/validator_spec.rb b/spec/lib/gitlab/ci/config/node/validator_spec.rb index ad875d55384..aa55ce90b37 100644 --- a/spec/lib/gitlab/ci/config/node/validator_spec.rb +++ b/spec/lib/gitlab/ci/config/node/validator_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Ci::Config::Node::Validator do it 'returns no errors' do validator_instance.validate - expect(validator_instance.full_errors).to be_empty + expect(validator_instance.messages).to be_empty end end @@ -36,7 +36,7 @@ describe Gitlab::Ci::Config::Node::Validator do it 'returns errors' do validator_instance.validate - expect(validator_instance.full_errors).not_to be_empty + expect(validator_instance.messages).not_to be_empty end end end -- cgit v1.2.1 From 58c49966fa73469e324c51e26d8bc9a482627818 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 23 Jun 2016 17:18:02 +0200 Subject: updated validator based on feedback --- app/validators/addressable_url_validator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index 64e8581e0d3..cbb80b5c68e 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -29,9 +29,7 @@ class AddressableUrlValidator < ActiveModel::EachValidator value.strip! - valid_uri?(value) && valid_protocol?(value) - rescue Addressable::URI::InvalidURIError - false + valid_protocol?(value) && valid_uri?(value) end def default_options @@ -40,6 +38,8 @@ class AddressableUrlValidator < ActiveModel::EachValidator def valid_uri?(value) Addressable::URI.parse(value).is_a?(Addressable::URI) + rescue Addressable::URI::InvalidURIError + false end def valid_protocol?(value) -- cgit v1.2.1 From 85cb5635babe5aa2c051fa2f40be4b28a106640f Mon Sep 17 00:00:00 2001 From: Pirate Praveen Date: Thu, 23 Jun 2016 10:15:10 -0400 Subject: bump google-omniauth-oauth2 to 0.4.1 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 092ea9d69b0..64889919bb7 100644 --- a/Gemfile +++ b/Gemfile @@ -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.5.0' gem 'omniauth-shibboleth', '~> 1.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index ba16e4bf337..eefb31d9a99 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -451,7 +451,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) @@ -918,7 +918,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.5.0) omniauth-shibboleth (~> 1.2.0) -- cgit v1.2.1 From 823970b570463bb011fbdc1117a1450310763da0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 24 Jun 2016 08:25:10 +0200 Subject: Fix ci config cache validation in legacy processor --- lib/ci/gitlab_ci_yaml_processor.rb | 4 ++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index e4710195687..33492775fe1 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -127,8 +127,8 @@ module Ci def validate_global_cache! @cache.keys.each do |key| - unless ALLOWED_CACHE_KEYS.include? key - raise ValidationError, "#{name} cache unknown parameter #{key}" + unless ALLOWED_CACHE_KEYS.include?(key) + raise ValidationError, "Cache config has unknown parameter: #{key}" 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 3f732d2ca2a..6ef6a59f186 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -591,7 +591,20 @@ module Ci end end - describe "Caches" do + describe 'cache' do + context 'when cache definition has unknown keys' do + it 'raises relevant validation error' do + config = YAML.dump( + { cache: { untracked: true, invalid: 'key' }, + rspec: { script: 'rspec' } }) + + expect { GitlabCiYamlProcessor.new(config) }.to raise_error( + GitlabCiYamlProcessor::ValidationError, + 'Cache config has unknown parameter: invalid' + ) + end + end + it "returns cache when defined globally" do config = YAML.dump({ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' }, -- cgit v1.2.1 From 04ece6664a04e7c352582100bdd6e8d78c3ea7cc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 24 Jun 2016 08:58:09 +0200 Subject: Add ci config class that represents a key value --- lib/gitlab/ci/config/node/key.rb | 18 ++++++++++++++++ lib/gitlab/ci/config/node/validators.rb | 10 +++++++++ spec/lib/gitlab/ci/config/node/key_spec.rb | 34 ++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 lib/gitlab/ci/config/node/key.rb create mode 100644 spec/lib/gitlab/ci/config/node/key_spec.rb diff --git a/lib/gitlab/ci/config/node/key.rb b/lib/gitlab/ci/config/node/key.rb new file mode 100644 index 00000000000..f8b461ca098 --- /dev/null +++ b/lib/gitlab/ci/config/node/key.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a key. + # + class Key < Entry + include Validatable + + validations do + validates :config, key: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index 56f7661daf0..f2b3a8a3f81 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -13,6 +13,16 @@ module Gitlab end end + class KeyValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_string(value) + record.errors.add(attribute, 'should be a string or symbol') + end + end + end + class TypeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) type = options[:with] diff --git a/spec/lib/gitlab/ci/config/node/key_spec.rb b/spec/lib/gitlab/ci/config/node/key_spec.rb new file mode 100644 index 00000000000..23e7fc46201 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/key_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Key do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) { 'test' } + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq 'test' + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { [ 'incorrect' ] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'Key config should be a string or symbol' + end + end + end + end +end -- cgit v1.2.1 From e017e1b62904fe323be359f1ac406c951dcd4ccd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 24 Jun 2016 09:49:54 +0200 Subject: Add ci config class that represents a boolean value --- lib/gitlab/ci/config/node/boolean.rb | 18 ++++++++++++++ lib/gitlab/ci/config/node/validators.rb | 10 ++++++++ spec/lib/gitlab/ci/config/node/boolean_spec.rb | 34 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 lib/gitlab/ci/config/node/boolean.rb create mode 100644 spec/lib/gitlab/ci/config/node/boolean_spec.rb diff --git a/lib/gitlab/ci/config/node/boolean.rb b/lib/gitlab/ci/config/node/boolean.rb new file mode 100644 index 00000000000..84b03ee7832 --- /dev/null +++ b/lib/gitlab/ci/config/node/boolean.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a boolean value. + # + class Boolean < Entry + include Validatable + + validations do + validates :config, boolean: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index f2b3a8a3f81..4082c161e81 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -13,6 +13,16 @@ module Gitlab end end + class BooleanValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_boolean(value) + record.errors.add(attribute, 'should be a boolean value') + end + end + end + class KeyValidator < ActiveModel::EachValidator include LegacyValidationHelpers diff --git a/spec/lib/gitlab/ci/config/node/boolean_spec.rb b/spec/lib/gitlab/ci/config/node/boolean_spec.rb new file mode 100644 index 00000000000..97f13b2d5fc --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/boolean_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Boolean do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is valid' do + let(:config) { false } + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq false + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not valid' do + let(:config) { [ 'incorrect' ] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'Boolean config should be a boolean value' + end + end + end + end +end -- cgit v1.2.1 From ce4478ed86e7487ea6bb45703561d1d5539ef5b4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 24 Jun 2016 09:54:52 +0200 Subject: Add ci config entry that represents array of paths --- lib/gitlab/ci/config/node/paths.rb | 18 +++++++++++++++ spec/lib/gitlab/ci/config/node/paths_spec.rb | 34 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 lib/gitlab/ci/config/node/paths.rb create mode 100644 spec/lib/gitlab/ci/config/node/paths_spec.rb diff --git a/lib/gitlab/ci/config/node/paths.rb b/lib/gitlab/ci/config/node/paths.rb new file mode 100644 index 00000000000..3c6d3a52966 --- /dev/null +++ b/lib/gitlab/ci/config/node/paths.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents an array of paths. + # + class Paths < Entry + include Validatable + + validations do + validates :config, array_of_strings: true + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/paths_spec.rb b/spec/lib/gitlab/ci/config/node/paths_spec.rb new file mode 100644 index 00000000000..0d95ad8abda --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/paths_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Paths do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is valid' do + let(:config) { ['some/file', 'some/path/'] } + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not valid' do + let(:config) { [ 1 ] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'Paths config should be an array of strings' + end + end + end + end +end -- cgit v1.2.1 From 8076d38a1487dd5b64153cd20eb696358b2f7acf Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 24 Jun 2016 11:35:32 +0200 Subject: added more info on how addressable URI differs from what we use in UrlValidator --- app/validators/addressable_url_validator.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index cbb80b5c68e..634a15aea01 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -1,6 +1,8 @@ # AddressableUrlValidator # -# Custom validator for URLs. This is a stricter version of UrlValidator. +# 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. -- cgit v1.2.1 From 6206f7f0a8fcb68df99edf96d52f8b3049ffce6a Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 26 Jun 2016 12:43:10 +0900 Subject: Fix dead links in the docs --- doc/api/services.md | 2 +- doc/ci/README.md | 2 +- doc/development/ci_setup.md | 2 +- doc/public_access/public_access.md | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api/services.md b/doc/api/services.md index ccfc0fccb7f..bda8677bb8d 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -8,7 +8,7 @@ Asana - Teamwork without email Set Asana service for a project. -> This service adds commit messages as comments to Asana tasks. Once enabled, commit messages are checked for Asana task URLs (for example, `https://app.asana.com/0/123456/987654`) or task IDs starting with # (for example, `#987654`). Every task ID found will get the commit comment added to it. You can also close a task with a message containing: `fix #123456`. You can find your Api Keys here: http://developer.asana.com/documentation/#api_keys +> This service adds commit messages as comments to Asana tasks. Once enabled, commit messages are checked for Asana task URLs (for example, `https://app.asana.com/0/123456/987654`) or task IDs starting with # (for example, `#987654`). Every task ID found will get the commit comment added to it. You can also close a task with a message containing: `fix #123456`. You can find your Api Keys here: https://asana.com/developers/documentation/getting-started/auth#api-key ``` PUT /projects/:id/services/asana diff --git a/doc/ci/README.md b/doc/ci/README.md index 3dd4e2bc230..a9d407528e8 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -16,5 +16,5 @@ - [Trigger builds through the API](triggers/README.md) - [Build artifacts](build_artifacts/README.md) - [User permissions](permissions/README.md) -- [API](../../api/ci/README.md) +- [API](../api/ci/README.md) - [CI services (linked docker containers)](services/README.md) diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md index 6776d9b083f..2f49b3564ab 100644 --- a/doc/development/ci_setup.md +++ b/doc/development/ci_setup.md @@ -21,7 +21,7 @@ We currently use three CI services to test GitLab: Core team has access to trigger builds if needed for GitLab CE. -We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/build_script_gitlab_ce.md) for testing with GitLab CI. +We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) for testing with GitLab CI. # Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index 17bb75ececd..9a5c5a5c92a 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -17,7 +17,7 @@ Public projects can be cloned **without any** authentication. They will also be listed on the public access directory (`/public`). -**Any logged in user** will have [Guest](../permissions/permissions) +**Any logged in user** will have [Guest](../permissions/permissions.md) permissions on the repository. ### Internal projects @@ -27,8 +27,8 @@ Internal projects can be cloned by any logged in user. They will also be listed on the public access directory (`/public`) for logged in users. -Any logged in user will have [Guest](../permissions/permissions) permissions on -the repository. +Any logged in user will have [Guest](../permissions/permissions.md) permissions +on the repository. ### How to change project visibility -- cgit v1.2.1 From e7aa8315af71339ecb6b01ae7da83f3c9b1e1957 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 27 Jun 2016 12:46:27 +0100 Subject: Added user avatar to header Closes #18543 --- app/assets/stylesheets/framework/dropdowns.scss | 6 ++++-- app/assets/stylesheets/framework/header.scss | 21 +++++++++++++++------ app/views/layouts/header/_default.html.haml | 14 +++++++++++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 00111dfa706..b39b8cbf50b 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -20,7 +20,8 @@ } .open { - .dropdown-menu { + .dropdown-menu, + .dropdown-menu-nav { display: block; } @@ -66,7 +67,8 @@ } } -.dropdown-menu { +.dropdown-menu, +.dropdown-menu-nav { display: none; position: absolute; top: 100%; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index c32ce5195c6..0c607071840 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -60,7 +60,7 @@ header { margin: ($header-height - 28) / 2 0; margin-left: 10px; height: 28px; - width: 28px; + min-width: 28px; line-height: 28px; text-align: center; @@ -241,14 +241,23 @@ header { .navbar-collapse { padding-left: 5px; - li { + .nav > li { display: table-cell; width: 1%; - - a { - margin-left: 8px !important; - } } } } } + +.header-user { + .dropdown-menu-nav { + width: 140px; + margin-top: -5px; + } +} + +.header-user-avatar { + float: left; + margin-right: 5px; + border-radius: 50%; +} diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 40a2c81eebd..5c76c4b8fa2 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -38,9 +38,17 @@ = link_to sherlock_transactions_path, title: 'Sherlock Transactions', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('tachometer fw') - %li - = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do - = icon('sign-out') + %li.header-user.dropdown + = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do + = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar" + %span.caret + .dropdown-menu-nav.dropdown-menu-align-right + %ul + %li + = link_to "Profile", current_user + %li.divider + %li + = link_to "Sign out", destroy_user_session_path, method: :delete, title: 'Sign out' - else %li %div -- cgit v1.2.1 From 56e88b8c28282976be258ba53a9f82662cc74703 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 24 Jun 2016 10:13:55 +0200 Subject: Add new ci config entry that handles cache config --- lib/gitlab/ci/config/node/cache.rb | 39 ++++++++++++++ lib/gitlab/ci/config/node/configurable.rb | 28 +++++----- lib/gitlab/ci/config/node/global.rb | 3 ++ spec/lib/gitlab/ci/config/node/cache_spec.rb | 60 ++++++++++++++++++++++ .../lib/gitlab/ci/config/node/configurable_spec.rb | 33 ++++++++++++ 5 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 lib/gitlab/ci/config/node/cache.rb create mode 100644 spec/lib/gitlab/ci/config/node/cache_spec.rb diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb new file mode 100644 index 00000000000..c6508e59c4b --- /dev/null +++ b/lib/gitlab/ci/config/node/cache.rb @@ -0,0 +1,39 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a cache configuration + # + class Cache < Entry + include Configurable + + validations do + validate :allowed_keys + + def unknown_keys + return [] unless @node.config.is_a?(Hash) + + @node.config.keys - @node.class.nodes.keys + end + + def allowed_keys + if unknown_keys.any? + errors.add(:config, "contains unknown keys #{unknown_keys}") + end + end + end + + node :key, Node::Key, + description: 'Cache key used to define a cache affinity.' + + node :untracked, Boolean, + description: 'Cache all untracked files.' + + node :paths, Paths, + description: 'Specify which paths should be cached across builds.' + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 590cf3d7b70..46a473ad092 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -32,28 +32,32 @@ module Gitlab class_methods do def nodes - Hash[@nodes.map { |key, factory| [key, factory.dup] }] + Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }] end private def node(symbol, entry_class, metadata) - define_method("#{symbol}_defined?") do - @nodes[symbol].try(:defined?) - end - - define_method("#{symbol}_value") do - raise Entry::InvalidError unless valid? - @nodes[symbol].try(:value) - end - - alias_method symbol.to_sym, "#{symbol}_value".to_sym - factory = Node::Factory.new(entry_class) .with(description: metadata[:description]) (@nodes ||= {}).merge!(symbol.to_sym => factory) end + + def helpers(*nodes) + nodes.each do |symbol| + define_method("#{symbol}_defined?") do + @nodes[symbol].try(:defined?) + end + + define_method("#{symbol}_value") do + raise Entry::InvalidError unless valid? + @nodes[symbol].try(:value) + end + + alias_method symbol.to_sym, "#{symbol}_value".to_sym + end + end end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 88f9bb3f43e..4ca379712c6 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -30,6 +30,9 @@ module Gitlab node :types, Stages, description: 'Stages for this pipeline (deprecated key).' + helpers :before_script, :image, :services, :after_script, :variables, + :stages, :types + def stages stages_defined? ? stages_value : types_value end diff --git a/spec/lib/gitlab/ci/config/node/cache_spec.rb b/spec/lib/gitlab/ci/config/node/cache_spec.rb new file mode 100644 index 00000000000..d6428f6b996 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/cache_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Cache do + let(:entry) { described_class.new(config) } + + describe 'validations' do + before { entry.process! } + + context 'when entry config value is correct' do + let(:config) do + { key: 'some key', + untracked: true, + paths: ['some/path/'] } + end + + describe '#value' do + it 'returns hash value' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + describe '#errors' do + context 'when is not a hash' do + let(:config) { 'ls' } + + it 'reports errors with config value' do + expect(entry.errors) + .to include 'Cache config should be a hash' + end + end + + context 'when descendants are invalid' do + let(:config) { { key: 1 } } + + it 'reports error with descendants' do + expect(entry.errors) + .to include 'Key config should be a string or symbol' + end + end + + context 'when there is an unknown key present' do + let(:config) { { invalid: true } } + + it 'reports error with descendants' do + expect(entry.errors) + .to include 'Cache config contains unknown keys [:invalid]' + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/configurable_spec.rb b/spec/lib/gitlab/ci/config/node/configurable_spec.rb index 2ac436cb4b2..4a1550517fb 100644 --- a/spec/lib/gitlab/ci/config/node/configurable_spec.rb +++ b/spec/lib/gitlab/ci/config/node/configurable_spec.rb @@ -7,6 +7,39 @@ describe Gitlab::Ci::Config::Node::Configurable do node.include(described_class) end + describe 'validations' do + let(:validator) { node.validator.new(instance) } + + before do + node.class_eval do + attr_reader :config + + def initialize(config) + @config = config + end + end + + validator.validate + end + + + context 'when node validator is invalid' do + let(:instance) { node.new('ls') } + + it 'returns invalid validator' do + expect(validator).to be_invalid + end + end + + context 'when node instance is valid' do + let(:instance) { node.new(key: 'value') } + + it 'returns valid validator' do + expect(validator).to be_valid + end + end + end + describe 'configured nodes' do before do node.class_eval do -- cgit v1.2.1 From c019585cb83b1852451184663085e6f0e0d12024 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 27 Jun 2016 14:12:47 +0200 Subject: Validate interface only with CI node validator --- lib/gitlab/ci/config/node/cache.rb | 29 +++++++++++---------- lib/gitlab/ci/config/node/validator.rb | 3 +-- spec/lib/gitlab/ci/config/node/validator_spec.rb | 32 ++++++------------------ 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb index c6508e59c4b..251d7aa9097 100644 --- a/lib/gitlab/ci/config/node/cache.rb +++ b/lib/gitlab/ci/config/node/cache.rb @@ -8,30 +8,33 @@ module Gitlab class Cache < Entry include Configurable + node :key, Key, + description: 'Cache key used to define a cache affinity.' + + node :untracked, Boolean, + description: 'Cache all untracked files.' + + node :paths, Paths, + description: 'Specify which paths should be cached across builds.' + validations do - validate :allowed_keys + validate :keys def unknown_keys - return [] unless @node.config.is_a?(Hash) - - @node.config.keys - @node.class.nodes.keys + return [] unless config.is_a?(Hash) + config.keys - allowed_keys end - def allowed_keys + def keys if unknown_keys.any? errors.add(:config, "contains unknown keys #{unknown_keys}") end end end - node :key, Node::Key, - description: 'Cache key used to define a cache affinity.' - - node :untracked, Boolean, - description: 'Cache all untracked files.' - - node :paths, Paths, - description: 'Specify which paths should be cached across builds.' + def allowed_keys + self.class.nodes.keys + end end end end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index 5f62d68710d..18e795d2c42 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -8,12 +8,11 @@ module Gitlab def initialize(node) super(node) - @node = node end def messages errors.full_messages.map do |error| - "#{@node.key} #{error}".humanize + "#{key} #{error}".humanize end end diff --git a/spec/lib/gitlab/ci/config/node/validator_spec.rb b/spec/lib/gitlab/ci/config/node/validator_spec.rb index aa55ce90b37..c293faa33ae 100644 --- a/spec/lib/gitlab/ci/config/node/validator_spec.rb +++ b/spec/lib/gitlab/ci/config/node/validator_spec.rb @@ -5,7 +5,13 @@ describe Gitlab::Ci::Config::Node::Validator do let(:validator_instance) { validator.new(node) } let(:node) { spy('node') } - shared_examples 'delegated validator' do + describe 'delegated validator' do + before do + validator.class_eval do + validates :test_attribute, presence: true + end + end + context 'when node is valid' do before do allow(node).to receive(:test_attribute).and_return('valid value') @@ -40,28 +46,4 @@ describe Gitlab::Ci::Config::Node::Validator do end end end - - describe 'attributes validations' do - before do - validator.class_eval do - validates :test_attribute, presence: true - end - end - - it_behaves_like 'delegated validator' - end - - describe 'interface validations' do - before do - validator.class_eval do - validate do - unless @node.test_attribute == 'valid value' - errors.add(:test_attribute, 'invalid value') - end - end - end - end - - it_behaves_like 'delegated validator' - end end -- cgit v1.2.1 From 9e33ca1c0bcff6d61f4aeb32211931648cc57ddc Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 27 Jun 2016 09:56:33 -0400 Subject: docs: fix some typos --- doc/api/merge_requests.md | 18 ++++++------ doc/api/projects.md | 70 +++++++++++++++++++++++------------------------ 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 2930f615fc1..169bfb913bf 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, @@ -113,10 +113,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, @@ -296,7 +296,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, @@ -465,7 +465,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, @@ -531,7 +531,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, diff --git a/doc/api/projects.md b/doc/api/projects.md index f5f195b97df..f1f37db3670 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,17 @@ 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", + "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", @@ -223,7 +223,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 +235,17 @@ 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", + "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": { @@ -537,17 +537,17 @@ 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", + "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", @@ -600,17 +600,17 @@ 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", + "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", @@ -660,7 +660,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 +672,17 @@ 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", + "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": { @@ -743,7 +743,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 +755,17 @@ 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", + "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": { @@ -965,11 +965,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 +1089,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 } -- cgit v1.2.1 From eb865bf3835d6c69289004f5c195c74847b767e1 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 27 Jun 2016 16:33:40 -0400 Subject: update_service: remove trailing whitespace --- app/services/projects/update_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 941df08995c..0c88022276e 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -6,7 +6,7 @@ module Projects 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 -- cgit v1.2.1 From 56aa6d23059dd47932af2110fb7798263aa8f222 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 27 Jun 2016 16:33:51 -0400 Subject: projects: add container_registry_enabled to API docs --- doc/api/projects.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/api/projects.md b/doc/api/projects.md index f1f37db3670..a15f3df498e 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -64,6 +64,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, @@ -112,6 +113,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, @@ -235,6 +237,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, @@ -537,6 +540,7 @@ Example response: "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, @@ -600,6 +604,7 @@ Example response: "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, @@ -672,6 +677,7 @@ Example response: "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, @@ -755,6 +761,7 @@ Example response: "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, -- cgit v1.2.1 From aa3a3fd12fc7152020ffbe54faa36a4f17eb94b2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 28 Jun 2016 16:46:16 +0800 Subject: Cleanup the tests a bit in order to extend it --- spec/requests/ci/api/builds_spec.rb | 49 +++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 1bc51783c3a..ea984924701 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -295,31 +295,50 @@ describe Ci::API::API do context 'build has been erased' do let(:build) { create(:ci_build, erased_at: Time.now) } - before { upload_artifacts(file_upload, headers_with_token) } + + before do + upload_artifacts(file_upload, headers_with_token) + end it 'should respond with forbidden' do expect(response.status).to eq 403 end end - context "should post artifact to running build" do - it "uses regual file post" do - upload_artifacts(file_upload, headers_with_token, false) - expect(response).to have_http_status(201) - expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + context 'should post artifact to running build' do + shared_examples 'post artifact' do + it 'updates successfully' do + response_filename = + json_response["artifacts_file"]["filename"] + + expect(response).to have_http_status(201) + expect(response_filename).to eq(file_upload.original_filename) + end + end + + context 'uses regular file post' do + before do + upload_artifacts(file_upload, headers_with_token, false) + end + + it_behaves_like 'post artifact' end - it "uses accelerated file post" do - upload_artifacts(file_upload, headers_with_token, true) - expect(response).to have_http_status(201) - expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + context 'uses accelerated file post' do + before do + upload_artifacts(file_upload, headers_with_token, true) + end + + it_behaves_like 'post artifact' end - it "updates artifact" do - upload_artifacts(file_upload, headers_with_token) - upload_artifacts(file_upload2, headers_with_token) - expect(response).to have_http_status(201) - expect(json_response["artifacts_file"]["filename"]).to eq(file_upload2.original_filename) + context 'updates artifact' do + before do + upload_artifacts(file_upload2, headers_with_token) + upload_artifacts(file_upload, headers_with_token) + end + + it_behaves_like 'post artifact' end end -- cgit v1.2.1 From fe0c59d2e61238e1241be448a37be0e3e702a5ce Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 28 Jun 2016 18:14:21 +0800 Subject: Introduce ci_builds.artifacts_sizes as JSON: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We store the sizes as a hash from path to bytes like: ``` ruby {'ci_artifacts.txt' => 27, 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' => 71759, 'other_artifacts_0.1.2/doc_sample.txt' => 1314, 'rails_sample.jpg' => 35255, 'tests_encoding/utf8 test dir ✓/regular_file_2' => 7} ``` So that it's easier to access than reading gzip file again. --- app/models/ci/build.rb | 21 ++++++++++++++++++++- ...160628085157_add_artifacts_sizes_to_ci_builds.rb | 11 +++++++++++ lib/ci/api/builds.rb | 1 + spec/requests/ci/api/builds_spec.rb | 14 ++++++++++++-- 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 2b0bec33131..d9544a4a279 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -5,6 +5,7 @@ module Ci belongs_to :erased_by, class_name: 'User' serialize :options + serialize :artifacts_sizes, JSON validates :coverage, numericality: true, allow_blank: true validates_presence_of :ref @@ -328,8 +329,19 @@ module Ci artifacts? && artifacts_metadata.exists? end + def artifacts_metadata_sizes + return unless artifacts_metadata? + + entries = new_artifacts_metadata('', recursive: true).find_entries! + + entries.inject({}) do |result, (path, metadata)| + result[path] = metadata[:size] if metadata[:size] + result + end + end + def artifacts_metadata_entry(path, **options) - Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry + new_artifacts_metadata(path, **options).to_entry end def erase_artifacts! @@ -375,6 +387,13 @@ module Ci private + def new_artifacts_metadata(path, **options) + Gitlab::Ci::Build::Artifacts::Metadata.new( + artifacts_metadata.path, + path, + **options) + end + def erase_trace! self.trace = nil end diff --git a/db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.rb b/db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.rb new file mode 100644 index 00000000000..bad260b83ea --- /dev/null +++ b/db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.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. + +class AddArtifactsSizesToCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + # Or :json if under PostgreSQL? + add_column(:ci_builds, :artifacts_sizes, :text) + end +end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 9f270f7b387..93eed4496e4 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -147,6 +147,7 @@ module Ci build.artifacts_file = artifacts build.artifacts_metadata = metadata build.artifacts_expire_in = params['expire_in'] + build.artifacts_sizes = build.artifacts_metadata_sizes if build.save present(build, with: Entities::BuildDetails) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index ea984924701..fb164c221d0 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -309,7 +309,7 @@ describe Ci::API::API do shared_examples 'post artifact' do it 'updates successfully' do response_filename = - json_response["artifacts_file"]["filename"] + json_response['artifacts_file']['filename'] expect(response).to have_http_status(201) expect(response_filename).to eq(file_upload.original_filename) @@ -344,10 +344,14 @@ describe Ci::API::API do context 'should post artifacts file and metadata file' do let!(:artifacts) { file_upload } - let!(:metadata) { file_upload2 } + let!(:metadata) do + fixture_file_upload( + Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz') + end let(:stored_artifacts_file) { build.reload.artifacts_file.file } let(:stored_metadata_file) { build.reload.artifacts_metadata.file } + let(:stored_artifacts_sizes) { build.reload.artifacts_sizes } before do post(post_url, post_data, headers_with_token) @@ -365,6 +369,12 @@ describe Ci::API::API do expect(response).to have_http_status(201) expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) + expect(stored_artifacts_sizes).to eq( + 'ci_artifacts.txt' => 27, + 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' => 71759, + 'other_artifacts_0.1.2/doc_sample.txt' => 1314, + 'rails_sample.jpg' => 35255, + 'tests_encoding/utf8 test dir ✓/regular_file_2' => 7) end end -- cgit v1.2.1 From 0a294698db01112c407575e690a8a368be6b15f8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 28 Jun 2016 11:16:48 +0000 Subject: Just save the size in total rather than individual files Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4964#note_12741046 --- app/models/ci/build.rb | 26 +++++----------------- ...160628085157_add_artifacts_size_to_ci_builds.rb | 10 +++++++++ ...60628085157_add_artifacts_sizes_to_ci_builds.rb | 11 --------- db/schema.rb | 3 ++- lib/ci/api/builds.rb | 2 +- spec/requests/ci/api/builds_spec.rb | 14 +++--------- 6 files changed, 22 insertions(+), 44 deletions(-) create mode 100644 db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb delete mode 100644 db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index d9544a4a279..2588274355b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -5,7 +5,6 @@ module Ci belongs_to :erased_by, class_name: 'User' serialize :options - serialize :artifacts_sizes, JSON validates :coverage, numericality: true, allow_blank: true validates_presence_of :ref @@ -329,19 +328,13 @@ module Ci artifacts? && artifacts_metadata.exists? end - def artifacts_metadata_sizes - return unless artifacts_metadata? - - entries = new_artifacts_metadata('', recursive: true).find_entries! - - entries.inject({}) do |result, (path, metadata)| - result[path] = metadata[:size] if metadata[:size] - result - end - end - def artifacts_metadata_entry(path, **options) - new_artifacts_metadata(path, **options).to_entry + metadata = Gitlab::Ci::Build::Artifacts::Metadata.new( + artifacts_metadata.path, + path, + **options) + + metadata.to_entry end def erase_artifacts! @@ -387,13 +380,6 @@ module Ci private - def new_artifacts_metadata(path, **options) - Gitlab::Ci::Build::Artifacts::Metadata.new( - artifacts_metadata.path, - path, - **options) - end - def erase_trace! self.trace = nil end diff --git a/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb b/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb new file mode 100644 index 00000000000..6e6e9dc3163 --- /dev/null +++ b/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb @@ -0,0 +1,10 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddArtifactsSizeToCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + add_column(:ci_builds, :artifacts_size, :integer) + end +end diff --git a/db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.rb b/db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.rb deleted file mode 100644 index bad260b83ea..00000000000 --- a/db/migrate/20160628085157_add_artifacts_sizes_to_ci_builds.rb +++ /dev/null @@ -1,11 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddArtifactsSizesToCiBuilds < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - def change - # Or :json if under PostgreSQL? - add_column(:ci_builds, :artifacts_sizes, :text) - end -end diff --git a/db/schema.rb b/db/schema.rb index 7a8377f687c..509a2d30f4d 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: 20160620115026) do +ActiveRecord::Schema.define(version: 20160628085157) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -163,6 +163,7 @@ ActiveRecord::Schema.define(version: 20160620115026) do t.datetime "erased_at" t.string "environment" t.datetime "artifacts_expire_at" + t.integer "artifacts_size" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 93eed4496e4..7bfcc40a9f1 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -147,7 +147,7 @@ module Ci build.artifacts_file = artifacts build.artifacts_metadata = metadata build.artifacts_expire_in = params['expire_in'] - build.artifacts_sizes = build.artifacts_metadata_sizes + build.artifacts_size = artifacts.size if build.save present(build, with: Entities::BuildDetails) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index fb164c221d0..08ec154dd5d 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -344,14 +344,11 @@ describe Ci::API::API do context 'should post artifacts file and metadata file' do let!(:artifacts) { file_upload } - let!(:metadata) do - fixture_file_upload( - Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz') - end + let!(:metadata) { file_upload2 } let(:stored_artifacts_file) { build.reload.artifacts_file.file } let(:stored_metadata_file) { build.reload.artifacts_metadata.file } - let(:stored_artifacts_sizes) { build.reload.artifacts_sizes } + let(:stored_artifacts_size) { build.reload.artifacts_size } before do post(post_url, post_data, headers_with_token) @@ -369,12 +366,7 @@ describe Ci::API::API do expect(response).to have_http_status(201) expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) - expect(stored_artifacts_sizes).to eq( - 'ci_artifacts.txt' => 27, - 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' => 71759, - 'other_artifacts_0.1.2/doc_sample.txt' => 1314, - 'rails_sample.jpg' => 35255, - 'tests_encoding/utf8 test dir ✓/regular_file_2' => 7) + expect(stored_artifacts_size).to eq(71759) end end -- cgit v1.2.1 From a44988ae4abb494b1194bb575b2415ef0de68878 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 27 Jun 2016 14:31:19 +0100 Subject: Fixed logout tests --- app/views/layouts/header/_default.html.haml | 2 +- features/project/issues/issues.feature | 2 +- features/search.feature | 8 +++++--- spec/support/login_helpers.rb | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 5c76c4b8fa2..8118afabe5b 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -48,7 +48,7 @@ = link_to "Profile", current_user %li.divider %li - = link_to "Sign out", destroy_user_session_path, method: :delete, title: 'Sign out' + = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", title: 'Sign out' - else %li %div diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 2259b7125c4..358e622b736 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -219,8 +219,8 @@ Feature: Project Issues When I click button "Unsubscribe" Then I should see that I am unsubscribed + @javascript Scenario: I submit new unassigned issue as guest - Given I logout Given public project "Community" When I visit project "Community" page And I visit project "Community" issues page diff --git a/features/search.feature b/features/search.feature index a946a836525..818ef436db6 100644 --- a/features/search.feature +++ b/features/search.feature @@ -73,13 +73,15 @@ Feature: Search Scenario: I logout and should see project I am looking for Given project "Shop" is public - And I logout + And I logout directly + And I visit dashboard search page And I search for "Sho" Then I should see "Shop" project link Scenario: I logout and should see issues I am looking for Given project "Shop" is public - And I logout + And I logout directly + And I visit dashboard search page And project has issues When I search for "Foo" And I click "Issues" link @@ -88,7 +90,7 @@ Feature: Search Scenario: I logout and should see project code I am looking for Given project "Shop" is public - And I logout + And I logout directly When I visit project "Shop" page And I search for "rspec" on project page Then I should see code results for project "Shop" diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 7a0f078c72b..ffdf2bb0a8a 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -39,7 +39,8 @@ module LoginHelpers # Requires Javascript driver. def logout - find(:css, ".fa.fa-sign-out").click + find(".header-user-dropdown-toggle").click + click_link "Sign out" end # Logout without JavaScript driver -- cgit v1.2.1 From de543359580ffdd67113e36fba80b3e1bd2262c2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 28 Jun 2016 20:32:32 +0800 Subject: Prefer Ci::Build#erase_artifacts! --- lib/ci/api/builds.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 7bfcc40a9f1..f6a8d907066 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -196,8 +196,7 @@ module Ci not_found! unless build authenticate_build_token!(build) - build.remove_artifacts_file! - build.remove_artifacts_metadata! + build.erase_artifacts! end end end -- cgit v1.2.1 From 1bc0d732f604d7a4a616ba34b8ccbb1987038951 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 28 Jun 2016 20:37:46 +0800 Subject: Also remove ci_builds.artifacts_size when erased --- app/models/ci/build.rb | 1 + spec/requests/ci/api/builds_spec.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 2588274355b..0f8c9511ce1 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -340,6 +340,7 @@ module Ci def erase_artifacts! remove_artifacts_file! remove_artifacts_metadata! + self.artifacts_size = nil save end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 08ec154dd5d..de1ec8fd40d 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -482,6 +482,7 @@ describe Ci::API::API do expect(response).to have_http_status(200) expect(build.artifacts_file.exists?).to be_falsy expect(build.artifacts_metadata.exists?).to be_falsy + expect(build.artifacts_size).to be_falsy end end -- cgit v1.2.1 From 7c511c2f55f3e181983253d8b3ae74cd84e6844c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 29 Jun 2016 09:05:14 +0200 Subject: Make it possible to set parent in CI config node --- lib/gitlab/ci/config/node/entry.rb | 4 ++-- lib/gitlab/ci/config/node/factory.rb | 3 ++- spec/lib/gitlab/ci/config/node/factory_spec.rb | 16 ++++++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 08d8020f8e7..f22dac44836 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -8,9 +8,9 @@ module Gitlab class Entry class InvalidError < StandardError; end - attr_reader :config - attr_accessor :description attr_writer :key + attr_reader :config + attr_accessor :parent, :description def initialize(config) @config = config diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 39b5784af25..85e28f345fe 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -32,8 +32,9 @@ module Gitlab end node.new(value).tap do |entry| - entry.description = @attributes[:description] entry.key = @attributes[:key] + entry.parent = @attributes[:parent] + entry.description = @attributes[:description] end end end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index dd5f6e62b3e..91ddef7bfbf 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Ci::Config::Node::Factory do let(:factory) { described_class.new(entry_class) } let(:entry_class) { Gitlab::Ci::Config::Node::Script } - context 'when value setting value' do + context 'when setting up a value' do it 'creates entry with valid value' do entry = factory .with(value: ['ls', 'pwd']) @@ -35,9 +35,21 @@ describe Gitlab::Ci::Config::Node::Factory do expect(entry.key).to eq 'test key' end end + + context 'when setting a parent' do + let(:parent) { Object.new } + + it 'creates entry with valid parent' do + entry = factory + .with(value: 'ls', parent: parent) + .create! + + expect(entry.parent).to eq parent + end + end end - context 'when not setting value' do + context 'when not setting up a value' do it 'raises error' do expect { factory.create! }.to raise_error( Gitlab::Ci::Config::Node::Factory::InvalidFactory -- cgit v1.2.1 From 92312786f13c72188abbbe4f0b6cbdd36de2331d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 29 Jun 2016 09:10:23 +0200 Subject: Add CI config entry location info to error message This CI config entry location in configuration Hash. --- lib/gitlab/ci/config/node/entry.rb | 7 +------ lib/gitlab/ci/config/node/validator.rb | 9 ++++++++- spec/lib/gitlab/ci/config/node/global_spec.rb | 6 ------ spec/lib/gitlab/ci/config/node/validator_spec.rb | 7 ++++++- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index f22dac44836..17d04fbdfec 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -8,9 +8,8 @@ module Gitlab class Entry class InvalidError < StandardError; end - attr_writer :key attr_reader :config - attr_accessor :parent, :description + attr_accessor :key, :parent, :description def initialize(config) @config = config @@ -35,10 +34,6 @@ module Gitlab self.class.nodes.none? end - def key - @key || self.class.name.demodulize.underscore - end - def valid? errors.none? end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index 18e795d2c42..d898d521548 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -8,17 +8,24 @@ module Gitlab def initialize(node) super(node) + @node = node end def messages errors.full_messages.map do |error| - "#{key} #{error}".humanize + "#{location} #{error}".humanize end end def self.name 'Validator' end + + private + + def location + key || @node.class.name.demodulize.underscore + end end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 6aef6b913cf..cf7ab13c8b1 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -13,12 +13,6 @@ describe Gitlab::Ci::Config::Node::Global do end end - describe '#key' do - it 'returns underscored class name' do - expect(global.key).to eq 'global' - end - end - context 'when hash is valid' do context 'when all entries defined' do let(:hash) do diff --git a/spec/lib/gitlab/ci/config/node/validator_spec.rb b/spec/lib/gitlab/ci/config/node/validator_spec.rb index c293faa33ae..87a1bbf55c0 100644 --- a/spec/lib/gitlab/ci/config/node/validator_spec.rb +++ b/spec/lib/gitlab/ci/config/node/validator_spec.rb @@ -5,6 +5,10 @@ describe Gitlab::Ci::Config::Node::Validator do let(:validator_instance) { validator.new(node) } let(:node) { spy('node') } + before do + allow(node).to receive(:key).and_return('node') + end + describe 'delegated validator' do before do validator.class_eval do @@ -42,7 +46,8 @@ describe Gitlab::Ci::Config::Node::Validator do it 'returns errors' do validator_instance.validate - expect(validator_instance.messages).not_to be_empty + expect(validator_instance.messages) + .to include "Node test attribute can't be blank" end end end -- cgit v1.2.1 From f4421817de474cda3598eac8cad0752d324608e1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 29 Jun 2016 09:39:04 +0200 Subject: Add global cache config entry to new CI config --- lib/gitlab/ci/config/node/cache.rb | 3 ++- lib/gitlab/ci/config/node/configurable.rb | 5 ++++- lib/gitlab/ci/config/node/entry.rb | 4 ++++ lib/gitlab/ci/config/node/global.rb | 7 +++++-- lib/gitlab/ci/config/node/validator.rb | 6 ++++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 26 ++++++++++++------------ spec/lib/gitlab/ci/config/node/boolean_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/cache_spec.rb | 6 +++--- spec/lib/gitlab/ci/config/node/global_spec.rb | 26 ++++++++++++++++++------ spec/lib/gitlab/ci/config/node/image_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/key_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/paths_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/script_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/services_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/stages_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/validator_spec.rb | 3 ++- 16 files changed, 64 insertions(+), 36 deletions(-) diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb index 251d7aa9097..01a9ef511ee 100644 --- a/lib/gitlab/ci/config/node/cache.rb +++ b/lib/gitlab/ci/config/node/cache.rb @@ -27,7 +27,8 @@ module Gitlab def keys if unknown_keys.any? - errors.add(:config, "contains unknown keys #{unknown_keys}") + unknown_list = unknown_keys.join(', ') + errors.add(:config, "contains unknown keys: #{unknown_list}") end end end diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 46a473ad092..4889a21a234 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -26,7 +26,10 @@ module Gitlab private def create_node(key, factory) - factory.with(value: @config[key], key: key) + factory.with(value: @config[key]) + factory.with(parent: self) + factory.with(key: key) + factory.create! end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 17d04fbdfec..985d1705191 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -34,6 +34,10 @@ module Gitlab self.class.nodes.none? end + def ancestors + @parent ? @parent.ancestors + [@parent] : [] + end + def valid? errors.none? end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 4ca379712c6..65919ef1eeb 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -30,8 +30,11 @@ module Gitlab node :types, Stages, description: 'Stages for this pipeline (deprecated key).' - helpers :before_script, :image, :services, :after_script, :variables, - :stages, :types + node :cache, Cache, + description: 'Configure caching between build jobs.' + + helpers :before_script, :image, :services, :after_script, + :variables, :stages, :types, :cache def stages stages_defined? ? stages_value : types_value diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index d898d521548..94a8af4d080 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -13,7 +13,7 @@ module Gitlab def messages errors.full_messages.map do |error| - "#{location} #{error}".humanize + "#{location} #{error}".downcase end end @@ -24,7 +24,9 @@ module Gitlab private def location - key || @node.class.name.demodulize.underscore + predecessors = ancestors.map(&:key).compact + current = key || @node.class.name.demodulize.underscore + predecessors.append(current).join(':') 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 147301b3128..262a91fedff 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -600,7 +600,7 @@ module Ci expect { GitlabCiYamlProcessor.new(config) }.to raise_error( GitlabCiYamlProcessor::ValidationError, - 'Cache config has unknown parameter: invalid' + 'cache config contains unknown keys: invalid' ) end end @@ -964,7 +964,7 @@ EOT config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Before script config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script config should be an array of strings") end it "returns errors if job before_script parameter is not an array of strings" do @@ -978,7 +978,7 @@ EOT config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "After script config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "after_script config should be an array of strings") end it "returns errors if job after_script parameter is not an array of strings" do @@ -992,7 +992,7 @@ EOT config = YAML.dump({ image: ["test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Image config should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a string") end it "returns errors if job name is blank" do @@ -1020,14 +1020,14 @@ EOT config = YAML.dump({ services: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Services config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings") end it "returns errors if services parameter is not an array of strings" do config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Services config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings") end it "returns errors if job services parameter is not an array" do @@ -1097,28 +1097,28 @@ EOT config = YAML.dump({ stages: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Stages config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages config should be an array of strings") end it "returns errors if stages is not an array of strings" do config = YAML.dump({ stages: [true, "test"], rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Stages config should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages config should be an array of strings") end it "returns errors if variables is not a map" do config = YAML.dump({ variables: "test", rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables config should be a hash of key value pairs") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables config should be a hash of key value pairs") end it "returns errors if variables is not a map of key-value strings" do config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Variables config should be a hash of key value pairs") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables config should be a hash of key value pairs") end it "returns errors if job when is not on_success, on_failure or always" do @@ -1174,21 +1174,21 @@ EOT config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:untracked parameter should be an boolean") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:untracked config should be a boolean value") end it "returns errors if cache:paths is not an array of strings" do config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths config should be an array of strings") end it "returns errors if cache:key is not a string" do config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:key parameter should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:key config should be a string or symbol") end it "returns errors if job cache:key is not an a string" do diff --git a/spec/lib/gitlab/ci/config/node/boolean_spec.rb b/spec/lib/gitlab/ci/config/node/boolean_spec.rb index 97f13b2d5fc..32639296e6d 100644 --- a/spec/lib/gitlab/ci/config/node/boolean_spec.rb +++ b/spec/lib/gitlab/ci/config/node/boolean_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::Node::Boolean do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'Boolean config should be a boolean value' + .to include 'boolean config should be a boolean value' end end end diff --git a/spec/lib/gitlab/ci/config/node/cache_spec.rb b/spec/lib/gitlab/ci/config/node/cache_spec.rb index d6428f6b996..50f619ce26e 100644 --- a/spec/lib/gitlab/ci/config/node/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/node/cache_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::Ci::Config::Node::Cache do it 'reports errors with config value' do expect(entry.errors) - .to include 'Cache config should be a hash' + .to include 'cache config should be a hash' end end @@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::Node::Cache do it 'reports error with descendants' do expect(entry.errors) - .to include 'Key config should be a string or symbol' + .to include 'key config should be a string or symbol' end end @@ -51,7 +51,7 @@ describe Gitlab::Ci::Config::Node::Cache do it 'reports error with descendants' do expect(entry.errors) - .to include 'Cache config contains unknown keys [:invalid]' + .to include 'cache config contains unknown keys: invalid' end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index cf7ab13c8b1..c87c9e97bc8 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -21,7 +21,8 @@ describe Gitlab::Ci::Config::Node::Global do services: ['postgres:9.1', 'mysql:5.5'], variables: { VAR: 'value' }, after_script: ['make clean'], - stages: ['build', 'pages'] } + stages: ['build', 'pages'], + cache: { key: 'k', untracked: true, paths: ['public/'] } } end describe '#process!' do @@ -32,7 +33,7 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 7 + expect(global.nodes.count).to eq 8 end it 'creates node object using valid class' do @@ -112,20 +113,27 @@ describe Gitlab::Ci::Config::Node::Global do end end end + + describe '#cache' do + it 'returns cache configuration' do + expect(global.cache) + .to eq(key: 'k', untracked: true, paths: ['public/']) + end + end end end context 'when most of entires not defined' do - let(:hash) { { rspec: {} } } + let(:hash) { { cache: { key: 'a' }, rspec: {} } } before { global.process! } describe '#nodes' do it 'instantizes all nodes' do - expect(global.nodes.count).to eq 7 + expect(global.nodes.count).to eq 8 end it 'contains undefined nodes' do - expect(global.nodes.last) + expect(global.nodes.first) .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined end end @@ -141,6 +149,12 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.stages).to eq %w[build test deploy] end end + + describe '#cache' do + it 'returns correct cache definition' do + expect(global.cache).to eq(key: 'a') + end + end end ## @@ -177,7 +191,7 @@ describe Gitlab::Ci::Config::Node::Global do describe '#errors' do it 'reports errors from child nodes' do expect(global.errors) - .to include 'Before script config should be an array of strings' + .to include 'before_script config should be an array of strings' end end diff --git a/spec/lib/gitlab/ci/config/node/image_spec.rb b/spec/lib/gitlab/ci/config/node/image_spec.rb index 0b0821ca558..d11bb39f328 100644 --- a/spec/lib/gitlab/ci/config/node/image_spec.rb +++ b/spec/lib/gitlab/ci/config/node/image_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::Ci::Config::Node::Image do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'Image config should be a string' + .to include 'image config should be a string' end end diff --git a/spec/lib/gitlab/ci/config/node/key_spec.rb b/spec/lib/gitlab/ci/config/node/key_spec.rb index 23e7fc46201..8cda43173fe 100644 --- a/spec/lib/gitlab/ci/config/node/key_spec.rb +++ b/spec/lib/gitlab/ci/config/node/key_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::Node::Key do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'Key config should be a string or symbol' + .to include 'key config should be a string or symbol' end end end diff --git a/spec/lib/gitlab/ci/config/node/paths_spec.rb b/spec/lib/gitlab/ci/config/node/paths_spec.rb index 0d95ad8abda..6fd744b3975 100644 --- a/spec/lib/gitlab/ci/config/node/paths_spec.rb +++ b/spec/lib/gitlab/ci/config/node/paths_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::Node::Paths do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'Paths config should be an array of strings' + .to include 'paths config should be an array of strings' end end end diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb index abd43aa1ee9..ee7395362a9 100644 --- a/spec/lib/gitlab/ci/config/node/script_spec.rb +++ b/spec/lib/gitlab/ci/config/node/script_spec.rb @@ -34,7 +34,7 @@ describe Gitlab::Ci::Config::Node::Script do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'Script config should be an array of strings' + .to include 'script config should be an array of strings' end end diff --git a/spec/lib/gitlab/ci/config/node/services_spec.rb b/spec/lib/gitlab/ci/config/node/services_spec.rb index e38f6f6923f..be0fe46befd 100644 --- a/spec/lib/gitlab/ci/config/node/services_spec.rb +++ b/spec/lib/gitlab/ci/config/node/services_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::Node::Services do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'Services config should be an array of strings' + .to include 'services config should be an array of strings' end end diff --git a/spec/lib/gitlab/ci/config/node/stages_spec.rb b/spec/lib/gitlab/ci/config/node/stages_spec.rb index dbf2eb8993d..1a3818d8997 100644 --- a/spec/lib/gitlab/ci/config/node/stages_spec.rb +++ b/spec/lib/gitlab/ci/config/node/stages_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::Node::Stages do describe '#errors' do it 'saves errors' do expect(entry.errors) - .to include 'Stages config should be an array of strings' + .to include 'stages config should be an array of strings' end end diff --git a/spec/lib/gitlab/ci/config/node/validator_spec.rb b/spec/lib/gitlab/ci/config/node/validator_spec.rb index 87a1bbf55c0..090fd63b844 100644 --- a/spec/lib/gitlab/ci/config/node/validator_spec.rb +++ b/spec/lib/gitlab/ci/config/node/validator_spec.rb @@ -7,6 +7,7 @@ describe Gitlab::Ci::Config::Node::Validator do before do allow(node).to receive(:key).and_return('node') + allow(node).to receive(:ancestors).and_return([]) end describe 'delegated validator' do @@ -47,7 +48,7 @@ describe Gitlab::Ci::Config::Node::Validator do validator_instance.validate expect(validator_instance.messages) - .to include "Node test attribute can't be blank" + .to include "node test attribute can't be blank" end end end -- cgit v1.2.1 From 7759242ae5da221ebe02e9e4b79be3e6aadc9bc6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 29 Jun 2016 09:49:46 +0200 Subject: Move global CI cache configuration to new CI classes --- lib/ci/gitlab_ci_yaml_processor.rb | 30 ++---------------------------- lib/gitlab/ci/config.rb | 2 +- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 33492775fe1..01ef13df57a 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -13,7 +13,7 @@ module Ci ALLOWED_CACHE_KEYS = [:key, :untracked, :paths] ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in] - attr_reader :path, :cache + attr_reader :path, :cache, :stages def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) @@ -44,10 +44,6 @@ module Ci end end - def stages - @stages - end - def global_variables @variables end @@ -68,8 +64,8 @@ module Ci @services = @ci_config.services @variables = @ci_config.variables @stages = @ci_config.stages + @cache = @ci_config.cache - @cache = @config[:cache] @jobs = {} @config.except!(*ALLOWED_YAML_KEYS) @@ -116,8 +112,6 @@ module Ci end def validate! - validate_global_cache! if @cache - @jobs.each do |name, job| validate_job!(name, job) end @@ -125,26 +119,6 @@ module Ci true end - def validate_global_cache! - @cache.keys.each do |key| - unless ALLOWED_CACHE_KEYS.include?(key) - raise ValidationError, "Cache config has unknown parameter: #{key}" - end - end - - if @cache[:key] && !validate_string(@cache[:key]) - raise ValidationError, "cache:key parameter should be a string" - end - - if @cache[:untracked] && !validate_boolean(@cache[:untracked]) - raise ValidationError, "cache:untracked parameter should be an boolean" - end - - if @cache[:paths] && !validate_array_of_strings(@cache[:paths]) - raise ValidationError, "cache:paths parameter should be an array of strings" - end - end - def validate_job!(name, job) validate_job_name!(name) validate_job_keys!(name, job) diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 61a2d2069a3..e6cc1529760 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -8,7 +8,7 @@ module Gitlab # Temporary delegations that should be removed after refactoring # delegate :before_script, :image, :services, :after_script, :variables, - :stages, to: :@global + :stages, :cache, to: :@global def initialize(config) @config = Loader.new(config).load! -- cgit v1.2.1 From b85d4969a973862414560bd23b5ff4192dfaa372 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 29 Jun 2016 10:02:02 +0200 Subject: Return compound value if CI config node is composite --- lib/gitlab/ci/config/node/entry.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 985d1705191..8fece12232b 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -47,7 +47,12 @@ module Gitlab end def value - @config + if leaf? + @config + else + defined = @nodes.select { |_key, value| value.defined? } + Hash[(defined).map { |key, node| [key, node.value] }] + end end def defined? -- cgit v1.2.1 From c8c930f3ff6e1218e7614e46874bb5279bc30fe9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 29 Jun 2016 10:46:30 +0200 Subject: Add CI config entry validator for allowed keys --- lib/gitlab/ci/config/node/cache.rb | 18 +----------------- lib/gitlab/ci/config/node/entry.rb | 2 +- lib/gitlab/ci/config/node/validator.rb | 5 +++++ lib/gitlab/ci/config/node/validators.rb | 10 ++++++++++ 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb index 01a9ef511ee..d81b2121a99 100644 --- a/lib/gitlab/ci/config/node/cache.rb +++ b/lib/gitlab/ci/config/node/cache.rb @@ -18,23 +18,7 @@ module Gitlab description: 'Specify which paths should be cached across builds.' validations do - validate :keys - - def unknown_keys - return [] unless config.is_a?(Hash) - config.keys - allowed_keys - end - - def keys - if unknown_keys.any? - unknown_list = unknown_keys.join(', ') - errors.add(:config, "contains unknown keys: #{unknown_list}") - end - end - end - - def allowed_keys - self.class.nodes.keys + validates :config, allowed_keys: true end end end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 8fece12232b..9e79e170a4f 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -51,7 +51,7 @@ module Gitlab @config else defined = @nodes.select { |_key, value| value.defined? } - Hash[(defined).map { |key, node| [key, node.value] }] + Hash[defined.map { |key, node| [key, node.value] }] end end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index 94a8af4d080..1ba2e1dc59d 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -21,6 +21,11 @@ module Gitlab 'Validator' end + def unknown_keys + return [] unless config.is_a?(Hash) + config.keys - @node.class.nodes.keys + end + private def location diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index 4082c161e81..7b2f57990b5 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -3,6 +3,16 @@ module Gitlab class Config module Node module Validators + class AllowedKeysValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if record.unknown_keys.any? + unknown_list = record.unknown_keys.join(', ') + record.errors.add(:config, + "contains unknown keys: #{unknown_list}") + end + end + end + class ArrayOfStringsValidator < ActiveModel::EachValidator include LegacyValidationHelpers -- cgit v1.2.1 From e65a703122fa4fa6c2cce2aeb76904d630e1c389 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 Jun 2016 10:38:32 +0100 Subject: Updating padding in dropdown menu --- app/assets/stylesheets/framework/dropdowns.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index b39b8cbf50b..2a90a1fef37 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -79,7 +79,7 @@ margin-bottom: 0; font-size: 15px; font-weight: normal; - padding: 10px 0; + padding: 8px 0; background-color: $dropdown-bg; border: 1px solid $dropdown-border-color; border-radius: $border-radius-base; @@ -103,12 +103,12 @@ li { text-align: left; list-style: none; - padding: 0 10px; + padding: 0 8px; } .divider { height: 1px; - margin: 8px 10px; + margin: 8px; padding: 0; background-color: $dropdown-divider-color; } @@ -124,7 +124,7 @@ a { display: block; position: relative; - padding: 5px 10px; + padding: 5px 8px; color: $dropdown-link-color; line-height: initial; text-overflow: ellipsis; -- cgit v1.2.1 From 9d8dca08e440ccb730f20dcd79b3b47aef8aeb2e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 29 Jun 2016 17:44:39 +0800 Subject: Use AR callbacks as suggested by: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4964#note_12744656 --- app/models/ci/build.rb | 6 +++++- lib/ci/api/builds.rb | 1 - spec/requests/ci/api/builds_spec.rb | 8 ++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 0f8c9511ce1..2079d5a2178 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -19,6 +19,7 @@ module Ci acts_as_taggable + before_save :update_artifacts_size, if: :artifacts_file_changed? before_destroy { project } after_create :execute_hooks @@ -340,7 +341,6 @@ module Ci def erase_artifacts! remove_artifacts_file! remove_artifacts_metadata! - self.artifacts_size = nil save end @@ -381,6 +381,10 @@ module Ci private + def update_artifacts_size + self.artifacts_size = artifacts_file.size + end + def erase_trace! self.trace = nil end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index f6a8d907066..260ac81f5fa 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -147,7 +147,6 @@ module Ci build.artifacts_file = artifacts build.artifacts_metadata = metadata build.artifacts_expire_in = params['expire_in'] - build.artifacts_size = artifacts.size if build.save present(build, with: Entities::BuildDetails) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index de1ec8fd40d..64cb7dd12d0 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -476,13 +476,17 @@ describe Ci::API::API do describe 'DELETE /builds/:id/artifacts' do let(:build) { create(:ci_build, :artifacts) } - before { delete delete_url, token: build.token } + + before do + delete delete_url, token: build.token + build.reload + end it 'should remove build artifacts' do expect(response).to have_http_status(200) expect(build.artifacts_file.exists?).to be_falsy expect(build.artifacts_metadata.exists?).to be_falsy - expect(build.artifacts_size).to be_falsy + expect(build.artifacts_size).to eq(0) end end -- cgit v1.2.1 From 7ef11ce3de797aa8ad0c39245e78aedd91ffa84c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 29 Jun 2016 12:20:45 +0200 Subject: Explicitly define entry node class in new CI config --- lib/gitlab/ci/config/node/cache.rb | 6 +++--- lib/gitlab/ci/config/node/global.rb | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb index d81b2121a99..cdf8ba2e35d 100644 --- a/lib/gitlab/ci/config/node/cache.rb +++ b/lib/gitlab/ci/config/node/cache.rb @@ -8,13 +8,13 @@ module Gitlab class Cache < Entry include Configurable - node :key, Key, + node :key, Node::Key, description: 'Cache key used to define a cache affinity.' - node :untracked, Boolean, + node :untracked, Node::Boolean, description: 'Cache all untracked files.' - node :paths, Paths, + node :paths, Node::Paths, description: 'Specify which paths should be cached across builds.' validations do diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 65919ef1eeb..fec2fe564ac 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -9,28 +9,28 @@ module Gitlab class Global < Entry include Configurable - node :before_script, Script, + node :before_script, Node::Script, description: 'Script that will be executed before each job.' - node :image, Image, + node :image, Node::Image, description: 'Docker image that will be used to execute jobs.' - node :services, Services, + node :services, Node::Services, description: 'Docker images that will be linked to the container.' - node :after_script, Script, + node :after_script, Node::Script, description: 'Script that will be executed after each job.' - node :variables, Variables, + node :variables, Node::Variables, description: 'Environment variables that will be used.' - node :stages, Stages, + node :stages, Node::Stages, description: 'Configuration of stages for this pipeline.' - node :types, Stages, + node :types, Node::Stages, description: 'Stages for this pipeline (deprecated key).' - node :cache, Cache, + node :cache, Node::Cache, description: 'Configure caching between build jobs.' helpers :before_script, :image, :services, :after_script, -- cgit v1.2.1 From 10444f61f85219eb6b2c10586996717d3b0afa8b Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 28 Jun 2016 18:19:04 -0500 Subject: Fixed privilege escalation issue where manually set external users would be reverted back to internal users if they logged in via OAuth and that provider was not in the `external_providers` list. --- doc/integration/omniauth.md | 12 +++++++++--- lib/gitlab/o_auth/user.rb | 2 -- spec/lib/gitlab/o_auth/user_spec.rb | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 820f40f81a9..46b260e7033 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -127,9 +127,15 @@ The chosen OmniAuth provider is now active and can be used to sign in to GitLab This setting was introduced with version 8.7 of GitLab You can define which OmniAuth providers you want to be `external` so that all users -creating accounts via these providers will not be able to have access to internal -projects. You will need to use the full name of the provider, like `google_oauth2` -for Google. Refer to the examples for the full names of the supported providers. +**creating accounts, or logging in via these providers** will not be able to have +access to internal projects. You will need to use the full name of the provider, +like `google_oauth2` for Google. Refer to the examples for the full names of the +supported providers. + +>**Note:** +If you decide to remove an OmniAuth provider from the external providers list +you will need to manually update the users that use this method to login, if you +want their accounts to be upgraded to full internal accounts. **For Omnibus installations** diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 7af75a9cc4c..0a91d3918d5 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -56,8 +56,6 @@ module Gitlab if external_provider? && @user @user.external = true - elsif @user - @user.external = false end @user diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 6727a83e58a..fbb5895c2ef 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -51,12 +51,25 @@ describe Gitlab::OAuth::User, lib: true do end context 'provider was external, now has been removed' do - it 'should mark existing user internal' do + it 'should not mark external user as internal' do create(:omniauth_user, extern_uid: 'my-uid', provider: 'twitter', external: true) stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['facebook']) oauth_user.save expect(gl_user).to be_valid - expect(gl_user.external).to be_falsey + expect(gl_user.external).to be_truthy + end + end + + context 'provider is not external' do + context 'when adding a new OAuth identity' do + it 'should not promote an external user to internal' do + user = create(:user, email: 'john@mail.com', external: true) + user.identities.create(provider: provider, extern_uid: uid) + + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user.external).to be_truthy + end end end -- cgit v1.2.1 From cee69f8968dd4d6cd3009541ad244b928a24f8d7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 29 Jun 2016 11:22:18 -0700 Subject: Fix broken migration in MySQL Closes #19344 --- db/migrate/20160616102642_remove_duplicated_keys.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb index 00a45d7fe73..c66da4e65d3 100644 --- a/db/migrate/20160616102642_remove_duplicated_keys.rb +++ b/db/migrate/20160616102642_remove_duplicated_keys.rb @@ -9,7 +9,7 @@ class RemoveDuplicatedKeys < ActiveRecord::Migration AND id != ( SELECT id FROM ( SELECT max(id) AS id - FROM keys + FROM #{quote_table_name(:keys)} WHERE fingerprint = #{fingerprint} ) max_ids ) -- cgit v1.2.1 From 094cd21c30513346379fc6e0668f203548b05a92 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 29 Jun 2016 13:09:27 -0700 Subject: Fix missing quote_table_name --- db/migrate/20160616102642_remove_duplicated_keys.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb index c66da4e65d3..180a75e0998 100644 --- a/db/migrate/20160616102642_remove_duplicated_keys.rb +++ b/db/migrate/20160616102642_remove_duplicated_keys.rb @@ -4,7 +4,7 @@ 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 ( -- cgit v1.2.1 From f31f78cea32b1650d5cb0a7784a28848b8446e89 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sat, 25 Jun 2016 15:48:12 -0600 Subject: Add emoji.rb in lib/gitlab instead of using the gitlab_emoji gem. No reason to split it into a separate gem when the gem barely did anything. We can use gemojione directly, making updating gemojione that much easier. Also fix the Rake task and update gemojione to 2.6.1. This adds the EmojiOne Spring update. Changelog: https://github.com/jonathanwiesel/gemojione/blob/master/CHANGELOG.md --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 6 +- app/assets/images/emoji.png | Bin 263346 -> 1025831 bytes app/assets/images/emoji@2x.png | Bin 689076 -> 2492919 bytes app/helpers/issues_helper.rb | 2 +- app/models/award_emoji.rb | 2 +- fixtures/emojis/digests.json | 4078 ++++++++++++++++++------------------- lib/banzai/filter/emoji_filter.rb | 4 +- lib/gitlab/emoji.rb | 21 + lib/tasks/gemojione.rake | 2 +- 11 files changed, 2069 insertions(+), 2049 deletions(-) create mode 100644 lib/gitlab/emoji.rb diff --git a/CHANGELOG b/CHANGELOG index 775ea606813..290077c823f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.10.0 (unreleased) - 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 + - Add Spring EmojiOne updates. - Fix pagination when sorting by columns with lots of ties (like priority) - Exclude email check from the standard health check - Fix changing issue state columns in milestone view diff --git a/Gemfile b/Gemfile index 52de9ef9813..2d5d56b92bc 100644 --- a/Gemfile +++ b/Gemfile @@ -223,7 +223,7 @@ 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' diff --git a/Gemfile.lock b/Gemfile.lock index 4c5350ba639..017a42fe172 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -261,7 +261,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) @@ -280,8 +280,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) @@ -868,10 +866,10 @@ DEPENDENCIES foreman fuubar (~> 2.0.0) gemnasium-gitlab-service (~> 0.2) + gemojione (~> 2.6) github-linguist (~> 4.7.0) github-markup (~> 1.3.1) 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) diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png index 99093d4725a..6bacb0e92b6 100644 Binary files a/app/assets/images/emoji.png and b/app/assets/images/emoji.png differ diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png index 48989942a9f..99588b56616 100644 Binary files a/app/assets/images/emoji@2x.png and b/app/assets/images/emoji@2x.png differ 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/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/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/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/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/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 -- cgit v1.2.1 From a99e5cd810b28dda83d3b7809fdf9f0f8031ef7a Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Tue, 28 Jun 2016 12:17:29 -0600 Subject: Split Cropper.js from the main JavaScript manifest. --- app/assets/javascripts/application.js.coffee | 1 - app/assets/javascripts/dispatcher.js.coffee | 1 - app/assets/javascripts/gl_crop.js.coffee | 152 --------------------- app/assets/javascripts/lib/cropper.js.coffee | 1 + app/assets/javascripts/profile.js.coffee | 80 ----------- .../javascripts/profile/application.js.coffee | 2 + app/assets/javascripts/profile/gl_crop.js.coffee | 152 +++++++++++++++++++++ app/assets/javascripts/profile/profile.js.coffee | 83 +++++++++++ app/views/profiles/show.html.haml | 4 + config/application.rb | 1 + 10 files changed, 243 insertions(+), 234 deletions(-) delete mode 100644 app/assets/javascripts/gl_crop.js.coffee create mode 100644 app/assets/javascripts/lib/cropper.js.coffee delete mode 100644 app/assets/javascripts/profile.js.coffee create mode 100644 app/assets/javascripts/profile/application.js.coffee create mode 100644 app/assets/javascripts/profile/gl_crop.js.coffee create mode 100644 app/assets/javascripts/profile/profile.js.coffee diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index b6dbf2d0cc1..715d71b565a 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -54,7 +54,6 @@ #= require_directory ./u2f #= require_directory . #= require fuzzaldrin-plus -#= require cropper #= require u2f window.slugify = (text) -> diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 7fbff9214cf..6f0ebf4322c 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -129,7 +129,6 @@ class Dispatcher when 'dashboard', 'root' shortcut_handler = new ShortcutsDashboardNavigation() when 'profiles' - new Profile() new NotificationsForm() new NotificationsDropdown() when 'projects' diff --git a/app/assets/javascripts/gl_crop.js.coffee b/app/assets/javascripts/gl_crop.js.coffee deleted file mode 100644 index df9bfdfa6cc..00000000000 --- a/app/assets/javascripts/gl_crop.js.coffee +++ /dev/null @@ -1,152 +0,0 @@ -class GitLabCrop - # Matches everything but the file name - FILENAMEREGEX = /^.*[\\\/]/ - - constructor: (input, opts = {}) -> - @fileInput = $(input) - - # We should rename to avoid spec to fail - # Form will submit the proper input filed with a file using FormData - @fileInput - .attr('name', "#{@fileInput.attr('name')}-trigger") - .attr('id', "#{@fileInput.attr('id')}-trigger") - - # Set defaults - { - @exportWidth = 200 - @exportHeight = 200 - @cropBoxWidth = 200 - @cropBoxHeight = 200 - @form = @fileInput.parents('form') - - # Required params - @filename - @previewImage - @modalCrop - @pickImageEl - @uploadImageBtn - @modalCropImg - } = opts - - # Ensure needed elements are jquery objects - # If selector is provided we will convert them to a jQuery Object - @filename = @getElement(@filename) - @previewImage = @getElement(@previewImage) - @pickImageEl = @getElement(@pickImageEl) - - # Modal elements usually are outside the @form element - @modalCrop = if _.isString(@modalCrop) then $(@modalCrop) else @modalCrop - @uploadImageBtn = if _.isString(@uploadImageBtn) then $(@uploadImageBtn) else @uploadImageBtn - @modalCropImg = if _.isString(@modalCropImg) then $(@modalCropImg) else @modalCropImg - - @cropActionsBtn = @modalCrop.find('[data-method]') - - @bindEvents() - - getElement: (selector) -> - $(selector, @form) - - bindEvents: -> - _this = @ - @fileInput.on 'change', (e) -> - _this.onFileInputChange(e, @) - - @pickImageEl.on 'click', @onPickImageClick - @modalCrop.on 'shown.bs.modal', @onModalShow - @modalCrop.on 'hidden.bs.modal', @onModalHide - @uploadImageBtn.on 'click', @onUploadImageBtnClick - @cropActionsBtn.on 'click', (e) -> - btn = @ - _this.onActionBtnClick(btn) - @croppedImageBlob = null - - onPickImageClick: => - @fileInput.trigger('click') - - onModalShow: => - _this = @ - @modalCropImg.cropper( - viewMode: 1 - center: false - aspectRatio: 1 - modal: true - scalable: false - rotatable: false - zoomable: true - dragMode: 'move' - guides: false - zoomOnTouch: false - zoomOnWheel: false - cropBoxMovable: false - cropBoxResizable: false - toggleDragModeOnDblclick: false - built: -> - $image = $(@) - container = $image.cropper 'getContainerData' - cropBoxWidth = _this.cropBoxWidth; - cropBoxHeight = _this.cropBoxHeight; - - $image.cropper('setCropBoxData', - width: cropBoxWidth, - height: cropBoxHeight, - left: (container.width - cropBoxWidth) / 2, - top: (container.height - cropBoxHeight) / 2 - ) - ) - - - onModalHide: => - @modalCropImg - .attr('src', '') # Remove attached image - .cropper('destroy') # Destroy cropper instance - - onUploadImageBtnClick: (e) => - e.preventDefault() - @setBlob() - @setPreview() - @modalCrop.modal('hide') - @fileInput.val('') - - onActionBtnClick: (btn) -> - data = $(btn).data() - - if @modalCropImg.data('cropper') && data.method - result = @modalCropImg.cropper data.method, data.option - - onFileInputChange: (e, input) -> - @readFile(input) - - readFile: (input) -> - _this = @ - reader = new FileReader - reader.onload = -> - _this.modalCropImg.attr('src', reader.result) - _this.modalCrop.modal('show') - - reader.readAsDataURL(input.files[0]) - - dataURLtoBlob: (dataURL) -> - binary = atob(dataURL.split(',')[1]) - array = [] - for v, k in binary - array.push(binary.charCodeAt(k)) - new Blob([new Uint8Array(array)], type: 'image/png') - - setPreview: -> - @previewImage.attr('src', @dataURL) - filename = @fileInput.val().replace(FILENAMEREGEX, '') - @filename.text(filename) - - setBlob: -> - @dataURL = @modalCropImg.cropper('getCroppedCanvas', - width: 200 - height: 200 - ).toDataURL('image/png') - @croppedImageBlob = @dataURLtoBlob(@dataURL) - - getBlob: -> - @croppedImageBlob - -$.fn.glCrop = (opts) -> - return @.each -> - $(@).data('glcrop', new GitLabCrop(@, opts)) 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/profile.js.coffee b/app/assets/javascripts/profile.js.coffee deleted file mode 100644 index 1583d1ba6f9..00000000000 --- a/app/assets/javascripts/profile.js.coffee +++ /dev/null @@ -1,80 +0,0 @@ -class @Profile - constructor: (opts = {}) -> - { - @form = $('.edit-user') - } = opts - - # Automatically submit the Preferences form when any of its radio buttons change - $('.js-preferences-form').on 'change.preference', 'input[type=radio]', -> - $(this).parents('form').submit() - - # Automatically submit email form when it changes - $('#user_notification_email').on 'change', -> - $(this).parents('form').submit() - - $('.update-username').on 'ajax:before', -> - $('.loading-username').show() - $(this).find('.update-success').hide() - $(this).find('.update-failed').hide() - - $('.update-username').on 'ajax:complete', -> - $('.loading-username').hide() - $(this).find('.btn-save').enable() - $(this).find('.loading-gif').hide() - - $('.update-notifications').on 'ajax:success', (e, data) -> - if data.saved - new Flash("Notification settings saved", "notice") - else - new Flash("Failed to save new settings", "alert") - - @bindEvents() - - cropOpts = - filename: '.js-avatar-filename' - previewImage: '.avatar-image .avatar' - modalCrop: '.modal-profile-crop' - pickImageEl: '.js-choose-user-avatar-button' - uploadImageBtn: '.js-upload-user-avatar' - modalCropImg: '.modal-profile-crop-image' - - @avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data 'glcrop' - - bindEvents: -> - @form.on 'submit', @onSubmitForm - - onSubmitForm: (e) => - e.preventDefault() - @saveForm() - - saveForm: -> - self = @ - formData = new FormData(@form[0]) - - avatarBlob = @avatarGlCrop.getBlob() - formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob? - - $.ajax - url: @form.attr('action') - type: @form.attr('method') - data: formData - dataType: "json" - processData: false - contentType: false - success: (response) -> - new Flash(response.message, 'notice') - error: (jqXHR) -> - new Flash(jqXHR.responseJSON.message, 'alert') - complete: -> - window.scrollTo 0, 0 - # Enable submit button after requests ends - self.form.find(':input[disabled]').enable() - -$ -> - # Extract the SSH Key title from its comment - $(document).on 'focusout.ssh_key', '#key_key', -> - $title = $('#key_title') - comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/) - - if comment && comment.length > 1 && $title.val() == '' - $title.val(comment[1]).change() 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/profile/gl_crop.js.coffee b/app/assets/javascripts/profile/gl_crop.js.coffee new file mode 100644 index 00000000000..df9bfdfa6cc --- /dev/null +++ b/app/assets/javascripts/profile/gl_crop.js.coffee @@ -0,0 +1,152 @@ +class GitLabCrop + # Matches everything but the file name + FILENAMEREGEX = /^.*[\\\/]/ + + constructor: (input, opts = {}) -> + @fileInput = $(input) + + # We should rename to avoid spec to fail + # Form will submit the proper input filed with a file using FormData + @fileInput + .attr('name', "#{@fileInput.attr('name')}-trigger") + .attr('id', "#{@fileInput.attr('id')}-trigger") + + # Set defaults + { + @exportWidth = 200 + @exportHeight = 200 + @cropBoxWidth = 200 + @cropBoxHeight = 200 + @form = @fileInput.parents('form') + + # Required params + @filename + @previewImage + @modalCrop + @pickImageEl + @uploadImageBtn + @modalCropImg + } = opts + + # Ensure needed elements are jquery objects + # If selector is provided we will convert them to a jQuery Object + @filename = @getElement(@filename) + @previewImage = @getElement(@previewImage) + @pickImageEl = @getElement(@pickImageEl) + + # Modal elements usually are outside the @form element + @modalCrop = if _.isString(@modalCrop) then $(@modalCrop) else @modalCrop + @uploadImageBtn = if _.isString(@uploadImageBtn) then $(@uploadImageBtn) else @uploadImageBtn + @modalCropImg = if _.isString(@modalCropImg) then $(@modalCropImg) else @modalCropImg + + @cropActionsBtn = @modalCrop.find('[data-method]') + + @bindEvents() + + getElement: (selector) -> + $(selector, @form) + + bindEvents: -> + _this = @ + @fileInput.on 'change', (e) -> + _this.onFileInputChange(e, @) + + @pickImageEl.on 'click', @onPickImageClick + @modalCrop.on 'shown.bs.modal', @onModalShow + @modalCrop.on 'hidden.bs.modal', @onModalHide + @uploadImageBtn.on 'click', @onUploadImageBtnClick + @cropActionsBtn.on 'click', (e) -> + btn = @ + _this.onActionBtnClick(btn) + @croppedImageBlob = null + + onPickImageClick: => + @fileInput.trigger('click') + + onModalShow: => + _this = @ + @modalCropImg.cropper( + viewMode: 1 + center: false + aspectRatio: 1 + modal: true + scalable: false + rotatable: false + zoomable: true + dragMode: 'move' + guides: false + zoomOnTouch: false + zoomOnWheel: false + cropBoxMovable: false + cropBoxResizable: false + toggleDragModeOnDblclick: false + built: -> + $image = $(@) + container = $image.cropper 'getContainerData' + cropBoxWidth = _this.cropBoxWidth; + cropBoxHeight = _this.cropBoxHeight; + + $image.cropper('setCropBoxData', + width: cropBoxWidth, + height: cropBoxHeight, + left: (container.width - cropBoxWidth) / 2, + top: (container.height - cropBoxHeight) / 2 + ) + ) + + + onModalHide: => + @modalCropImg + .attr('src', '') # Remove attached image + .cropper('destroy') # Destroy cropper instance + + onUploadImageBtnClick: (e) => + e.preventDefault() + @setBlob() + @setPreview() + @modalCrop.modal('hide') + @fileInput.val('') + + onActionBtnClick: (btn) -> + data = $(btn).data() + + if @modalCropImg.data('cropper') && data.method + result = @modalCropImg.cropper data.method, data.option + + onFileInputChange: (e, input) -> + @readFile(input) + + readFile: (input) -> + _this = @ + reader = new FileReader + reader.onload = -> + _this.modalCropImg.attr('src', reader.result) + _this.modalCrop.modal('show') + + reader.readAsDataURL(input.files[0]) + + dataURLtoBlob: (dataURL) -> + binary = atob(dataURL.split(',')[1]) + array = [] + for v, k in binary + array.push(binary.charCodeAt(k)) + new Blob([new Uint8Array(array)], type: 'image/png') + + setPreview: -> + @previewImage.attr('src', @dataURL) + filename = @fileInput.val().replace(FILENAMEREGEX, '') + @filename.text(filename) + + setBlob: -> + @dataURL = @modalCropImg.cropper('getCroppedCanvas', + width: 200 + height: 200 + ).toDataURL('image/png') + @croppedImageBlob = @dataURLtoBlob(@dataURL) + + getBlob: -> + @croppedImageBlob + +$.fn.glCrop = (opts) -> + return @.each -> + $(@).data('glcrop', new GitLabCrop(@, opts)) diff --git a/app/assets/javascripts/profile/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee new file mode 100644 index 00000000000..b276242f506 --- /dev/null +++ b/app/assets/javascripts/profile/profile.js.coffee @@ -0,0 +1,83 @@ +class @Profile + constructor: (opts = {}) -> + { + @form = $('.edit-user') + } = opts + + # Automatically submit the Preferences form when any of its radio buttons change + $('.js-preferences-form').on 'change.preference', 'input[type=radio]', -> + $(this).parents('form').submit() + + # Automatically submit email form when it changes + $('#user_notification_email').on 'change', -> + $(this).parents('form').submit() + + $('.update-username').on 'ajax:before', -> + $('.loading-username').show() + $(this).find('.update-success').hide() + $(this).find('.update-failed').hide() + + $('.update-username').on 'ajax:complete', -> + $('.loading-username').hide() + $(this).find('.btn-save').enable() + $(this).find('.loading-gif').hide() + + $('.update-notifications').on 'ajax:success', (e, data) -> + if data.saved + new Flash("Notification settings saved", "notice") + else + new Flash("Failed to save new settings", "alert") + + @bindEvents() + + cropOpts = + filename: '.js-avatar-filename' + previewImage: '.avatar-image .avatar' + modalCrop: '.modal-profile-crop' + pickImageEl: '.js-choose-user-avatar-button' + uploadImageBtn: '.js-upload-user-avatar' + modalCropImg: '.modal-profile-crop-image' + + @avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data 'glcrop' + + bindEvents: -> + @form.on 'submit', @onSubmitForm + + onSubmitForm: (e) => + e.preventDefault() + @saveForm() + + saveForm: -> + self = @ + formData = new FormData(@form[0]) + + avatarBlob = @avatarGlCrop.getBlob() + formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob? + + $.ajax + url: @form.attr('action') + type: @form.attr('method') + data: formData + dataType: "json" + processData: false + contentType: false + success: (response) -> + new Flash(response.message, 'notice') + error: (jqXHR) -> + new Flash(jqXHR.responseJSON.message, 'alert') + complete: -> + window.scrollTo 0, 0 + # Enable submit button after requests ends + self.form.find(':input[disabled]').enable() + +$ -> + # Extract the SSH Key title from its comment + $(document).on 'focusout.ssh_key', '#key_key', -> + $title = $('#key_title') + comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/) + + if comment && comment.length > 1 && $title.val() == '' + $title.val(comment[1]).change() + + if $('body').attr('data-page').split(':').first() == 'profiles' + new Profile() diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index eef50d887c7..e8a70cfd84b 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,3 +1,7 @@ +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/cropper.js') + = page_specific_javascript_tag('profile/application.js') + = 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/config/application.rb b/config/application.rb index 2b0595ede2b..21e7cc7b6e8 100644 --- a/config/application.rb +++ b/config/application.rb @@ -84,6 +84,7 @@ 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" -- cgit v1.2.1 From 7b9b2ce3c520df8475f1fe4b8aa72a8ce3a687b4 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 29 Jun 2016 15:32:52 -0600 Subject: Add a helper function for getting the page path from JS. --- app/assets/javascripts/lib/utils/common_utils.js.coffee | 7 +++++-- app/assets/javascripts/profile/profile.js.coffee | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) 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/profile/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee index b276242f506..f3b05f2c646 100644 --- a/app/assets/javascripts/profile/profile.js.coffee +++ b/app/assets/javascripts/profile/profile.js.coffee @@ -79,5 +79,5 @@ $ -> if comment && comment.length > 1 && $title.val() == '' $title.val(comment[1]).change() - if $('body').attr('data-page').split(':').first() == 'profiles' + if gl.utils.getPagePath() == 'profiles' new Profile() -- cgit v1.2.1 From 491c213af67ab65ea3f4b40e8cf39558fb378e6b Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Tue, 21 Jun 2016 16:00:01 -0600 Subject: Fix unescaped strings in Underscore templates. --- app/assets/javascripts/issuable.js.coffee | 8 ++++---- app/assets/javascripts/labels_select.js.coffee | 6 +++--- app/assets/javascripts/milestone_select.js.coffee | 6 +++--- app/assets/javascripts/users_select.js.coffee | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee index 0527c66461c..c71d4ecf505 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){ %> - - - <%= _.escape(label.title) %> + + + <%- label.title %> - diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index e95fd96a83f..ce859fedb2d 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){ %> - issues?label_name[]=<%= _.escape(label.title) %>"> - - <%= _.escape(label.title) %> + issues?label_name[]=<%- label.title %>"> + + <%- label.title %> <% }); %>' 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( - '<%= _.escape(title) %>' + '<%- title %>' ) milestoneLinkNoneTemplate = 'None' collapsedSidebarLabelTemplate = _.template( - ' - <%= _.escape(title) %> + ' + <%- title %> ' ) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 2548efb2186..4e032ab1ff1 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -61,8 +61,8 @@ class @UsersSelect collapsedAssigneeTemplate = _.template( '<% if( avatar ) { %> - - + + Toni Boehm <% } else { %> @@ -72,13 +72,13 @@ class @UsersSelect assigneeTemplate = _.template( '<% if (username) { %> - + <% if( avatar ) { %> - + <% } %> - <%= name %> + <%- name %> - @<%= username %> + @<%- username %> <% } else { %> -- cgit v1.2.1 From b4f03e8b1e94863949c567a305c8072b34d7e6a1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 30 Jun 2016 12:59:17 +0200 Subject: Improve description of CI types node and in specs --- lib/gitlab/ci/config/node/global.rb | 2 +- spec/lib/gitlab/ci/config/node/undefined_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index fec2fe564ac..f92e1eccbcf 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -28,7 +28,7 @@ module Gitlab description: 'Configuration of stages for this pipeline.' node :types, Node::Stages, - description: 'Stages for this pipeline (deprecated key).' + description: 'Deprecated: stages for this pipeline.' node :cache, Node::Cache, description: 'Configure caching between build jobs.' diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb index 4318dfe6e53..0c6608d906d 100644 --- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -27,13 +27,13 @@ describe Gitlab::Ci::Config::Node::Undefined do allow(entry).to receive(:default).and_return('some value') end - it 'returns default value for entry that is undefined' do + it 'returns default value for entry' do expect(undefined.value).to eq 'some value' end end describe '#undefined?' do - it 'is not a concrete entry that is defined' do + it 'is not a defined entry' do expect(undefined.defined?).to be false end end -- cgit v1.2.1 From 5b893d603dd68f263129523f13e8eb68b67fe790 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 30 Jun 2016 13:17:37 +0200 Subject: few changes based on feedback --- CHANGELOG | 4 +--- app/models/project.rb | 4 ++-- app/validators/addressable_url_validator.rb | 13 +++++-------- db/migrate/20160620110927_fix_no_validatable_import_url.rb | 6 +++--- lib/gitlab/url_sanitizer.rb | 10 +++++++++- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9f76b8fa2d9..118811cdda5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.10.0 (unreleased) - Check for conflicts with existing Project's wiki path when creating a new project. - Add API endpoint for a group issues !4520 (mahcsig) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) + - Set import_url validation to be more strict v 8.9.3 (unreleased) - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem @@ -66,9 +67,6 @@ v 8.9.1 - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 - Remove duplicate 'New Page' button on edit wiki page -v 8.9.1 (unreleased) - - Set import_url validation to be more strict - v 8.9.0 - Fix builds API response not including commit data - Fix error when CI job variables key specified but not defined diff --git a/app/models/project.rb b/app/models/project.rb index 2b1b25ab9d2..89ce61b95ec 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -445,11 +445,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) - rescue Addressable::URI::InvalidURIError - errors.add(:import_url, 'must be a valid URL.') end def import_url diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index 634a15aea01..c97acf7da95 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -18,6 +18,9 @@ # 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") @@ -29,15 +32,9 @@ class AddressableUrlValidator < ActiveModel::EachValidator def valid_url?(value) return false unless value - value.strip! - valid_protocol?(value) && valid_uri?(value) end - def default_options - @default_options ||= { protocols: %w(http https ssh git) } - end - def valid_uri?(value) Addressable::URI.parse(value).is_a?(Addressable::URI) rescue Addressable::URI::InvalidURIError @@ -45,7 +42,7 @@ class AddressableUrlValidator < ActiveModel::EachValidator end def valid_protocol?(value) - options = default_options.merge(self.options) - !!(value =~ /\A#{URI.regexp(options[:protocols])}\z/) + options = DEFAULT_OPTIONS.merge(self.options) + value =~ /\A#{URI.regexp(options[:protocols])}\z/ end end diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb index 9cb84faaec1..e111691ea3c 100644 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -38,8 +38,6 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration def valid_url?(value) return false unless value - value.strip! - valid_uri?(value) && valid_protocol?(value) rescue Addressable::URI::InvalidURIError false @@ -50,11 +48,13 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration end def valid_protocol?(value) - !!(value =~ /\A#{URI.regexp(%w(http https ssh git))}\z/) + value =~ /\A#{URI.regexp(%w(http https ssh git))}\z/ end end def up + return unless defined?(Addressable::URI::InvalidURIError) + say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.') invalid_import_url_project_ids.each { |project_id| cleanup_import_url(project_id) } diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 7d02fe3c971..2eb6085a3ca 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -1,5 +1,9 @@ module Gitlab class UrlSanitizer + + attr_reader :valid + alias_method :valid?, :valid + def self.sanitize(content) regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git']) @@ -7,8 +11,12 @@ module Gitlab end def initialize(url, credentials: nil) - @url = Addressable::URI.parse(url) + @valid = true + @url = Addressable::URI.parse(url.strip) @credentials = credentials + rescue Addressable::URI::InvalidURIError + @valid = false + raise end def sanitized_url -- cgit v1.2.1 From cc324eb4ab6f10386dccd891f6b3fdd14b91d2e6 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 30 Jun 2016 13:46:35 +0200 Subject: Ensure that branch and tag names are given in API --- lib/api/branches.rb | 8 ++++---- lib/api/tags.rb | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 231840148d9..9f9ae75ff65 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -25,7 +25,7 @@ module API # branch (required) - The name of the branch # Example Request: # GET /projects/:id/repository/branches/:branch - get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do + get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do @branch = user_project.repository.branches.find { |item| item.name == params[:branch] } not_found!("Branch") unless @branch present @branch, with: Entities::RepoObject, project: user_project @@ -39,7 +39,7 @@ module API # Example Request: # PUT /projects/:id/repository/branches/:branch/protect put ':id/repository/branches/:branch/protect', - requirements: { branch: /.*/ } do + requirements: { branch: /.+/ } do authorize_admin_project @@ -59,7 +59,7 @@ module API # Example Request: # PUT /projects/:id/repository/branches/:branch/unprotect put ':id/repository/branches/:branch/unprotect', - requirements: { branch: /.*/ } do + requirements: { branch: /.+/ } do authorize_admin_project @@ -101,7 +101,7 @@ module API # Example Request: # DELETE /projects/:id/repository/branches/:branch delete ":id/repository/branches/:branch", - requirements: { branch: /.*/ } do + requirements: { branch: /.+/ } do authorize_push_project result = DeleteBranchService.new(user_project, current_user). execute(params[:branch]) diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 3e1ed3fe5c7..7b675e05fbb 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -61,7 +61,7 @@ module API # tag_name (required) - The name of the tag # Example Request: # DELETE /projects/:id/repository/tags/:tag - delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.*/ } do + delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do authorize_push_project result = DeleteTagService.new(user_project, current_user). execute(params[:tag_name]) @@ -83,7 +83,7 @@ module API # description (required) - Release notes with markdown support # Example Request: # POST /projects/:id/repository/tags/:tag_name/release - post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do + post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do authorize_push_project required_attributes! [:description] result = CreateReleaseService.new(user_project, current_user). @@ -104,7 +104,7 @@ module API # description (required) - Release notes with markdown support # Example Request: # PUT /projects/:id/repository/tags/:tag_name/release - put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do + put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do authorize_push_project required_attributes! [:description] result = UpdateReleaseService.new(user_project, current_user). -- cgit v1.2.1 From 545b92af067832d560846b7ec7cb26caf3302275 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 30 Jun 2016 14:30:07 +0200 Subject: use class method --- lib/gitlab/url_sanitizer.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 2eb6085a3ca..50febfc18f8 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -1,22 +1,23 @@ module Gitlab class UrlSanitizer - attr_reader :valid - alias_method :valid?, :valid - def self.sanitize(content) regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git']) content.gsub(regexp) { |url| new(url).masked_url } end + def self.valid?(url) + Addressable::URI.parse(url.strip) + + true + rescue Addressable::URI::InvalidURIError + false + end + def initialize(url, credentials: nil) - @valid = true @url = Addressable::URI.parse(url.strip) @credentials = credentials - rescue Addressable::URI::InvalidURIError - @valid = false - raise end def sanitized_url -- cgit v1.2.1 From b4676c3337d656bcd8c14edd011b832b6b7ee7e3 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 28 Jun 2016 19:09:35 +0100 Subject: _Hacked_ in a better error message Changed message to 'attaching the file failed' for all attachment errors --- app/assets/javascripts/dropzone_input.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) -> -- cgit v1.2.1 From ef5713546bacc653f598eb692b728e35abdb8ab7 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 30 Jun 2016 17:22:56 +0200 Subject: few more changes from suggestions --- CHANGELOG | 2 -- app/validators/addressable_url_validator.rb | 1 - db/migrate/20160620110927_fix_no_validatable_import_url.rb | 5 ++++- lib/gitlab/url_sanitizer.rb | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5b12ba589b8..3145178d547 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,8 +25,6 @@ v 8.10.0 (unreleased) - Add Bugzilla integration !4930 (iamtjg) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Set import_url validation to be more strict - -v 8.9.3 (unreleased) - Add basic system information like memory and disk usage to the admin panel v 8.9.4 (unreleased) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index c97acf7da95..63761c81721 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -18,7 +18,6 @@ # end # class AddressableUrlValidator < ActiveModel::EachValidator - DEFAULT_OPTIONS = { protocols: %w(http https ssh git) } def validate_each(record, attribute, value) diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb index e111691ea3c..3e3837ab7e9 100644 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -53,7 +53,10 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration end def up - return unless defined?(Addressable::URI::InvalidURIError) + unless defined?(Addressable::URI::InvalidURIError) + say('Skipping cleaning up invalid import URLs as class from Addressable iss missing') + return + end say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.') diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 50febfc18f8..86ed18fb50d 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -1,6 +1,5 @@ module Gitlab class UrlSanitizer - def self.sanitize(content) regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git']) -- cgit v1.2.1 From db0d3fc3e96e5f2b0f642ea3240d5265c3ee659c Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 27 Jun 2016 13:24:08 +0100 Subject: Ensure logged-out users can't see private refs --- CHANGELOG | 3 +++ app/models/concerns/mentionable.rb | 2 +- app/services/todo_service.rb | 2 +- spec/models/concerns/mentionable_spec.rb | 37 ++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 775ea606813..eb5a5f7fcf4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,9 @@ v 8.10.0 (unreleased) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Add basic system information like memory and disk usage to the admin panel +v 8.9.4 (unreleased) + - Ensure references to private repos aren't shown to logged-out users + v 8.9.3 - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 - Fix rendering of commit notes. !4953 diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index f00b5b8497c..8cac47246db 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -45,7 +45,7 @@ module Mentionable def all_references(current_user = nil, text = nil, extractor: nil) extractor ||= Gitlab::ReferenceExtractor. - new(project, current_user || author) + new(project, current_user) if text extractor.analyze(text, author: author) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 239bd17a035..6bb0a72d30e 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -237,7 +237,7 @@ class TodoService end def filter_mentioned_users(project, target, author) - mentioned_users = target.mentioned_users + mentioned_users = target.mentioned_users(author) mentioned_users = reject_users_without_access(mentioned_users, project, target) mentioned_users.delete(author) mentioned_users.uniq diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index cb33edde820..0344dae8b5d 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -29,6 +29,43 @@ describe Issue, "Mentionable" do it { is_expected.not_to include(user2) } end + describe '#referenced_mentionables' do + context 'with an issue on a private project' do + let(:project) { create(:empty_project, :public) } + let(:issue) { create(:issue, project: project) } + let(:public_issue) { create(:issue, project: project) } + let(:private_project) { create(:empty_project, :private) } + let(:private_issue) { create(:issue, project: private_project) } + let(:user) { create(:user) } + + def referenced_issues(current_user) + text = "#{private_issue.to_reference(project)} and #{public_issue.to_reference}" + + issue.referenced_mentionables(current_user, text) + end + + context 'when the current user can see the issue' do + before { private_project.team << [user, Gitlab::Access::DEVELOPER] } + + it 'includes the reference' do + expect(referenced_issues(user)).to contain_exactly(private_issue, public_issue) + end + end + + context 'when the current user cannot see the issue' do + it 'does not include the reference' do + expect(referenced_issues(user)).to contain_exactly(public_issue) + end + end + + context 'when there is no current user' do + it 'does not include the reference' do + expect(referenced_issues(nil)).to contain_exactly(public_issue) + end + end + end + end + describe '#create_cross_references!' do let(:project) { create(:project) } let(:author) { double('author') } -- cgit v1.2.1 From 7b06acea1c13b7fa9067902faaed73c7210f4bb3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 1 Jul 2016 00:00:35 +0800 Subject: Use nil for non-existing files rather than 0 --- app/models/ci/build.rb | 4 +++- spec/requests/ci/api/builds_spec.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 2079d5a2178..850895845f4 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -382,7 +382,9 @@ module Ci private def update_artifacts_size - self.artifacts_size = artifacts_file.size + self.artifacts_size = if artifacts_file.exists? + artifacts_file.size + end end def erase_trace! diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 64cb7dd12d0..666fdbdd2b5 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -486,7 +486,7 @@ describe Ci::API::API do expect(response).to have_http_status(200) expect(build.artifacts_file.exists?).to be_falsy expect(build.artifacts_metadata.exists?).to be_falsy - expect(build.artifacts_size).to eq(0) + expect(build.artifacts_size).to be_nil end end -- cgit v1.2.1 From 26ce833a2143485bb0485c8b01d78561adf7c86d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 30 Jun 2016 18:19:34 +0200 Subject: typo --- db/migrate/20160620110927_fix_no_validatable_import_url.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb index 3e3837ab7e9..82a616c62d9 100644 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -54,7 +54,7 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration def up unless defined?(Addressable::URI::InvalidURIError) - say('Skipping cleaning up invalid import URLs as class from Addressable iss missing') + say('Skipping cleaning up invalid import URLs as class from Addressable is missing') return end -- cgit v1.2.1 From e8c787bb24be3a169b165066ccedf709bee85414 Mon Sep 17 00:00:00 2001 From: Max Raab Date: Thu, 30 Jun 2016 18:29:55 +0200 Subject: Remove not released status --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index d2d1b04f893..cb32920a6df 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. -- cgit v1.2.1 From a87b229b5d6da23e12e34d899f824f1f7e2dc28a Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Thu, 30 Jun 2016 10:42:07 -0600 Subject: Fix preferences tests. --- app/views/profiles/_head.html.haml | 3 +++ app/views/profiles/accounts/show.html.haml | 1 + app/views/profiles/audit_log.html.haml | 1 + app/views/profiles/emails/index.html.haml | 1 + app/views/profiles/keys/show.html.haml | 1 + app/views/profiles/notifications/show.html.haml | 1 + app/views/profiles/personal_access_tokens/index.html.haml | 1 + app/views/profiles/preferences/show.html.haml | 1 + app/views/profiles/show.html.haml | 4 +--- app/views/profiles/two_factor_auths/show.html.haml | 1 + 10 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 app/views/profiles/_head.html.haml 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/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..b4d35dc9a3e 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 diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index e8a70cfd84b..d9fa74fad90 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,6 +1,4 @@ -- content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/cropper.js') - = page_specific_javascript_tag('profile/application.js') += 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..5890456bee2 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,5 +1,6 @@ - page_title 'Two-Factor Authentication', 'Account' - header_title "Two-Factor Authentication", profile_two_factor_auth_path += render 'profiles/head' .row.prepend-top-default .col-lg-3 -- cgit v1.2.1 From 44230e12bb2a7ea3d91a6a9a2bfd1543198f1b9b Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Thu, 30 Jun 2016 16:11:57 -0600 Subject: Upgrade Sprockets and Sprockets Rails. Upgrade Sprockets from 3.6.0 to 3.6.2. Changelog: https://github.com/rails/sprockets/blob/3.x/CHANGELOG.md Upgrade Sprockets Rails from 3.0.4 to 3.1.1. Changelog: https://github.com/rails/sprockets-rails/compare/v3.0.4...v3.1.1 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f99b373dbbd..5e5554d4fc9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -700,10 +700,10 @@ GEM spring (>= 0.9.1) spring-commands-teaspoon (0.0.2) spring (>= 0.9.1) - sprockets (3.6.0) + sprockets (3.6.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.0.4) + sprockets-rails (3.1.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) -- cgit v1.2.1 From eb70f051a6fb69542aa4436d808bf32271219da8 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Thu, 30 Jun 2016 16:18:05 -0600 Subject: Remove quiet_assets in favor of built-in sprockets-rails config. quiet_assets has been seemingly abandoned, and now sprockets-rails has the feature built-in! From this PR: https://github.com/rails/sprockets-rails/pull/355 --- Gemfile | 1 - Gemfile.lock | 3 --- config/environments/development.rb | 3 +++ 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 5213a59cab0..58efe6a2525 100644 --- a/Gemfile +++ b/Gemfile @@ -251,7 +251,6 @@ group :development do gem 'brakeman', '~> 3.3.0', require: false gem 'letter_opener_web', '~> 1.3.0' - gem 'quiet_assets', '~> 1.0.2' gem 'rerun', '~> 0.11.0' gem 'bullet', require: false gem 'rblineprof', platform: :mri, require: false diff --git a/Gemfile.lock b/Gemfile.lock index 5e5554d4fc9..b1f2c8c142c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -499,8 +499,6 @@ GEM pry-rails (0.3.4) pry (>= 0.9.10) pyu-ruby-sasl (0.0.3.3) - quiet_assets (1.0.3) - railties (>= 3.1, < 5.0) rack (1.6.4) rack-accept (0.4.5) rack (>= 0.4) @@ -920,7 +918,6 @@ DEPENDENCIES poltergeist (~> 1.9.0) premailer-rails (~> 1.9.0) pry-rails - quiet_assets (~> 1.0.2) rack-attack (~> 4.3.1) rack-cors (~> 0.4.0) rack-oauth2 (~> 1.2.1) diff --git a/config/environments/development.rb b/config/environments/development.rb index 8cca0039b4a..45a8c1add3e 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -42,4 +42,7 @@ Rails.application.configure do config.action_mailer.preview_path = 'spec/mailers/previews' config.eager_load = false + + # Do not log asset requests + config.assets.quiet = true end -- cgit v1.2.1 From 8f7cde0f61d743f35d6907a0b3a1fca340ed987d Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Thu, 30 Jun 2016 16:30:16 -0600 Subject: Upgrade sass-rails. Upgrade sass-rails from 5.0.4 to 5.0.5. Includes support for Rails 5. Changelog: https://github.com/rails/sass-rails/releases --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f99b373dbbd..1bed6ea15a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -634,8 +634,8 @@ GEM sanitize (2.1.0) nokogiri (>= 1.4.4) sass (3.4.22) - sass-rails (5.0.4) - railties (>= 4.0.0, < 5.0) + sass-rails (5.0.5) + railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) -- cgit v1.2.1 From fe833adccd0797bb9a00c051ac718d1df3adacc4 Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Fri, 1 Jul 2016 03:17:39 +0000 Subject: adding link to .gitlab-ci.yml templates - closes #18998 --- doc/ci/examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 27bc21c2922..c134106bfd0 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -14,3 +14,4 @@ - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) +- [A collection of useful .gitlab-ci.yml templates](https://gitlab.com/gitlab-org/gitlab-ci-yml) -- cgit v1.2.1 From 15eea9c7157b3c28037e983b80d3d1ac35cae96a Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Fri, 1 Jul 2016 03:28:10 +0000 Subject: Fix wording around NGINX Shibboleth setup --- doc/integration/shibboleth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: -- cgit v1.2.1 From 9f2101804083ba48fc85efce9846eca2d56474bf Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 1 Jul 2016 14:19:46 +0800 Subject: Remove migration guide comment: They're accessible after doing `rails g migration` anyway. Though I somehow feel this comment could be useful for someone who's new and just browsing the source. --- db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb b/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb index 6e6e9dc3163..61dd726fac7 100644 --- a/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb +++ b/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb @@ -1,6 +1,3 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class AddArtifactsSizeToCiBuilds < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers -- cgit v1.2.1 From ef960286e552007a8c4e039de9ea5b3aa71e4669 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 1 Jul 2016 14:30:39 +0800 Subject: Rename shared_examples, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4964#note_12817406 --- spec/requests/ci/api/builds_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 666fdbdd2b5..0c4e9be96ff 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -306,7 +306,7 @@ describe Ci::API::API do end context 'should post artifact to running build' do - shared_examples 'post artifact' do + shared_examples 'artifacts sender' do it 'updates successfully' do response_filename = json_response['artifacts_file']['filename'] @@ -321,7 +321,7 @@ describe Ci::API::API do upload_artifacts(file_upload, headers_with_token, false) end - it_behaves_like 'post artifact' + it_behaves_like 'artifacts sender' end context 'uses accelerated file post' do @@ -329,7 +329,7 @@ describe Ci::API::API do upload_artifacts(file_upload, headers_with_token, true) end - it_behaves_like 'post artifact' + it_behaves_like 'artifacts sender' end context 'updates artifact' do @@ -338,7 +338,7 @@ describe Ci::API::API do upload_artifacts(file_upload, headers_with_token) end - it_behaves_like 'post artifact' + it_behaves_like 'artifacts sender' end end -- cgit v1.2.1 From 54a50bf81d7bb304adaedffd8eb3e0bc0fc348a9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 1 Jul 2016 09:02:45 +0200 Subject: refactor url validator to use sanitizer for check --- app/validators/addressable_url_validator.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index 63761c81721..09bfa613cbe 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -35,9 +35,7 @@ class AddressableUrlValidator < ActiveModel::EachValidator end def valid_uri?(value) - Addressable::URI.parse(value).is_a?(Addressable::URI) - rescue Addressable::URI::InvalidURIError - false + Gitlab::UrlSanitizer.valid?(value) end def valid_protocol?(value) -- cgit v1.2.1 From a1f224d3f7b43bf2f58917e5045dcc2bdb33964d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 11 Mar 2016 16:04:42 -0300 Subject: Add Todos API --- app/models/todo.rb | 7 ++ app/views/dashboard/todos/_todo.html.haml | 2 +- lib/api/api.rb | 1 + lib/api/entities.rb | 29 ++++++++ lib/api/todos.rb | 54 +++++++++++++++ spec/factories/todos.rb | 4 ++ spec/requests/api/todos_spec.rb | 107 ++++++++++++++++++++++++++++++ 7 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 lib/api/todos.rb create mode 100644 spec/requests/api/todos_spec.rb diff --git a/app/models/todo.rb b/app/models/todo.rb index 2792fa9b9a8..42faecdf7f2 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -34,6 +34,13 @@ class Todo < ActiveRecord::Base action == BUILD_FAILED end + def action_name + case action + when Todo::ASSIGNED then 'assigned you' + when Todo::MENTIONED then 'mentioned you on' + end + end + def body if note.present? note.note diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 98f302d2f93..421885eef5b 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -11,7 +11,7 @@ - else (removed) %span.todo-label - = todo_action_name(todo) + = todo.action_name - if todo.target = todo_target_link(todo) - else diff --git a/lib/api/api.rb b/lib/api/api.rb index c3fff8b2f8f..3d7d67510a8 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -58,6 +58,7 @@ module API mount ::API::SystemHooks mount ::API::Tags mount ::API::Templates + mount ::API::Todos mount ::API::Triggers mount ::API::Users mount ::API::Variables diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4e2a43e45e2..171a5da6420 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -56,6 +56,10 @@ module API expose :id expose :name, :name_with_namespace expose :path, :path_with_namespace + + expose :web_url do |project, options| + Gitlab::Application.routes.url_helpers.namespace_project_url(project.namespace, project) + end end class Project < Grape::Entity @@ -272,6 +276,31 @@ module API expose :id, :project_id, :group_id, :group_access end + class Todo < Grape::Entity + expose :id + expose :project, using: Entities::BasicProjectDetails + expose :author, using: Entities::UserBasic + expose :action_name + expose :target_id + expose :target_type + expose :target_reference do |todo, options| + todo.target.to_reference + end + + expose :target_url do |todo, options| + target_type = todo.target_type.underscore + target_url = "namespace_project_#{target_type}_url" + target_anchor = "note_#{todo.note_id}" if todo.note.present? + + Gitlab::Application.routes.url_helpers.public_send(target_url, + todo.project.namespace, todo.project, todo.target, anchor: target_anchor) + end + + expose :body + expose :state + expose :created_at + end + class Namespace < Grape::Entity expose :id, :path, :kind end diff --git a/lib/api/todos.rb b/lib/api/todos.rb new file mode 100644 index 00000000000..f45c0ae634a --- /dev/null +++ b/lib/api/todos.rb @@ -0,0 +1,54 @@ +module API + # Todos API + class Todos < Grape::API + before { authenticate! } + + resource :todos do + helpers do + def find_todos + TodosFinder.new(current_user, params).execute + end + end + + # Get a todo list + # + # Example Request: + # GET /todos + get do + @todos = find_todos + @todos = paginate @todos + + present @todos, with: Entities::Todo + end + + # Mark todo as done + # + # Parameters: + # id: (required) - The ID of the todo being marked as done + # + # Example Request: + # + # DELETE /todos/:id + # + delete ':id' do + @todo = current_user.todos.find(params[:id]) + @todo.done + + present @todo, with: Entities::Todo + end + + # Mark all todos as done + # + # Example Request: + # + # DELETE /todos + # + delete do + @todos = find_todos + @todos.each(&:done) + + present @todos, with: Entities::Todo + end + end + end +end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index f426e27afed..7fc20cd5555 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -22,5 +22,9 @@ FactoryGirl.define do trait :build_failed do action { Todo::BUILD_FAILED } end + + trait :done do + state :done + end end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb new file mode 100644 index 00000000000..20d57828674 --- /dev/null +++ b/spec/requests/api/todos_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper' + +describe API::Todos, api: true do + include ApiHelpers + + let(:project_1) { create(:project) } + let(:project_2) { create(:project) } + let(:author_1) { create(:user) } + let(:author_2) { create(:user) } + let(:john_doe) { create(:user, username: 'john_doe') } + let(:merge_request) { create(:merge_request, source_project: project_1) } + let!(:pending_1) { create(:todo, project: project_1, author: author_1, user: john_doe) } + let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } + let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } + let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } + + describe 'GET /todos' do + context 'when unauthenticated' do + it 'should return authentication error' do + get api('/todos') + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'should return an array of pending todos for current user' do + get api('/todos', john_doe) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + end + + context 'and using the author filter' do + it 'should filter based on author_id param' do + get api('/todos', john_doe), { author_id: author_2.id } + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + end + end + + context 'and using the type filter' do + it 'should filter based on type param' do + get api('/todos', john_doe), { type: 'MergeRequest' } + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end + + context 'and using the state filter' do + it 'should filter based on state param' do + get api('/todos', john_doe), { state: 'done' } + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end + + context 'and using the project filter' do + it 'should filter based on project_id param' do + project_2.team << [john_doe, :developer] + get api('/todos', john_doe), { project_id: project_2.id } + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end + end + end + + describe 'DELETE /todos/:id' do + context 'when unauthenticated' do + it 'should return authentication error' do + delete api("/todos/#{pending_1.id}") + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'should mark a todo as done' do + delete api("/todos/#{pending_1.id}", john_doe) + expect(response.status).to eq(200) + expect(pending_1.reload).to be_done + end + end + end + + describe 'DELETE /todos' do + context 'when unauthenticated' do + it 'should return authentication error' do + delete api('/todos') + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'should mark all todos as done' do + delete api('/todos', john_doe) + expect(response.status).to eq(200) + expect(pending_1.reload).to be_done + expect(pending_2.reload).to be_done + expect(pending_3.reload).to be_done + end + end + end +end -- cgit v1.2.1 From 25dcd051372f6426b0ca5c73a4be6b8b075a21e7 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 18 May 2016 23:18:04 +0200 Subject: Fix rebase --- lib/api/entities.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 171a5da6420..67d2c396b32 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -56,10 +56,6 @@ module API expose :id expose :name, :name_with_namespace expose :path, :path_with_namespace - - expose :web_url do |project, options| - Gitlab::Application.routes.url_helpers.namespace_project_url(project.namespace, project) - end end class Project < Grape::Entity -- cgit v1.2.1 From b94088d5124b08349e5bd99c49a8ae3fcc5f5e6b Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 20 May 2016 23:17:13 +0200 Subject: Make tests follow the guidelines --- spec/requests/api/todos_spec.rb | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 20d57828674..7ad6d26c42f 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -16,23 +16,26 @@ describe API::Todos, api: true do describe 'GET /todos' do context 'when unauthenticated' do - it 'should return authentication error' do + it 'returns authentication error' do get api('/todos') + expect(response.status).to eq(401) end end context 'when authenticated' do - it 'should return an array of pending todos for current user' do + it 'returns an array of pending todos for current user' do get api('/todos', john_doe) + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) end context 'and using the author filter' do - it 'should filter based on author_id param' do + it 'filters based on author_id param' do get api('/todos', john_doe), { author_id: author_2.id } + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) @@ -40,8 +43,9 @@ describe API::Todos, api: true do end context 'and using the type filter' do - it 'should filter based on type param' do + it 'filters based on type param' do get api('/todos', john_doe), { type: 'MergeRequest' } + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -49,8 +53,9 @@ describe API::Todos, api: true do end context 'and using the state filter' do - it 'should filter based on state param' do + it 'filters based on state param' do get api('/todos', john_doe), { state: 'done' } + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -58,9 +63,10 @@ describe API::Todos, api: true do end context 'and using the project filter' do - it 'should filter based on project_id param' do + it 'filters based on project_id param' do project_2.team << [john_doe, :developer] get api('/todos', john_doe), { project_id: project_2.id } + expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -71,15 +77,17 @@ describe API::Todos, api: true do describe 'DELETE /todos/:id' do context 'when unauthenticated' do - it 'should return authentication error' do + it 'returns authentication error' do delete api("/todos/#{pending_1.id}") + expect(response.status).to eq(401) end end context 'when authenticated' do - it 'should mark a todo as done' do + it 'marks a todo as done' do delete api("/todos/#{pending_1.id}", john_doe) + expect(response.status).to eq(200) expect(pending_1.reload).to be_done end @@ -88,15 +96,17 @@ describe API::Todos, api: true do describe 'DELETE /todos' do context 'when unauthenticated' do - it 'should return authentication error' do + it 'returns authentication error' do delete api('/todos') + expect(response.status).to eq(401) end end context 'when authenticated' do - it 'should mark all todos as done' do + it 'marks all todos as done' do delete api('/todos', john_doe) + expect(response.status).to eq(200) expect(pending_1.reload).to be_done expect(pending_2.reload).to be_done -- cgit v1.2.1 From 39e6f504fcb8c6cab511a50cdefd76e821bfec17 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 May 2016 19:01:11 +0200 Subject: Move to helper, no instance variables --- app/models/todo.rb | 7 ------- app/views/dashboard/todos/_todo.html.haml | 2 +- lib/api/todos.rb | 18 +++++++++--------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index 42faecdf7f2..2792fa9b9a8 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -34,13 +34,6 @@ class Todo < ActiveRecord::Base action == BUILD_FAILED end - def action_name - case action - when Todo::ASSIGNED then 'assigned you' - when Todo::MENTIONED then 'mentioned you on' - end - end - def body if note.present? note.note diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 421885eef5b..98f302d2f93 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -11,7 +11,7 @@ - else (removed) %span.todo-label - = todo.action_name + = todo_action_name(todo) - if todo.target = todo_target_link(todo) - else diff --git a/lib/api/todos.rb b/lib/api/todos.rb index f45c0ae634a..db1f16cec59 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -14,11 +14,11 @@ module API # # Example Request: # GET /todos + # get do - @todos = find_todos - @todos = paginate @todos + todos = find_todos - present @todos, with: Entities::Todo + present paginate(todos), with: Entities::Todo end # Mark todo as done @@ -31,10 +31,10 @@ module API # DELETE /todos/:id # delete ':id' do - @todo = current_user.todos.find(params[:id]) - @todo.done + todo = current_user.todos.find(params[:id]) + todo.done - present @todo, with: Entities::Todo + present todo, with: Entities::Todo end # Mark all todos as done @@ -44,10 +44,10 @@ module API # DELETE /todos # delete do - @todos = find_todos - @todos.each(&:done) + todos = find_todos + todos.each(&:done) - present @todos, with: Entities::Todo + present paginate(todos), with: Entities::Todo end end end -- cgit v1.2.1 From f3abd18c9c7da9c53bea2f08c2326a15ba5948f3 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 10 Jun 2016 12:24:38 +0200 Subject: Add user to project to see todos --- lib/api/entities.rb | 2 +- spec/requests/api/todos_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 67d2c396b32..88f7fc7ff6c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -276,7 +276,7 @@ module API expose :id expose :project, using: Entities::BasicProjectDetails expose :author, using: Entities::UserBasic - expose :action_name + #expose :action_name expose :target_id expose :target_type expose :target_reference do |todo, options| diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 7ad6d26c42f..147028ba1c9 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -14,6 +14,11 @@ describe API::Todos, api: true do let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } + before do + project_1.team << [john_doe, :developer] + project_2.team << [john_doe, :developer] + end + describe 'GET /todos' do context 'when unauthenticated' do it 'returns authentication error' do -- cgit v1.2.1 From 69397d559f837cb55fd50d5d0459523854dcec06 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 10 Jun 2016 13:02:41 +0200 Subject: Assert response body --- spec/requests/api/todos_spec.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 147028ba1c9..1960db59c99 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -35,6 +35,16 @@ describe API::Todos, api: true do expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) + expect(json_response[0]['id']).to eq(pending_3.id) + expect(json_response[0]['project']).to be_a Hash + expect(json_response[0]['author']).to be_a Hash + expect(json_response[0]['target_id']).to be_present + expect(json_response[0]['target_type']).to be_present + expect(json_response[0]['target_reference']).to be_present + expect(json_response[0]['target_url']).to be_present + expect(json_response[0]['body']).to be_present + expect(json_response[0]['state']).to eq('pending') + expect(json_response[0]['created_at']).to be_present end context 'and using the author filter' do @@ -69,7 +79,6 @@ describe API::Todos, api: true do context 'and using the project filter' do it 'filters based on project_id param' do - project_2.team << [john_doe, :developer] get api('/todos', john_doe), { project_id: project_2.id } expect(response.status).to eq(200) @@ -113,6 +122,8 @@ describe API::Todos, api: true do delete api('/todos', john_doe) expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) expect(pending_1.reload).to be_done expect(pending_2.reload).to be_done expect(pending_3.reload).to be_done -- cgit v1.2.1 From 631765748ebff2307a078dc6d50ef8367f3c5ff0 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 15 Jun 2016 13:20:30 +0200 Subject: Expose action_name --- app/models/todo.rb | 11 +++++++++++ lib/api/entities.rb | 2 +- spec/requests/api/todos_spec.rb | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index 2792fa9b9a8..3ba67078d48 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -4,6 +4,13 @@ class Todo < ActiveRecord::Base BUILD_FAILED = 3 MARKED = 4 + ACTION_NAMES = { + ASSIGNED => :assigned, + MENTIONED => :mentioned, + BUILD_FAILED => :build_failed, + MARKED => :marked + } + belongs_to :author, class_name: "User" belongs_to :note belongs_to :project @@ -34,6 +41,10 @@ class Todo < ActiveRecord::Base action == BUILD_FAILED end + def action_name + ACTION_NAMES[action] + end + def body if note.present? note.note diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 88f7fc7ff6c..67d2c396b32 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -276,7 +276,7 @@ module API expose :id expose :project, using: Entities::BasicProjectDetails expose :author, using: Entities::UserBasic - #expose :action_name + expose :action_name expose :target_id expose :target_type expose :target_reference do |todo, options| diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 1960db59c99..8d54b4fd3d8 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -44,6 +44,7 @@ describe API::Todos, api: true do expect(json_response[0]['target_url']).to be_present expect(json_response[0]['body']).to be_present expect(json_response[0]['state']).to eq('pending') + expect(json_response[0]['action_name']).to eq('assigned') expect(json_response[0]['created_at']).to be_present end -- cgit v1.2.1 From 40c685c510fff48e0dc6a49c61704e8244ec6034 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 15 Jun 2016 16:06:38 +0200 Subject: pass paginated array when deleting notes --- lib/api/todos.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index db1f16cec59..10fd2aac092 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -47,7 +47,7 @@ module API todos = find_todos todos.each(&:done) - present paginate(todos), with: Entities::Todo + present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo end end end -- cgit v1.2.1 From 4bcad1cbddca92e27c19a1c6c0872a01ef318f69 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 1 Jul 2016 11:46:56 +0200 Subject: Groundwork for Kerberos SPNEGO (EE feature) --- app/controllers/projects/git_http_controller.rb | 39 ++++++++++++++++++++++--- app/helpers/kerberos_spnego_helper.rb | 9 ++++++ spec/requests/git_http_spec.rb | 27 +++++++++-------- 3 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 app/helpers/kerberos_spnego_helper.rb diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index f907d63258b..62c3fa8de53 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,4 +1,9 @@ +# This file should be identical in GitLab Community Edition and Enterprise Edition + class Projects::GitHttpController < Projects::ApplicationController + include ActionController::HttpAuthentication::Basic + include KerberosSpnegoHelper + attr_reader :user # Git clients will not know what authenticity token to send along @@ -40,9 +45,12 @@ class Projects::GitHttpController < Projects::ApplicationController private def authenticate_user - return if project && project.public? && upload_pack? + if project && project.public? && upload_pack? + return # Allow access + end - authenticate_or_request_with_http_basic do |login, password| + if allow_basic_auth? && basic_auth_provided? + login, password = user_name_and_password(request) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) if auth_result.type == :ci && upload_pack? @@ -53,8 +61,31 @@ class Projects::GitHttpController < Projects::ApplicationController @user = auth_result.user end - ci? || user + if ci? || user + return # Allow access + end + elsif allow_kerberos_spnego_auth? && spnego_provided? + @user = find_kerberos_user + + if user + send_final_spnego_response + return # Allow access + end end + + send_challenges + render plain: "HTTP Basic: Access denied\n", status: 401 + end + + def basic_auth_provided? + has_basic_credentials?(request) + end + + def send_challenges + challenges = [] + challenges << 'Basic realm="GitLab"' if allow_basic_auth? + challenges << spnego_challenge if allow_kerberos_spnego_auth? + headers['Www-Authenticate'] = challenges.join("\n") if challenges.any? end def ensure_project_found! @@ -120,7 +151,7 @@ class Projects::GitHttpController < Projects::ApplicationController end def render_not_found - render text: 'Not Found', status: :not_found + render plain: 'Not Found', status: :not_found end def ci? diff --git a/app/helpers/kerberos_spnego_helper.rb b/app/helpers/kerberos_spnego_helper.rb new file mode 100644 index 00000000000..f5b0aa7549a --- /dev/null +++ b/app/helpers/kerberos_spnego_helper.rb @@ -0,0 +1,9 @@ +module KerberosSpnegoHelper + def allow_basic_auth? + true # different behavior in GitLab Enterprise Edition + end + + def allow_kerberos_spnego_auth? + false # different behavior in GitLab Enterprise Edition + end +end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index bae56334be4..82ab582beac 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -350,23 +350,23 @@ describe 'Git HTTP requests', lib: true do end def clone_get(project, options={}) - get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password)) + get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) end def clone_post(project, options={}) - post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password)) + post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) end def push_get(project, options={}) - get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password)) + get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token)) end def push_post(project, options={}) - post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password)) + post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token)) end - def download(project, user: nil, password: nil) - args = [project, { user: user, password: password }] + def download(project, user: nil, password: nil, spnego_request_token: nil) + args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }] clone_get(*args) yield response @@ -375,8 +375,8 @@ describe 'Git HTTP requests', lib: true do yield response end - def upload(project, user: nil, password: nil) - args = [project, { user: user, password: password }] + def upload(project, user: nil, password: nil, spnego_request_token: nil) + args = [project, { user: user, password: password, spnego_request_token: spnego_request_token }] push_get(*args) yield response @@ -385,11 +385,14 @@ describe 'Git HTTP requests', lib: true do yield response end - def auth_env(user, password) + def auth_env(user, password, spnego_request_token) + env = {} if user && password - { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) } - else - {} + env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password) + elsif spnego_request_token + env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}" end + + env end end -- cgit v1.2.1 From fd9cd5ae8cd2d2f5488635b264eb86d89d768d66 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 16 Jun 2016 11:12:42 +0200 Subject: Add todos API documentation and changelog --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/todos.md | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/api/todos.rb | 4 +- 4 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 doc/api/todos.md diff --git a/CHANGELOG b/CHANGELOG index 4b754c2aba3..98d23ea6824 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.10.0 (unreleased) - Fix changing issue state columns in milestone view - Add notification settings dropdown for groups - Allow importing from Github using Personal Access Tokens. (Eric K Idema) + - API: Todos !3188 (Robert Schilling) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - PipelinesFinder uses git cache data - Check for conflicts with existing Project's wiki path when creating a new project. diff --git a/doc/api/README.md b/doc/api/README.md index 288f7f9ee69..d1e6c54c521 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -36,6 +36,7 @@ following locations: - [System Hooks](system_hooks.md) - [Tags](tags.md) - [Users](users.md) +- [Todos](todos.md) ### Internal CI API diff --git a/doc/api/todos.md b/doc/api/todos.md new file mode 100644 index 00000000000..1d38e4acf13 --- /dev/null +++ b/doc/api/todos.md @@ -0,0 +1,217 @@ +# Todos + +**Note:** This feature was [introduced][ce-3188] in GitLab 8.10 + +## Get a list of todos + +Returns a list of todos. When no filter is applied, it returns all pending todos +for the current user. Different filters allow the user to + +``` +GET /todos +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `action_id` | integer | no | The ID of the action of the todo. See the table below for the ID mapping | +| `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` | +| `type` | string | no | The type of an todo. Can be either `Issue` or `MergeRequest` | + +| `action_id` | Action | +| ----------- | ------ | +| 1 | Issuable assigned | +| 2 | Mentioned in issuable | +| 3 | Build failed | +| 4 | Todo marked for you | + + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos +``` + +Example Response: + +```json +[ + { + "id": 130, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "assigned", + "target_id": 71, + "target_type": "Issue", + "target_reference": "#1", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", + "body": "At voluptas qui nulla soluta qui et.", + "state": "pending", + "created_at": "2016-05-20T20:52:00.626Z" + }, + { + "id": 129, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "mentioned", + "target_id": 79, + "target_type": "Issue", + "target_reference": "#9", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", + "body": "@root Fix this shit", + "state": "pending", + "created_at": "2016-05-20T20:51:51.503Z" + } +] +``` + +## Mark a todo as done + +Marks a single pending todo given by its ID for the current user as done. The to +marked as done is returned in the response. + +``` +DELETE /todos/:id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a todo | + +```bash +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130 +``` + +Example Response: + +```json +{ + "id": 130, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "assigned", + "target_id": 71, + "target_type": "Issue", + "target_reference": "#1", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", + "body": "At voluptas qui nulla soluta qui et.", + "state": "done", + "created_at": "2016-05-20T20:52:00.626Z" +} +``` + +## Mark all todos as done + +Marks all pending todos for the current user as done. All todos marked as done +are returned in the response. + +``` +DELETE /todos +``` + +```bash +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos +``` + +Example Response: + +```json +[ + { + "id": 130, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "assigned", + "target_id": 71, + "target_type": "Issue", + "target_reference": "#1", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", + "body": "At voluptas qui nulla soluta qui et.", + "state": "done", + "created_at": "2016-05-20T20:52:00.626Z" + }, + { + "id": 129, + "project": { + "id": 1, + "name": "Underscore", + "name_with_namespace": "Documentcloud / Underscore", + "path": "underscore", + "path_with_namespace": "documentcloud/underscore" + }, + "author": { + "name": "Juwan Abbott", + "username": "halle", + "id": 8, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/halle" + }, + "action_name": "mentioned", + "target_id": 79, + "target_type": "Issue", + "target_reference": "#9", + "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", + "body": "@root Fix this shit", + "state": "done", + "created_at": "2016-05-20T20:51:51.503Z" + } +] +``` + +[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188 diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 10fd2aac092..67714a796c7 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -21,13 +21,12 @@ module API present paginate(todos), with: Entities::Todo end - # Mark todo as done + # Mark a todo as done # # Parameters: # id: (required) - The ID of the todo being marked as done # # Example Request: - # # DELETE /todos/:id # delete ':id' do @@ -40,7 +39,6 @@ module API # Mark all todos as done # # Example Request: - # # DELETE /todos # delete do -- cgit v1.2.1 From 3942621329b20307c1676d60324c8f47ea1e1b37 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 22 Jun 2016 19:15:09 +0200 Subject: Expose target, filter by state as string --- app/finders/todos_finder.rb | 21 ++ doc/api/todos.md | 447 ++++++++++++++++++++++++++++++---------- lib/api/entities.rb | 8 +- lib/api/todos.rb | 6 +- spec/requests/api/todos_spec.rb | 15 +- 5 files changed, 377 insertions(+), 120 deletions(-) diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 58a00f88af7..7806d9e4cc5 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -25,6 +25,7 @@ class TodosFinder def execute items = current_user.todos items = by_action_id(items) + items = by_action(items) items = by_author(items) items = by_project(items) items = by_state(items) @@ -43,6 +44,18 @@ class TodosFinder params[:action_id] end + def to_action_id + Todo::ACTION_NAMES.key(action.to_sym) + end + + def action? + action.present? && to_action_id + end + + def action + params[:action] + end + def author? params[:author_id].present? end @@ -96,6 +109,14 @@ class TodosFinder params[:type] end + def by_action(items) + if action? + items = items.where(action: to_action_id) + end + + items + end + def by_action_id(items) if action_id? items = items.where(action: action_id) diff --git a/doc/api/todos.md b/doc/api/todos.md index 1d38e4acf13..29e73664410 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -5,7 +5,7 @@ ## Get a list of todos Returns a list of todos. When no filter is applied, it returns all pending todos -for the current user. Different filters allow the user to +for the current user. Different filters allow the user to precise the request. ``` GET /todos @@ -15,19 +15,11 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `action_id` | integer | no | The ID of the action of the todo. See the table below for the ID mapping | +| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, or `marked`. | | `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` | -| `type` | string | no | The type of an todo. Can be either `Issue` or `MergeRequest` | - -| `action_id` | Action | -| ----------- | ------ | -| 1 | Issuable assigned | -| 2 | Mentioned in issuable | -| 3 | Build failed | -| 4 | Todo marked for you | - +| `type` | string | no | The type of a todo. Can be either `Issue` or `MergeRequest` | ```bash curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos @@ -38,64 +30,158 @@ Example Response: ```json [ { - "id": 130, + "id": 102, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Administrator", + "username": "root", + "id": 1, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" }, - "action_name": "assigned", - "target_id": 71, - "target_type": "Issue", - "target_reference": "#1", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", - "body": "At voluptas qui nulla soluta qui et.", + "action_name": "marked", + "target_type": "MergeRequest", + "target": { + "id": 34, + "iid": 7, + "project_id": 2, + "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", + "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.", + "state": "opened", + "created_at": "2016-06-17T07:49:24.419Z", + "updated_at": "2016-06-17T07:52:43.484Z", + "target_branch": "tutorials_git_tricks", + "source_branch": "DNSBL_docs", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" + }, + "assignee": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "source_project_id": 2, + "target_project_id": 2, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 32, + "iid": 2, + "project_id": 2, + "title": "v1.0", + "description": "Assumenda placeat ea voluptatem voluptate qui.", + "state": "active", + "created_at": "2016-06-17T07:47:34.163Z", + "updated_at": "2016-06-17T07:47:34.163Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": true, + "user_notes_count": 7 + }, + "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7", + "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", "state": "pending", - "created_at": "2016-05-20T20:52:00.626Z" + "created_at": "2016-06-17T07:52:35.225Z" }, { - "id": 129, + "id": 98, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" }, - "action_name": "mentioned", - "target_id": 79, - "target_type": "Issue", - "target_reference": "#9", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", - "body": "@root Fix this shit", + "action_name": "assigned", + "target_type": "MergeRequest", + "target": { + "id": 34, + "iid": 7, + "project_id": 2, + "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", + "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.", + "state": "opened", + "created_at": "2016-06-17T07:49:24.419Z", + "updated_at": "2016-06-17T07:52:43.484Z", + "target_branch": "tutorials_git_tricks", + "source_branch": "DNSBL_docs", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" + }, + "assignee": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "source_project_id": 2, + "target_project_id": 2, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 32, + "iid": 2, + "project_id": 2, + "title": "v1.0", + "description": "Assumenda placeat ea voluptatem voluptate qui.", + "state": "active", + "created_at": "2016-06-17T07:47:34.163Z", + "updated_at": "2016-06-17T07:47:34.163Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": true, + "user_notes_count": 7 + }, + "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7", + "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", "state": "pending", - "created_at": "2016-05-20T20:51:51.503Z" + "created_at": "2016-06-17T07:49:24.624Z" } ] ``` ## Mark a todo as done -Marks a single pending todo given by its ID for the current user as done. The to -marked as done is returned in the response. +Marks a single pending todo given by its ID for the current user as done. The +todo marked as done is returned in the response. ``` DELETE /todos/:id @@ -115,30 +201,77 @@ Example Response: ```json { - "id": 130, - "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" - }, - "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" - }, - "action_name": "assigned", - "target_id": 71, - "target_type": "Issue", - "target_reference": "#1", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", - "body": "At voluptas qui nulla soluta qui et.", - "state": "done", - "created_at": "2016-05-20T20:52:00.626Z" + "id": 102, + "project": { + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" + }, + "author": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "action_name": "marked", + "target_type": "MergeRequest", + "target": { + "id": 34, + "iid": 7, + "project_id": 2, + "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", + "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.", + "state": "opened", + "created_at": "2016-06-17T07:49:24.419Z", + "updated_at": "2016-06-17T07:52:43.484Z", + "target_branch": "tutorials_git_tricks", + "source_branch": "DNSBL_docs", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" + }, + "assignee": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "source_project_id": 2, + "target_project_id": 2, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 32, + "iid": 2, + "project_id": 2, + "title": "v1.0", + "description": "Assumenda placeat ea voluptatem voluptate qui.", + "state": "active", + "created_at": "2016-06-17T07:47:34.163Z", + "updated_at": "2016-06-17T07:47:34.163Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": true, + "user_notes_count": 7 + }, + "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7", + "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", + "state": "done", + "created_at": "2016-06-17T07:52:35.225Z" } ``` @@ -160,57 +293,151 @@ Example Response: ```json [ { - "id": 130, + "id": 102, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Administrator", + "username": "root", + "id": 1, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" }, - "action_name": "assigned", - "target_id": 71, - "target_type": "Issue", - "target_reference": "#1", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/1", - "body": "At voluptas qui nulla soluta qui et.", + "action_name": "marked", + "target_type": "MergeRequest", + "target": { + "id": 34, + "iid": 7, + "project_id": 2, + "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", + "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.", + "state": "opened", + "created_at": "2016-06-17T07:49:24.419Z", + "updated_at": "2016-06-17T07:52:43.484Z", + "target_branch": "tutorials_git_tricks", + "source_branch": "DNSBL_docs", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" + }, + "assignee": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "source_project_id": 2, + "target_project_id": 2, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 32, + "iid": 2, + "project_id": 2, + "title": "v1.0", + "description": "Assumenda placeat ea voluptatem voluptate qui.", + "state": "active", + "created_at": "2016-06-17T07:47:34.163Z", + "updated_at": "2016-06-17T07:47:34.163Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": true, + "user_notes_count": 7 + }, + "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7", + "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", "state": "done", - "created_at": "2016-05-20T20:52:00.626Z" + "created_at": "2016-06-17T07:52:35.225Z" }, { - "id": 129, + "id": 98, "project": { - "id": 1, - "name": "Underscore", - "name_with_namespace": "Documentcloud / Underscore", - "path": "underscore", - "path_with_namespace": "documentcloud/underscore" + "id": 2, + "name": "Gitlab Ce", + "name_with_namespace": "Gitlab Org / Gitlab Ce", + "path": "gitlab-ce", + "path_with_namespace": "gitlab-org/gitlab-ce" }, "author": { - "name": "Juwan Abbott", - "username": "halle", - "id": 8, + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/a0086c7b9e0d73312f32ff745fdcb43e?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/halle" + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" }, - "action_name": "mentioned", - "target_id": 79, - "target_type": "Issue", - "target_reference": "#9", - "target_url": "https://gitlab.example.com/documentcloud/underscore/issues/9#note_959", - "body": "@root Fix this shit", + "action_name": "assigned", + "target_type": "MergeRequest", + "target": { + "id": 34, + "iid": 7, + "project_id": 2, + "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", + "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.", + "state": "opened", + "created_at": "2016-06-17T07:49:24.419Z", + "updated_at": "2016-06-17T07:52:43.484Z", + "target_branch": "tutorials_git_tricks", + "source_branch": "DNSBL_docs", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" + }, + "assignee": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "source_project_id": 2, + "target_project_id": 2, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 32, + "iid": 2, + "project_id": 2, + "title": "v1.0", + "description": "Assumenda placeat ea voluptatem voluptate qui.", + "state": "active", + "created_at": "2016-06-17T07:47:34.163Z", + "updated_at": "2016-06-17T07:47:34.163Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "cannot_be_merged", + "subscribed": true, + "user_notes_count": 7 + }, + "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7", + "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.", "state": "done", - "created_at": "2016-05-20T20:51:51.503Z" - } + "created_at": "2016-06-17T07:49:24.624Z" + }, ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 67d2c396b32..8cc4368b5c2 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -277,16 +277,16 @@ module API expose :project, using: Entities::BasicProjectDetails expose :author, using: Entities::UserBasic expose :action_name - expose :target_id expose :target_type - expose :target_reference do |todo, options| - todo.target.to_reference + + expose :target do |todo, options| + Entities.const_get(todo.target_type).represent(todo.target, options) end expose :target_url do |todo, options| target_type = todo.target_type.underscore target_url = "namespace_project_#{target_type}_url" - target_anchor = "note_#{todo.note_id}" if todo.note.present? + target_anchor = "note_#{todo.note_id}" if todo.note_id? Gitlab::Application.routes.url_helpers.public_send(target_url, todo.project.namespace, todo.project, todo.target, anchor: target_anchor) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 67714a796c7..8334baad1b9 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -18,7 +18,7 @@ module API get do todos = find_todos - present paginate(todos), with: Entities::Todo + present paginate(todos), with: Entities::Todo, current_user: current_user end # Mark a todo as done @@ -33,7 +33,7 @@ module API todo = current_user.todos.find(params[:id]) todo.done - present todo, with: Entities::Todo + present todo, with: Entities::Todo, current_user: current_user end # Mark all todos as done @@ -45,7 +45,7 @@ module API todos = find_todos todos.each(&:done) - present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo + present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo, current_user: current_user end end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 8d54b4fd3d8..f93f37e3591 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -9,7 +9,7 @@ describe API::Todos, api: true do let(:author_2) { create(:user) } let(:john_doe) { create(:user, username: 'john_doe') } let(:merge_request) { create(:merge_request, source_project: project_1) } - let!(:pending_1) { create(:todo, project: project_1, author: author_1, user: john_doe) } + let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) } let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } @@ -38,9 +38,8 @@ describe API::Todos, api: true do expect(json_response[0]['id']).to eq(pending_3.id) expect(json_response[0]['project']).to be_a Hash expect(json_response[0]['author']).to be_a Hash - expect(json_response[0]['target_id']).to be_present expect(json_response[0]['target_type']).to be_present - expect(json_response[0]['target_reference']).to be_present + expect(json_response[0]['target']).to be_a Hash expect(json_response[0]['target_url']).to be_present expect(json_response[0]['body']).to be_present expect(json_response[0]['state']).to eq('pending') @@ -87,6 +86,16 @@ describe API::Todos, api: true do expect(json_response.length).to eq(1) end end + + context 'and using the action filter' do + it 'filters based on action param' do + get api('/todos', john_doe), { action: 'mentioned' } + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end end end -- cgit v1.2.1 From 87ac9c9850d602fd18654498ab3fa005d2b85ac7 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 28 Jun 2016 18:04:44 +0200 Subject: Support creating a todo on issuables via API --- doc/api/issues.md | 93 +++++++++++++++++++++++++++++++++++++- doc/api/merge_requests.md | 98 +++++++++++++++++++++++++++++++++++++++++ lib/api/todos.rb | 30 +++++++++++++ spec/requests/api/todos_spec.rb | 49 ++++++++++++++++++++- 4 files changed, 268 insertions(+), 2 deletions(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index 708fc691f67..3ced787b23e 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -594,12 +594,103 @@ Example response: "id": 11, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "http://lgitlab.example.com/u/orville" + "web_url": "https://gitlab.example.com/u/orville" }, "subscribed": false } ``` +## Create a todo + +Manually creates a todo for the current user on an issue. If the request is +successful, status code `200` together with the created todo is returned. If +there already exists a todo for the user on that issue, status code `304` is +returned. + +``` +POST /projects/:id/issues/:issue_id/todo +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `issue_id` | integer | yes | The ID of a project's issue | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/todo +``` + +Example response: + +```json +{ + "id": 112, + "project": { + "id": 5, + "name": "Gitlab Ci", + "name_with_namespace": "Gitlab Org / Gitlab Ci", + "path": "gitlab-ci", + "path_with_namespace": "gitlab-org/gitlab-ci" + }, + "author": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "action_name": "marked", + "target_type": "Issue", + "target": { + "id": 93, + "iid": 10, + "project_id": 5, + "title": "Vel voluptas atque dicta mollitia adipisci qui at.", + "description": "Tempora laboriosam sint magni sed voluptas similique.", + "state": "closed", + "created_at": "2016-06-17T07:47:39.486Z", + "updated_at": "2016-07-01T11:09:13.998Z", + "labels": [], + "milestone": { + "id": 26, + "iid": 1, + "project_id": 5, + "title": "v0.0", + "description": "Accusantium nostrum rerum quae quia quis nesciunt suscipit id.", + "state": "closed", + "created_at": "2016-06-17T07:47:33.832Z", + "updated_at": "2016-06-17T07:47:33.832Z", + "due_date": null + }, + "assignee": { + "name": "Jarret O'Keefe", + "username": "francisca", + "id": 14, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/francisca" + }, + "author": { + "name": "Maxie Medhurst", + "username": "craig_rutherford", + "id": 12, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/craig_rutherford" + }, + "subscribed": true, + "user_notes_count": 7, + "upvotes": 0, + "downvotes": 0 + }, + "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10", + "body": "Vel voluptas atque dicta mollitia adipisci qui at.", + "state": "pending", + "created_at": "2016-07-01T11:09:13.992Z" +} +``` + ## Comments on issues Comments are done via the [notes](notes.md) resource. diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 2930f615fc1..f60b0d0ebc6 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -776,3 +776,101 @@ Example response: "subscribed": false } ``` + +## Create a todo + +Manually creates a todo for the current user on a merge request. If the +request is successful, status code `200` together with the created todo is +returned. If there already exists a todo for the user on that merge request, +status code `304` is returned. + +``` +POST /projects/:id/merge_requests/:merge_request_id/todo +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `merge_request_id` | integer | yes | The ID of the merge request | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/27/todo +``` + +Example response: + +```json +{ + "id": 113, + "project": { + "id": 3, + "name": "Gitlab Ci", + "name_with_namespace": "Gitlab Org / Gitlab Ci", + "path": "gitlab-ci", + "path_with_namespace": "gitlab-org/gitlab-ci" + }, + "author": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/root" + }, + "action_name": "marked", + "target_type": "MergeRequest", + "target": { + "id": 27, + "iid": 7, + "project_id": 3, + "title": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", + "description": "Veniam sunt nihil modi earum cumque illum delectus. Nihil ad quis distinctio quia. Autem eligendi at quibusdam repellendus.", + "state": "opened", + "created_at": "2016-06-17T07:48:04.330Z", + "updated_at": "2016-07-01T11:14:15.537Z", + "target_branch": "allow_regex_for_project_skip_ref", + "source_branch": "backup", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Jarret O'Keefe", + "username": "francisca", + "id": 14, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/francisca" + }, + "assignee": { + "name": "Dr. Gabrielle Strosin", + "username": "barrett.krajcik", + "id": 4, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/733005fcd7e6df12d2d8580171ccb966?s=80&d=identicon", + "web_url": "https://gitlab.example.com/u/barrett.krajcik" + }, + "source_project_id": 3, + "target_project_id": 3, + "labels": [], + "work_in_progress": false, + "milestone": { + "id": 27, + "iid": 2, + "project_id": 3, + "title": "v1.0", + "description": "Quis ea accusantium animi hic fuga assumenda.", + "state": "active", + "created_at": "2016-06-17T07:47:33.840Z", + "updated_at": "2016-06-17T07:47:33.840Z", + "due_date": null + }, + "merge_when_build_succeeds": false, + "merge_status": "unchecked", + "subscribed": true, + "user_notes_count": 7 + }, + "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.", + "state": "pending", + "created_at": "2016-07-01T11:14:15.530Z" +} +``` diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 8334baad1b9..2a6bfa98ca4 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -3,6 +3,36 @@ module API class Todos < Grape::API before { authenticate! } + ISSUABLE_TYPES = { + 'merge_requests' => ->(id) { user_project.merge_requests.find(id) }, + 'issues' => ->(id) { find_project_issue(id) } + } + + resource :projects do + ISSUABLE_TYPES.each do |type, finder| + type_id_str = "#{type.singularize}_id".to_sym + + # Create a todo on an issuable + # + # Parameters: + # id (required) - The ID of a project + # issuable_id (required) - The ID of an issuable + # Example Request: + # POST /projects/:id/issues/:issuable_id/todo + # POST /projects/:id/merge_requests/:issuable_id/todo + post ":id/#{type}/:#{type_id_str}/todo" do + issuable = instance_exec(params[type_id_str], &finder) + todo = TodoService.new.mark_todo(issuable, current_user).first + + if todo + present todo, with: Entities::Todo, current_user: current_user + else + not_modified! + end + end + end + end + resource :todos do helpers do def find_todos diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index f93f37e3591..92a4fa216cd 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -11,7 +11,7 @@ describe API::Todos, api: true do let(:merge_request) { create(:merge_request, source_project: project_1) } let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) } let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } - let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } + let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) } let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } before do @@ -59,6 +59,8 @@ describe API::Todos, api: true do context 'and using the type filter' do it 'filters based on type param' do + create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) + get api('/todos', john_doe), { type: 'MergeRequest' } expect(response.status).to eq(200) @@ -140,4 +142,49 @@ describe API::Todos, api: true do end end end + + shared_examples 'an issuable' do |issuable_type| + it 'creates a todo on an issuable' do + post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe) + + expect(response.status).to eq(201) + expect(json_response['project']).to be_a Hash + expect(json_response['author']).to be_a Hash + expect(json_response['target_type']).to eq(issuable.class.name) + expect(json_response['target']).to be_a Hash + expect(json_response['target_url']).to be_present + expect(json_response['body']).to be_present + expect(json_response['state']).to eq('pending') + expect(json_response['action_name']).to eq('marked') + expect(json_response['created_at']).to be_present + end + + it 'returns 304 there already exist a todo on that issuable' do + create(:todo, project: project_1, author: author_1, user: john_doe, target: issuable) + + post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe) + + expect(response.status).to eq(304) + end + + it 'returns 404 if the issuable is not found' do + post api("/projects/#{project_1.id}/#{issuable_type}/123/todo", john_doe) + + expect(response.status).to eq(404) + end + end + + describe 'POST :id/issuable_type/:issueable_id/todo' do + context 'for an issue' do + it_behaves_like 'an issuable', 'issues' do + let(:issuable) { create(:issue, author: author_1, project: project_1) } + end + end + + context 'for a merge request' do + it_behaves_like 'an issuable', 'merge_requests' do + let(:issuable) { merge_request } + end + end + end end -- cgit v1.2.1 From c231178a7ee69f13e19c5110e7176ce9f05743a7 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 1 Jul 2016 07:53:45 -0600 Subject: Upgrade oauth2 from 1.0.0 to 1.2.0. Changelog: https://github.com/intridea/oauth2/compare/v1.0.0...v1.2.0 Follow-up on !3434 since 1.2.0 doesn't limit the JWT version we can use. --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 5213a59cab0..4fac6e1e63d 100644 --- a/Gemfile +++ b/Gemfile @@ -339,7 +339,7 @@ gem 'activerecord-session_store', '~> 1.0.0' gem "nested_form", '~> 0.3.2' # OAuth -gem 'oauth2', '~> 1.0.0' +gem 'oauth2', '~> 1.2.0' # Soft deletion gem "paranoia", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index f99b373dbbd..9308744bf7b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -355,7 +355,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 +395,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 +408,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) @@ -898,7 +898,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) -- cgit v1.2.1 From 1586846790045fe294ec558a8f9ee65e94fbe49f Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 1 Jul 2016 07:57:01 -0600 Subject: Upgrade seed-fu from 2.3.5 to 2.3.6 Adds Rails 5 support. Changelog: https://github.com/mbleigh/seed-fu/blob/master/CHANGELOG.md#version-236 --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f99b373dbbd..8e125fc8e09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -649,9 +649,9 @@ GEM sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) - seed-fu (2.3.5) - activerecord (>= 3.1, < 4.3) - activesupport (>= 3.1, < 4.3) + seed-fu (2.3.6) + activerecord (>= 3.1) + activesupport (>= 3.1) select2-rails (3.5.9.3) thor (~> 0.14) sentry-raven (1.1.0) -- cgit v1.2.1 From 44544b34819ae65d0f500fce7259f74a1ec1bde3 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 1 Jul 2016 08:02:04 -0600 Subject: Upgrade Sidekiq from 4.1.2 to 4.1.4. Adds a dependency on Sinatra and allows Sinatra 2 for eventual support of Rack 2. Changelog: https://github.com/mperham/sidekiq/blob/master/Changes.md#414 --- Gemfile.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f99b373dbbd..b8f34d88ae2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -662,10 +662,11 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.1.2) + sidekiq (4.1.4) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) redis (~> 3.2, >= 3.2.1) + sinatra (>= 1.4.7) sidekiq-cron (0.4.0) redis-namespace (>= 1.5.2) rufus-scheduler (>= 2.0.24) @@ -676,8 +677,8 @@ GEM json (~> 1.8) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - sinatra (1.4.6) - rack (~> 1.4) + sinatra (1.4.7) + rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) six (0.2.0) -- cgit v1.2.1 From cc154fc47b4ae2c81d9b8d92d725b4a082ed7727 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jul 2016 15:03:36 +0100 Subject: Cache autocomplete results --- app/assets/javascripts/gfm_auto_complete.js.coffee | 7 +++++-- app/views/layouts/_init_auto_complete.html.haml | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 190bb38504c..b7d040bae85 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -4,7 +4,7 @@ window.GitLab ?= {} GitLab.GfmAutoComplete = dataLoading: false dataLoaded: false - + cachedData: {} dataSource: '' # Emoji @@ -55,7 +55,7 @@ GitLab.GfmAutoComplete = @setupAtWho() if @dataSource - if !@dataLoading + if not @dataLoading and not @cachedData @dataLoading = true # We should wait until initializations are done @@ -70,6 +70,8 @@ GitLab.GfmAutoComplete = @loadData(data) , 1000) + if @cachedData? + @loadData(@cachedData) setupAtWho: -> # Emoji @@ -205,6 +207,7 @@ GitLab.GfmAutoComplete = $.getJSON(dataSource) loadData: (data) -> + @cachedData = data @dataLoaded = true # load members diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 96b38485425..12e7ed0e792 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -3,4 +3,5 @@ - if @noteable :javascript GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}" + GitLab.GfmAutoComplete.cachedData = undefined; GitLab.GfmAutoComplete.setup(); -- cgit v1.2.1 From bca03d0875111fe4a067689939eb4260539ca39f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 1 Jul 2016 08:18:33 -0700 Subject: Add troubleshooting section for SMTP settings [ci skip] --- doc/administration/troubleshooting/debug.md | 53 +++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md index e5701b86cf3..d127d7b85e5 100644 --- a/doc/administration/troubleshooting/debug.md +++ b/doc/administration/troubleshooting/debug.md @@ -3,9 +3,58 @@ Sometimes things don't work the way they should. Here are some tips on debugging issues out in production. -## The GNU Project Debugger (gdb) +## Mail not working -`gdb` is a must-have tool for debugging issues. To install on Ubuntu/Debian: +A common problem is that mails are not being sent for some reason. Suppose you configured +an SMTP server, but you're not seeing mail delivered. Here's how to check the settings: + +1. Run a Rails console: + + ```sh + sudo gitlab-rails console production + ``` + + or for source installs: + + ```sh + bundle exec rails console production + ``` + +2. Look at the ActionMailer `delivery_method` to make sure it matches what you + intended. If you configured SMTP, it should say `:smtp`. If you're using + Sendmail, it should say `:sendmail`: + + ```ruby + irb(main):001:0> ActionMailer::Base.delivery_method + => :smtp + ``` + +3. If you're using SMTP, check the mail settings: + + ```ruby + irb(main):002:0> ActionMailer::Base.smtp_settings + => {:address=>"localhost", :port=>25, :domain=>"localhost.localdomain", :user_name=>nil, :password=>nil, :authentication=>nil, :enable_starttls_auto=>true}``` + ``` + + In the example above, the SMTP server is configured for the local machine. If this is intended, you may need to check your local mail + logs (e.g. `/var/log/mail.log`) for more details. + +4. Send a test message via the console. + + ```ruby + irb(main):003:0> Notify.test_email('youremail@email.com', 'Hello World', 'This is a test message').deliver_now + ``` + + If you do not receive an e-mail and/or see an error message, then check + your mail server settings. + +## Advanced Issues + +For more advanced issues, `gdb` is a must-have tool for debugging issues. + +### The GNU Project Debugger (gdb) + +To install on Ubuntu/Debian: ``` sudo apt-get install gdb -- cgit v1.2.1 From fb056c78a7348b8f00024c908fa4b13c606cc1a7 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 1 Jul 2016 09:34:02 -0600 Subject: Upgrade Thin from 1.6.1 to 1.7.0. Includes support for Rack 2. Changelog: https://github.com/macournoyer/thin/blob/master/CHANGELOG --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 5213a59cab0..1c360d165f1 100644 --- a/Gemfile +++ b/Gemfile @@ -265,7 +265,7 @@ group :development do gem "sdoc", '~> 0.3.20' # thin instead webrick - gem 'thin', '~> 1.6.1' + gem 'thin', '~> 1.7.0' end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index f99b373dbbd..195cc7a9d3b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -727,10 +727,10 @@ GEM temple (0.7.7) test_after_commit (0.4.2) activerecord (>= 3.2) - thin (1.6.4) + thin (1.7.0) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) - rack (~> 1.0) + rack (>= 1, < 3) thor (0.19.1) thread_safe (0.3.5) tilt (2.0.5) @@ -973,7 +973,7 @@ DEPENDENCIES teaspoon (~> 1.1.0) teaspoon-jasmine (~> 2.2.0) test_after_commit (~> 0.4.2) - thin (~> 1.6.1) + thin (~> 1.7.0) tinder (~> 1.10.0) turbolinks (~> 2.5.0) u2f (~> 0.2.1) -- cgit v1.2.1 From 747456342512165cc2ac35a87f02e61a2a76795e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 1 Jul 2016 08:43:12 -0700 Subject: Fix CHANGELOG typo: by_pass -> bypass [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c71e54a9148..dd46cc703a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -64,7 +64,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 -- cgit v1.2.1 From bd78f5733ca546bf940438b84aefa2fa3abacb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 27 Jun 2016 16:20:57 +0200 Subject: Exclude requesters from Project#members, Group#members and User#members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And create new Project#requesters, Group#requesters scopes. Signed-off-by: Rémy Coutable --- app/controllers/admin/groups_controller.rb | 1 + app/controllers/admin/projects_controller.rb | 3 +- app/controllers/concerns/membership_actions.rb | 5 +- app/controllers/groups/group_members_controller.rb | 6 +- .../projects/project_members_controller.rb | 9 ++- app/helpers/members_helper.rb | 11 ++++ app/models/group.rb | 9 +-- app/models/member.rb | 2 - app/models/project.rb | 8 ++- app/models/project_team.rb | 14 ++--- app/views/admin/groups/show.html.haml | 8 +-- app/views/admin/projects/show.html.haml | 12 ++-- app/views/groups/group_members/index.html.haml | 10 ++-- app/views/layouts/nav/_group_settings.html.haml | 2 +- app/views/layouts/nav/_project.html.haml | 2 +- .../_shared_group_members.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 6 +- .../members/_access_request_buttons.html.haml | 16 ++---- app/views/shared/members/_requests.html.haml | 6 +- .../groups/group_members_controller_spec.rb | 6 +- .../projects/project_members_controller_spec.rb | 6 +- .../members/owner_manages_access_requests_spec.rb | 2 +- .../groups/members/user_requests_access_spec.rb | 8 +-- .../members/master_manages_access_requests_spec.rb | 2 +- .../projects/members/user_requests_access_spec.rb | 8 +-- spec/helpers/members_helper_spec.rb | 66 ++++++++++++++++++++++ spec/mailers/notify_spec.rb | 10 ++-- spec/models/concerns/access_requestable_spec.rb | 4 +- spec/models/group_spec.rb | 29 ++++++++++ spec/models/member_spec.rb | 20 +------ spec/models/project_spec.rb | 30 ++++++++++ 31 files changed, 225 insertions(+), 98 deletions(-) diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index a6db4690df0..94b5aaa71d0 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -10,6 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController def show @members = @group.members.order("access_level DESC").page(params[:members_page]) + @requesters = @group.requesters @projects = @group.projects.page(params[:projects_page]) end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 87986fdf8b1..4c9c6362ffc 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -20,7 +20,8 @@ class Admin::ProjectsController < Admin::ApplicationController @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]) end - @project_members = @project.project_members.page(params[:project_members_page]) + @project_members = @project.members.page(params[:project_members_page]) + @requesters = @project.requesters end def transfer diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 52dc396af6a..52682ef9dc9 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -10,7 +10,7 @@ module MembershipActions end def approve_access_request - @member = membershipable.members.request.find(params[:id]) + @member = membershipable.requesters.find(params[:id]) return render_403 unless can?(current_user, action_member_permission(:update, @member), @member) @@ -20,7 +20,8 @@ module MembershipActions end def leave - @member = membershipable.members.find_by(user_id: current_user) + @member = membershipable.members.find_by(user_id: current_user) || + membershipable.requesters.find_by(user_id: current_user) Members::DestroyService.new(@member, current_user).execute source_type = @member.real_source_type.humanize(capitalize: false) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 2c49fe3833e..9fc41a12536 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -7,7 +7,7 @@ class Groups::GroupMembersController < Groups::ApplicationController def index @project = @group.projects.find(params[:project_id]) if params[:project_id] @members = @group.group_members - @members = @members.non_pending unless can?(current_user, :admin_group, @group) + @members = @members.non_invite unless can?(current_user, :admin_group, @group) if params[:search].present? users = @group.users.search(params[:search]).to_a @@ -15,6 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController end @members = @members.order('access_level DESC').page(params[:page]).per(50) + @requesters = @group.requesters if can?(current_user, :admin_group, @group) @group_member = @group.group_members.new end @@ -34,7 +35,8 @@ class Groups::GroupMembersController < Groups::ApplicationController end def destroy - @group_member = @group.group_members.find(params[:id]) + @group_member = @group.members.find_by(id: params[:id]) || + @group.requesters.find_by(id: params[:id]) Members::DestroyService.new(@group_member, current_user).execute diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 6ba32d33403..3435a118964 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,7 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @project_members = @project.project_members - @project_members = @project_members.non_pending unless can?(current_user, :admin_project, @project) + @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? users = @project.users.search(params[:search]).to_a @@ -19,7 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController if @group @group_members = @group.group_members - @group_members = @group_members.non_pending unless can?(current_user, :admin_group, @group) + @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group) if params[:search].present? users = @group.users.search(params[:search]).to_a @@ -29,6 +29,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController @group_members = @group_members.order('access_level DESC') end + @requesters = @project.requesters if can?(current_user, :admin_project, @project) + @project_member = @project.project_members.new @project_group_links = @project.project_group_links end @@ -48,7 +50,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def destroy - @project_member = @project.project_members.find(params[:id]) + @project_member = @project.members.find_by(id: params[:id]) || + @project.requesters.find_by(id: params[:id]) Members::DestroyService.new(@project_member, current_user).execute diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index ec106418f2d..c70cd19b587 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -12,6 +12,17 @@ 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/models/group.rb b/app/models/group.rb index c70c719e338..a8be7004ee8 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -6,15 +6,16 @@ class Group < Namespace include AccessRequestable include Referable - has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' alias_method :members, :group_members - has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members - + has_many :users, through: :group_members has_many :owners, - -> { where(members: { requested_at: nil, access_level: Gitlab::Access::OWNER }) }, + -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :user + has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' + has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: :project has_many :notification_settings, dependent: :destroy, as: :source diff --git a/app/models/member.rb b/app/models/member.rb index 57161397e2b..44db3d977fa 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -30,8 +30,6 @@ class Member < ActiveRecord::Base scope :invite, -> { where.not(invite_token: nil) } scope :non_invite, -> { where(invite_token: nil) } scope :request, -> { where.not(requested_at: nil) } - scope :non_request, -> { where(requested_at: nil) } - scope :non_pending, -> { non_request.non_invite } scope :has_access, -> { where('access_level > 0') } scope :guests, -> { where(access_level: GUEST) } diff --git a/app/models/project.rb b/app/models/project.rb index 6a950ee830d..ae96f00a705 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -108,9 +108,13 @@ class Project < ActiveRecord::Base has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy - has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' + + has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember' alias_method :members, :project_members - has_many :users, -> { where(members: { requested_at: nil }) }, through: :project_members + has_many :users, through: :project_members + + has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember' + has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 0865b979ce0..0b700930641 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -22,12 +22,12 @@ class ProjectTeam end def find_member(user_id) - member = project.members.non_request.find_by(user_id: user_id) + member = project.members.find_by(user_id: user_id) # If user is not in project members # we should check for group membership if group && !member - member = group.members.non_request.find_by(user_id: user_id) + member = group.members.find_by(user_id: user_id) end member @@ -137,10 +137,10 @@ class ProjectTeam def max_member_access(user_id) access = [] - access += project.members.non_request.where(user_id: user_id).has_access.pluck(:access_level) + access += project.members.where(user_id: user_id).has_access.pluck(:access_level) if group - access += group.members.non_request.where(user_id: user_id).has_access.pluck(:access_level) + access += group.members.where(user_id: user_id).has_access.pluck(:access_level) end if project.invited_groups.any? && project.allowed_to_share_with_group? @@ -168,14 +168,14 @@ class ProjectTeam end def fetch_members(level = nil) - project_members = project.members.non_request - group_members = group ? group.members.non_request : [] + project_members = project.members + group_members = group ? group.members : [] invited_members = [] if project.invited_groups.any? && project.allowed_to_share_with_group? project.project_group_links.each do |group_link| invited_group = group_link.group - im = invited_group.members.non_request + im = invited_group.members if level int_level = GroupMember.access_level_roles[level.to_s.singularize.titleize] diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 50770465f07..522153b37e3 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -89,16 +89,16 @@ %hr = button_tag 'Add users to group', class: "btn btn-create" - = render 'shared/members/requests', membership_source: @group, members: @members.request + = render 'shared/members/requests', membership_source: @group, requesters: @requesters .panel.panel-default .panel-heading %strong= @group.name group members - %span.badge= @group.members.non_request.size + %span.badge= @group.members.size .pull-right = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs" %ul.well-list.group-users-list.content-list - = render partial: 'shared/members/member', collection: @members.non_request, as: :member, locals: { show_controls: false } + = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } .panel-footer - = paginate @members.non_request, param_name: 'members_page', theme: 'gitlab' + = paginate @members, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 461d588415d..82d3169c6f9 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -137,16 +137,16 @@ .panel-heading %strong= @group.name group members - %span.badge= @group_members.non_request.size + %span.badge= @group_members.size .pull-right = link_to admin_group_path(@group), class: 'btn btn-xs' do = icon('pencil-square-o', text: 'Manage Access') %ul.well-list.content-list - = render partial: 'shared/members/member', collection: @group_members.non_request, as: :member, locals: { show_controls: false } + = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false } .panel-footer - = paginate @group_members.non_request, param_name: 'group_members_page', theme: 'gitlab' + = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' - = render 'shared/members/requests', membership_source: @project, members: @project_members.request + = render 'shared/members/requests', membership_source: @project, requesters: @requesters .panel.panel-default .panel-heading @@ -156,6 +156,6 @@ .pull-right = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs" %ul.well-list.project_members.content-list - = render partial: 'shared/members/member', collection: @project_members.non_request, as: :member, locals: { show_controls: false } + = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } .panel-footer - = paginate @project_members.non_request, param_name: 'project_members_page', theme: 'gitlab' + = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index d6acade84f1..90f362c052b 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,7 +1,7 @@ - page_title "Members" .group-members-page.prepend-top-default - - if current_user && current_user.can?(:admin_group_member, @group) + - if can?(current_user, :admin_group_member, @group) .panel.panel-default .panel-heading Add new user to group @@ -11,13 +11,13 @@ .new-group-member-holder = render "new_group_member" - = render 'shared/members/requests', membership_source: @group, members: @members.request + = render 'shared/members/requests', membership_source: @group, requesters: @requesters .panel.panel-default .panel-heading %strong #{@group.name} group members - %span.badge= @members.non_request.size + %span.badge= @members.size .controls = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group @@ -25,8 +25,8 @@ = button_tag class: 'btn', title: 'Search' do = icon("search") %ul.content-list - = render partial: 'shared/members/member', collection: @members.non_request, as: :member - = paginate @members.non_request, theme: 'gitlab' + = render partial: 'shared/members/member', collection: @members, as: :member + = paginate @members, theme: 'gitlab' :javascript $('form.member-search-form').on('submit', function(event) { diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index 3a24b09ab7e..bf9a7ecb786 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,6 +1,6 @@ - if current_user - can_edit = can?(current_user, :admin_group, @group) - - member = @group.members.non_request.find_by(user_id: current_user.id) + - member = @group.members.find_by(user_id: current_user.id) - can_leave = member && can?(current_user, :destroy_group_member, member) .controls diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index dcef427cda3..9e65d94186b 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -7,7 +7,7 @@ %ul.dropdown-menu.dropdown-menu-align-right - can_edit = can?(current_user, :admin_project, @project) -# We don't use @project.team.find_member because it searches for group members too... - - member = @project.members.non_request.find_by(user_id: current_user.id) + - member = @project.members.find_by(user_id: current_user.id) - can_leave = member && can?(current_user, :destroy_project_member, member) = render 'layouts/nav/project_settings', can_edit: can_edit diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml index 840b57c2e63..77370c14def 100644 --- a/app/views/projects/project_members/_shared_group_members.html.haml +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -1,6 +1,6 @@ - @project_group_links.each do |group_links| - shared_group = group_links.group - - shared_group_members = shared_group.members.non_request + - shared_group_members = shared_group.members - shared_group_users_count = shared_group_members.size .panel.panel-default .panel-heading diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index a2026c41d01..9031f01b496 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -13,12 +13,12 @@ Users with access to this project are listed below. = render "new_project_member" - = render 'shared/members/requests', membership_source: @project, members: @project_members.request + = render 'shared/members/requests', membership_source: @project, requesters: @requesters - = render 'team', members: @project_members.non_request + = render 'team', members: @project_members - if @group - = render "group_members", members: @group_members.non_request + = render "group_members", members: @group_members - if @project_group_links.any? && @project.allowed_to_share_with_group? = render "shared_group_members" diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index c56418f052a..35dcdccc921 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,13 +1,9 @@ -- member = source.members.find_by(user_id: current_user.id) -- group_member = source.group.members.find_by(user_id: current_user.id) if source.respond_to?(:group) && source.group - -- unless group_member - - if member - - if member.request? - = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: remove_member_message(member) }, - class: 'btn' +- if can_see_request_access_button?(source) + - if requester = source.requesters.find_by(user_id: current_user.id) + = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: remove_member_message(requester) }, + class: 'btn' - else = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), method: :post, diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml index e4bd2bdc265..40b39e850b0 100644 --- a/app/views/shared/members/_requests.html.haml +++ b/app/views/shared/members/_requests.html.haml @@ -1,8 +1,8 @@ -- if members.any? +- if requesters.any? .panel.panel-default .panel-heading %strong= membership_source.name access requests - %span.badge= members.size + %span.badge= requesters.size %ul.content-list - = render partial: 'shared/members/member', collection: members, as: :member + = render partial: 'shared/members/member', collection: requesters, as: :member diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index ddc54108a7b..c34475976c6 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -133,7 +133,7 @@ describe Groups::GroupMembersController do expect(response).to set_flash.to 'Your access request to the group has been withdrawn.' expect(response).to redirect_to(group_path(group)) - expect(group.members.request).to be_empty + expect(group.requesters).to be_empty expect(group.users).not_to include user end end @@ -153,7 +153,7 @@ describe Groups::GroupMembersController do expect(response).to set_flash.to 'Your request for access has been queued for review.' expect(response).to redirect_to(group_path(group)) - expect(group.members.request.exists?(user_id: user)).to be_truthy + expect(group.requesters.exists?(user_id: user)).to be_truthy expect(group.users).not_to include user end end @@ -175,7 +175,7 @@ describe Groups::GroupMembersController do let(:group_requester) { create(:user) } let(:member) do group.request_access(group_requester) - group.members.request.find_by(user_id: group_requester) + group.requesters.find_by(user_id: group_requester) end context 'when user does not have enough rights' do diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 29aaceb2302..5e2a8cf3849 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -187,7 +187,7 @@ describe Projects::ProjectMembersController do expect(response).to set_flash.to 'Your access request to the project has been withdrawn.' expect(response).to redirect_to(namespace_project_path(project.namespace, project)) - expect(project.members.request).to be_empty + expect(project.requesters).to be_empty expect(project.users).not_to include user end end @@ -210,7 +210,7 @@ describe Projects::ProjectMembersController do expect(response).to redirect_to( namespace_project_path(project.namespace, project) ) - expect(project.members.request.exists?(user_id: user)).to be_truthy + expect(project.requesters.exists?(user_id: user)).to be_truthy expect(project.users).not_to include user end end @@ -233,7 +233,7 @@ describe Projects::ProjectMembersController do let(:team_requester) { create(:user) } let(:member) do project.request_access(team_requester) - project.members.request.find_by(user_id: team_requester.id) + project.requesters.find_by(user_id: team_requester.id) end context 'when user does not have enough rights' do diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 321c9bad7d0..2aae3e00261 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -41,7 +41,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do def expect_visible_access_request(group, user) - expect(group.members.request.exists?(user_id: user)).to be_truthy + expect(group.requesters.exists?(user_id: user)).to be_truthy expect(page).to have_content "#{group.name} access requests 1" expect(page).to have_content user.name end diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb index 4944301c938..d1a6a98ab72 100644 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ b/spec/features/groups/members/user_requests_access_spec.rb @@ -18,7 +18,7 @@ feature 'Groups > Members > User requests access', feature: true do expect(ActionMailer::Base.deliveries.last.to).to eq [owner.notification_email] expect(ActionMailer::Base.deliveries.last.subject).to match "Request to join the #{group.name} group" - expect(group.members.request.exists?(user_id: user)).to be_truthy + expect(group.requesters.exists?(user_id: user)).to be_truthy expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' @@ -42,7 +42,7 @@ feature 'Groups > Members > User requests access', feature: true do scenario 'user is not listed in the group members page' do click_link 'Request Access' - expect(group.members.request.exists?(user_id: user)).to be_truthy + expect(group.requesters.exists?(user_id: user)).to be_truthy click_link 'Members' @@ -54,11 +54,11 @@ feature 'Groups > Members > User requests access', feature: true do scenario 'user can withdraw its request for access' do click_link 'Request Access' - expect(group.members.request.exists?(user_id: user)).to be_truthy + expect(group.requesters.exists?(user_id: user)).to be_truthy click_link 'Withdraw Access Request' - expect(group.members.request.exists?(user_id: user)).to be_falsey + expect(group.requesters.exists?(user_id: user)).to be_falsey expect(page).to have_content 'Your access request to the group has been withdrawn.' end end diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index aa2d906fa2e..f7fcd9b6731 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -40,7 +40,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do end def expect_visible_access_request(project, user) - expect(project.members.request.exists?(user_id: user)).to be_truthy + expect(project.requesters.exists?(user_id: user)).to be_truthy expect(page).to have_content "#{project.name} access requests 1" expect(page).to have_content user.name end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index af420c170ef..f2fe3ef364d 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -17,7 +17,7 @@ feature 'Projects > Members > User requests access', feature: true do expect(ActionMailer::Base.deliveries.last.to).to eq [master.notification_email] expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.name_with_namespace} project" - expect(project.members.request.exists?(user_id: user)).to be_truthy + expect(project.requesters.exists?(user_id: user)).to be_truthy expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' @@ -27,7 +27,7 @@ feature 'Projects > Members > User requests access', feature: true do scenario 'user is not listed in the project members page' do click_link 'Request Access' - expect(project.members.request.exists?(user_id: user)).to be_truthy + expect(project.requesters.exists?(user_id: user)).to be_truthy open_project_settings_menu click_link 'Members' @@ -41,11 +41,11 @@ feature 'Projects > Members > User requests access', feature: true do scenario 'user can withdraw its request for access' do click_link 'Request Access' - expect(project.members.request.exists?(user_id: user)).to be_truthy + expect(project.requesters.exists?(user_id: user)).to be_truthy click_link 'Withdraw Access Request' - expect(project.members.request.exists?(user_id: user)).to be_falsey + expect(project.requesters.exists?(user_id: user)).to be_falsey expect(page).to have_content 'Your access request to the project has been withdrawn.' end diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb index f75fdb739f6..7b2155e9a4e 100644 --- a/spec/helpers/members_helper_spec.rb +++ b/spec/helpers/members_helper_spec.rb @@ -57,6 +57,72 @@ 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/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index ae55a01ebea..e527d649e34 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -406,7 +406,7 @@ describe Notify do let(:user) { create(:user) } let(:project_member) do project.request_access(user) - project.members.request.find_by(user_id: user.id) + project.requesters.find_by(user_id: user.id) end subject { Notify.member_access_requested_email('project', project_member.id) } @@ -433,7 +433,7 @@ describe Notify do let(:user) { create(:user) } let(:project_member) do project.request_access(user) - project.members.request.find_by(user_id: user.id) + project.requesters.find_by(user_id: user.id) end subject { Notify.member_access_requested_email('project', project_member.id) } @@ -459,7 +459,7 @@ describe Notify do let(:user) { create(:user) } let(:project_member) do project.request_access(user) - project.members.request.find_by(user_id: user.id) + project.requesters.find_by(user_id: user.id) end subject { Notify.member_access_denied_email('project', project.id, user.id) } @@ -684,7 +684,7 @@ describe Notify do let(:user) { create(:user) } let(:group_member) do group.request_access(user) - group.members.request.find_by(user_id: user.id) + group.requesters.find_by(user_id: user.id) end subject { Notify.member_access_requested_email('group', group_member.id) } @@ -705,7 +705,7 @@ describe Notify do let(:user) { create(:user) } let(:group_member) do group.request_access(user) - group.members.request.find_by(user_id: user.id) + group.requesters.find_by(user_id: user.id) end subject { Notify.member_access_denied_email('group', group.id, user.id) } diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb index 98307876962..96eee0e8bdd 100644 --- a/spec/models/concerns/access_requestable_spec.rb +++ b/spec/models/concerns/access_requestable_spec.rb @@ -16,7 +16,7 @@ describe AccessRequestable do before { group.request_access(user) } - it { expect(group.members.request.exists?(user_id: user)).to be_truthy } + it { expect(group.requesters.exists?(user_id: user)).to be_truthy } end end @@ -34,7 +34,7 @@ describe AccessRequestable do before { project.request_access(user) } - it { expect(project.members.request.exists?(user_id: user)).to be_truthy } + it { expect(project.requesters.exists?(user_id: user)).to be_truthy } end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 2c19aa3f67f..a878ff1b227 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -7,9 +7,38 @@ describe Group, models: true do it { is_expected.to have_many :projects } it { is_expected.to have_many(:group_members).dependent(:destroy) } it { is_expected.to have_many(:users).through(:group_members) } + it { is_expected.to have_many(:owners).through(:group_members) } + it { is_expected.to have_many(:requesters).dependent(:destroy) } it { is_expected.to have_many(:project_group_links).dependent(:destroy) } it { is_expected.to have_many(:shared_projects).through(:project_group_links) } it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + + describe '#members & #requesters' do + let(:requester) { create(:user) } + let(:developer) { create(:user) } + before do + group.request_access(requester) + group.add_developer(developer) + end + + describe '#members' do + it 'includes members and exclude requesters' do + member_user_ids = group.members.pluck(:user_id) + + expect(member_user_ids).to include(developer.id) + expect(member_user_ids).not_to include(requester.id) + end + end + + describe '#requesters' do + it 'does not include requesters' do + requester_user_ids = group.requesters.pluck(:user_id) + + expect(requester_user_ids).to include(requester.id) + expect(requester_user_ids).not_to include(developer.id) + end + end + end end describe 'modules' do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index e9134a3d283..40181a8b906 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -73,10 +73,10 @@ describe Member, models: true do @accepted_invite_member = project.members.invite.find_by_invite_email('toto2@example.com').tap { |u| u.accept_invite!(accepted_invite_user) } requested_user = create(:user).tap { |u| project.request_access(u) } - @requested_member = project.members.request.find_by(user_id: requested_user.id) + @requested_member = project.requesters.find_by(user_id: requested_user.id) accepted_request_user = create(:user).tap { |u| project.request_access(u) } - @accepted_request_member = project.members.request.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request } + @accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request } end describe '.invite' do @@ -103,22 +103,6 @@ describe Member, models: true do it { expect(described_class.request).not_to include @accepted_request_member } end - describe '.non_request' do - it { expect(described_class.non_request).to include @master } - it { expect(described_class.non_request).to include @invited_member } - it { expect(described_class.non_request).to include @accepted_invite_member } - it { expect(described_class.non_request).not_to include @requested_member } - it { expect(described_class.non_request).to include @accepted_request_member } - end - - describe '.non_pending' do - it { expect(described_class.non_pending).to include @master } - it { expect(described_class.non_pending).not_to include @invited_member } - it { expect(described_class.non_pending).to include @accepted_invite_member } - it { expect(described_class.non_pending).not_to include @requested_member } - it { expect(described_class.non_pending).to include @accepted_request_member } - end - describe '.owners_and_masters' do it { expect(described_class.owners_and_masters).to include @owner } it { expect(described_class.owners_and_masters).to include @master } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 42308035d8c..a8c777d1e3e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -11,6 +11,8 @@ describe Project, models: true do it { is_expected.to have_many(:issues).dependent(:destroy) } it { is_expected.to have_many(:milestones).dependent(:destroy) } it { is_expected.to have_many(:project_members).dependent(:destroy) } + it { is_expected.to have_many(:users).through(:project_members) } + it { is_expected.to have_many(:requesters).dependent(:destroy) } it { is_expected.to have_many(:notes).dependent(:destroy) } it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) } it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) } @@ -31,6 +33,34 @@ describe Project, models: true do it { is_expected.to have_many(:environments).dependent(:destroy) } it { is_expected.to have_many(:deployments).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } + + describe '#members & #requesters' do + let(:project) { create(:project) } + let(:requester) { create(:user) } + let(:developer) { create(:user) } + before do + project.request_access(requester) + project.team << [developer, :developer] + end + + describe '#members' do + it 'includes members and exclude requesters' do + member_user_ids = project.members.pluck(:user_id) + + expect(member_user_ids).to include(developer.id) + expect(member_user_ids).not_to include(requester.id) + end + end + + describe '#requesters' do + it 'does not include requesters' do + requester_user_ids = project.requesters.pluck(:user_id) + + expect(requester_user_ids).to include(requester.id) + expect(requester_user_ids).not_to include(developer.id) + end + end + end end describe 'modules' do -- cgit v1.2.1 From 3c2236cbe1b50629155b99d99bd2e44d9cbe133e Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 1 Jul 2016 10:08:51 -0600 Subject: Upgrade rspec-rails from 3.4.2 to 3.5.0. Also upgrade its dependencies. Includes Rails 5 support. Changelogs: rspec-rails: https://github.com/rspec/rspec-rails/compare/v3.4.2...v3.5.0 rspec-core: https://github.com/rspec/rspec-core/compare/v3.4.0...v3.5.0 rspec-support: https://github.com/rspec/rspec-support/compare/v3.4.0...v3.5.0 rspec-expectations: https://github.com/rspec/rspec-expectations/compare/v3.4.0...v3.5.0 rspec-mocks: https://github.com/rspec/rspec-mocks/compare/v3.4.0...v3.5.0 rspec: https://github.com/rspec/rspec/compare/v3.4.0...v3.5.0 --- Gemfile | 2 +- Gemfile.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 58efe6a2525..18c9b67d360 100644 --- a/Gemfile +++ b/Gemfile @@ -276,7 +276,7 @@ group :development, :test do gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails', '~> 4.6.0' - gem 'rspec-rails', '~> 3.4.0' + gem 'rspec-rails', '~> 3.5.0' gem 'rspec-retry' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' diff --git a/Gemfile.lock b/Gemfile.lock index b1f2c8c142c..85ececb7cd7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -585,29 +585,29 @@ GEM chunky_png rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) - rspec (3.4.0) - rspec-core (~> 3.4.0) - rspec-expectations (~> 3.4.0) - rspec-mocks (~> 3.4.0) - rspec-core (3.4.4) - rspec-support (~> 3.4.0) - rspec-expectations (3.4.0) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.0) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.4.0) - rspec-mocks (3.4.1) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.4.0) - rspec-rails (3.4.2) - actionpack (>= 3.0, < 4.3) - activesupport (>= 3.0, < 4.3) - railties (>= 3.0, < 4.3) - rspec-core (~> 3.4.0) - rspec-expectations (~> 3.4.0) - rspec-mocks (~> 3.4.0) - rspec-support (~> 3.4.0) + rspec-support (~> 3.5.0) + rspec-rails (3.5.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-support (~> 3.5.0) rspec-retry (0.4.5) rspec-core - rspec-support (3.4.1) + rspec-support (3.5.0) rubocop (0.40.0) parser (>= 2.3.1.0, < 3.0) powerpack (~> 0.1) @@ -936,7 +936,7 @@ DEPENDENCIES responders (~> 2.0) rouge (~> 1.11) rqrcode-rails3 (~> 0.1.7) - rspec-rails (~> 3.4.0) + rspec-rails (~> 3.5.0) rspec-retry rubocop (~> 0.40.0) rubocop-rspec (~> 1.5.0) -- cgit v1.2.1 From 4bfe10d5f972c64241cfdd0393d4d55a63092dc4 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 30 Jun 2016 12:12:16 -0700 Subject: Fix emoji paths in relative root configurations If a site specifies a relative URL root, emoji files would omit the path from the URL, leading to lots of 404s. Closes #15642 --- lib/gitlab/award_emoji.rb | 11 ++++++++++- spec/lib/gitlab/award_emoji_spec.rb | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb index 51b1df9ecbd..c94bfc0e65f 100644 --- a/lib/gitlab/award_emoji.rb +++ b/lib/gitlab/award_emoji.rb @@ -66,8 +66,17 @@ module Gitlab def self.urls @urls ||= begin path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json') + # Construct the full asset path ourselves because + # ActionView::Helpers::AssetUrlHelper.asset_url is slow for hundreds + # of entries since it has to do a lot of extra work (e.g. regexps). prefix = Gitlab::Application.config.assets.prefix digest = Gitlab::Application.config.assets.digest + base = + if defined?(Gitlab::Application.config.relative_url_root) && Gitlab::Application.config.relative_url_root + Gitlab::Application.config.relative_url_root + else + '' + end JSON.parse(File.read(path)).map do |hash| if digest @@ -76,7 +85,7 @@ module Gitlab fname = hash['unicode'] end - { name: hash['name'], path: "#{prefix}/#{fname}.png" } + { name: hash['name'], path: File.join(base, prefix, "#{fname}.png") } end end end diff --git a/spec/lib/gitlab/award_emoji_spec.rb b/spec/lib/gitlab/award_emoji_spec.rb index 0f3852b1729..00a110e31f8 100644 --- a/spec/lib/gitlab/award_emoji_spec.rb +++ b/spec/lib/gitlab/award_emoji_spec.rb @@ -2,6 +2,10 @@ require 'spec_helper' describe Gitlab::AwardEmoji do describe '.urls' do + after do + Gitlab::AwardEmoji.instance_variable_set(:@urls, nil) + end + subject { Gitlab::AwardEmoji.urls } it { is_expected.to be_an_instance_of(Array) } @@ -15,6 +19,17 @@ describe Gitlab::AwardEmoji do end end end + + context 'handles relative root' do + it 'includes the full path' do + allow(Gitlab::Application.config).to receive(:relative_url_root).and_return('/gitlab') + + subject.each do |hash| + expect(hash[:name]).to be_an_instance_of(String) + expect(hash[:path]).to start_with('/gitlab') + end + end + end end describe '.emoji_by_category' do -- cgit v1.2.1 From d71983f599afd3b81bdda42eec3e989ef7ea02d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 1 Jul 2016 17:15:48 +0200 Subject: Fix snippets comments not displayed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The issue was that @notes were not passed to Banzai::NoteRenderer.render in Projects::SnippetsController#show. This was forgotten in d470f3d1. Signed-off-by: Rémy Coutable --- app/controllers/projects/snippets_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 6d2901a24a4..6d0a7ee1031 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -54,7 +54,7 @@ class Projects::SnippetsController < Projects::ApplicationController def show @note = @project.notes.new(noteable: @snippet) - @notes = @snippet.notes.fresh + @notes = Banzai::NoteRenderer.render(@snippet.notes.fresh, @project, current_user) @noteable = @snippet end -- cgit v1.2.1 From 8353cc6111e12fa819debe188776d7b6195ffaad Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 1 Jul 2016 16:04:24 -0300 Subject: Fix import button when import fail due the namespace already been taken --- app/assets/javascripts/importer_status.js.coffee | 2 +- app/views/import/base/create.js.haml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index b0edc895649..ec42c992e98 100644 --- a/app/assets/javascripts/importer_status.js.coffee +++ b/app/assets/javascripts/importer_status.js.coffee @@ -13,7 +13,7 @@ class @ImporterStatus id = $tr.attr('id').replace('repo_', '') if $tr.find('.import-target input').length > 0 new_namespace = $tr.find('.import-target input').prop('value') - $tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}") + $tr.find('.import-target').empty().append("#{new_namespace}/#{$tr.find('.import-target').data('project_name')}") $btn .disable() diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index dfebf7768d9..804ad88468f 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -1,6 +1,8 @@ - if @already_been_taken :plain - target_field = $("tr#repo_#{@repo_id} .import-target") + tr = $("tr#repo_#{@repo_id}") + target_field = tr.find(".import-target") + import_button = tr.find(".btn-import") origin_target = target_field.text() project_name = "#{@project_name}" origin_namespace = "#{@target_namespace}" @@ -10,6 +12,7 @@ target_field.append("/" + project_name) target_field.data("project_name", project_name) target_field.find('input').prop("value", origin_namespace) + import_button.enable().removeClass('is-loading') - elsif @access_denied :plain job = $("tr#repo_#{@repo_id}") -- cgit v1.2.1 From 3d69d01e1fe78427f00e3e89e94975819c764806 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 1 Jul 2016 16:43:02 -0300 Subject: Cache results from jQuery selectors to retrieve namespace name --- app/assets/javascripts/importer_status.js.coffee | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index ec42c992e98..eb046eb2eff 100644 --- a/app/assets/javascripts/importer_status.js.coffee +++ b/app/assets/javascripts/importer_status.js.coffee @@ -7,13 +7,16 @@ class @ImporterStatus $('.js-add-to-import') .off 'click' .on 'click', (e) => - new_namespace = null $btn = $(e.currentTarget) $tr = $btn.closest('tr') + $target_field = $tr.find('.import-target') + $namespace_input = $target_field.find('input') id = $tr.attr('id').replace('repo_', '') - if $tr.find('.import-target input').length > 0 - new_namespace = $tr.find('.import-target input').prop('value') - $tr.find('.import-target').empty().append("#{new_namespace}/#{$tr.find('.import-target').data('project_name')}") + new_namespace = null + + if $namespace_input.length > 0 + new_namespace = $namespace_input.prop('value') + $target_field.empty().append("#{new_namespace}/#{$target_field.data('project_name')}") $btn .disable() -- cgit v1.2.1 From 9dab692a2be2624ee2dddd6ae54accb2c28e4f93 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 1 Jul 2016 16:05:39 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ac14af9d2d0..2f93fcdbaa0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 8.9.5 (unreleased) - Fix assigning shared runners as admins. !4961 - Show "locked" label for locked runners on runners admin. !4961 - 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. v 8.9.4 - Fix privilege escalation issue with OAuth external users. -- cgit v1.2.1 From 9e211091a85c20adea63b89111240350d6d8ffcb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 1 Jul 2016 21:56:17 +0200 Subject: Enable Style/EmptyLines cop, remove redundant ones --- .rubocop.yml | 2 +- app/controllers/admin/hooks_controller.rb | 1 - app/controllers/confirmations_controller.rb | 1 - app/controllers/import/base_controller.rb | 1 - app/controllers/import/fogbugz_controller.rb | 2 -- app/controllers/import/gitorious_controller.rb | 1 - app/controllers/import/google_code_controller.rb | 2 -- app/controllers/invites_controller.rb | 1 - app/controllers/projects/issues_controller.rb | 1 - app/controllers/projects/network_controller.rb | 1 - app/controllers/projects/wikis_controller.rb | 1 - app/helpers/emails_helper.rb | 1 - app/helpers/issuables_helper.rb | 1 - app/helpers/search_helper.rb | 1 - app/models/concerns/issuable.rb | 1 - app/models/members/project_member.rb | 1 - app/models/project_services/bugzilla_service.rb | 2 -- app/models/project_services/custom_issue_tracker_service.rb | 2 -- app/models/project_services/drone_ci_service.rb | 1 - app/models/project_services/issue_tracker_service.rb | 1 - app/models/project_services/jira_service.rb | 1 - app/models/project_services/redmine_service.rb | 1 - app/models/repository.rb | 1 - app/models/user.rb | 1 - app/services/create_release_service.rb | 1 - app/services/issues/base_service.rb | 1 - app/services/merge_requests/base_service.rb | 1 - app/services/merge_requests/merge_when_build_succeeds_service.rb | 1 - app/services/milestones/destroy_service.rb | 1 - app/services/projects/download_service.rb | 1 - app/services/projects/import_export/export_service.rb | 1 - app/services/system_note_service.rb | 1 - app/services/update_release_service.rb | 1 - app/services/wiki_pages/base_service.rb | 1 - config/initializers/1_settings.rb | 4 ---- config/routes.rb | 2 -- features/steps/dashboard/new_project.rb | 1 - features/steps/explore/projects.rb | 3 --- features/steps/project/archived.rb | 1 - features/steps/project/issues/issues.rb | 5 ----- features/steps/project/project_find_file.rb | 1 - features/steps/shared/issuable.rb | 1 - features/steps/snippet_search.rb | 1 - lib/api/award_emoji.rb | 1 - lib/api/branches.rb | 2 -- lib/api/builds.rb | 1 - lib/api/milestones.rb | 1 - lib/api/project_hooks.rb | 1 - lib/api/project_members.rb | 1 - lib/api/projects.rb | 2 -- lib/api/services.rb | 1 - lib/banzai/filter/image_link_filter.rb | 1 - lib/banzai/filter/wiki_link_filter.rb | 1 - lib/banzai/pipeline/full_pipeline.rb | 1 - lib/ci/charts.rb | 1 - lib/disable_email_interceptor.rb | 1 - lib/gitlab/asciidoc.rb | 1 - lib/gitlab/backend/grack_auth.rb | 1 - lib/gitlab/diff/parser.rb | 1 - lib/gitlab/import_export/importer.rb | 1 - lib/gitlab/import_export/members_mapper.rb | 1 - lib/gitlab/import_export/project_creator.rb | 1 - lib/gitlab/import_export/project_tree_restorer.rb | 1 - lib/gitlab/import_export/reader.rb | 2 -- lib/gitlab/import_export/relation_factory.rb | 1 - lib/gitlab/import_export/shared.rb | 1 - lib/gitlab/import_export/uploads_saver.rb | 1 - lib/gitlab/import_export/version_checker.rb | 1 - lib/gitlab/import_export/version_saver.rb | 1 - lib/gitlab/import_sources.rb | 2 -- lib/gitlab/lfs/response.rb | 1 - lib/gitlab/other_markup.rb | 1 - lib/gitlab/regex.rb | 6 ------ lib/gitlab/saml/auth_hash.rb | 2 -- lib/gitlab/saml/config.rb | 2 -- lib/gitlab/saml/user.rb | 1 - lib/uploaded_file.rb | 1 - spec/controllers/application_controller_spec.rb | 1 - spec/controllers/profiles/accounts_controller_spec.rb | 1 - spec/controllers/projects/branches_controller_spec.rb | 2 -- spec/controllers/projects/forks_controller_spec.rb | 2 -- spec/controllers/projects/labels_controller_spec.rb | 1 - spec/controllers/projects/merge_requests_controller_spec.rb | 2 -- spec/controllers/projects/repositories_controller_spec.rb | 1 - spec/controllers/projects/tree_controller_spec.rb | 1 - spec/controllers/projects_controller_spec.rb | 3 --- spec/controllers/users_controller_spec.rb | 1 - spec/features/admin/admin_hooks_spec.rb | 2 -- spec/features/dashboard/user_filters_projects_spec.rb | 1 - spec/features/gitlab_flavored_markdown_spec.rb | 2 -- spec/features/groups/members/owner_manages_access_requests_spec.rb | 1 - spec/features/issues/filter_issues_spec.rb | 6 ------ spec/features/issues_spec.rb | 4 ---- spec/features/projects/commit/builds_spec.rb | 1 - spec/features/projects/commits/cherry_pick_spec.rb | 1 - spec/features/projects/labels/issues_sorted_by_priority_spec.rb | 3 --- spec/features/search_spec.rb | 5 ----- spec/features/security/group/internal_access_spec.rb | 1 - spec/features/security/group/private_access_spec.rb | 1 - spec/features/security/group/public_access_spec.rb | 1 - spec/features/signup_spec.rb | 2 -- spec/features/tags/master_deletes_tag_spec.rb | 1 - spec/features/users_spec.rb | 1 - spec/finders/group_projects_finder_spec.rb | 2 -- spec/finders/snippets_finder_spec.rb | 1 - spec/helpers/visibility_level_helper_spec.rb | 2 -- spec/initializers/settings_spec.rb | 2 -- spec/lib/banzai/pipeline/wiki_pipeline_spec.rb | 1 - spec/lib/ci/charts_spec.rb | 1 - spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 1 - spec/lib/gitlab/asciidoc_spec.rb | 3 --- spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb | 1 - spec/lib/gitlab/diff/inline_diff_marker_spec.rb | 1 - spec/lib/gitlab/fogbugz_import/client_spec.rb | 1 - spec/lib/gitlab/git_access_spec.rb | 1 - spec/lib/gitlab/github_import/label_formatter_spec.rb | 1 - spec/lib/gitlab/google_code_import/importer_spec.rb | 1 - spec/lib/gitlab/import_export/members_mapper_spec.rb | 1 - spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 1 - spec/lib/gitlab/import_export/project_tree_saver_spec.rb | 2 -- spec/lib/gitlab/import_export/reader_spec.rb | 1 - spec/lib/gitlab/import_export/repo_bundler_spec.rb | 1 - spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb | 1 - spec/lib/gitlab/ldap/auth_hash_spec.rb | 1 - spec/lib/gitlab/o_auth/user_spec.rb | 3 --- spec/lib/gitlab/push_data_builder_spec.rb | 1 - spec/lib/gitlab/saml/user_spec.rb | 1 - spec/lib/gitlab/url_sanitizer_spec.rb | 1 - spec/mailers/notify_spec.rb | 5 ----- spec/models/build_spec.rb | 1 - spec/models/concerns/issuable_spec.rb | 1 - spec/models/concerns/strip_attribute_spec.rb | 1 - spec/models/email_spec.rb | 2 -- spec/models/forked_project_link_spec.rb | 3 --- spec/models/identity_spec.rb | 1 - spec/models/members/project_member_spec.rb | 1 - spec/models/namespace_spec.rb | 1 - spec/models/project_services/jira_service_spec.rb | 2 -- .../models/project_services/slack_service/wiki_page_message_spec.rb | 1 - spec/models/repository_spec.rb | 1 - spec/models/service_spec.rb | 2 -- spec/requests/api/api_helpers_spec.rb | 1 - spec/requests/api/award_emoji_spec.rb | 1 - spec/requests/api/commit_statuses_spec.rb | 1 - spec/requests/api/doorkeeper_access_spec.rb | 1 - spec/requests/api/labels_spec.rb | 1 - spec/requests/api/notes_spec.rb | 3 --- spec/requests/api/projects_spec.rb | 1 - spec/requests/api/services_spec.rb | 1 - spec/requests/api/settings_spec.rb | 1 - spec/requests/api/users_spec.rb | 1 - spec/routing/admin_routing_spec.rb | 1 - spec/routing/project_routing_spec.rb | 1 - spec/routing/routing_spec.rb | 1 - spec/services/git_hooks_service_spec.rb | 2 -- spec/services/git_push_service_spec.rb | 6 ------ spec/services/merge_requests/refresh_service_spec.rb | 1 - spec/services/notification_service_spec.rb | 5 ----- spec/services/projects/transfer_service_spec.rb | 1 - spec/support/jira_service_helper.rb | 1 - spec/tasks/gitlab/backup_rake_spec.rb | 1 - spec/workers/project_cache_worker_spec.rb | 1 - 162 files changed, 1 insertion(+), 238 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index cdcfd8150e5..cd13f581517 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -193,7 +193,7 @@ Style/EmptyLineBetweenDefs: # Don't use several empty lines in a row. Style/EmptyLines: - Enabled: false + Enabled: true # Keep blank lines around access modifiers. Style/EmptyLinesAroundAccessModifier: diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 4e85b6b4cf2..cbfc4581411 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -22,7 +22,6 @@ class Admin::HooksController < Admin::ApplicationController redirect_to admin_hooks_path end - def test @hook = SystemHook.find(params[:hook_id]) data = { diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 7b66ad3f92c..3da44b9b888 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -1,5 +1,4 @@ class ConfirmationsController < Devise::ConfirmationsController - def almost_there flash[:notice] = nil render layout: "devise_empty" diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 93a7ace3530..7e8597a5eb3 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -1,5 +1,4 @@ class Import::BaseController < ApplicationController - private def get_or_create_namespace diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 18300390851..99b10b2f9b3 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -5,7 +5,6 @@ class Import::FogbugzController < Import::BaseController rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized def new - end def callback @@ -22,7 +21,6 @@ class Import::FogbugzController < Import::BaseController end def new_user_map - end def create_user_map diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb index eecbe380c9e..a4c4ad23027 100644 --- a/app/controllers/import/gitorious_controller.rb +++ b/app/controllers/import/gitorious_controller.rb @@ -44,5 +44,4 @@ class Import::GitoriousController < Import::BaseController def verify_gitorious_import_enabled render_404 unless gitorious_import_enabled? end - end diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb index e0de31f2251..8d0de158f98 100644 --- a/app/controllers/import/google_code_controller.rb +++ b/app/controllers/import/google_code_controller.rb @@ -3,7 +3,6 @@ class Import::GoogleCodeController < Import::BaseController before_action :user_map, only: [:new_user_map, :create_user_map] def new - end def callback @@ -34,7 +33,6 @@ class Import::GoogleCodeController < Import::BaseController end def new_user_map - end def create_user_map diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 94bb108c5f5..58964a0e65d 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -5,7 +5,6 @@ class InvitesController < ApplicationController respond_to :html def show - end def accept diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 8b8df680739..b6e80762e3c 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -76,7 +76,6 @@ class Projects::IssuesController < Projects::ApplicationController render json: @issue.to_json(include: [:milestone, :labels]) end end - end def create diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index b181c47baec..34318391dd9 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -7,7 +7,6 @@ class Projects::NetworkController < Projects::ApplicationController before_action :authorize_download_code! def show - @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json)) @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s") diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 7ec1e73b3be..607fe9c7fed 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -124,5 +124,4 @@ class Projects::WikisController < Projects::ApplicationController def wiki_params params[:wiki].slice(:title, :content, :format, :message) end - end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 8466d0aa0ba..2843ad96efa 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -1,5 +1,4 @@ module EmailsHelper - # Google Actions # https://developers.google.com/gmail/markup/reference/go-to-action def email_action(url) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8231ce49fac..294b7e92b8d 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -1,5 +1,4 @@ module IssuablesHelper - def sidebar_gutter_toggle_icon sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right') end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index d2f94d4ae6f..f9fc525df6f 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,5 +1,4 @@ module SearchHelper - def search_autocomplete_opts(term) return unless current_user diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index d6f55885dd6..acb6f5a2998 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -58,7 +58,6 @@ module Issuable scope :references_project, -> { references(:project) } scope :non_archived, -> { join_project.where(projects: { archived: false }) } - delegate :name, :email, to: :author, diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index e9d3a82ba15..f39afc61ce9 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -15,7 +15,6 @@ class ProjectMember < Member before_destroy :delete_member_todos class << self - # Add users to project teams with passed access option # # access can be an integer representing a access code diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb index 260f6030957..81af55aa29a 100644 --- a/app/models/project_services/bugzilla_service.rb +++ b/app/models/project_services/bugzilla_service.rb @@ -1,5 +1,4 @@ class BugzillaService < IssueTrackerService - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url def title @@ -21,5 +20,4 @@ class BugzillaService < IssueTrackerService def to_param 'bugzilla' end - end diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 8f2db46a7ba..63a5ed14484 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -1,5 +1,4 @@ class CustomIssueTrackerService < IssueTrackerService - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url def title @@ -31,5 +30,4 @@ class CustomIssueTrackerService < IssueTrackerService { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' } ] end - end diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 966dbc41d73..5e4dd101c53 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -1,5 +1,4 @@ class DroneCiService < CiService - prop_accessor :drone_url, :token, :enable_ssl_verification validates :drone_url, presence: true, url: true, if: :activated? diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 87ecb3b8b86..d1df6d0292f 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -1,5 +1,4 @@ class IssueTrackerService < Service - validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? default_value_for :category, 'issue_tracker' diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 8b3296c712f..27bf08bf7d9 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -190,7 +190,6 @@ class JiraService < IssueTrackerService end end - def auth require 'base64' Base64.urlsafe_encode64("#{self.username}:#{self.password}") diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index 11cce3e0561..f634e0772c0 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -1,5 +1,4 @@ class RedmineService < IssueTrackerService - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url def title diff --git a/app/models/repository.rb b/app/models/repository.rb index eb232ea681b..11ecb281a55 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -875,7 +875,6 @@ class Repository merge_base(ancestor_id, descendant_id) == ancestor_id end - def search_files(query, ref) offset = 2 args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) diff --git a/app/models/user.rb b/app/models/user.rb index eac716b120b..5036a3e300c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -852,7 +852,6 @@ class User < ActiveRecord::Base projects.select(:id), groups.joins(:shared_projects).select(:project_id)] - if min_access_level scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } } relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) } diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb index e06a6f2f47a..f029db72d40 100644 --- a/app/services/create_release_service.rb +++ b/app/services/create_release_service.rb @@ -2,7 +2,6 @@ require_relative 'base_service' class CreateReleaseService < BaseService def execute(tag_name, release_description) - repository = project.repository existing_tag = repository.find_tag(tag_name) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 772f5c5fffa..089b0f527e2 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -1,6 +1,5 @@ module Issues class BaseService < ::IssuableBaseService - def hook_data(issue, action) issue_data = issue.to_hook_data(current_user) issue_url = Gitlab::UrlBuilder.build(issue) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index bc93ba2552d..bc3606a14c2 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -1,6 +1,5 @@ module MergeRequests class BaseService < ::IssuableBaseService - def create_note(merge_request) SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, merge_request.state, nil) end 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 12edfb2d671..870f5705184 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -40,6 +40,5 @@ module MergeRequests error("Can't cancel the automatic merge", 406) end end - end end diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb index 2414966505b..e457212508f 100644 --- a/app/services/milestones/destroy_service.rb +++ b/app/services/milestones/destroy_service.rb @@ -1,7 +1,6 @@ module Milestones class DestroyService < Milestones::BaseService def execute(milestone) - Milestone.transaction do update_params = { milestone: nil } diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb index 6386f57fb0d..f06a3d44c17 100644 --- a/app/services/projects/download_service.rb +++ b/app/services/projects/download_service.rb @@ -1,6 +1,5 @@ module Projects class DownloadService < BaseService - WHITELIST = [ /^[^.]+\.fogbugz.com$/ ] diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 80c7193efcb..3f507d5c400 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -1,7 +1,6 @@ module Projects module ImportExport class ExportService < BaseService - def execute(_options = {}) @shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.path_with_namespace, 'work')) save_all diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 4e8fa0818b9..b868d2e7e83 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -293,7 +293,6 @@ class SystemNoteService end end - def self.cross_reference?(note_text) note_text.start_with?(cross_reference_note_prefix) end diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb index 25eb13ef09a..0c0f68d169b 100644 --- a/app/services/update_release_service.rb +++ b/app/services/update_release_service.rb @@ -2,7 +2,6 @@ require_relative 'base_service' class UpdateReleaseService < BaseService def execute(tag_name, release_description) - repository = project.repository existing_tag = repository.find_tag(tag_name) diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb index 4c0a2c6b4d8..14317ea65c8 100644 --- a/app/services/wiki_pages/base_service.rb +++ b/app/services/wiki_pages/base_service.rb @@ -1,6 +1,5 @@ module WikiPages class BaseService < ::BaseService - def hook_data(page, action) hook_data = { object_kind: page.class.name.underscore, diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index a93996cec72..51d93e8cde0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -96,7 +96,6 @@ class Settings < Settingslogic end end - # Default settings Settings['ldap'] ||= Settingslogic.new({}) Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil? @@ -124,7 +123,6 @@ if Settings.ldap['enabled'] || Rails.env.test? end end - Settings['omniauth'] ||= Settingslogic.new({}) Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil? @@ -218,7 +216,6 @@ Settings.gitlab['restricted_signup_domains'] ||= [] Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project] Settings.gitlab['trusted_proxies'] ||= [] - # # CI # @@ -348,7 +345,6 @@ Settings.git['timeout'] ||= 10 Settings['satellites'] ||= Settingslogic.new({}) Settings.satellites['path'] = File.expand_path(Settings.satellites['path'] || "tmp/repo_satellites/", Rails.root) - # # Extra customization # diff --git a/config/routes.rb b/config/routes.rb index c04780fec88..1572656b8c5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -133,7 +133,6 @@ Rails.application.routes.draw do # resources :notification_settings, only: [:create, :update] - # # Import # @@ -466,7 +465,6 @@ Rails.application.routes.draw do resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? service for project # diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb index 8aa6f8f124a..f0fb6084a35 100644 --- a/lib/banzai/filter/image_link_filter.rb +++ b/lib/banzai/filter/image_link_filter.rb @@ -8,7 +8,6 @@ module Banzai # of the anchor, and then replace the img with the link-wrapped version. def call doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img| - div = doc.document.create_element( 'div', class: 'image-container' diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb index 1bb6d6bba87..269d5bf74fa 100644 --- a/lib/banzai/filter/wiki_link_filter.rb +++ b/lib/banzai/filter/wiki_link_filter.rb @@ -8,7 +8,6 @@ module Banzai # Context options: # :project_wiki class WikiLinkFilter < HTML::Pipeline::Filter - def call return doc unless project_wiki? diff --git a/lib/banzai/pipeline/full_pipeline.rb b/lib/banzai/pipeline/full_pipeline.rb index d47ddfda4be..3c974f73176 100644 --- a/lib/banzai/pipeline/full_pipeline.rb +++ b/lib/banzai/pipeline/full_pipeline.rb @@ -1,7 +1,6 @@ module Banzai module Pipeline class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline) - end end end diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb index 5270108ef0f..1d7126a432d 100644 --- a/lib/ci/charts.rb +++ b/lib/ci/charts.rb @@ -13,7 +13,6 @@ module Ci collect end - def push(from, to, format) @labels << from.strftime(format) @total << project.builds. diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb index 1b80be112a4..cee664b8951 100644 --- a/lib/disable_email_interceptor.rb +++ b/lib/disable_email_interceptor.rb @@ -1,6 +1,5 @@ # Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails class DisableEmailInterceptor - def self.delivering_email(message) message.perform_deliveries = false Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}" diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index 0b9c2e730f9..1a22ad9acf5 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -4,7 +4,6 @@ module Gitlab # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters # the resulting HTML through HTML pipeline filters. module Asciidoc - DEFAULT_ADOC_ATTRS = [ 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab', 'env-gitlab', 'source-highlighter=html-pipeline' diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index ad412f56cca..478f145bfed 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -8,7 +8,6 @@ module Grack end class Auth < Rack::Auth::Basic - attr_accessor :user, :project, :env def call(env) diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb index 522dd2b9428..59a2367b65d 100644 --- a/lib/gitlab/diff/parser.rb +++ b/lib/gitlab/diff/parser.rb @@ -40,7 +40,6 @@ module Gitlab line_obj_index += 1 end - case line[0] when "+" line_new += 1 diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index 595b20a09bd..8f66f48cbfe 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class Importer - def initialize(project) @archive_file = project.import_source @current_user = project.creator diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb index c569a35a48b..b459054c198 100644 --- a/lib/gitlab/import_export/members_mapper.rb +++ b/lib/gitlab/import_export/members_mapper.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class MembersMapper - attr_reader :missing_author_ids def initialize(exported_members:, user:, project:) diff --git a/lib/gitlab/import_export/project_creator.rb b/lib/gitlab/import_export/project_creator.rb index 89388d1984b..77bb3ca6581 100644 --- a/lib/gitlab/import_export/project_creator.rb +++ b/lib/gitlab/import_export/project_creator.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class ProjectCreator - def initialize(namespace_id, current_user, file, project_path) @namespace_id = namespace_id @current_user = current_user diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index dd71b92c522..0ac6ff01e3b 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class ProjectTreeRestorer - def initialize(user:, shared:, project:) @path = File.join(shared.export_path, 'project.json') @user = user diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb index 19defd8f03a..15f5dd31035 100644 --- a/lib/gitlab/import_export/reader.rb +++ b/lib/gitlab/import_export/reader.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class Reader - attr_reader :tree def initialize(shared:) @@ -55,7 +54,6 @@ module Gitlab @json_config_hash end - # If the model is a hash, process the sub_models, which could also be hashes # If there is a list, add to an existing array, otherwise use hash syntax # +current_key+ main model that will be a key in the hash diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 3fd89e08f09..9824df3f274 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class RelationFactory - OVERRIDES = { snippets: :project_snippets, pipelines: 'Ci::Pipeline', statuses: 'commit_status', diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb index 6aff05b886a..5d6de8bc475 100644 --- a/lib/gitlab/import_export/shared.rb +++ b/lib/gitlab/import_export/shared.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class Shared - attr_reader :errors, :opts def initialize(opts) diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb index 7292e9d9712..d6f4fa57510 100644 --- a/lib/gitlab/import_export/uploads_saver.rb +++ b/lib/gitlab/import_export/uploads_saver.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class UploadsSaver - def initialize(project:, shared:) @project = project @shared = shared diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb index cf5c62c5e3c..abfc694b879 100644 --- a/lib/gitlab/import_export/version_checker.rb +++ b/lib/gitlab/import_export/version_checker.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class VersionChecker - def self.check!(*args) new(*args).check! end diff --git a/lib/gitlab/import_export/version_saver.rb b/lib/gitlab/import_export/version_saver.rb index f7f73dc9343..9b642d740b7 100644 --- a/lib/gitlab/import_export/version_saver.rb +++ b/lib/gitlab/import_export/version_saver.rb @@ -1,7 +1,6 @@ module Gitlab module ImportExport class VersionSaver - def initialize(shared:) @shared = shared end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 948d43582cf..59a05411fe9 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -24,8 +24,6 @@ module Gitlab 'GitLab export' => 'gitlab_project' } end - end - end end diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index e3ed2f6791d..811363405a8 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -1,7 +1,6 @@ module Gitlab module Lfs class Response - def initialize(project, user, ci, request) @origin_project = project @project = storage_project(project) diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb index 746ec283330..4e2f8ed5587 100644 --- a/lib/gitlab/other_markup.rb +++ b/lib/gitlab/other_markup.rb @@ -1,7 +1,6 @@ module Gitlab # Parser/renderer for markups without other special support code. module OtherMarkup - # Public: Converts the provided markup into HTML. # # input - the source text in a markup format diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index c84c68f96f6..ffad5e17c78 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -13,7 +13,6 @@ module Gitlab "Cannot start with '-' or end in '.'." \ end - def namespace_name_regex @namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze end @@ -22,7 +21,6 @@ module Gitlab "can contain only letters, digits, '_', '.', dash and space." end - def project_name_regex @project_name_regex ||= /\A[\p{Alnum}_][\p{Alnum}\p{Pd}_\. ]*\z/.freeze end @@ -32,7 +30,6 @@ module Gitlab "It must start with letter, digit or '_'." end - def project_path_regex @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(? User filters projects", feature: true do - describe 'filtering personal projects' do before do user = create(:user) diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 7852c39fee2..a89ac09f236 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -81,7 +81,6 @@ describe "GitLab Flavored Markdown", feature: true do end end - describe "for merge requests" do before do @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix #{issue.to_reference}") @@ -100,7 +99,6 @@ describe "GitLab Flavored Markdown", feature: true do end end - describe "for milestones" do before do @milestone = create(:milestone, diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 321c9bad7d0..51690e39490 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -39,7 +39,6 @@ feature 'Groups > Members > Owner manages access requests', feature: true do expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was denied" end - def expect_visible_access_request(group, user) expect(group.members.request.exists?(user_id: user)).to be_truthy expect(page).to have_content "#{group.name} access requests 1" diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 4bcb105b17d..006a06b8235 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -14,7 +14,6 @@ describe 'Filter issues', feature: true do end describe 'Filter issues for assignee from issues#index' do - before do visit namespace_project_issues_path(project.namespace, project) @@ -36,7 +35,6 @@ describe 'Filter issues', feature: true do expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) end - it 'should not change when all link is clicked' do find('.issues-state-filters a', text: "All").click @@ -46,7 +44,6 @@ describe 'Filter issues', feature: true do end describe 'Filter issues for milestone from issues#index' do - before do visit namespace_project_issues_path(project.namespace, project) @@ -68,7 +65,6 @@ describe 'Filter issues', feature: true do expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) end - it 'should not change when all link is clicked' do find('.issues-state-filters a', text: "All").click @@ -113,7 +109,6 @@ describe 'Filter issues', feature: true do end describe 'Filter issues for assignee and label from issues#index' do - before do visit namespace_project_issues_path(project.namespace, project) @@ -144,7 +139,6 @@ describe 'Filter issues', feature: true do expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) end - it 'should not change when all link is clicked' do find('.issues-state-filters a', text: "All").click diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 17df66e73b4..d51c9abea19 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -361,7 +361,6 @@ describe 'Issues', feature: true do let(:issue) { create(:issue, project: project, author: @user, assignee: @user) } context 'by authorized user' do - it 'allows user to select unassigned', js: true do visit namespace_project_issue_path(project.namespace, project, issue) @@ -420,7 +419,6 @@ describe 'Issues', feature: true do end context 'by unauthorized user' do - let(:guest) { create(:user) } before do @@ -442,8 +440,6 @@ describe 'Issues', feature: true do let!(:milestone) { create(:milestone, project: project) } context 'by authorized user' do - - it 'allows user to select unassigned', js: true do visit namespace_project_issue_path(project.namespace, project, issue) diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb index 15c381c0f5a..fcdf7870f34 100644 --- a/spec/features/projects/commit/builds_spec.rb +++ b/spec/features/projects/commit/builds_spec.rb @@ -20,7 +20,6 @@ feature 'project commit builds' do visit builds_namespace_project_commit_path(project.namespace, project, project.commit.sha) - expect(page).to have_content('Builds') end end diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb index f88c0616b52..1b4ff6b6f1b 100644 --- a/spec/features/projects/commits/cherry_pick_spec.rb +++ b/spec/features/projects/commits/cherry_pick_spec.rb @@ -5,7 +5,6 @@ describe 'Cherry-pick Commits' do let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') } - before do login_as :user project.team << [@user, :master] diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb index 461f1737928..81b0c991d4f 100644 --- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb +++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' feature 'Issue prioritization', feature: true do - let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } @@ -15,7 +14,6 @@ feature 'Issue prioritization', feature: true do # According to https://gitlab.com/gitlab-org/gitlab-ce/issues/14189#note_4360653 context 'when issues have one label' do scenario 'Are sorted properly' do - # Issues issue_1 = create(:issue, title: 'issue_1', project: project) issue_2 = create(:issue, title: 'issue_2', project: project) @@ -46,7 +44,6 @@ feature 'Issue prioritization', feature: true do context 'when issues have multiple labels' do scenario 'Are sorted properly' do - # Issues issue_1 = create(:issue, title: 'issue_1', project: project) issue_2 = create(:issue, title: 'issue_2', project: project) diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index b9e63a7152c..85923f0a19d 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -48,9 +48,7 @@ describe "Search", feature: true do end end - describe 'Right header search field', feature: true do - describe 'Search in project page' do before do visit namespace_project_path(project.namespace, project) @@ -73,7 +71,6 @@ describe "Search", feature: true do end context 'click the links in the category search dropdown', js: true do - before do page.find('#search').click end @@ -124,6 +121,4 @@ describe "Search", feature: true do end end end - - end diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index 71b783b7276..35fcef7a712 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -76,7 +76,6 @@ describe 'Internal Group access', feature: true do it { is_expected.to be_denied_for :visitor } end - describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index cc9aee802f9..75a93342628 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -76,7 +76,6 @@ describe 'Private Group access', feature: true do it { is_expected.to be_denied_for :visitor } end - describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index db986683dbe..6c5ee93970b 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -76,7 +76,6 @@ describe 'Public Group access', feature: true do it { is_expected.to be_allowed_for :visitor } end - describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index 4229e82b443..a752c1d7235 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' feature 'Signup', feature: true do describe 'signup with no errors' do - context "when sending confirmation email" do before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) } @@ -40,7 +39,6 @@ feature 'Signup', feature: true do expect(page).to have_content("Welcome! You have signed up successfully.") end end - end describe 'signup with errors' do diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb index f0990118e3c..0f30f562539 100644 --- a/spec/features/tags/master_deletes_tag_spec.rb +++ b/spec/features/tags/master_deletes_tag_spec.rb @@ -22,7 +22,6 @@ feature 'Master deletes tag', feature: true do namespace_project_tags_path(project.namespace, project)) expect(page).not_to have_content 'v1.1.0' end - end context 'from a specific tag page' do diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index cf116040394..b5a94fe0383 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -47,5 +47,4 @@ feature 'Users', feature: true do def number_of_errors_on_page(page) page.find('#error_explanation').find('ul').all('li').count end - end diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index fdd3849816f..fbe09b28b3c 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -12,14 +12,12 @@ describe GroupProjectsFinder do let!(:shared_project_2) { create(:project, :private, path: '4') } let!(:shared_project_3) { create(:project, :internal, path: '5') } - before do shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) shared_project_2.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) shared_project_3.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) end - describe 'with a group member current user' do before { group.add_user(current_user, Gitlab::Access::MASTER) } diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 810016c9658..28bdc18e840 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -69,7 +69,6 @@ describe SnippetsFinder do expect(snippets).to include(@snippet3) expect(snippets).not_to include(@snippet2, @snippet1) end - end context 'by_project filter' do diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index 5e7594170c5..db3ad1b99e9 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe VisibilityLevelHelper do - let(:project) { build(:project) } let(:group) { build(:group) } let(:personal_snippet) { build(:personal_snippet) } @@ -90,6 +89,5 @@ describe VisibilityLevelHelper do expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey end end - end end diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb index 1bcae8a27db..47b4e431823 100644 --- a/spec/initializers/settings_spec.rb +++ b/spec/initializers/settings_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' require_relative '../../config/initializers/1_settings' describe Settings, lib: true do - describe '#host_without_www' do context 'URL with protocol' do it 'returns the host' do @@ -41,5 +40,4 @@ describe Settings, lib: true do end end end - end diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 72bc6a0b704..51c89ac4889 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -59,7 +59,6 @@ describe Banzai::Pipeline::WikiPipeline do { "when GitLab is hosted at a root URL" => '/', "when GitLab is hosted at a relative URL" => '/nested/relative/gitlab' }.each do |test_name, relative_url_root| - context test_name do before do allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return(relative_url_root) diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb index 9c6b4ea5086..97f2e97b062 100644 --- a/spec/lib/ci/charts_spec.rb +++ b/spec/lib/ci/charts_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Ci::Charts, lib: true do - context "build_times" do before do @pipeline = FactoryGirl.create(:ci_pipeline) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 2ca9f554b07..ec658668c61 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -143,7 +143,6 @@ module Ci end it "returns build only for specified type" do - config = YAML.dump({ before_script: ["pwd"], rspec: { script: "rspec", type: "test", only: ["master", "deploy"] }, diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 736bf787208..32ca8239845 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -3,13 +3,11 @@ require 'nokogiri' module Gitlab describe Asciidoc, lib: true do - let(:input) { 'ascii' } let(:context) { {} } let(:html) { 'H2O' } context "without project" do - it "should convert the input using Asciidoctor and default options" do expected_asciidoc_opts = { safe: :secure, @@ -24,7 +22,6 @@ module Gitlab end context "with asciidoc_opts" do - let(:asciidoc_opts) { { safe: :safe, attributes: ['foo'] } } it "should merge the options with default ones" do diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb index 711a3e1c7d4..abc93e1b44a 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -128,7 +128,6 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do subject { |example| path(example).children } it { expect(subject.count).to eq 3 } end - end describe 'path/dir_1/subdir/subfile', path: 'path/dir_1/subdir/subfile' do diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb index ea5c31011f0..198ff977f24 100644 --- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb +++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe Gitlab::Diff::InlineDiffMarker, lib: true do describe '#inline_diffs' do - context "when the rich text is html safe" do let(:raw) { "abc 'def'" } let(:rich) { %{abc 'def'}.html_safe } diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb index 2dc71be0254..252cd4c55c7 100644 --- a/spec/lib/gitlab/fogbugz_import/client_spec.rb +++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Gitlab::FogbugzImport::Client, lib: true do - let(:client) { described_class.new(uri: '', token: '') } let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } } let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } } diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 9b3a0e3a75f..9b7986fa12d 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -65,7 +65,6 @@ describe Gitlab::GitAccess, lib: true do expect(access.can_push_to_branch?(@branch.name)).to be_falsey end end - end describe 'download_access_check' do diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb index e94440a7fb0..87593e32db0 100644 --- a/spec/lib/gitlab/github_import/label_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Gitlab::GithubImport::LabelFormatter, lib: true do - describe '#attributes' do it 'returns formatted attributes' do project = create(:project) diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 647631271e0..54f85f8cffc 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -19,7 +19,6 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do end describe "#execute" do - it "imports status labels" do subject.execute diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index f135a285dfb..6d5aa0d04a2 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe Gitlab::ImportExport::MembersMapper, services: true do describe 'map members' do - let(:user) { create(:user) } let(:project) { create(:project, :public, name: 'searchable_project') } let(:user2) { create(:user) } 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 e401ca99077..a72aaa44e82 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do describe 'restore project tree' do - let(:user) { create(:user) } let(:namespace) { create(:namespace, owner: user) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } 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 3b98045a2fc..a75eaa4d51f 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeSaver, services: true do describe 'saves the project tree into a json object' do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } let(:project_tree_saver) { described_class.new(project: project, shared: shared) } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } @@ -23,7 +22,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do end context 'JSON' do - let(:saved_project_json) do project_tree_saver.save project_json(project_tree_saver.full_path) diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb index 211ef68dfab..b76e14deca1 100644 --- a/spec/lib/gitlab/import_export/reader_spec.rb +++ b/spec/lib/gitlab/import_export/reader_spec.rb @@ -25,7 +25,6 @@ describe Gitlab::ImportExport::Reader, lib: true do end context 'individual scenarios' do - it 'generates the correct hash for a single project relation' do setup_yaml(project_tree: [:issues]) diff --git a/spec/lib/gitlab/import_export/repo_bundler_spec.rb b/spec/lib/gitlab/import_export/repo_bundler_spec.rb index 590a9a7e1a5..135e99bc953 100644 --- a/spec/lib/gitlab/import_export/repo_bundler_spec.rb +++ b/spec/lib/gitlab/import_export/repo_bundler_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe Gitlab::ImportExport::RepoSaver, services: true do describe 'bundle a project Git repo' do - let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } diff --git a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb index b9ffc8694a5..b628da0f3e8 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe Gitlab::ImportExport::WikiRepoSaver, services: true do describe 'bundle a wiki Git repo' do - let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb index 6a53ed1db64..69c49051156 100644 --- a/spec/lib/gitlab/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb @@ -32,7 +32,6 @@ describe Gitlab::LDAP::AuthHash, lib: true do end context "without overridden attributes" do - it "has the correct username" do expect(auth_hash.username).to eq("123456") end diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 5ec5ab40b6f..dd113d73342 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -128,7 +128,6 @@ describe Gitlab::OAuth::User, lib: true do end context "and no account for the LDAP user" do - it "creates a user with dual LDAP and omniauth identities" do oauth_user.save @@ -169,7 +168,6 @@ describe Gitlab::OAuth::User, lib: true do end end end - end describe 'blocking' do @@ -255,7 +253,6 @@ describe Gitlab::OAuth::User, lib: true do end end - context 'sign-in' do before do oauth_user.save diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb index 7fc34139eff..6bd7393aaa7 100644 --- a/spec/lib/gitlab/push_data_builder_spec.rb +++ b/spec/lib/gitlab/push_data_builder_spec.rb @@ -4,7 +4,6 @@ describe Gitlab::PushDataBuilder, lib: true do let(:project) { create(:project) } let(:user) { create(:user) } - describe '.build_sample' do let(:data) { described_class.build_sample(project, user) } diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index 2753aecc1f4..56bf08e7041 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -214,7 +214,6 @@ describe Gitlab::Saml::User, lib: true do end end end - end describe 'blocking' do diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index de55334118f..59024d3290b 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -64,5 +64,4 @@ describe Gitlab::UrlSanitizer, lib: true do expect(sanitizer.full_url).to eq('user@server:project.git') end end - end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index ae55a01ebea..016856a62f8 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -984,7 +984,6 @@ describe Notify do end context "when set to send from committer email if domain matches" do - let(:send_from_committer_email) { true } before do @@ -992,7 +991,6 @@ describe Notify do end context "when the committer email domain is within the GitLab domain" do - before do user.update_attribute(:email, "user@company.com") user.confirm @@ -1010,7 +1008,6 @@ describe Notify do end context "when the committer email domain is not completely within the GitLab domain" do - before do user.update_attribute(:email, "user@something.company.com") user.confirm @@ -1028,7 +1025,6 @@ describe Notify do end context "when the committer email domain is outside the GitLab domain" do - before do user.update_attribute(:email, "user@mpany.com") user.confirm @@ -1084,5 +1080,4 @@ describe Notify do is_expected.to have_body_text /#{diff_path}/ end end - end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 97b28686d82..e8171788872 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -323,7 +323,6 @@ describe Ci::Build, models: true do expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false) is_expected.to be_falsey end - end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 89730ab8eb8..60e4bbc8564 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -170,7 +170,6 @@ describe Issue, "Issuable" do end end - describe '#subscribed?' do context 'user is not a participant in the issue' do before { allow(issue).to receive(:participants).with(user).and_return([]) } diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb index 6445e29c3ef..c3af7a0960f 100644 --- a/spec/models/concerns/strip_attribute_spec.rb +++ b/spec/models/concerns/strip_attribute_spec.rb @@ -16,5 +16,4 @@ describe Milestone, "StripAttribute" do it { expect(milestone.title).to eq('8.3') } end - end diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb index 5d0bd31db5a..d9df9e0f907 100644 --- a/spec/models/email_spec.rb +++ b/spec/models/email_spec.rb @@ -1,11 +1,9 @@ require 'spec_helper' describe Email, models: true do - describe 'validations' do it_behaves_like 'an object with email-formated attributes', :email do subject { build(:email) } end end - end diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 3b817608ce0..fa1a0d4e0c7 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -23,14 +23,12 @@ describe :forked_from_project do let(:project_from) { create(:project) } let(:project_to) { create(:project, forked_project_link: forked_project_link) } - before :each do forked_project_link.forked_from_project = project_from forked_project_link.forked_to_project = project_to forked_project_link.save! end - it "project_to should know it is forked" do expect(project_to.forked?).to be_truthy end @@ -43,7 +41,6 @@ describe :forked_from_project do expect(forked_project_link).to receive(:destroy) project_to.destroy end - end def fork_project(from_project, user) diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index 1b987588f59..b3aed66a5b6 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' RSpec.describe Identity, models: true do - describe 'relations' do it { is_expected.to belong_to(:user) } end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index bbf65edb27c..4c103462433 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -119,7 +119,6 @@ describe ProjectMember, models: true do it { expect(@project_1.users).to include(@user_1) } it { expect(@project_1.users).to include(@user_2) } - it { expect(@project_2.users).to include(@user_1) } it { expect(@project_2.users).to include(@user_2) } end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index cbea407f9bf..5f68cd2b066 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -109,7 +109,6 @@ describe Namespace, models: true do end describe ".clean_path" do - let!(:user) { create(:user, username: "johngitlab-etc") } let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") } diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index c9517324541..5a97cf370da 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -154,11 +154,9 @@ describe JiraService, models: true do expect(@jira_service.password).to eq("password") expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2") end - end end - describe "Validations" do context "active" do before do diff --git a/spec/models/project_services/slack_service/wiki_page_message_spec.rb b/spec/models/project_services/slack_service/wiki_page_message_spec.rb index 6ecab645b49..46dedb66c7c 100644 --- a/spec/models/project_services/slack_service/wiki_page_message_spec.rb +++ b/spec/models/project_services/slack_service/wiki_page_message_spec.rb @@ -47,7 +47,6 @@ describe SlackService::WikiPageMessage, models: true do context 'when :action == "create"' do before { args[:object_attributes][:action] = 'create' } - it 'it returns the attachment for a new wiki page' do expect(subject.attachments).to eq([ { diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 8ae083d7132..851b8b241d7 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -855,7 +855,6 @@ describe Repository, models: true do repository.after_create end - end describe "#copy_gitattributes" do diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 2f000dbc01a..96bbbec9ea1 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Service, models: true do - describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } @@ -176,7 +175,6 @@ describe Service, models: true do ) end - it "returns nil when the property has not been assigned a new value" do service.username = "key_changed" expect(service.bamboo_url_was).to be_nil diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb index f22db61e744..83025953889 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/api_helpers_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe API::Helpers, api: true do - include API::Helpers include ApiHelpers diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index ed78d582bd0..72a6d45f47d 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -62,7 +62,6 @@ describe API::API, api: true do end end - describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do context 'on an issue' do it "returns the award emoji" do diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 6668f3543f6..2da01da7fa1 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -11,7 +11,6 @@ describe API::CommitStatuses, api: true do let(:developer) { create_user(:developer) } let(:sha) { commit.id } - describe "GET /projects/:id/repository/commits/:sha/statuses" do let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 881b818b5e9..5262a623761 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -7,7 +7,6 @@ describe API::API, api: true do let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) } let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id } - describe "when unauthenticated" do it "returns authentication success" do get api("/user"), access_token: token.token diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 39736779986..0404cf31ff7 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -11,7 +11,6 @@ describe API::API, api: true do project.team << [user, :master] end - describe 'GET /projects/:id/labels' do it 'should return project labels' do get api("/projects/#{project.id}/labels", user) diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index bacd01f8e74..65c53211dd3 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -159,7 +159,6 @@ describe API::API, api: true do end end - context "and current user can view the note" do it "should return an issue note by id" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user) @@ -221,7 +220,6 @@ describe API::API, api: true do expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) end end - end context "when noteable is a Snippet" do @@ -396,5 +394,4 @@ describe API::API, api: true do end end end - end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5c909d8b3b3..611dd2a2a88 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -707,7 +707,6 @@ describe API::API, api: true do end describe 'DELETE /projects/:id/fork' do - it "shouldn't be visible to users outside group" do delete api("/projects/#{project_fork_target.id}/fork", user) expect(response).to have_http_status(404) diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index bf7eaaaaaed..a2446e12804 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -87,7 +87,6 @@ describe API::API, api: true do expect(response).to have_http_status(403) end - end end end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 6629a5a65e2..684c2cd8e24 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -6,7 +6,6 @@ describe API::API, 'Settings', api: true do let(:user) { create(:user) } let(:admin) { create(:admin) } - describe "GET /application/settings" do it "should return application settings" do get api("/application/settings", admin) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 056256a29f5..e43e3e269bf 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -246,7 +246,6 @@ describe API::API, api: true do end describe "GET /users/sign_up" do - it "should redirect to sign in page" do get "/users/sign_up" expect(response).to have_http_status(302) diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index b5ed8584c8a..8b19936ae6d 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -95,7 +95,6 @@ describe Admin::HooksController, "routing" do it "to #destroy" do expect(delete("/admin/hooks/1")).to route_to('admin/hooks#destroy', id: '1') end - end # admin_logs GET /admin/logs(.:format) admin/logs#show diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 538f44e4f3f..620f328a114 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -165,7 +165,6 @@ describe Projects::TagsController, 'routing' do end end - # project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index # POST /:project_id/deploy_keys(.:format) deploy_keys#create # new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index de13c0db5d1..8a8e131c57b 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -253,7 +253,6 @@ describe RootController, 'routing' do end end - # new_user_session GET /users/sign_in(.:format) devise/sessions#new # user_session POST /users/sign_in(.:format) devise/sessions#create # destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index 2bb9c3b3db3..6367ac832e8 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -16,7 +16,6 @@ describe GitHooksService, services: true do end describe '#execute' do - context 'when receive hooks were successful' do it 'should call post-receive hook' do hook = double(trigger: true) @@ -48,6 +47,5 @@ describe GitHooksService, services: true do end.to raise_error(GitHooksService::PreReceiveError) end end - end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 48d374883d7..afabeed4a80 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -14,7 +14,6 @@ describe GitPushService, services: true do end describe 'Push branches' do - let(:oldrev) { @oldrev } let(:newrev) { @newrev } @@ -23,7 +22,6 @@ describe GitPushService, services: true do end context 'new branch' do - let(:oldrev) { @blankrev } it { is_expected.to be_truthy } @@ -55,7 +53,6 @@ describe GitPushService, services: true do end context 'existing branch' do - it { is_expected.to be_truthy } it 'flushes general cached data' do @@ -79,7 +76,6 @@ describe GitPushService, services: true do end context 'rm branch' do - let(:newrev) { @blankrev } it { is_expected.to be_truthy } @@ -223,7 +219,6 @@ describe GitPushService, services: true do end end - describe "Webhooks" do context "execute webhooks" do it "when pushing a branch for the first time" do @@ -491,7 +486,6 @@ describe GitPushService, services: true do end end - it 'increments the push counter' do expect(housekeeping).to receive(:increment!) diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 31b93850c7c..7d5cb876063 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -175,7 +175,6 @@ describe MergeRequests::RefreshService, services: true do end end - def reload_mrs @merge_request.reload @fork_merge_request.reload diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 776a6ab5edb..54719cbb8d8 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -95,7 +95,6 @@ describe NotificationService, services: true do notification.new_note(note) end - it { should_not_email(@u_lazy_participant) } end end @@ -377,7 +376,6 @@ describe NotificationService, services: true do end describe '#reassigned_issue' do - before do update_custom_notification(:reassign_issue, @u_guest_custom, project) update_custom_notification(:reassign_issue, @u_custom_global) @@ -566,7 +564,6 @@ describe NotificationService, services: true do end describe '#close_issue' do - before do update_custom_notification(:close_issue, @u_guest_custom, project) update_custom_notification(:close_issue, @u_custom_global) @@ -712,7 +709,6 @@ describe NotificationService, services: true do should_email(subscriber) end - context 'participating' do context 'by assignee' do before do @@ -880,7 +876,6 @@ describe NotificationService, services: true do end describe '#merged_merge_request' do - before do update_custom_notification(:merge_merge_request, @u_guest_custom, project) update_custom_notification(:merge_merge_request, @u_custom_global) diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index d5aa115a074..57c71544dff 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -71,5 +71,4 @@ describe Projects::TransferService, services: true do it { expect(private_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) } end end - end diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb index 5ebe095743b..f3ea206f387 100644 --- a/spec/support/jira_service_helper.rb +++ b/spec/support/jira_service_helper.rb @@ -1,5 +1,4 @@ module JiraServiceHelper - def jira_service_settings properties = { "title" => "JIRA tracker", diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 02308530d13..d2c056d8e14 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -76,7 +76,6 @@ describe 'gitlab:app namespace rake task' do expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end end - end # backup_restore task describe 'backup_create' do diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index 7e59bd2fced..5785a6a06ff 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -7,7 +7,6 @@ describe ProjectCacheWorker do describe '#perform' do it 'updates project cache data' do - expect_any_instance_of(Repository).to receive(:size) expect_any_instance_of(Repository).to receive(:commit_count) -- cgit v1.2.1 From d500ce9d3352e199be7370380c7e2b16958f7ecf Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 1 Jul 2016 15:34:27 -0600 Subject: Upgrade Doorkeeper from 3.1.0 to 4.0.0. Includes Rails 5 support and various bug fixes. Changelog: https://github.com/doorkeeper-gem/doorkeeper/blob/master/NEWS.md#400 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index d622af6b0a3..88b225ca888 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ 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' diff --git a/Gemfile.lock b/Gemfile.lock index 45cb327168c..119b29077b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -840,7 +840,7 @@ 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) -- cgit v1.2.1 From 26525064a8623851f1047bbf0f00ee9061cabe9e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 1 Jul 2016 12:51:21 -0700 Subject: Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq See: https://github.com/mperham/sidekiq/blob/master/Changes.md#413 https://gitlab.com/gitlab-org/gitlab-ce/issues/19441 --- CHANGELOG | 1 + Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2f93fcdbaa0..85c785270e6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.9.5 (unreleased) - 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. diff --git a/Gemfile.lock b/Gemfile.lock index 45cb327168c..b8022d31040 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -554,7 +554,7 @@ GEM recaptcha (3.0.0) json redcarpet (3.3.3) - redis (3.3.0) + redis (3.2.2) redis-actionpack (4.0.1) actionpack (~> 4) redis-rack (~> 1.5.0) -- cgit v1.2.1 From 52008045e5785772f74b3ab5c686e6babb4fd45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Sat, 2 Jul 2016 17:44:19 -0400 Subject: Fix typo in Merge Requests API documentation --- doc/api/merge_requests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index f60b0d0ebc6..aee94b3fc36 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -433,7 +433,7 @@ Parameters: - `merge_request_id` (required) - ID of MR - `merge_commit_message` (optional) - Custom merge commit message - `should_remove_source_branch` (optional) - if `true` removes the source branch -- `merged_when_build_succeeds` (optional) - if `true` the MR is merged when the build succeeds +- `merge_when_build_succeeds` (optional) - if `true` the MR is merged when the build succeeds - `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail ```json -- cgit v1.2.1 From b6863388984853ccd6e9b49aadac9c714454257f Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Mon, 27 Jun 2016 00:18:46 +0900 Subject: Update RedCloth to 4.3.2 for CVE-2012-6684 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index d622af6b0a3..e409e66aab0 100644 --- a/Gemfile +++ b/Gemfile @@ -107,7 +107,7 @@ gem 'html-pipeline', '~> 1.11.0' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'github-markup', '~> 1.3.1' gem 'redcarpet', '~> 3.3.3' -gem 'RedCloth', '~> 4.2.9' +gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' diff --git a/Gemfile.lock b/Gemfile.lock index 45cb327168c..34138decc13 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - RedCloth (4.2.9) + RedCloth (4.3.2) ace-rails-ap (4.0.2) actionmailer (4.2.6) actionpack (= 4.2.6) @@ -803,7 +803,7 @@ PLATFORMS ruby DEPENDENCIES - RedCloth (~> 4.2.9) + RedCloth (~> 4.3.2) ace-rails-ap (~> 4.0.2) activerecord-session_store (~> 1.0.0) acts-as-taggable-on (~> 3.4) -- cgit v1.2.1 From a034374f004ab2a9e96619438962201b4a6ab222 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Mon, 27 Jun 2016 00:34:28 +0900 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 2f93fcdbaa0..2f29a64df1b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,7 @@ v 8.9.5 (unreleased) - Show "locked" label for locked runners on runners admin. !4961 - 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. -- cgit v1.2.1 From 3286dd7a1db69460573a5fd2c9e997039b1f406b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 3 Jul 2016 19:58:58 -0400 Subject: Don't garbage collect commits that have related DB records like comments --- CHANGELOG | 1 + app/models/ci/pipeline.rb | 6 ++++++ app/models/ci/trigger_request.rb | 2 +- app/models/merge_request.rb | 12 +++++++++--- app/models/merge_request_diff.rb | 11 ++++++++++- app/models/note.rb | 7 +++++++ app/models/repository.rb | 20 ++++++++++++++++++++ app/models/sent_notification.rb | 8 ++++++++ app/models/todo.rb | 8 ++++++++ spec/models/merge_request_spec.rb | 4 ++-- spec/models/note_spec.rb | 4 ++++ spec/models/repository_spec.rb | 8 ++++++++ 12 files changed, 84 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2f93fcdbaa0..099e0d83815 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.10.0 (unreleased) - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - 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 v 8.9.5 (unreleased) - Improve the request / withdraw access button. !4860 diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 10324bf2257..fa4071e2482 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -16,6 +16,7 @@ module Ci # Invalidate object and save if when touched after_touch :update_state + after_save :keep_around_commits def self.truncate_sha(sha) sha[0...8] @@ -212,5 +213,10 @@ module Ci self.duration = statuses.latest.duration save end + + def keep_around_commits + project.repository.keep_around(self.sha) + project.repository.keep_around(self.before_sha) + end end end diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index b69ae37668c..fcf2b6dc5e2 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -1,7 +1,7 @@ module Ci class TriggerRequest < ActiveRecord::Base extend Ci::Model - + belongs_to :trigger, class_name: 'Ci::Trigger' belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id has_many :builds, class_name: 'Ci::Build' diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5ebc8f0c99f..cb0f871897a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -117,6 +117,8 @@ class MergeRequest < ActiveRecord::Base scope :join_project, -> { joins(:target_project) } scope :references_project, -> { references(:target_project) } + after_save :keep_around_commit + def self.reference_prefix '!' end @@ -536,12 +538,12 @@ class MergeRequest < ActiveRecord::Base "refs/merge-requests/#{iid}/head" end - def ref_is_fetched? - File.exist?(File.join(project.repository.path_to_repo, ref_path)) + def ref_fetched? + project.repository.ref_exists?(ref_path) end def ensure_ref_fetched - fetch_ref unless ref_is_fetched? + fetch_ref unless ref_fetched? end def in_locked_state @@ -600,4 +602,8 @@ class MergeRequest < ActiveRecord::Base def can_be_cherry_picked? merge_commit end + + def keep_around_commit + project.repository.keep_around(self.merge_commit_sha) + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 86331a33c05..0fcde6fc8f1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -24,6 +24,7 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_diffs after_create :reload_content, unless: :importing? + after_save :keep_around_commit def reload_content reload_commits @@ -145,7 +146,11 @@ class MergeRequestDiff < ActiveRecord::Base end new_attributes[:st_diffs] = new_diffs - new_attributes[:base_commit_sha] = self.repository.merge_base(self.head, self.base) + + 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) update_columns_serialized(new_attributes) end @@ -217,4 +222,8 @@ class MergeRequestDiff < ActiveRecord::Base update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone)) reload end + + def keep_around_commit + self.repository.keep_around(self.base_commit_sha) + end end diff --git a/app/models/note.rb b/app/models/note.rb index c2bb117eb03..81b5c47b738 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -66,6 +66,7 @@ class Note < ActiveRecord::Base end before_validation :clear_blank_line_code! + after_save :keep_around_commit class << self def model_name @@ -215,4 +216,10 @@ class Note < ActiveRecord::Base original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] Gitlab::AwardEmoji.normalize_emoji_name(original_name) end + + private + + def keep_around_commit + project.repository.keep_around(self.commit_id) + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 11ecb281a55..e3ad33a896a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -203,6 +203,26 @@ class Repository branch_names.include?(branch_name) end + def ref_exists?(ref) + rugged.references.exist?(ref) + end + + def keep_around(sha) + return unless sha && commit(sha) + + return if kept_around?(sha) + + rugged.references.create(keep_around_ref_name(sha), sha) + end + + def kept_around?(sha) + ref_exists?(keep_around_ref_name(sha)) + end + + def keep_around_ref_name(sha) + "refs/keep-around/#{sha}" + end + def tag_names cache.fetch(:tag_names) { raw_repository.tag_names } end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 375f195dba7..a2df899d012 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -9,6 +9,8 @@ class SentNotification < ActiveRecord::Base validates :commit_id, presence: true, if: :for_commit? validates :line_code, line_code: true, allow_blank: true + after_save :keep_around_commit + class << self def reply_key SecureRandom.hex(16) @@ -67,4 +69,10 @@ class SentNotification < ActiveRecord::Base def to_param self.reply_key end + + private + + def keep_around_commit + project.repository.keep_around(self.commit_id) + end end diff --git a/app/models/todo.rb b/app/models/todo.rb index 3ba67078d48..ac3fdbc7f3b 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -37,6 +37,8 @@ class Todo < ActiveRecord::Base state :done end + after_save :keep_around_commit + def build_failed? action == BUILD_FAILED end @@ -73,4 +75,10 @@ class Todo < ActiveRecord::Base target.to_reference end end + + private + + def keep_around_commit + project.repository.keep_around(self.commit_id) + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 3b199f4d98d..ceb4d64698f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -464,7 +464,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).to receive_message_chain(:repository, :can_be_merged?) { true } + allow(project.repository).to receive(:can_be_merged?) { true } expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') end @@ -481,7 +481,7 @@ describe MergeRequest, models: true do context 'when it has conflicts' do before do allow(subject).to receive(:broken?) { false } - allow(project).to receive_message_chain(:repository, :can_be_merged?) { false } + allow(project.repository).to receive(:can_be_merged?) { false } end it 'becomes unmergeable' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 285ab19cfaf..6549791f675 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -70,6 +70,10 @@ describe Note, models: true do it "should be recognized by #for_commit?" do expect(note).to be_for_commit end + + it "keeps the commit around" do + expect(note.project.repository.kept_around?(commit.id)).to be_truthy + end end describe 'authorization' do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 851b8b241d7..e753306a31f 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1117,6 +1117,14 @@ describe Repository, models: true do end end + describe "#keep_around" do + it "stores a reference to the specified commit sha so it isn't garbage collected" do + repository.keep_around(sample_commit.id) + + expect(repository.kept_around?(sample_commit.id)).to be_truthy + end + end + def create_remote_branch(remote_name, branch_name, target) rugged = repository.rugged rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target) -- cgit v1.2.1 From 16ecd7c6dd642c46bda5806569032bc91d6ee70d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 3 Jul 2016 20:30:55 -0400 Subject: Document Repository#keep_around --- app/models/repository.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index e3ad33a896a..078ca8f4e13 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -207,6 +207,10 @@ class Repository rugged.references.exist?(ref) end + # Makes sure a commit is kept around when Git garbage collection runs. + # Git GC will delete commits from the repository that are no longer in any + # branches or tags, but we want to keep some of these commits around, for + # example if they have comments or CI builds. def keep_around(sha) return unless sha && commit(sha) @@ -219,10 +223,6 @@ class Repository ref_exists?(keep_around_ref_name(sha)) end - def keep_around_ref_name(sha) - "refs/keep-around/#{sha}" - end - def tag_names cache.fetch(:tag_names) { raw_repository.tag_names } end @@ -1038,4 +1038,8 @@ class Repository def tags_sorted_by_committed_date tags.sort_by { |tag| commit(tag.target).committed_date } end + + def keep_around_ref_name(sha) + "refs/keep-around/#{sha}" + end end -- cgit v1.2.1 From eb151e77ff389c3e22454145cfd55cfaa4be7948 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Jul 2016 11:29:59 +0200 Subject: Extract CI configuration entry node factory method --- lib/gitlab/ci/config/node/factory.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 85e28f345fe..5919a283283 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -21,20 +21,24 @@ module Gitlab def create! raise InvalidFactory unless @attributes.has_key?(:value) + fabricate.tap do |entry| + entry.key = @attributes[:key] + entry.parent = @attributes[:parent] + entry.description = @attributes[:description] + end + end + + private + + def fabricate ## # We assume that unspecified entry is undefined. # See issue #18775. # if @attributes[:value].nil? - node, value = Node::Undefined, @node + Node::Undefined.new(@node) else - node, value = @node, @attributes[:value] - end - - node.new(value).tap do |entry| - entry.key = @attributes[:key] - entry.parent = @attributes[:parent] - entry.description = @attributes[:description] + @node.new(@attributes[:value]) end end end -- cgit v1.2.1 From 90dc6f1211ab1a9d10c6799d90310dde79581d62 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 4 Jul 2016 12:20:57 +0200 Subject: Instrument Rinku usage --- CHANGELOG | 1 + config/initializers/metrics.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 5f593336895..bd4a919f039 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.10.0 (unreleased) - Better caching of git calls on ProjectsController#show. - Add API endpoint for a group issues !4520 (mahcsig) - Add Bugzilla integration !4930 (iamtjg) + - Instrument Rinku usage - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - Add basic system information like memory and disk usage to the admin panel 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 -- cgit v1.2.1 From 00aac15c6f7f7f30b09ae6bc5de423d6413452d1 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 4 Jul 2016 12:25:09 +0200 Subject: Bump Rinku to 2.0.0 --- CHANGELOG | 1 + Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5f593336895..08b076d20de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.10.0 (unreleased) - PipelinesFinder uses git cache data - Check for conflicts with existing Project's wiki path when creating a new project. - 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 - Better caching of git calls on ProjectsController#show. - Add API endpoint for a group issues !4520 (mahcsig) diff --git a/Gemfile.lock b/Gemfile.lock index 238af158838..e7070a555fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -578,7 +578,7 @@ 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) rqrcode (0.7.0) -- cgit v1.2.1 From 7cc6a703f6fdec3aba3dfe857624dc3ad54ad7cf Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 4 Jul 2016 19:04:51 +0800 Subject: Rename to "successful artifacts upload", feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4964#note_12861577 --- spec/requests/ci/api/builds_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 0c4e9be96ff..6b64a16404f 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -306,7 +306,7 @@ describe Ci::API::API do end context 'should post artifact to running build' do - shared_examples 'artifacts sender' do + shared_examples 'successful artifacts upload' do it 'updates successfully' do response_filename = json_response['artifacts_file']['filename'] @@ -321,7 +321,7 @@ describe Ci::API::API do upload_artifacts(file_upload, headers_with_token, false) end - it_behaves_like 'artifacts sender' + it_behaves_like 'successful artifacts upload' end context 'uses accelerated file post' do @@ -329,7 +329,7 @@ describe Ci::API::API do upload_artifacts(file_upload, headers_with_token, true) end - it_behaves_like 'artifacts sender' + it_behaves_like 'successful artifacts upload' end context 'updates artifact' do @@ -338,7 +338,7 @@ describe Ci::API::API do upload_artifacts(file_upload, headers_with_token) end - it_behaves_like 'artifacts sender' + it_behaves_like 'successful artifacts upload' end end -- cgit v1.2.1 From 79d7e71487db07926065f17c0430df2e8a9fc574 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 4 Jul 2016 19:06:34 +0800 Subject: Use describe rather than context, feedback from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4964#note_12861588 --- spec/requests/ci/api/builds_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 6b64a16404f..89cb60bdd0b 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -305,7 +305,7 @@ describe Ci::API::API do end end - context 'should post artifact to running build' do + describe 'uploading artifacts for a running build' do shared_examples 'successful artifacts upload' do it 'updates successfully' do response_filename = -- cgit v1.2.1 From 25dd39f8cfb33c643cef2389b1d9d8f2a4efe915 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 4 Jul 2016 19:09:24 +0800 Subject: Add a new column `artifacts_size` to table `ci_builds` !4964 --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 6506f49174a..b2ba9dfed79 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.10.0 (unreleased) - Replace Haml with Hamlit to make view rendering faster. !3666 - Wrap code blocks on Activies and Todos page. !4783 (winniehell) - Add Sidekiq queue duration to transaction metrics. + - Add a new column `artifacts_size` to table `ci_builds` !4964 - 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 -- cgit v1.2.1 From 1854a7a065333ae24c392de2bf8eb2fa3fdfd6cd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 4 Jul 2016 14:25:47 +0200 Subject: Update spring to 1.7.2 to fix hanging rails console --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 238af158838..35c3770d42c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -692,7 +692,7 @@ GEM spinach (>= 0.4) spinach-rerun-reporter (0.0.2) spinach (~> 0.8) - spring (1.7.1) + spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.1.0) -- cgit v1.2.1 From bf218337ed5bd535856204ef1878a1495fa37dfe Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 4 Jul 2016 15:30:22 +0300 Subject: Better message for git hooks and file locks --- CHANGELOG | 1 + app/services/create_branch_service.rb | 4 ++-- app/services/create_tag_service.rb | 4 ++-- app/services/delete_branch_service.rb | 4 ++-- app/services/git_hooks_service.rb | 6 ++++-- lib/gitlab/git/hook.rb | 13 +++++++++---- spec/models/repository_spec.rb | 18 +++++++++--------- spec/services/create_tag_service_spec.rb | 4 ++-- spec/services/git_hooks_service_spec.rb | 10 +++++----- 9 files changed, 36 insertions(+), 28 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5f593336895..3b89fa05801 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ v 8.10.0 (unreleased) - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - 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 v 8.9.5 (unreleased) - Improve the request / withdraw access button. !4860 diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 9f4481a8153..cc128563437 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -34,8 +34,8 @@ class CreateBranchService < BaseService else error('Invalid reference name') end - rescue GitHooksService::PreReceiveError - error('Branch creation was rejected by Git hook') + rescue GitHooksService::PreReceiveError => ex + error(ex.message) end def success(branch) diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 91ed0e354d0..bd8d982e1fb 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -13,8 +13,8 @@ class CreateTagService < BaseService new_tag = repository.add_tag(current_user, tag_name, target, message) rescue Rugged::TagError return error("Tag #{tag_name} already exists") - rescue GitHooksService::PreReceiveError - return error('Tag creation was rejected by Git hook') + rescue GitHooksService::PreReceiveError => ex + return error(ex.message) end if new_tag diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index fae069ee4a5..752a7029952 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -30,8 +30,8 @@ class DeleteBranchService < BaseService else error('Failed to remove branch') end - rescue GitHooksService::PreReceiveError - error('Branch deletion was rejected by Git hook') + rescue GitHooksService::PreReceiveError => ex + error(ex.message) end def error(message, return_code = 400) diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index d7a0c25a044..172bd85dade 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -9,8 +9,10 @@ class GitHooksService @ref = ref %w(pre-receive update).each do |hook_name| - unless run_hook(hook_name) - raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook") + status, message = run_hook(hook_name) + + unless status + raise PreReceiveError, message end end diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 07b856ca64c..5415f4844d3 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -29,8 +29,8 @@ module Gitlab def call_receive_hook(gl_id, oldrev, newrev, ref) changes = [oldrev, newrev, ref].join(" ") - # function will return true if succesful exit_status = false + exit_message = nil vars = { 'GL_ID' => gl_id, @@ -41,7 +41,7 @@ module Gitlab chdir: repo_path } - Open3.popen2(vars, path, options) do |stdin, _, wait_thr| + Open3.popen3(vars, path, options) do |stdin, _, stderr, wait_thr| exit_status = true stdin.sync = true @@ -60,16 +60,21 @@ module Gitlab unless wait_thr.value == 0 exit_status = false + exit_message = stderr.gets end end - exit_status + [exit_status, exit_message] end def call_update_hook(gl_id, oldrev, newrev, ref) + status = nil + Dir.chdir(repo_path) do - system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev) + status = system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev) end + + [status, nil] end end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index e753306a31f..7975fc64e59 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -308,14 +308,14 @@ describe Repository, models: true do describe :add_branch do context 'when pre hooks were successful' do it 'should run without errors' do - hook = double(trigger: true) + hook = double(trigger: [true, nil]) expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error end it 'should create the branch' do - 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]) branch = repository.add_branch(user, 'new_feature', 'master') @@ -331,7 +331,7 @@ describe Repository, models: true do context 'when pre hooks failed' do it 'should get an error' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do repository.add_branch(user, 'new_feature', 'master') @@ -339,7 +339,7 @@ describe Repository, models: true do end it 'should not create the branch' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do repository.add_branch(user, 'new_feature', 'master') @@ -352,13 +352,13 @@ describe Repository, models: true 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) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) expect { repository.rm_branch(user, 'feature') }.not_to raise_error end it 'should delete the branch' do - 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]) expect { repository.rm_branch(user, 'feature') }.not_to raise_error @@ -368,7 +368,7 @@ describe Repository, models: true do context 'when pre hooks failed' do it 'should get an error' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do repository.rm_branch(user, 'new_feature') @@ -376,7 +376,7 @@ describe Repository, models: true do end it 'should not delete the branch' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do repository.rm_branch(user, 'feature') @@ -408,7 +408,7 @@ describe Repository, models: true do context 'when pre hooks failed' do it 'should get an error' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do repository.commit_with_hooks(user, 'feature') { sample_commit.id } diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/create_tag_service_spec.rb index 91f9e663b66..7dc43c50b0d 100644 --- a/spec/services/create_tag_service_spec.rb +++ b/spec/services/create_tag_service_spec.rb @@ -41,12 +41,12 @@ describe CreateTagService, services: true do it 'returns an error' do expect(repository).to receive(:add_tag). with(user, 'v1.1.0', 'master', 'Foo'). - and_raise(GitHooksService::PreReceiveError) + and_raise(GitHooksService::PreReceiveError, 'something went wrong') response = service.execute('v1.1.0', 'master', 'Foo') expect(response).to eq(status: :error, - message: 'Tag creation was rejected by Git hook') + message: 'something went wrong') end end end diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index 6367ac832e8..3fc37a315c0 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -18,16 +18,16 @@ describe GitHooksService, services: true do describe '#execute' do context 'when receive hooks were successful' do it 'should call post-receive hook' do - hook = double(trigger: true) + hook = double(trigger: [true, nil]) expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) - expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq(true) + expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq([true, nil]) end end context 'when pre-receive hook failed' do it 'should not call post-receive hook' do - expect(service).to receive(:run_hook).with('pre-receive').and_return(false) + expect(service).to receive(:run_hook).with('pre-receive').and_return([false, '']) expect(service).not_to receive(:run_hook).with('post-receive') expect do @@ -38,8 +38,8 @@ describe GitHooksService, services: true do context 'when update hook failed' do it 'should not call post-receive hook' do - expect(service).to receive(:run_hook).with('pre-receive').and_return(true) - expect(service).to receive(:run_hook).with('update').and_return(false) + expect(service).to receive(:run_hook).with('pre-receive').and_return([true, nil]) + expect(service).to receive(:run_hook).with('update').and_return([false, '']) expect(service).not_to receive(:run_hook).with('post-receive') expect do -- cgit v1.2.1 From 4834e2e60936d9f59f3dd315d6d9d4ef82eecd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 4 Jul 2016 16:29:28 +0200 Subject: Fix diff comments not showing up in activity feed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/models/event.rb | 4 ++-- spec/models/event_spec.rb | 32 +++++++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3b89fa05801..6cc62793a57 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.9.5 (unreleased) - 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. + - Fix diff comments not showing up in activity feed. !5069 - Security: Update RedCloth to 4.3.2 (Takuya Noguchi) v 8.9.4 diff --git a/app/models/event.rb b/app/models/event.rb index d7d23c7ae6d..e0c52fed6fb 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" + %w[Note LegacyDiffNote].include?(target_type) end def issue? diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 166a1dc4ddb..00925591a5e 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 note event' do + let(:target) { create(: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 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(: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 -- cgit v1.2.1 From b5a2319764be166c5db1d66d1da1dd608555bb0c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 4 Jul 2016 22:56:23 +0800 Subject: Explicitly set to nil when artifacts don't exist: Feedback from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4964#note_12867273 --- app/models/ci/build.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 48f88849989..5c973749975 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -384,6 +384,8 @@ module Ci def update_artifacts_size self.artifacts_size = if artifacts_file.exists? artifacts_file.size + else + nil end end -- cgit v1.2.1 From 36a73929df7cb47a428fb04740ee81f497327edb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 4 Jul 2016 23:05:05 +0800 Subject: Use describe rather than context for this: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4964/diffs#note_12867416 Guidelines: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/testing.md#general-guidelines > Use `context` to test branching logic. --- spec/requests/ci/api/builds_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 89cb60bdd0b..e7cbc3dd3a7 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -293,7 +293,7 @@ describe Ci::API::API do allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/') end - context 'build has been erased' do + describe 'build has been erased' do let(:build) { create(:ci_build, erased_at: Time.now) } before do -- cgit v1.2.1 From eac81b988e34744a56d3c85625a9b8121c659f38 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 Jun 2016 13:07:12 +0100 Subject: Updated project header Closes #18544 --- app/assets/stylesheets/framework/avatar.scss | 2 +- app/assets/stylesheets/pages/projects.scss | 335 +++++++++++---------------- app/views/projects/_home_panel.html.haml | 54 ++--- app/views/projects/_last_commit.html.haml | 19 +- app/views/projects/buttons/_fork.html.haml | 5 +- app/views/projects/show.html.haml | 99 ++++---- 6 files changed, 221 insertions(+), 293 deletions(-) diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index bb8d71fbae8..f805ee59938 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -15,7 +15,7 @@ &.s24 { margin-right: 4px; } } - &.group-avatar, &.project-avatar, &.avatar-tile { + &.group-avatar, &.avatar-tile { @include border-radius(0); } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 817c2982923..37f21b99d3f 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -64,86 +64,39 @@ } .project-home-panel { - background: $white-light; - text-align: left; - padding: 24px 0; + padding-top: 24px; + padding-bottom: 24px; + border-bottom: 1px solid $border-color; - .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; - } - } - } - - .cover-controls { - .project-settings-dropdown { - margin-left: 10px; - display: inline-block; - - .dropdown-menu { - left: auto; - width: auto; - right: 0; - max-width: 240px; - } - } - } - - .cover-title { - margin-bottom: 0; + .project-avatar { + float: none; + margin-left: auto; + margin-right: auto; } - .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); - - h1 { - font-size: 24px; - font-weight: normal; - margin: 0; - } + .project-home-desc { + margin-bottom: 15px; - .project-home-desc { - p { - margin: 0; - } + > p { + margin-bottom: 0; } } - .identicon { - float: left; - @include border-radius(50%); - } - - .avatar { - float: none; - } - .notifications-btn { - .fa-bell, .fa-spinner { margin-right: 6px; @@ -153,127 +106,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; - } - - &: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; + .count-with-arrow { + display: inline-block; + position: relative; + margin-left: 4px; + + .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; + } } } } @@ -421,36 +353,33 @@ a.deploy-project-label { } .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: $gl-padding; + padding-bottom: $gl-padding; } - .nav li { + .nav > li { display: inline-block; - margin: 16px 0; - margin-right: 16px; + + &:not(:last-child) { + margin-right: $gl-padding; + } } .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 { @@ -466,10 +395,6 @@ a.deploy-project-label { background-color: $gray-normal; } } - - &.row-content-block.second-block { - margin-top: 0; - } } pre.light-well { @@ -557,6 +482,21 @@ pre.light-well { } .project-last-commit { + margin-top: $gl-padding; + + &.container-fluid { + padding-top: 12px; + padding-bottom: 12px; + background-color: $background-color; + border: 1px solid $border-color; + } + + &.container-limited { + @media (min-width: 1281px) { + border-radius: $border-radius-base; + } + } + .ci-status { margin-right: 16px; } @@ -601,15 +541,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 { diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 540efa4780f..c935d05636d 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,41 +1,29 @@ - empty_repo = @project.empty_repo? -.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)} - %div{ class: container_class } - .row - .project-image-container - = project_icon(@project, alt: '', class: 'project-avatar avatar s70') - .project-info - .cover-title.project-home-desc - %h1 - = @project.name - %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)} - = visibility_level_icon(@project.visibility_level, fw: false) +.project-home-panel.text-center{ class: ("empty-project" if empty_repo) } + %div{class: container_class } + = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70') + %h1.project-title + = @project.name + %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)} + = visibility_level_icon(@project.visibility_level, fw: false) - - if @project.description.present? - .cover-desc.project-home-desc - = markdown(@project.description, pipeline: :description) + - if @project.description.present? + .project-home-desc + = 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 + .cover-desc + 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/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/show.html.haml b/app/views/projects/show.html.haml index f6e81af2638..50c252e1359 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.pull-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? -- cgit v1.2.1 From 16ca8ec57f2a7576bcc1cd383ea209d9be24a7af Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 Jun 2016 14:16:47 +0100 Subject: Updated spacing CHANGELOG item --- CHANGELOG | 1 + app/assets/stylesheets/pages/projects.scss | 23 ++++++++++++++--------- app/views/projects/_home_panel.html.haml | 13 +++++++------ app/views/projects/show.html.haml | 2 +- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3b89fa05801..de51f0b8a10 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.10.0 (unreleased) - Fix check for New Branch button on Issue page !4630 (winniehell) - Fix MR-auto-close text added to description. !4836 - Fix pagination when sorting by columns with lots of ties (like priority) + - Updated project header design - Exclude email check from the standard health check - Fix changing issue state columns in milestone view - Add notification settings dropdown for groups diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 37f21b99d3f..94cd9ffa6af 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -89,7 +89,10 @@ } .project-home-desc { + margin-left: auto; + margin-right: auto; margin-bottom: 15px; + max-width: 480px; > p { margin-bottom: 0; @@ -367,6 +370,15 @@ a.deploy-project-label { &: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 { @@ -498,7 +510,7 @@ pre.light-well { } .ci-status { - margin-right: 16px; + margin-right: $gl-padding; } .commit-row-message { @@ -506,19 +518,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; } diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index c935d05636d..f81f25d0f1e 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -9,13 +9,14 @@ - if @project.description.present? .project-home-desc - = markdown(@project.description, pipeline: :description) + - 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 diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 50c252e1359..58d8e068754 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -60,7 +60,7 @@ %li.missing = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do Set Up CI - %li.pull-right + %li.project-repo-buttons-right .project-repo-buttons.project-right-buttons - if current_user = render 'shared/members/access_request_buttons', source: @project -- cgit v1.2.1 From 211246c691242bf1fb37a60ede86360bcb23b299 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 Jun 2016 14:22:24 +0100 Subject: Mobile spacing --- app/assets/stylesheets/pages/projects.scss | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 94cd9ffa6af..3b3efb829eb 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -66,7 +66,10 @@ .project-home-panel { padding-top: 24px; padding-bottom: 24px; - border-bottom: 1px solid $border-color; + + @media (min-width: $screen-sm-min) { + border-bottom: 1px solid $border-color; + } .project-avatar { float: none; @@ -494,13 +497,22 @@ pre.light-well { } .project-last-commit { - margin-top: $gl-padding; + @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 { -- cgit v1.2.1 From 509c2ca3098af215b5469b4e2d343059a92512ba Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 29 Jun 2016 16:56:27 +0100 Subject: Fixed issue with Forked from text not being visible --- app/views/projects/_home_panel.html.haml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index f81f25d0f1e..270562e2e32 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -7,16 +7,15 @@ %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? - .project-home-desc - - if @project.description.present? - = 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 - %p - 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 -- cgit v1.2.1 From 111fecc639f3443c08ef508addde24b9874cdb85 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 4 Jul 2016 16:27:20 +0100 Subject: Reverted project avatar radius Fixed spacing in missing links --- app/assets/stylesheets/framework/avatar.scss | 2 +- app/assets/stylesheets/pages/projects.scss | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index f805ee59938..bb8d71fbae8 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -15,7 +15,7 @@ &.s24 { margin-right: 4px; } } - &.group-avatar, &.avatar-tile { + &.group-avatar, &.project-avatar, &.avatar-tile { @include border-radius(0); } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3b3efb829eb..75f70891bb3 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -75,6 +75,10 @@ float: none; margin-left: auto; margin-right: auto; + + &.identicon { + border-radius: 50%; + } } .project-title { @@ -402,6 +406,8 @@ a.deploy-project-label { border-radius: $border-radius-default; a { + padding-left: 10px; + padding-right: 10px; color: $notes-light-color; display: block; } -- cgit v1.2.1 From bfad4c61f10f689868817cf0b94cddaa1de22240 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 4 Jul 2016 11:37:28 +0200 Subject: Add minor improvements in readability in CI config --- lib/gitlab/ci/config/node/configurable.rb | 4 +--- lib/gitlab/ci/config/node/stages.rb | 2 +- lib/gitlab/ci/config/node/validator.rb | 1 + spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- spec/lib/gitlab/ci/config/node/boolean_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/configurable_spec.rb | 1 - 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 4889a21a234..37936fc8242 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -26,9 +26,7 @@ module Gitlab private def create_node(key, factory) - factory.with(value: @config[key]) - factory.with(parent: self) - factory.with(key: key) + factory.with(value: @config[key], key: key, parent: self) factory.create! end diff --git a/lib/gitlab/ci/config/node/stages.rb b/lib/gitlab/ci/config/node/stages.rb index 88d88252bce..b1fe45357ff 100644 --- a/lib/gitlab/ci/config/node/stages.rb +++ b/lib/gitlab/ci/config/node/stages.rb @@ -13,7 +13,7 @@ module Gitlab end def self.default - %w(build test deploy) + %w[build test deploy] end end end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index 1ba2e1dc59d..758a6cf4356 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -23,6 +23,7 @@ module Gitlab def unknown_keys return [] unless config.is_a?(Hash) + config.keys - @node.class.nodes.keys end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 262a91fedff..33b9d5f8f22 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -551,8 +551,8 @@ module Ci config_processor = GitlabCiYamlProcessor.new(config, path) ## - # When variables config is empty, we asumme this is a correct, - # see issue #18775 + # When variables config is empty, we assume this is a valid + # configuration, see issue #18775 # expect(config_processor.job_variables(:rspec)) .to be_an_instance_of(Array).and be_empty diff --git a/spec/lib/gitlab/ci/config/node/boolean_spec.rb b/spec/lib/gitlab/ci/config/node/boolean_spec.rb index 32639296e6d..deafa8bf8a7 100644 --- a/spec/lib/gitlab/ci/config/node/boolean_spec.rb +++ b/spec/lib/gitlab/ci/config/node/boolean_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::Node::Boolean do end context 'when entry value is not valid' do - let(:config) { [ 'incorrect' ] } + let(:config) { ['incorrect'] } describe '#errors' do it 'saves errors' do diff --git a/spec/lib/gitlab/ci/config/node/configurable_spec.rb b/spec/lib/gitlab/ci/config/node/configurable_spec.rb index 4a1550517fb..c468ecf957b 100644 --- a/spec/lib/gitlab/ci/config/node/configurable_spec.rb +++ b/spec/lib/gitlab/ci/config/node/configurable_spec.rb @@ -22,7 +22,6 @@ describe Gitlab::Ci::Config::Node::Configurable do validator.validate end - context 'when node validator is invalid' do let(:instance) { node.new('ls') } -- cgit v1.2.1 From edcdeb86fe40f5b66f3d1a1f57965f2299de3008 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 4 Jul 2016 19:47:24 +0300 Subject: Remove icons from file edit tabs Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/blob/edit.html.haml | 2 -- 1 file changed, 2 deletions(-) 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 -- cgit v1.2.1 From 926a8ab4765fe9249bfb54926ef8efa3ee78fd16 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 4 Jul 2016 20:04:29 +0300 Subject: Handle custom Git hook result in GitLab UI --- CHANGELOG | 1 + app/services/merge_requests/merge_service.rb | 5 ++++- lib/gitlab/git/hook.rb | 9 +++++++-- spec/services/merge_requests/merge_service_spec.rb | 10 ++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3b89fa05801..c05d81f56ba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -33,6 +33,7 @@ v 8.10.0 (unreleased) - Add basic system information like memory and disk usage to the admin panel - Don't garbage collect commits that have related DB records like comments - More descriptive message for git hooks and file locks + - Handle custom Git hook result in GitLab UI v 8.9.5 (unreleased) - Improve the request / withdraw access button. !4860 diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 9aaf5a5e561..3bec66cea88 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -36,10 +36,13 @@ module MergeRequests commit_id = repository.merge(current_user, merge_request.source_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) + false rescue StandardError => e merge_request.update(merge_error: "Something went wrong during merge") Rails.logger.error(e.message) - return false + false end def after_merge diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 5415f4844d3..420c6883c45 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -41,7 +41,7 @@ module Gitlab chdir: repo_path } - Open3.popen3(vars, path, options) do |stdin, _, stderr, wait_thr| + Open3.popen3(vars, path, options) do |stdin, stdout, stderr, wait_thr| exit_status = true stdin.sync = true @@ -60,7 +60,7 @@ module Gitlab unless wait_thr.value == 0 exit_status = false - exit_message = stderr.gets + exit_message = retrieve_error_message(stderr, stdout) end end @@ -76,6 +76,11 @@ module Gitlab [status, nil] end + + def retrieve_error_message(stderr, stdout) + err_message = stderr.gets + err_message.blank? ? stdout.gets : err_message + end end end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 1b0396eb686..2f72cd60071 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -65,6 +65,16 @@ describe MergeRequests::MergeService, services: true do expect(merge_request.merge_error).to eq("Something went wrong during merge") end + + it 'saves error if there is an PreReceiveError exception' do + allow(service).to receive(:repository).and_raise(GitHooksService::PreReceiveError, "error") + + allow(service).to receive(:execute_hooks) + + service.execute(merge_request) + + expect(merge_request.merge_error).to eq("error") + end end end end -- cgit v1.2.1 From 88889d164de2d0fed05ccaee8013dffaa127cd19 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jul 2016 13:42:42 -0400 Subject: Cancel creating or editing note by hitting Escape --- app/assets/javascripts/notes.js.coffee | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 17f7e180127..1881162addb 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -100,13 +100,25 @@ 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 + keydownNoteText: (e) => + return if isMetaKey e + + $textarea = $(e.target) + if $textarea.val() is '' and e.which is 38 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]) + if e.which is 27 + discussionNoteForm = $textarea.closest(".js-discussion-note-form") + if discussionNoteForm.length + @removeDiscussionNoteForm(discussionNoteForm) + return + + editNote = $textarea.closest(".note") + if editNote.length + @removeNoteEditForm(editNote) + isMetaKey = (e) -> (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey) @@ -401,9 +413,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") -- cgit v1.2.1 From 7e1c91e0d6c9fe7d8c7a392d09225aa76c254c47 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jul 2016 13:54:58 -0400 Subject: Add confirmation when canceling creating/editing with changes --- app/assets/javascripts/notes.js.coffee | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 1881162addb..63f0738031c 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -104,19 +104,31 @@ class @Notes return if isMetaKey e $textarea = $(e.target) + + # Edit previous note when UP arrow is hit if $textarea.val() is '' and e.which is 38 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 if e.which is 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) -- cgit v1.2.1 From 0deec938ceb3c290c18e26a99ee117c20aedf95e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jul 2016 13:58:05 -0400 Subject: Double to single quotes --- app/assets/javascripts/notes.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 63f0738031c..d0aca50b514 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -114,7 +114,7 @@ class @Notes # Cancel creating diff note or editing any note when ESCAPE is hit if e.which is 27 - discussionNoteForm = $textarea.closest(".js-discussion-note-form") + 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?') -- cgit v1.2.1 From 589a9caf0a53be6da39ecf1c635c005fa995f8ff Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Sat, 2 Jul 2016 02:14:12 +0300 Subject: replace " to ' in Gemfile --- Gemfile | 72 ++++++++++++++++++++++++++++++++--------------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/Gemfile b/Gemfile index 2c9fa2a0538..de7cee25c8c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "https://rubygems.org" +source 'https://rubygems.org' gem 'rails', '4.2.6' gem 'rails-deprecated_sanitizer', '~> 1.0.3' @@ -11,11 +11,11 @@ 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' @@ -48,16 +48,16 @@ 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 @@ -65,7 +65,7 @@ gem 'gollum-lib', '~> 4.1.0', 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,13 +94,13 @@ 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' @@ -124,12 +124,12 @@ 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' @@ -143,10 +143,10 @@ gem 'sidekiq-cron', '~> 0.4.0' gem 'redis-namespace' # 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,9 +214,9 @@ 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' @@ -247,7 +247,7 @@ group :metrics do end group :development do - gem "foreman" + gem 'foreman' gem 'brakeman', '~> 3.3.0', require: false gem 'letter_opener_web', '~> 1.3.0' @@ -261,7 +261,7 @@ 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' @@ -309,7 +309,7 @@ group :development, :test do gem 'benchmark-ips', require: false - gem "license_finder", require: false + gem 'license_finder', require: false gem 'knapsack' end @@ -322,26 +322,26 @@ group :test do 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' # Soft deletion -gem "paranoia", "~> 2.0" +gem 'paranoia', '~> 2.0' # Health check gem 'health_check', '~> 1.5.1' -- cgit v1.2.1 From 59eeac2962f36f693a5496e25f9f007fa71c4301 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Sat, 2 Jul 2016 02:37:31 +0300 Subject: change `require: nil` to `require: false` require: nil and require: false get same result, but i think we shouldn't write it differently in other places. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index de7cee25c8c..4c422b7e026 100644 --- a/Gemfile +++ b/Gemfile @@ -137,7 +137,7 @@ gem 'after_commit_queue' 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' -- cgit v1.2.1 From bfccdab70967c93592c70a71774cb355aae76ca3 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Sat, 2 Jul 2016 02:54:17 +0300 Subject: set version for gems. better gem's version control for prevent dependency errors. --- Gemfile | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index 4c422b7e026..245eeb8a276 100644 --- a/Gemfile +++ b/Gemfile @@ -131,7 +131,7 @@ end # State machine 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' @@ -140,7 +140,7 @@ gem 'acts-as-taggable-on', '~> 3.4' 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' @@ -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 @@ -268,8 +268,8 @@ group :development do 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' @@ -303,14 +303,14 @@ group :development, :test do 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,7 +318,7 @@ 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 -- cgit v1.2.1 From f617bd76907e45d9103d768a02fbccb451524b35 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 5 Jul 2016 10:20:32 +0530 Subject: Assert against `ActionMailer::Base.deliveries` relatively. - Look for a `change` in its size rather than asserting against an actual size. - This previously failed because another spec had an email in `ActionMailer::Base.deliveries`, which failed this `be_nil` assertion. --- spec/controllers/registrations_controller_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 209fa37d97d..026f41c926b 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -14,8 +14,7 @@ describe RegistrationsController do before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false) } it 'logs user in directly' do - post(:create, user_params) - expect(ActionMailer::Base.deliveries.last).to be_nil + expect { post(:create, user_params) }.not_to change{ ActionMailer::Base.deliveries.size } expect(subject.current_user).not_to be_nil end end -- cgit v1.2.1 From f51af496769f2fe181d4633f810b85103efd181e Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 15 Jun 2016 11:15:01 +0530 Subject: Support wildcard matches for protected branches at the model level. 1. The main implementation is in the `ProtectedBranch` model. The wildcard is converted to a Regex and compared. This has been tested thoroughly. - While `Project#protected_branch?` is the main entry point, `project#open_branches` and `project#developers_can_push_to_protected_branch?` have also been modified to work with wildcard protected branches. - The regex is memoized (within the `ProtectedBranch` instance) 2. Improve the performance of `Project#protected_branch?` - This method is called from `Project#open_branches` once _per branch_ in the project, to check if that branch is protected or not. - Before, `#protected_branch?` was making a database call every time it was invoked (in the above case, that amounts to once per branch), which is expensive. - This commit caches the list of protected branches in memory, which reduces the number of database calls down to 1. - A downside to this approach is that `#protected_branch?` _could_ return a stale value (due to the caching), but this is an acceptable tradeoff. 3. Remove the (now) unused `Project#protected_branch_names` method. - This was previously used to check for protected branch status. --- app/models/project.rb | 17 ++--- app/models/protected_branch.rb | 36 ++++++++++ spec/models/project_spec.rb | 64 +++++++++++++++++- spec/models/protected_branch_spec.rb | 125 +++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+), 15 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index ae96f00a705..d5d57bafb98 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -803,17 +803,7 @@ class Project < ActiveRecord::Base end 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) + repository.branches.reject { |branch| self.protected_branch?(branch.name) } end def root_ref?(branch) @@ -830,11 +820,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/protected_branch.rb b/app/models/protected_branch.rb index 33cf046fa75..3db1ab0e5f9 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -8,4 +8,40 @@ 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 + + # 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 + + 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/spec/models/project_spec.rb b/spec/models/project_spec.rb index a8c777d1e3e..117ffd551e4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -437,6 +437,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 "does not include 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)).not_to include('feature') + end end describe '#star_count' do @@ -937,15 +945,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 -- cgit v1.2.1 From 2a5cb7ec5259123cbbecb0577b9b4afacaf7546a Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 16 Jun 2016 13:03:30 +0530 Subject: Modify the frontend for wildcard protected branches. 1. Allow entering any branch name for a protected branch. - Either pick from a list of options, or enter it manually - You can enter wildcards. 2. Display branches matching a protected branch. - Add a `ProtectedBranches#show` page that displays the branches matching the given protected branch, or a message if there are no matches. - On the `index` page, display the last commit for an exact match, or the number of matching branches for a wildcard match. - Add an `iid` column to `protected_branches` - this is what we use for the `show` page URL. - On the off chance that this feature is unnecessary, this commit encapsulates it neatly, so it can be removed without affecting anything else. 3. Remove the "Last Commit" column from the list of protected branches. - There's no way to pull these for wildcard protected branches, so it's best left for the `show` page. - Rename the `@branches` instance variable to `@protected_branches` - Minor styling changes with the "Unprotect" button - floated right like the "Revoke" button for personal access tokens 4. Paginate the list of protected branches. 5. Move the instructions to the left side of the page. --- .../javascripts/protected_branches.js.coffee | 3 +- .../projects/protected_branches_controller.rb | 26 +++++++++-------- app/models/protected_branch.rb | 11 ++++++++ .../protected_branches/_branches_list.html.haml | 33 ++++++---------------- .../protected_branches/_matching_branch.html.haml | 9 ++++++ .../protected_branches/_protected_branch.html.haml | 21 ++++++++++++++ .../projects/protected_branches/index.html.haml | 16 ++++++++++- .../projects/protected_branches/show.html.haml | 25 ++++++++++++++++ config/routes.rb | 2 +- 9 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 app/views/projects/protected_branches/_matching_branch.html.haml create mode 100644 app/views/projects/protected_branches/_protected_branch.html.haml create mode 100644 app/views/projects/protected_branches/show.html.haml 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/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index efa7bf14d0f..026c5b74eb9 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 } } }) 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/models/protected_branch.rb b/app/models/protected_branch.rb index 3db1ab0e5f9..d3d5e1d98b2 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -19,6 +19,12 @@ class ProtectedBranch < ActiveRecord::Base (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? @@ -26,6 +32,11 @@ class ProtectedBranch < ActiveRecord::Base exact_match?(branch_name) || wildcard_match?(branch_name) end + # Checks if this protected branch contains a wildcard + def wildcard? + self.name.include?('*') + end + protected def exact_match?(branch_name) 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/_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..8eaef1f2904 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -21,7 +21,14 @@ .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"}}) + = f.text_field(:name) + %p.help-block + Wildcards such as + %code *-stable + or + %code production/* + are supported. + .form-group = f.check_box :developers_can_push, class: "pull-left" .prepend-left-20 @@ -31,3 +38,10 @@ = f.submit "Protect", class: "btn-create btn" %hr = render "branches_list" + +:javascript + $("#protected_branch_name").select2({ + placeholder: "Select branch", + createSearchChoice: function(term) { return { id: term, text: term }; }, + data: gon.open_branches + }) 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/config/routes.rb b/config/routes.rb index 1572656b8c5..18a4ead2b37 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -720,7 +720,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] -- cgit v1.2.1 From eb16e1e3c2614f385c4b992c919fd26768cfc3d8 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 17 Jun 2016 11:21:17 +0530 Subject: Improve the error message displayed when branch creation fails. Note: This feature was developed independently on master while this was in review. I've removed the conflicting bits and left the relevant additions, mainly a test for `Gitlab::Git::Hook`. The original commit message follows: 1. `gitlab-shell` outputs errors to `stderr`, but we weren't using this information, prior to this commit. Now we capture the `stderr`, and display it in the flash message when branch creation fails. 2. This can be used to display better errors for other git operation failures with small tweaks. 3. The return value of `Gitlab::Git::Hook#trigger` is changed from a simple `true`/`false` to a tuple of `[status, errors]`. All usages and tests have been updated to reflect this change. 4. This is only relevant to branch creation _from the Web UI_, since SSH and HTTP pushes access `gitlab-shell` either directly or through `gitlab-workhorse`. 5. A few minor changes need to be made on the `gitlab-shell` end. Right now, the `stderr` message it outputs is prefixed by "GitLab: ", which shows up in our flash message. This is better removed. --- .../projects/protected_branches/index.html.haml | 14 ++--- lib/gitlab/git/hook.rb | 9 +-- spec/lib/gitlab/git/hook_spec.rb | 70 ++++++++++++++++++++++ spec/support/test_env.rb | 2 +- 4 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 spec/lib/gitlab/git/hook_spec.rb diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 8eaef1f2904..75c27d85e9f 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -4,17 +4,17 @@ .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 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"} + .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) @@ -35,7 +35,7 @@ = 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/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 420c6883c45..db87d447358 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -14,7 +14,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" @@ -68,13 +68,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/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/support/test_env.rb b/spec/support/test_env.rb index 9f9ef20f99b..6b99b0f24cb 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -63,7 +63,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 -- cgit v1.2.1 From d8475276c4344e41b3121b9ff958e1a5f0be2d7d Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 17 Jun 2016 14:19:02 +0530 Subject: Add a feature spec for protected branch creation. 1. Doesn't seem like there's an easy way to do this for the actual branch protection, since we'd have to test an actual `git push`. 2. Testing branch creation the web UI is also not straightforward, since the factory repo doesn't have any hooks, and so access checks at the `gitlab-shell` level aren't run. --- spec/features/protected_branches_spec.rb | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 spec/features/protected_branches_spec.rb diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb new file mode 100644 index 00000000000..9a552e93c24 --- /dev/null +++ b/spec/features/protected_branches_spec.rb @@ -0,0 +1,82 @@ +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) + page.execute_script("$('#protected_branch_name').val('#{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 -- cgit v1.2.1 From 5de79c4f53e23aae4f07d9ca9d9e354db2998892 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 20 Jun 2016 13:09:33 +0530 Subject: Add documentation for wildcard protected branches. --- .../projects/protected_branches/index.html.haml | 3 ++- doc/workflow/protected_branches.md | 28 +++++++++++++++++++-- .../protected_branches/protected_branches1.png | Bin 155815 -> 195061 bytes .../protected_branches/protected_branches2.png | Bin 23208 -> 41179 bytes .../protected_branches/protected_branches3.png | Bin 0 -> 110160 bytes 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 doc/workflow/protected_branches/protected_branches3.png diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 75c27d85e9f..684cb175e68 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -23,7 +23,8 @@ = f.label :name, "Branch", class: "label-light" = f.text_field(:name) %p.help-block - Wildcards such as + = link_to "Wildcards", help_page_path(category: 'workflow', file: 'protected_branches', format: 'md', anchor: "wildcard-protected-branches") + such as %code *-stable or %code production/* diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md index d854ec1e025..67adfc2f43a 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. @@ -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 index bb3ab7d7913..c00443803de 100644 Binary files a/doc/workflow/protected_branches/protected_branches1.png and b/doc/workflow/protected_branches/protected_branches1.png differ diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png index 58ace31ac57..a4f664d3b21 100644 Binary files a/doc/workflow/protected_branches/protected_branches2.png and b/doc/workflow/protected_branches/protected_branches2.png differ diff --git a/doc/workflow/protected_branches/protected_branches3.png b/doc/workflow/protected_branches/protected_branches3.png new file mode 100644 index 00000000000..2a50cb174bb Binary files /dev/null and b/doc/workflow/protected_branches/protected_branches3.png differ -- cgit v1.2.1 From 16d8251093b80ec544578481f20e0074b392d738 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Sun, 3 Jul 2016 20:09:46 +0200 Subject: Add index on both Award Emoji user and name --- CHANGELOG | 1 + .../20160703180340_add_index_on_award_emoji_user_and_name.rb | 11 +++++++++++ db/schema.rb | 3 ++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb diff --git a/CHANGELOG b/CHANGELOG index 4fac555e12a..bda8c3d50bc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.9.5 (unreleased) - 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 + - Add index on the user and emoji name on AwardEmoji table !5061 - 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) 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/schema.rb b/db/schema.rb index 0f5f9f243fa..5b9ed985fac 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: 20160703180340) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -112,6 +112,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| -- cgit v1.2.1 From 4ddcac8ca8a1880b82c63b94b87ce91727129f23 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 24 Jun 2016 16:37:49 +0100 Subject: Added blank state to issues --- app/assets/stylesheets/framework/blank.scss | 26 +++++++++++++++- app/helpers/appearances_helper.rb | 4 +-- app/views/projects/issues/index.html.haml | 46 +++++++++++++++++++---------- app/views/shared/icons/_issues.svg | 13 -------- app/views/shared/icons/_issues.svg.erb | 4 +++ 5 files changed, 62 insertions(+), 31 deletions(-) delete mode 100644 app/views/shared/icons/_issues.svg create mode 100644 app/views/shared/icons/_issues.svg.erb diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index 40b5171a8c6..3e0ee4d66bd 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -1,3 +1,19 @@ +.blank-state-welcome { + text-align: center; + border-bottom: 1px solid $border-color; + + .blank-state-text { + margin-bottom: 0; + } +} + +.blank-state-welcome-title { + margin-top: 0; + margin-bottom: 5px; + font-size: 24px; + font-weight: normal; +} + .blank-state { padding-top: 20px; padding-bottom: 20px; @@ -6,7 +22,15 @@ .blank-state-no-icon { padding-top: 40px; - padding-bottom: 40px; + padding-bottom: 40px; +} + +.blank-state-icon { + padding-bottom: 20px; + + path { + fill: #ccc; + } } .blank-state-title { diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index f240584ccbf..950f323e383 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 navbar_icon(icon_name, size: 16) + render "shared/icons/#{icon_name}.svg", size: size end end diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 7ce4c1e5555..5f06431ffec 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.nil? + .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-welcome-title + Welcome to GitLab Issues + %p.blank-state-text + Code, test, and deploy together + .blank-state + .blank-state-icon + = navbar_icon("issues", size: 50) + %h3.blank-state-title + You don't have any issues right now. + %p.blank-state-text + Issues is the best way to track you 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 New Issue - - = render 'shared/issuable/filter', type: :issues - - .issues-holder - = render "issues" 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 @@ - - - - Group - Created with Sketch. - - - - - - - - \ 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 @@ + + + + -- cgit v1.2.1 From d10d32a324411ba010f3b0ea9da407cc736529b4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 24 Jun 2016 16:43:04 +0100 Subject: Uses any method instead of nil to check for issues --- app/views/projects/issues/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 5f06431ffec..a4795cddf2a 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -7,7 +7,7 @@ = 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) } - - if @project.issues.nil? + - if @project.issues.any? .top-area = render 'shared/issuable/nav', type: :issues .nav-controls -- cgit v1.2.1 From 5fcf475bc62651909dc5ddffac508f407781b082 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 24 Jun 2016 17:45:55 +0100 Subject: Updated tests --- features/dashboard/dashboard.feature | 1 + features/project/merge_requests.feature | 7 ------- features/steps/shared/project.rb | 5 +++++ spec/features/issues/filter_issues_spec.rb | 4 +++- spec/features/search_spec.rb | 2 ++ 5 files changed, 11 insertions(+), 8 deletions(-) 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/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 @@ -88,13 +88,6 @@ Feature: Project Merge Requests And I visit project "Shop" merge requests page 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 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/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/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) -- cgit v1.2.1 From d91c6c0738086f4ff8f99fabb78efd90bed990a0 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Tue, 5 Jul 2016 10:03:08 +0200 Subject: Throttle the update of `project.pushes_since_gc` to 1 minute --- CHANGELOG | 1 + app/services/projects/housekeeping_service.rb | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4fac555e12a..573ab21a5e2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.10.0 (unreleased) - API: Todos !3188 (Robert Schilling) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - PipelinesFinder uses git cache data + - Throttle the update of `project.pushes_since_gc` to 1 minute. - Check for conflicts with existing Project's wiki path when creating a new project. - Don't instantiate a git tree on Projects show default view - Remove unused front-end variable -> default_issues_tracker diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index a47df22f1ba..752c11d7ae6 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -27,7 +27,7 @@ module Projects 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) + update_pushes_since_gc(0) end end @@ -37,12 +37,18 @@ module Projects def increment! Gitlab::Metrics.measure(:increment_pushes_since_gc) do - @project.increment!(:pushes_since_gc) + update_pushes_since_gc(@project.pushes_since_gc + 1) end end private + def update_pushes_since_gc(new_value) + if Gitlab::ExclusiveLease.new("project_housekeeping:update_pushes_since_gc:#{project.id}", timeout: 60).try_obtain + @project.update_column(:pushes_since_gc, new_value) + end + end + def try_obtain_lease Gitlab::Metrics.measure(:obtain_housekeeping_lease) do lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) -- cgit v1.2.1 From e3390901a3d614a30d073ecb890d5c335e02616e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Jul 2016 09:19:01 +0100 Subject: Chnaged padding of project header nav --- app/assets/stylesheets/pages/projects.scss | 4 ++-- app/views/projects/_home_panel.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 75f70891bb3..3325b586496 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -367,8 +367,8 @@ a.deploy-project-label { border-bottom: 1px solid $border-color; .nav { - padding-top: $gl-padding; - padding-bottom: $gl-padding; + padding-top: 12px; + padding-bottom: 12px; } .nav > li { diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 270562e2e32..cf11723dc8e 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,6 +1,6 @@ - empty_repo = @project.empty_repo? .project-home-panel.text-center{ class: ("empty-project" if empty_repo) } - %div{class: container_class } + %div{ class: container_class } = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70') %h1.project-title = @project.name -- cgit v1.2.1 From 57365b86db8eddaf9355dc29cdf6642745ccbbf4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 5 Jul 2016 00:33:18 +0300 Subject: Add missing privileges to MySQL database [ci skip] --- doc/install/database_mysql.md | 2 +- doc/update/8.8-to-8.9.md | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) 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/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: -- cgit v1.2.1 From e9a4d117f2fb6f10af3357a8a6591ba7022d9c62 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 29 Jun 2016 16:05:57 +0200 Subject: Instrument cache fetch hit and cache fetch misses --- lib/gitlab/metrics/subscribers/rails_cache.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb index 8e345e8ae4a..277c860fbef 100644 --- a/lib/gitlab/metrics/subscribers/rails_cache.rb +++ b/lib/gitlab/metrics/subscribers/rails_cache.rb @@ -21,6 +21,18 @@ module Gitlab increment(:cache_exists, event.duration) end + def cache_fetch_hit(event) + return unless current_transaction + + current_transaction.increment(:cache_fetch_hit, 1) + end + + def cache_generate(event) + return unless current_transaction + + current_transaction.increment(:cache_fetch_miss, 1) + end + def increment(key, duration) return unless current_transaction -- cgit v1.2.1 From 330de255b75fa7b9897844582b8fd7e020f4efa7 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 29 Jun 2016 17:03:20 +0200 Subject: RailsCache metrics now includes fetch_hit/fetch_miss and read_hit/read_miss info. --- CHANGELOG | 1 + lib/gitlab/metrics/subscribers/rails_cache.rb | 14 ++- .../gitlab/metrics/subscribers/rails_cache_spec.rb | 103 +++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bda8c3d50bc..174422ef95c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.10.0 (unreleased) - Add API endpoint for a group issues !4520 (mahcsig) - Add Bugzilla integration !4930 (iamtjg) - 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) - 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 diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb index 277c860fbef..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) @@ -24,13 +34,13 @@ module Gitlab def cache_fetch_hit(event) return unless current_transaction - current_transaction.increment(:cache_fetch_hit, 1) + current_transaction.increment(:cache_read_hit_count, 1) end def cache_generate(event) return unless current_transaction - current_transaction.increment(:cache_fetch_miss, 1) + current_transaction.increment(:cache_read_miss_count, 1) end def increment(key, duration) 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 -- cgit v1.2.1 From bf5d28ea8d10c41b80a80c825526400aa3edfa15 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Jul 2016 11:54:49 +0100 Subject: Fixed markdown buttons in FF --- app/assets/javascripts/lib/utils/text_utility.js.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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") -- cgit v1.2.1 From d250e8690deb6a52d5820bf86e9ff9453fc95ccd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 5 Jul 2016 14:38:46 +0300 Subject: Add documentation for custom Git hook error message in GitLab's UI --- doc/hooks/custom_hooks.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index 820934f97f1..a3b7094da58 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -39,3 +39,16 @@ type. For example, if the script is in Ruby the shebang will probably be 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 and/or SDOUT message of the hook will be present in GitLab's UI. + +![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 -- cgit v1.2.1 From f56a685a30d931a6517f82642c8fed8bbfbc7069 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 5 Jul 2016 14:39:25 +0300 Subject: Follow doc styleguide --- doc/hooks/custom_hooks.md | 38 ++++++++++++++++--------------- doc/hooks/img/custom_hooks_error_msg.png | Bin 0 -> 159486 bytes 2 files changed, 20 insertions(+), 18 deletions(-) create mode 100644 doc/hooks/img/custom_hooks_error_msg.png diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index a3b7094da58..9fd7b71d2dc 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -1,41 +1,43 @@ # Custom Git Hooks -**Note: Custom git hooks must be configured on the filesystem of the GitLab +> +**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).** +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](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) -for more information about each hook type. +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. +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. +is exactly the same once the hook is created, though. -1. Pick a project that needs a custom git hook. +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//.git`. For Omnibus installs the path is -usually `/var/opt/gitlab/git-data/repositories//.git`. + For an installation from source the path is usually + `/home/git/repositories//.git`. For Omnibus installs the path is + usually `/var/opt/gitlab/git-data/repositories//.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. + 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`. +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. diff --git a/doc/hooks/img/custom_hooks_error_msg.png b/doc/hooks/img/custom_hooks_error_msg.png new file mode 100644 index 00000000000..92e87e15fb3 Binary files /dev/null and b/doc/hooks/img/custom_hooks_error_msg.png differ -- cgit v1.2.1 From d498ebba0d67b719e62a08a7dbfaddf38d254c53 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 5 Jul 2016 15:04:44 +0300 Subject: Change doc location of custom hooks --- doc/README.md | 2 +- doc/administration/custom_hooks.md | 56 ++++++++++++++++++++++ doc/administration/img/custom_hooks_error_msg.png | Bin 0 -> 159486 bytes doc/hooks/custom_hooks.md | 55 +-------------------- doc/hooks/img/custom_hooks_error_msg.png | Bin 159486 -> 0 bytes 5 files changed, 58 insertions(+), 55 deletions(-) create mode 100644 doc/administration/custom_hooks.md create mode 100644 doc/administration/img/custom_hooks_error_msg.png delete mode 100644 doc/hooks/img/custom_hooks_error_msg.png diff --git a/doc/README.md b/doc/README.md index b98d6812a81..53a12d2a455 100644 --- a/doc/README.md +++ b/doc/README.md @@ -23,7 +23,7 @@ - [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/custom_hooks.md b/doc/administration/custom_hooks.md new file mode 100644 index 00000000000..9fd7b71d2dc --- /dev/null +++ b/doc/administration/custom_hooks.md @@ -0,0 +1,56 @@ +# 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//.git`. For Omnibus installs the path is + usually `/var/opt/gitlab/git-data/repositories//.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 and/or SDOUT message of the hook will be present in GitLab's UI. + +![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/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png new file mode 100644 index 00000000000..92e87e15fb3 Binary files /dev/null and b/doc/administration/img/custom_hooks_error_msg.png differ diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index 9fd7b71d2dc..1d5e5dd6e15 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -1,56 +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 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//.git`. For Omnibus installs the path is - usually `/var/opt/gitlab/git-data/repositories//.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 and/or SDOUT message of the hook will be present in GitLab's UI. - -![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 +This document was moved to [administration/custom_hooks.md](../administration/custom_hooks.md). diff --git a/doc/hooks/img/custom_hooks_error_msg.png b/doc/hooks/img/custom_hooks_error_msg.png deleted file mode 100644 index 92e87e15fb3..00000000000 Binary files a/doc/hooks/img/custom_hooks_error_msg.png and /dev/null differ -- cgit v1.2.1 From 22ba5d8a7f0920f39ba33bdc4af54531ffe40b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 5 Jul 2016 14:24:58 +0200 Subject: New :request_access ability to replace a ugly helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Group / project members cannot request access - Group members cannot request access to a group's project This addresses an issue where project owners could request access to their own project, leading to UI inconsistency where their requester status would replace their owner status. Signed-off-by: Rémy Coutable --- app/helpers/members_helper.rb | 11 ---- app/models/ability.rb | 30 ++++++++-- .../members/_access_request_buttons.html.haml | 2 +- ...er_cannot_request_access_to_his_project_spec.rb | 16 ++++++ ...not_request_access_to_his_group_project_spec.rb | 3 - ...er_cannot_request_access_to_his_project_spec.rb | 16 ++++++ ...er_cannot_request_access_to_his_project_spec.rb | 16 ++++++ spec/helpers/members_helper_spec.rb | 66 ---------------------- 8 files changed, 73 insertions(+), 87 deletions(-) create mode 100644 spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb create mode 100644 spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb create mode 100644 spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb 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/models/ability.rb b/app/models/ability.rb index ba1f2ae4075..ec4ef287421 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 = project.owner == user || + (project.group && project.group.has_owner?(user)) || + user.admin? + if owner rules.push(*project_owner_rules) end @@ -169,6 +170,15 @@ class Ability # Allow to read builds for internal projects rules << :read_build if project.public_builds? + + group_member = + project.group && + ( + project.group.members.exists?(user_id: user.id) || + project.group.requesters.exists?(user_id: user.id) + ) + + rules << :request_access unless owner || project.team.member?(user) || group_member end if project.archived? @@ -345,8 +355,11 @@ class Ability rules = [] rules << :read_group if can_read_group?(user, group) + owner = group.has_owner?(user) || user.admin? + master = owner || user.admin? + # 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 +367,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 +376,10 @@ class Ability ] end + if (group.public? || (group.internal? && !user.external?)) + rules << :request_access unless group.users.include?(user) + end + rules.flatten end @@ -484,7 +501,8 @@ class Ability target_user = subject.user project = subject.project - unless target_user == project.owner + # Allow owners that requested access to their own project to destroy themselves + if target_user != project.owner || subject.request? can_manage = project_abilities(user, project).include?(:admin_project_member) if can_manage 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/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/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/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) } -- cgit v1.2.1 From 1a5348d55429661a7c896983241722b7564613ea Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 5 Jul 2016 15:40:29 +0300 Subject: Add profile settings link to header user dropdown Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/header/_default.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index d59a93a8fd7..ad1fa4c1d86 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -46,6 +46,8 @@ %ul %li = link_to "Profile", current_user + %li + = link_to "Profile Settings", profile_path %li.divider %li = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", title: 'Sign out' -- cgit v1.2.1 From bca47688d4e1348875c3c8bd6c647d5de264dc7e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 4 Jul 2016 22:04:46 +0300 Subject: Remove current user link to the profile from sidebar Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/gitlab-theme.scss | 11 ----------- app/assets/stylesheets/framework/sidebar.scss | 20 ++------------------ app/views/layouts/_page.html.haml | 5 ----- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 0a8603b6702..d1ff63bd099 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -20,17 +20,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/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index e8d6a7f2775..ff35786b1a5 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -40,32 +40,16 @@ } } -.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: 0px; width: $sidebar_width; overflow-y: auto; overflow-x: hidden; @media (min-width: $sidebar-breakpoint) { - bottom: 115px; + bottom: 50px; } &.navbar-collapse { diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 2234bf79c87..8596bbfdef6 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -8,11 +8,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') -- cgit v1.2.1 From 7facedfb19555cd9685a79c315871355fab2d095 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 5 Jul 2016 11:20:01 +0300 Subject: Fix profile test to click on header user profile link Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/sidebar.scss | 2 +- features/steps/profile/profile.rb | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index ff35786b1a5..188823054fd 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -43,7 +43,7 @@ .nav-sidebar { position: absolute; top: 50px; - bottom: 0px; + bottom: 0; width: $sidebar_width; overflow-y: auto; overflow-x: hidden; 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 -- cgit v1.2.1 From 05e722728cc61c14a3a1b2df005659a915c83f79 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 5 Jul 2016 15:10:46 +0300 Subject: Set user data in profile link in the header Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/header/_default.html.haml | 2 +- spec/features/admin/admin_users_spec.rb | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index ad1fa4c1d86..11cee421a99 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -45,7 +45,7 @@ .dropdown-menu-nav.dropdown-menu-align-right %ul %li - = link_to "Profile", current_user + = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } %li = link_to "Profile Settings", profile_path %li.divider 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 -- cgit v1.2.1 From c7b68b6e66a487b8b12556fe10dd1dde78581eca Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 5 Jul 2016 10:51:11 -0400 Subject: Dumb-down avatar presence check in `avatar_url` methods `avatar.present?` goes through CarrierWave, and checks that the file exists on disk and checks its filesize. Because we're hitting the disk, this adds extra overhead to something where the worst-case scenario is rendering a broken image. Instead, we now just check that the _database attribute_ is present, which is good enough for our purposes. See https://gitlab.com/gitlab-org/gitlab-ce/issues/19273 --- app/models/group.rb | 2 +- app/models/project.rb | 2 +- app/models/user.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/project.rb b/app/models/project.rb index ae96f00a705..e5fae15cb19 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -701,7 +701,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) diff --git a/app/models/user.rb b/app/models/user.rb index 5036a3e300c..695a47ba6eb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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) -- cgit v1.2.1 From 9ea80a196f14f55599ab9c9831788dd970a36966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 5 Jul 2016 16:58:27 +0200 Subject: Fix condition in Ability and start with cheaper checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/ability.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index ec4ef287421..2c0fd0338fd 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -157,9 +157,9 @@ class Ability # Push abilities on the users team role rules.push(*project_team_rules(project.team, user)) - owner = 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) @@ -178,7 +178,7 @@ class Ability project.group.requesters.exists?(user_id: user.id) ) - rules << :request_access unless owner || project.team.member?(user) || group_member + rules << :request_access unless owner || group_member || project.team.member?(user) end if project.archived? @@ -355,8 +355,8 @@ class Ability rules = [] rules << :read_group if can_read_group?(user, group) - owner = group.has_owner?(user) || user.admin? - master = owner || user.admin? + owner = user.admin? || group.has_owner?(user) + master = owner || group.has_master?(user) # Only group masters and group owners can create new projects if master @@ -376,7 +376,7 @@ class Ability ] end - if (group.public? || (group.internal? && !user.external?)) + if group.public? || (group.internal? && !user.external?) rules << :request_access unless group.users.include?(user) end -- cgit v1.2.1 From 4a62a11f20dc109b0976b76a8f8e6d0b2ab35353 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 5 Jul 2016 16:58:42 +0200 Subject: add more debug info to project export --- app/services/projects/import_export/export_service.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 3f507d5c400..6afc048576d 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -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 -- cgit v1.2.1 From 0db54b1587795ebc604353859861b05e478bfa8b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 5 Jul 2016 16:16:49 +0100 Subject: Fixed avatar alignment in new MR view --- app/assets/stylesheets/pages/merge_requests.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 124f4afaa0d..d9756b66af0 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -167,7 +167,8 @@ .commit { margin: 0; - padding: 2px 0; + padding-top: 2px; + padding-bottom: 2px; list-style: none; &:hover { background: none; -- cgit v1.2.1 From 19b80e82521384284227b31003889c9ac41b7c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 5 Jul 2016 18:55:35 +0200 Subject: Add a migration to remove requesters that are owners of their project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/ability.rb | 22 ++++++------ ...0705163108_remove_requesters_that_are_owners.rb | 40 ++++++++++++++++++++++ db/schema.rb | 2 +- 3 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 db/migrate/20160705163108_remove_requesters_that_are_owners.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index 2c0fd0338fd..eeb0ceba081 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -171,14 +171,9 @@ class Ability # Allow to read builds for internal projects rules << :read_build if project.public_builds? - group_member = - project.group && - ( - project.group.members.exists?(user_id: user.id) || - project.group.requesters.exists?(user_id: user.id) - ) - - rules << :request_access unless owner || group_member || project.team.member?(user) + unless owner || project.team.member?(user) || project_group_member?(project, user) + rules << :request_access + end end if project.archived? @@ -501,8 +496,7 @@ class Ability target_user = subject.user project = subject.project - # Allow owners that requested access to their own project to destroy themselves - if target_user != project.owner || subject.request? + unless target_user == project.owner can_manage = project_abilities(user, project).include?(:admin_project_member) if can_manage @@ -582,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/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/schema.rb b/db/schema.rb index 5b9ed985fac..c1e88c1ed7e 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: 20160703180340) do +ActiveRecord::Schema.define(version: 20160705163108) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 8c2894aeaff910051fa67acba0bbfbb3557ca4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 5 Jul 2016 19:04:53 +0200 Subject: Update installation & update guides for 8.10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/install/installation.md | 6 +- doc/update/8.9-to-8.10.md | 191 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 doc/update/8.9-to-8.10.md 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/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md new file mode 100644 index 00000000000..a51790b0bda --- /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.1.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. -- cgit v1.2.1 From 5d87f4fc09401e1b66b405b20c79a2dce06de505 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Tue, 5 Jul 2016 12:10:39 -0600 Subject: Fix Changelog [ci skip] --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d47023cb50d..92d3acaa6a1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,8 +39,6 @@ v 8.10.0 (unreleased) - More descriptive message for git hooks and file locks - 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) - Improve the request / withdraw access button. !4860 - Fix assigning shared runners as admins. !4961 -- cgit v1.2.1 From 09c85ccb6ec3738b640f365c89f9007615751749 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 5 Jul 2016 17:50:47 -0400 Subject: Use switch/when statement --- app/assets/javascripts/notes.js.coffee | 45 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index d0aca50b514..7c1d943667b 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -106,30 +106,33 @@ class @Notes $textarea = $(e.target) # Edit previous note when UP arrow is hit - if $textarea.val() is '' and e.which is 38 - 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]) + switch e.which + when 38 + return unless $textarea.val() is '' - # Cancel creating diff note or editing any note when ESCAPE is hit - if e.which is 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?') + 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]) - @removeDiscussionNoteForm(discussionNoteForm) - return + # 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?') - 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?') + @removeDiscussionNoteForm(discussionNoteForm) + return - @removeNoteEditForm(editNote) + 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) -> @@ -427,7 +430,7 @@ class @Notes ### cancelEdit: (e) => e.preventDefault() - note = $(e.target).closest(".note") + note = $(e.target).closest('.note') @removeNoteEditForm(note) removeNoteEditForm: (note) -> -- cgit v1.2.1 From 512adc21feff5135de94d23ed6808296b365490a Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 15 Jun 2016 17:30:55 -0500 Subject: Add setting that allows admins to choose which Git access protocols are enabled --- CHANGELOG | 1 + app/assets/stylesheets/framework/buttons.scss | 7 +++ .../admin/application_settings_controller.rb | 1 + app/helpers/application_settings_helper.rb | 22 ++++++++ app/helpers/projects_helper.rb | 10 +++- app/models/application_setting.rb | 2 + .../admin/application_settings/_form.html.haml | 6 ++ app/views/shared/_clone_panel.html.haml | 23 +++++--- ...git_access_protocols_to_application_settings.rb | 11 ++++ db/schema.rb | 1 + doc/api/settings.md | 1 + .../admin_disables_git_access_protocol_spec.rb | 66 ++++++++++++++++++++++ 12 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb create mode 100644 spec/features/admin/admin_disables_git_access_protocol_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 3da5583d452..62cfc81cc0b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -176,6 +176,7 @@ v 8.9.0 - Fix horizontal scrollbar for long commit message. - GitLab Performance Monitoring now tracks the total method execution time and call count per method - Add Environments and Deployments + - Add "Enabled Git access protocols" to Application Settings - Redesign account and email confirmation emails - Don't fail builds for projects that are deleted - Support Docker Registry manifest v1 diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 1e3083cce55..1b900d10859 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -281,3 +281,10 @@ color: $gl-icon-color; } } + +.clone-dropdown-btn a { + color: #555; + &:hover { + text-decoration: none; + } +} diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 5f65dd3aff0..51ae9264ed7 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -110,6 +110,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :send_user_confirmation_email, :container_registry_token_expire_delay, :repository_storage, + :enabled_git_access_protocols, restricted_visibility_levels: [], import_sources: [], disabled_oauth_sign_in_sources: [] diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 6e580c62ccd..bc196fb2918 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_protocols.present? + end + + def enabled_protocol + case current_application_settings.enabled_git_access_protocols + when 'http' + gitlab_config.protocol + when 'ssh' + 'ssh' + end + end + + def enabled_project_tooltip(project, protocol) + case protocol + when 'ssh' + sanitize(ssh_clone_button(project), tags: %w(a), attributes: %w(id class title data-html data-container data-placement data-title data-original-title aria-describedby)) + else + sanitize(http_clone_button(project), tags: %w(a), attributes: %w(id class title data-html data-container data-placement data-title data-original-title aria-describedby)) + 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/projects_helper.rb b/app/helpers/projects_helper.rb index f312a7ccca3..88787576dd3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -206,10 +206,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 diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 5fa6eacd234..7d0114fc549 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -59,6 +59,8 @@ class ApplicationSetting < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } + validates_inclusion_of :enabled_git_access_protocols, 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| diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index c1f70bc1866..5647ac90a16 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -43,6 +43,12 @@ = link_to "(?)", help_page_path("integration", "bitbucket") and GitLab.com = 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_protocols, [['Both SSH and HTTP', 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 diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 84b3f44c0ad..565bd869749 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 + %span + = enabled_project_tooltip(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/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..c75e20880db --- /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_protocols, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index c1e88c1ed7e..e0fe35f6b5f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -86,6 +86,7 @@ ActiveRecord::Schema.define(version: 20160705163108) do t.integer "container_registry_token_expire_delay", default: 5 t.text "after_sign_up_text" t.string "repository_storage", default: "default" + t.string "enabled_git_access_protocols" end create_table "audit_events", force: :cascade do |t| diff --git a/doc/api/settings.md b/doc/api/settings.md index 741c5a29581..baadad18ce8 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_protocols` | 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/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..550dcb62453 --- /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_protocols').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_protocols').find(:xpath, 'option[3]').select_option + click_on 'Save' + end +end -- cgit v1.2.1 From 82652013f2a9303e141a18923d85d0fe4870f7ae Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 16 Jun 2016 13:27:25 -0500 Subject: Fix CSS --- app/assets/stylesheets/framework/buttons.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 1b900d10859..a431fa59a57 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -283,7 +283,7 @@ } .clone-dropdown-btn a { - color: #555; + color: $dropdown-link-color; &:hover { text-decoration: none; } -- cgit v1.2.1 From ea9d910c8bd2774cf48a5b6092704143a7505011 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 17 Jun 2016 13:06:15 -0500 Subject: Refactor clone button sanitation to its own method to avoid duplication. --- app/helpers/application_settings_helper.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index bc196fb2918..4cbb7c54cb7 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -47,12 +47,16 @@ module ApplicationSettingsHelper def enabled_project_tooltip(project, protocol) case protocol when 'ssh' - sanitize(ssh_clone_button(project), tags: %w(a), attributes: %w(id class title data-html data-container data-placement data-title data-original-title aria-describedby)) + sanitize_clone_button(ssh_clone_button(project)) else - sanitize(http_clone_button(project), tags: %w(a), attributes: %w(id class title data-html data-container data-placement data-title data-original-title aria-describedby)) + sanitize_clone_button(http_clone_button(project)) end end + def sanitize_clone_button(input) + sanitize(input, tags: %w(a), attributes: %w(id class title data-html data-container data-placement data-title data-original-title aria-describedby)) + end + # Return a group of checkboxes that use Bootstrap's button plugin for a # toggle button effect. def restricted_level_checkboxes(help_block_id) -- cgit v1.2.1 From 7735ef86f0714a5b2a4cb4db8ec0471654563885 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 20 Jun 2016 20:40:56 -0500 Subject: Only allow Git Access on the allowed protocol --- app/controllers/projects/git_http_controller.rb | 2 +- app/helpers/application_settings_helper.rb | 4 ++-- app/helpers/button_helper.rb | 8 ++++---- app/models/application_setting.rb | 3 ++- lib/api/internal.rb | 7 +++++-- lib/gitlab/git/hook.rb | 3 ++- lib/gitlab/git_access.rb | 19 +++++++++++++++++-- lib/gitlab/protocol_access.rb | 13 +++++++++++++ 8 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 lib/gitlab/protocol_access.rb diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 62c3fa8de53..79a7e61e3fe 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -162,7 +162,7 @@ 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? + Gitlab::GitAccess.new(user, project, 'http').download_access_check.allowed? else ci? || project.public? end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 4cbb7c54cb7..19403388dc6 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -47,9 +47,9 @@ module ApplicationSettingsHelper def enabled_project_tooltip(project, protocol) case protocol when 'ssh' - sanitize_clone_button(ssh_clone_button(project)) + sanitize_clone_button(ssh_clone_button(project, 'bottom')) else - sanitize_clone_button(http_clone_button(project)) + sanitize_clone_button(http_clone_button(project, 'bottom')) end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 9051a493b9b..a64e96eaec9 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -40,7 +40,7 @@ module ButtonHelper type: :button end - def http_clone_button(project) + def http_clone_button(project, placement = 'right') klass = 'http-selector' klass << ' has-tooltip' if current_user.try(:require_password?) @@ -51,13 +51,13 @@ module ButtonHelper href: project.http_url_to_repo, data: { html: true, - placement: 'right', + placement: placement, container: 'body', title: "Set a password on your account
to pull or push via #{protocol}" } end - def ssh_clone_button(project) + def ssh_clone_button(project, placement = 'right') klass = 'ssh-selector' klass << ' has-tooltip' if current_user.try(:require_ssh_key?) @@ -66,7 +66,7 @@ module ButtonHelper href: project.ssh_url_to_repo, data: { html: true, - placement: 'right', + placement: placement, container: 'body', title: 'Add an SSH key to your profile
to pull or push via SSH.' } diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 7d0114fc549..314e69fa8b6 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -59,7 +59,8 @@ class ApplicationSetting < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } - validates_inclusion_of :enabled_git_access_protocols, in: %w(ssh http), allow_blank: true, allow_nil: true + validates :enabled_git_access_protocols, + inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true } validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? diff --git a/lib/api/internal.rb b/lib/api/internal.rb index b32503e8516..d5dfba5e0cc 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]) diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 420c6883c45..0b61c8bf332 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -34,7 +34,8 @@ module Gitlab vars = { 'GL_ID' => gl_id, - 'PWD' => repo_path + 'PWD' => repo_path, + 'PROTOCOL' => 'web' } options = { diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index d2a0e316cbe..7aec650d1a1 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 = nil) @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 @@ -72,6 +75,8 @@ module Gitlab end def download_access_check + return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? + if user user_download_access_check elsif deploy_key @@ -82,6 +87,8 @@ module Gitlab end def push_access_check(changes) + return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? + if user user_push_access_check(changes) elsif deploy_key @@ -92,6 +99,8 @@ module Gitlab end def user_download_access_check + return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? + unless user.can?(:download_code, project) return build_status_object(false, "You are not allowed to download code from this project.") end @@ -100,6 +109,8 @@ module Gitlab end def user_push_access_check(changes) + return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? + if changes.blank? return build_status_object(true) end @@ -188,6 +199,10 @@ module Gitlab Gitlab::UserAccess.allowed?(user) end + def protocol_allowed? + protocol ? Gitlab::ProtocolAccess.allowed?(protocol) : true + end + def branch_name(ref) ref = ref.to_s if Gitlab::Git.branch_ref?(ref) diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb new file mode 100644 index 00000000000..0498a72d4cf --- /dev/null +++ b/lib/gitlab/protocol_access.rb @@ -0,0 +1,13 @@ +module Gitlab + module ProtocolAccess + def self.allowed?(protocol) + if protocol.to_s == 'web' + true + elsif !current_application_settings.enabled_git_access_protocols.present? + true + else + protocol.to_s == current_application_settings.enabled_git_access_protocols + end + end + end +end -- cgit v1.2.1 From 8b14d1d2c20a5b8c7ef985007f90fd3aa12c3277 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 22 Jun 2016 13:03:24 -0500 Subject: Rename ENV['PROTOCOL'] to ENV['GL_PROTOCOL'] to conform to what GitLab Shell expects and make the `protocol` param in `GitAccess` mandatory. --- app/helpers/branches_helper.rb | 2 +- app/models/merge_request.rb | 2 +- app/services/commits/change_service.rb | 2 +- app/services/files/base_service.rb | 2 +- app/views/admin/application_settings/_form.html.haml | 2 +- lib/gitlab/git/hook.rb | 2 +- lib/gitlab/git_access.rb | 14 ++++---------- spec/lib/gitlab/git_access_spec.rb | 2 +- spec/lib/gitlab/git_access_wiki_spec.rb | 2 +- 9 files changed, 12 insertions(+), 18 deletions(-) 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/models/merge_request.rb b/app/models/merge_request.rb index cb0f871897a..4f7e1d2f302 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -481,7 +481,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? 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/files/base_service.rb b/app/services/files/base_service.rb index 0326a8823e9..4bdb68a3698 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -43,7 +43,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") diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 5647ac90a16..99bf2701f64 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -46,7 +46,7 @@ .form-group %label.control-label.col-sm-2 Enabled Git access protocols .col-sm-10 - = select(:application_setting, :enabled_git_access_protocols, [['Both SSH and HTTP', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control') + = select(:application_setting, :enabled_git_access_protocols, [['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 diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 0b61c8bf332..125240c8a8b 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -35,7 +35,7 @@ module Gitlab vars = { 'GL_ID' => gl_id, 'PWD' => repo_path, - 'PROTOCOL' => 'web' + 'GL_PROTOCOL' => 'web' } options = { diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 7aec650d1a1..d5f2713e935 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :actor, :project, :protocol - def initialize(actor, project, protocol = nil) + def initialize(actor, project, protocol) @actor = actor @project = project @protocol = protocol @@ -50,6 +50,8 @@ module Gitlab end def check(cmd, changes = nil) + return build_status_object(false, 'Access denied due to unspecified Git access protocol') unless protocol + return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? unless actor @@ -75,8 +77,6 @@ module Gitlab end def download_access_check - return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? - if user user_download_access_check elsif deploy_key @@ -87,8 +87,6 @@ module Gitlab end def push_access_check(changes) - return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? - if user user_push_access_check(changes) elsif deploy_key @@ -99,8 +97,6 @@ module Gitlab end def user_download_access_check - return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? - unless user.can?(:download_code, project) return build_status_object(false, "You are not allowed to download code from this project.") end @@ -109,8 +105,6 @@ module Gitlab end def user_push_access_check(changes) - return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? - if changes.blank? return build_status_object(true) end @@ -200,7 +194,7 @@ module Gitlab end def protocol_allowed? - protocol ? Gitlab::ProtocolAccess.allowed?(protocol) : true + Gitlab::ProtocolAccess.allowed?(protocol) end def branch_name(ref) diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 9b7986fa12d..7e1922260ea 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 } 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) } -- cgit v1.2.1 From c98f89eac7e2ebf6af4f242d94253c1260517f39 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 22 Jun 2016 13:08:02 -0500 Subject: Simplify access checks --- app/controllers/projects/git_http_controller.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 79a7e61e3fe..f124333bd5b 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -162,12 +162,18 @@ class Projects::GitHttpController < Projects::ApplicationController return false unless Gitlab.config.gitlab_shell.upload_pack if user - Gitlab::GitAccess.new(user, project, 'http').download_access_check.allowed? + access.allowed? else ci? || project.public? end end + def access + return @access if defined?(@access) + + @access = Gitlab::GitAccess.new(user, project, 'http').check('git-upload-pack') + end + def receive_pack_allowed? return false unless Gitlab.config.gitlab_shell.receive_pack -- cgit v1.2.1 From 42fb2516d999e64598ac34b92d0a69b068fa7800 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 22 Jun 2016 19:16:24 -0500 Subject: Add more tests to the allowed protocols feature --- spec/lib/gitlab/git_access_spec.rb | 37 ++++++++++++++++++++ spec/requests/api/internal_spec.rb | 71 +++++++++++++++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 7e1922260ea..ddccd2d9eb3 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -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_protocols, 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/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index fcea45f19ba..1f49cdad044 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -207,26 +207,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_protocols, '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_protocols, '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_protocols, '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 +297,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 -- cgit v1.2.1 From ace309d7755d6d50f85169649429e237ebb32b76 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 23 Jun 2016 09:53:21 -0500 Subject: Raise an error if no protocol is passed to the GitAccess check. --- lib/gitlab/git/hook.rb | 3 ++- lib/gitlab/git_access.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 125240c8a8b..57c41b41298 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) @@ -35,7 +36,7 @@ module Gitlab vars = { 'GL_ID' => gl_id, 'PWD' => repo_path, - 'GL_PROTOCOL' => 'web' + 'GL_PROTOCOL' => GL_PROTOCOL } options = { diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index d5f2713e935..beec56fcc62 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -50,7 +50,7 @@ module Gitlab end def check(cmd, changes = nil) - return build_status_object(false, 'Access denied due to unspecified Git access protocol') unless protocol + raise 'Access denied due to unspecified Git access protocol' unless protocol return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? -- cgit v1.2.1 From 41c87b9a23d7ebf24c3c100a4c261b8d2a68d0ff Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 23 Jun 2016 17:37:57 -0500 Subject: Return :forbidden if HTTP protocol access is not allowed --- app/controllers/projects/git_http_controller.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index f124333bd5b..072702ec9a2 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 !upload_pack_allowed? + 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 json: access.to_json, status: :forbidden + end + def ci? @ci.present? end -- cgit v1.2.1 From 5841851551db6b9aa682b17a075535cd431c2c2a Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 23 Jun 2016 18:43:36 -0500 Subject: Added documentation on the access restrictions. --- doc/README.md | 1 + doc/administration/access_restrictions.md | 38 +++++++++++++++++++++++++ doc/administration/img/access_restrictions.png | Bin 0 -> 317529 bytes doc/administration/img/restricted_url.png | Bin 0 -> 188210 bytes 4 files changed, 39 insertions(+) create mode 100644 doc/administration/access_restrictions.md create mode 100644 doc/administration/img/access_restrictions.png create mode 100644 doc/administration/img/restricted_url.png diff --git a/doc/README.md b/doc/README.md index 53a12d2a455..cf7a828d91e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -21,6 +21,7 @@ ## 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](administration/custom_hooks.md) Custom Git hooks (on the filesystem) for when webhooks aren't enough. diff --git a/doc/administration/access_restrictions.md b/doc/administration/access_restrictions.md new file mode 100644 index 00000000000..d3a58b8c144 --- /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) + +![](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. + +![](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/img/access_restrictions.png b/doc/administration/img/access_restrictions.png new file mode 100644 index 00000000000..66fd9491e85 Binary files /dev/null and b/doc/administration/img/access_restrictions.png differ diff --git a/doc/administration/img/restricted_url.png b/doc/administration/img/restricted_url.png new file mode 100644 index 00000000000..0a677433dcf Binary files /dev/null and b/doc/administration/img/restricted_url.png differ -- cgit v1.2.1 From 08018b7a7a6e885952f0ade3b62e2059239bddc7 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 23 Jun 2016 22:01:44 -0500 Subject: Render :forbidden *only* if HTTP is disabled. --- app/controllers/projects/git_http_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 072702ec9a2..ef7ccccd9e5 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -19,7 +19,7 @@ class Projects::GitHttpController < Projects::ApplicationController render_ok elsif receive_pack? && receive_pack_allowed? render_ok - elsif !upload_pack_allowed? + elsif http_blocked? render_not_allowed else render_not_found @@ -180,6 +180,10 @@ class Projects::GitHttpController < Projects::ApplicationController @access = Gitlab::GitAccess.new(user, project, 'http').check('git-upload-pack') end + def http_blocked? + access.message.include?('HTTP') + end + def receive_pack_allowed? return false unless Gitlab.config.gitlab_shell.receive_pack -- cgit v1.2.1 From 0f54e2ae6c6b5e1d196bf133de5ef92e907ea816 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 24 Jun 2016 09:41:10 -0500 Subject: Render the status message with `plain:` so that the message gets passed to the Git client. --- app/controllers/projects/git_http_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index ef7ccccd9e5..273be813435 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -157,7 +157,7 @@ class Projects::GitHttpController < Projects::ApplicationController end def render_not_allowed - render json: access.to_json, status: :forbidden + render plain: access.message, status: :forbidden end def ci? -- cgit v1.2.1 From da15471bb1c862111300a9202fe06c6a531fb283 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 27 Jun 2016 10:08:17 -0500 Subject: Clarify protocol access check, and make Git HTTP access call more specific. --- app/controllers/projects/git_http_controller.rb | 12 ++++++------ lib/gitlab/protocol_access.rb | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 273be813435..3d0055c9be3 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -157,7 +157,7 @@ class Projects::GitHttpController < Projects::ApplicationController end def render_not_allowed - render plain: access.message, status: :forbidden + render plain: download_access.message, status: :forbidden end def ci? @@ -168,20 +168,20 @@ class Projects::GitHttpController < Projects::ApplicationController return false unless Gitlab.config.gitlab_shell.upload_pack if user - access.allowed? + download_access.allowed? else ci? || project.public? end end - def access - return @access if defined?(@access) + def download_access + return @download_access if defined?(@download_access) - @access = Gitlab::GitAccess.new(user, project, 'http').check('git-upload-pack') + @download_access = Gitlab::GitAccess.new(user, project, 'http').check('git-upload-pack') end def http_blocked? - access.message.include?('HTTP') + download_access.protocol_allowed? end def receive_pack_allowed? diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb index 0498a72d4cf..836ff8a34ba 100644 --- a/lib/gitlab/protocol_access.rb +++ b/lib/gitlab/protocol_access.rb @@ -3,7 +3,7 @@ module Gitlab def self.allowed?(protocol) if protocol.to_s == 'web' true - elsif !current_application_settings.enabled_git_access_protocols.present? + elsif current_application_settings.enabled_git_access_protocols.blank? true else protocol.to_s == current_application_settings.enabled_git_access_protocols -- cgit v1.2.1 From 9397ce9137a8784bff4b63acfce3d4bc1e123cdf Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 27 Jun 2016 11:14:44 -0500 Subject: Correct access control flow for Git HTTP requests. --- app/controllers/projects/git_http_controller.rb | 10 ++++++++-- lib/gitlab/git_access.rb | 8 ++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 3d0055c9be3..40a8b7940d9 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -174,14 +174,20 @@ class Projects::GitHttpController < Projects::ApplicationController 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 = Gitlab::GitAccess.new(user, project, 'http').check('git-upload-pack') + @download_access = access.check('git-upload-pack') end def http_blocked? - download_access.protocol_allowed? + !access.protocol_allowed? end def receive_pack_allowed? diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index beec56fcc62..7dd9594ce68 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -169,6 +169,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) @@ -193,10 +197,6 @@ module Gitlab Gitlab::UserAccess.allowed?(user) end - def protocol_allowed? - Gitlab::ProtocolAccess.allowed?(protocol) - end - def branch_name(ref) ref = ref.to_s if Gitlab::Git.branch_ref?(ref) -- cgit v1.2.1 From d1151f762169e48e14b2007cf7990bee0f17e01f Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 28 Jun 2016 10:18:58 -0500 Subject: Don't allow empty strings in the `protocol` check. --- lib/gitlab/git_access.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 7dd9594ce68..ae609021eb6 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -50,7 +50,7 @@ module Gitlab end def check(cmd, changes = nil) - raise 'Access denied due to unspecified Git access protocol' unless protocol + raise 'Access denied due to unspecified Git access protocol' unless protocol.present? return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? -- cgit v1.2.1 From 120a1189bfa8b2afa7a8274c5617808f88f36101 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 28 Jun 2016 12:09:45 -0500 Subject: Add `alt` text to the images in the documentation. --- doc/administration/access_restrictions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/administration/access_restrictions.md b/doc/administration/access_restrictions.md index d3a58b8c144..51d7996effd 100644 --- a/doc/administration/access_restrictions.md +++ b/doc/administration/access_restrictions.md @@ -13,7 +13,7 @@ to choose between: - Only SSH - Only HTTP(s) -![](img/access_restrictions.png) +![Settings Overview](img/access_restrictions.png) ## Enabled Protocol @@ -27,7 +27,7 @@ When you choose to allow only one of the protocols, a couple of things will happ - 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. -![](img/restricted_url.png) +![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. -- cgit v1.2.1 From fbaabb3911c6fec25edc25bfffad94ae2a7c0e28 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 29 Jun 2016 14:30:27 -0500 Subject: Rename `enabled_git_access_protocols` to singular. --- CHANGELOG | 3 ++- app/helpers/application_settings_helper.rb | 4 ++-- app/models/application_setting.rb | 2 +- app/views/admin/application_settings/_form.html.haml | 2 +- ...3316_add_enabled_git_access_protocols_to_application_settings.rb | 2 +- lib/gitlab/protocol_access.rb | 4 ++-- spec/features/admin/admin_disables_git_access_protocol_spec.rb | 4 ++-- spec/lib/gitlab/git_access_spec.rb | 2 +- spec/requests/api/internal_spec.rb | 6 +++--- 9 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 62cfc81cc0b..11dd510d802 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,8 @@ v 8.10.0 (unreleased) - Add notification settings dropdown for groups - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - API: Todos !3188 (Robert Schilling) + - Add "Enabled Git access protocols" to Application Settings + - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - PipelinesFinder uses git cache data - Check for conflicts with existing Project's wiki path when creating a new project. @@ -176,7 +178,6 @@ v 8.9.0 - Fix horizontal scrollbar for long commit message. - GitLab Performance Monitoring now tracks the total method execution time and call count per method - Add Environments and Deployments - - Add "Enabled Git access protocols" to Application Settings - Redesign account and email confirmation emails - Don't fail builds for projects that are deleted - Support Docker Registry manifest v1 diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 19403388dc6..6b0dde5dfe6 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -32,11 +32,11 @@ module ApplicationSettingsHelper end def allowed_protocols_present? - current_application_settings.enabled_git_access_protocols.present? + current_application_settings.enabled_git_access_protocol.present? end def enabled_protocol - case current_application_settings.enabled_git_access_protocols + case current_application_settings.enabled_git_access_protocol when 'http' gitlab_config.protocol when 'ssh' diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 314e69fa8b6..7bf618d60b9 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -59,7 +59,7 @@ class ApplicationSetting < ActiveRecord::Base presence: true, inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } - validates :enabled_git_access_protocols, + 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| diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 99bf2701f64..eb325576e4f 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -46,7 +46,7 @@ .form-group %label.control-label.col-sm-2 Enabled Git access protocols .col-sm-10 - = select(:application_setting, :enabled_git_access_protocols, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control') + = 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 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 index c75e20880db..013904b3f4f 100644 --- 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 @@ -6,6 +6,6 @@ class AddEnabledGitAccessProtocolsToApplicationSettings < ActiveRecord::Migratio include Gitlab::Database::MigrationHelpers def change - add_column :application_settings, :enabled_git_access_protocols, :string + add_column :application_settings, :enabled_git_access_protocol, :string end end diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb index 836ff8a34ba..4c90654c59c 100644 --- a/lib/gitlab/protocol_access.rb +++ b/lib/gitlab/protocol_access.rb @@ -3,10 +3,10 @@ module Gitlab def self.allowed?(protocol) if protocol.to_s == 'web' true - elsif current_application_settings.enabled_git_access_protocols.blank? + elsif current_application_settings.enabled_git_access_protocol.blank? true else - protocol.to_s == current_application_settings.enabled_git_access_protocols + protocol.to_s == current_application_settings.enabled_git_access_protocol 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 index 550dcb62453..5b1c0460274 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -54,13 +54,13 @@ feature 'Admin disables Git access protocol', feature: true do def disable_http_protocol visit admin_application_settings_path - find('#application_setting_enabled_git_access_protocols').find(:xpath, 'option[2]').select_option + 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_protocols').find(:xpath, 'option[3]').select_option + find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[3]').select_option click_on 'Save' end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index ddccd2d9eb3..c79ba11f782 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -70,7 +70,7 @@ describe Gitlab::GitAccess, lib: true do describe '#check with single protocols allowed' do def disable_protocol(protocol) settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocols, protocol) + settings.update_attribute(:enabled_git_access_protocol, protocol) end context 'ssh disabled' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 1f49cdad044..e567d36afa8 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -211,7 +211,7 @@ describe API::API, api: true do context 'ssh access has been disabled' do before do settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocols, 'http') + settings.update_attribute(:enabled_git_access_protocol, 'http') end it 'rejects the SSH push' do @@ -234,7 +234,7 @@ describe API::API, api: true do context 'http access has been disabled' do before do settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocols, 'ssh') + settings.update_attribute(:enabled_git_access_protocol, 'ssh') end it 'rejects the HTTP push' do @@ -257,7 +257,7 @@ describe API::API, api: true do context 'web actions are always allowed' do it 'allows WEB push' do settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocols, 'ssh') + settings.update_attribute(:enabled_git_access_protocol, 'ssh') project.team << [user, :developer] push(key, project, 'web') -- cgit v1.2.1 From 29c50c53159333bdd124d4d3584ae826f49c28ad Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 29 Jun 2016 15:25:04 -0500 Subject: Default Git access protocol to `web` --- app/helpers/application_settings_helper.rb | 10 +++------- app/helpers/branches_helper.rb | 2 +- app/helpers/button_helper.rb | 8 ++++---- app/models/merge_request.rb | 2 +- app/services/commits/change_service.rb | 2 +- app/services/files/base_service.rb | 2 +- lib/gitlab/git_access.rb | 4 +--- lib/gitlab/protocol_access.rb | 4 ++-- spec/lib/gitlab/git_access_spec.rb | 2 +- 9 files changed, 15 insertions(+), 21 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 6b0dde5dfe6..92166461462 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -44,19 +44,15 @@ module ApplicationSettingsHelper end end - def enabled_project_tooltip(project, protocol) + def enabled_project_button(project, protocol) case protocol when 'ssh' - sanitize_clone_button(ssh_clone_button(project, 'bottom')) + ssh_clone_button(project, 'bottom', false) else - sanitize_clone_button(http_clone_button(project, 'bottom')) + http_clone_button(project, 'bottom', false) end end - def sanitize_clone_button(input) - sanitize(input, tags: %w(a), attributes: %w(id class title data-html data-container data-placement data-title data-original-title aria-describedby)) - 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/branches_helper.rb b/app/helpers/branches_helper.rb index 601df5c18df..c533659b600 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, 'web').can_push_to_branch?(branch_name) + ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) end def project_branches diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index a64e96eaec9..7fd20d13010 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -40,7 +40,7 @@ module ButtonHelper type: :button end - def http_clone_button(project, placement = 'right') + def http_clone_button(project, placement = 'right', append_link = true) klass = 'http-selector' klass << ' has-tooltip' if current_user.try(:require_password?) @@ -48,7 +48,7 @@ module ButtonHelper content_tag :a, protocol, class: klass, - href: project.http_url_to_repo, + href: (project.http_url_to_repo if append_link), data: { html: true, placement: placement, @@ -57,13 +57,13 @@ module ButtonHelper } end - def ssh_clone_button(project, placement = 'right') + 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', class: klass, - href: project.ssh_url_to_repo, + href: (project.ssh_url_to_repo if append_link), data: { html: true, placement: placement, diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4f7e1d2f302..cb0f871897a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -481,7 +481,7 @@ class MergeRequest < ActiveRecord::Base end def can_be_merged_by?(user) - ::Gitlab::GitAccess.new(user, project, 'web').can_push_to_branch?(target_branch) + ::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch) end def mergeable_ci_state? diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index c578097376a..6b69cb53b2c 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, 'web').can_push_to_branch?(@target_branch) + allowed = ::Gitlab::GitAccess.new(current_user, project).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/files/base_service.rb b/app/services/files/base_service.rb index 4bdb68a3698..0326a8823e9 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -43,7 +43,7 @@ module Files end def validate - allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch) + allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) unless allowed raise_error("You are not allowed to push into this branch") diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index ae609021eb6..93b75a7bb05 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :actor, :project, :protocol - def initialize(actor, project, protocol) + def initialize(actor, project, protocol = 'web') @actor = actor @project = project @protocol = protocol @@ -50,8 +50,6 @@ module Gitlab end def check(cmd, changes = nil) - raise 'Access denied due to unspecified Git access protocol' unless protocol.present? - return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? unless actor diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb index 4c90654c59c..21aefc884be 100644 --- a/lib/gitlab/protocol_access.rb +++ b/lib/gitlab/protocol_access.rb @@ -1,12 +1,12 @@ module Gitlab module ProtocolAccess def self.allowed?(protocol) - if protocol.to_s == 'web' + if protocol == 'web' true elsif current_application_settings.enabled_git_access_protocol.blank? true else - protocol.to_s == current_application_settings.enabled_git_access_protocol + protocol == current_application_settings.enabled_git_access_protocol end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index c79ba11f782..81530bb2db7 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, 'web') } + let(:access) { Gitlab::GitAccess.new(actor, project) } let(:project) { create(:project) } let(:user) { create(:user) } let(:actor) { user } -- cgit v1.2.1 From 2e96fcff12a8944b1f9bd5179381094ed4498bd5 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 29 Jun 2016 15:26:02 -0500 Subject: Clone button should not be clickable when there is a disabled protocol --- CHANGELOG | 1 - app/assets/stylesheets/framework/buttons.scss | 11 +++++++++++ app/views/shared/_clone_panel.html.haml | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 11dd510d802..8ef934bf80d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,7 +24,6 @@ v 8.10.0 (unreleased) - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - API: Todos !3188 (Robert Schilling) - Add "Enabled Git access protocols" to Application Settings - - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - PipelinesFinder uses git cache data - Check for conflicts with existing Project's wiki path when creating a new project. diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index a431fa59a57..590b8f54363 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -288,3 +288,14 @@ 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/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 565bd869749..3b82d8e686f 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -3,9 +3,9 @@ .git-clone-holder.input-group .input-group-btn -if allowed_protocols_present? - .clone-dropdown-btn.btn + .clone-dropdown-btn.btn.btn-static %span - = enabled_project_tooltip(project, enabled_protocol) + = enabled_project_button(project, enabled_protocol) - else %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', data: { toggle: 'dropdown' }} %span -- cgit v1.2.1 From cb24650ab8558b716fce286afdde56737da9bbb4 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 30 Jun 2016 15:11:52 -0500 Subject: Rebasing caused `enabled_git_access_protocol` to become plural. Fixed here. --- app/controllers/admin/application_settings_controller.rb | 2 +- db/schema.rb | 2 +- doc/api/settings.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 51ae9264ed7..cbdf2859898 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -110,7 +110,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :send_user_confirmation_email, :container_registry_token_expire_delay, :repository_storage, - :enabled_git_access_protocols, + :enabled_git_access_protocol, restricted_visibility_levels: [], import_sources: [], disabled_oauth_sign_in_sources: [] diff --git a/db/schema.rb b/db/schema.rb index e0fe35f6b5f..f6465136e6a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -86,7 +86,7 @@ ActiveRecord::Schema.define(version: 20160705163108) do t.integer "container_registry_token_expire_delay", default: 5 t.text "after_sign_up_text" t.string "repository_storage", default: "default" - t.string "enabled_git_access_protocols" + t.string "enabled_git_access_protocol" end create_table "audit_events", force: :cascade do |t| diff --git a/doc/api/settings.md b/doc/api/settings.md index baadad18ce8..d9b68eaeadf 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -68,7 +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_protocols` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. +| `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 -- cgit v1.2.1 From be221a30ac3df7a2954f3be9dddac48aa2179c76 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 30 Jun 2016 16:01:49 -0500 Subject: Revert back to not defining a default Git access protocol. --- app/helpers/branches_helper.rb | 2 +- app/models/merge_request.rb | 2 +- app/services/commits/change_service.rb | 2 +- app/services/files/base_service.rb | 2 +- lib/gitlab/git_access.rb | 2 +- spec/lib/gitlab/git_access_spec.rb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) 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/models/merge_request.rb b/app/models/merge_request.rb index cb0f871897a..4f7e1d2f302 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -481,7 +481,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? 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/files/base_service.rb b/app/services/files/base_service.rb index 0326a8823e9..4bdb68a3698 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -43,7 +43,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") diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 93b75a7bb05..7679c7e4bb8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :actor, :project, :protocol - def initialize(actor, project, protocol = 'web') + def initialize(actor, project, protocol) @actor = actor @project = project @protocol = protocol diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 81530bb2db7..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 } -- cgit v1.2.1 From 0bdf6fe4ba90f0a1dc7777d17651667776dfb91b Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 5 Jul 2016 16:48:48 -0500 Subject: Use keyword arguments for boolean values and use `span` instead of `a` for clone "button" --- app/helpers/application_settings_helper.rb | 4 ++-- app/helpers/button_helper.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 92166461462..78c0b79d2bd 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -47,9 +47,9 @@ module ApplicationSettingsHelper def enabled_project_button(project, protocol) case protocol when 'ssh' - ssh_clone_button(project, 'bottom', false) + ssh_clone_button(project, 'bottom', append_link: false) else - http_clone_button(project, 'bottom', false) + http_clone_button(project, 'bottom', append_link: false) end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 7fd20d13010..0f097f86816 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -40,13 +40,13 @@ module ButtonHelper type: :button end - def http_clone_button(project, placement = 'right', append_link = true) + 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 if append_link), data: { @@ -57,11 +57,11 @@ module ButtonHelper } end - def ssh_clone_button(project, placement = 'right', append_link = true) + 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 if append_link), data: { -- cgit v1.2.1 From ecc279f74b925f77d5af8b6c2478550fda4d6d1b Mon Sep 17 00:00:00 2001 From: Yatish Mehta Date: Tue, 5 Jul 2016 15:24:35 -0700 Subject: Fix typo in factory for projects.rb --- spec/factories/projects.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/, '_') } -- cgit v1.2.1 From e186626d25d5a24e2f2c5f0b5082b79bc8bd0ddf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 16 Jun 2016 20:09:13 -0300 Subject: Allow '?', or '&' for label titles --- app/assets/javascripts/labels_select.js.coffee | 4 +-- app/models/label.rb | 20 +++++++++++-- spec/models/label_spec.rb | 13 ++++----- spec/requests/api/issues_spec.rb | 40 +++++++++++++++----------- spec/requests/api/labels_spec.rb | 10 +++---- spec/requests/api/merge_requests_spec.rb | 26 ++++++++++------- 6 files changed, 69 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index ce859fedb2d..b88bc402801 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){ %> - issues?label_name[]=<%- label.title %>"> + issues?label_name[]=<%= encodeURIComponent(label.title) %>"> - <%- label.title %> + <%= label.title %> <% }); %>' diff --git a/app/models/label.rb b/app/models/label.rb index 49c352cc239..115f38c6dfe 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 @@ -114,7 +114,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 +132,18 @@ class Label < ActiveRecord::Base def nullify_priority self.priority = nil if priority.blank? end + + def sanitize_title(value) + unnescape_html_entities(Sanitize.clean(value.to_s)) + end + + def unnescape_html_entities(value) + value.to_s.gsub(/(>)|(<)|(&)/, Label::TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) + end + + TABLE_FOR_ESCAPE_HTML_ENTITIES = { + '&' => '&', + '<' => '<', + '>' => '>' + } end 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: "test") } - - it "sanitizes title" do - expect(label.title).to eq("test") + describe '#title' do + it 'sanitizes title' do + label = described_class.new(title: 'foo & bar?') + expect(label.title).to eq('foo & bar?') 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..5d81844fb84 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -243,17 +243,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 @@ -492,13 +494,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 -- cgit v1.2.1 From ab811b6ab929d3f220e060c15c49bc075d91e5f2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 20 Jun 2016 18:33:01 -0300 Subject: Render references for labels that name contains ?, or & --- app/assets/javascripts/gfm_auto_complete.js.coffee | 2 +- app/helpers/labels_helper.rb | 12 +++++- app/models/label.rb | 16 ++----- lib/banzai/filter/label_reference_filter.rb | 8 +++- spec/helpers/labels_helper_spec.rb | 6 +++ .../banzai/filter/label_reference_filter_spec.rb | 50 ++++++++++++++++++++++ 6 files changed, 77 insertions(+), 17 deletions(-) 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/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 5e9f5837101..1f0d5d545c0 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -1,6 +1,12 @@ module LabelsHelper include ActionView::Helpers::TagHelper + TABLE_FOR_ESCAPE_HTML_ENTITIES = { + '&' => '&', + '<' => '<', + '>' => '>' + } + # Link to a Label # # label - Label object to link to @@ -130,7 +136,11 @@ module LabelsHelper label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' end + def unescape_html_entities(value) + value.to_s.gsub(/(>)|(<)|(&)/, TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) + end + # Required for Banzai::Filter::LabelReferenceFilter module_function :render_colored_label, :render_colored_cross_project_label, - :text_color_for_bg, :escape_once + :text_color_for_bg, :escape_once, :unescape_html_entities end diff --git a/app/models/label.rb b/app/models/label.rb index 115f38c6dfe..086007d1864 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -58,8 +58,8 @@ class Label < ActiveRecord::Base (?: (?\d+) | # Integer-based label ID, or (? - [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 @@ -134,16 +134,6 @@ class Label < ActiveRecord::Base end def sanitize_title(value) - unnescape_html_entities(Sanitize.clean(value.to_s)) + LabelsHelper.unescape_html_entities(Sanitize.clean(value.to_s)) end - - def unnescape_html_entities(value) - value.to_s.gsub(/(>)|(<)|(&)/, Label::TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) - end - - TABLE_FOR_ESCAPE_HTML_ENTITIES = { - '&' => '&', - '<' => '<', - '>' => '>' - } end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index e4d3f87d0aa..7d016d78669 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) + LabelsHelper.unescape_html_entities(text) + end end end end diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 501f150cfda..1457eea7cb2 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -77,4 +77,10 @@ describe LabelsHelper do expect(text_color_for_bg('#000')).to eq '#FFFFFF' end end + + describe 'unescape_html_entities' do + it 'decodes &, <, and > named entities' do + expect(unescape_html_entities('foo & bar < zoo > boo é')).to eq 'foo & bar < zoo > boo é' + end + end end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index f1064a701d8..9e3d2f5825d 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -104,6 +104,31 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end + context 'String-based single-word references with special characters' do + let(:label) { create(:label, name: '?gfm&', 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 ?gfm&' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(\?gfm&
\.\))) + end + + it 'ignores invalid label names' do + act = "Label #{Label.reference_prefix}#{label.name.reverse}" + exp = "Label #{Label.reference_prefix}&mfg?" + + 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 +153,31 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end + context 'String-based multi-word references with special characters in quotes' do + let(:label) { create(:label, name: 'gfm & 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 gfm & references?' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(gfm & references\?\.\))) + end + + it 'ignores invalid label names' do + act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") + exp = %(Label #{Label.reference_prefix}"?secnerefer & mfg\") + + expect(reference_filter(act).to_html).to eq exp + end + end + describe 'edge cases' do it 'gracefully handles non-references matching the pattern' do exp = act = '(format nil "~0f" 3.0) ; 3.0' -- cgit v1.2.1 From d6b60e83edb755347c56e38770fcdffab9edbfa0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 27 Jun 2016 17:08:39 -0300 Subject: Move `unescape_html_entities` from LabelsHelper to Label model --- app/helpers/labels_helper.rb | 12 +----------- app/models/label.rb | 12 +++++++++++- lib/banzai/filter/label_reference_filter.rb | 2 +- spec/helpers/labels_helper_spec.rb | 6 ------ 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 1f0d5d545c0..5e9f5837101 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -1,12 +1,6 @@ module LabelsHelper include ActionView::Helpers::TagHelper - TABLE_FOR_ESCAPE_HTML_ENTITIES = { - '&' => '&', - '<' => '<', - '>' => '>' - } - # Link to a Label # # label - Label object to link to @@ -136,11 +130,7 @@ module LabelsHelper label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' end - def unescape_html_entities(value) - value.to_s.gsub(/(>)|(<)|(&)/, TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) - end - # Required for Banzai::Filter::LabelReferenceFilter module_function :render_colored_label, :render_colored_cross_project_label, - :text_color_for_bg, :escape_once, :unescape_html_entities + :text_color_for_bg, :escape_once end diff --git a/app/models/label.rb b/app/models/label.rb index 086007d1864..b0e2cb448b8 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -10,6 +10,12 @@ class Label < ActiveRecord::Base DEFAULT_COLOR = '#428BCA' + TABLE_FOR_ESCAPE_HTML_ENTITIES = { + '&' => '&', + '<' => '<', + '>' => '>' + } + default_value_for :color, DEFAULT_COLOR belongs_to :project @@ -134,6 +140,10 @@ class Label < ActiveRecord::Base end def sanitize_title(value) - LabelsHelper.unescape_html_entities(Sanitize.clean(value.to_s)) + unescape_html_entities(Sanitize.clean(value.to_s)) + end + + def unescape_html_entities(value) + value.to_s.gsub(/(>)|(<)|(&)/, TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 7d016d78669..fdd4afce606 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -68,7 +68,7 @@ module Banzai end def unescape_html_entities(text) - LabelsHelper.unescape_html_entities(text) + text.to_s.gsub(/(>)|(<)|(&)/, Label::TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) end end end diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 1457eea7cb2..501f150cfda 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -77,10 +77,4 @@ describe LabelsHelper do expect(text_color_for_bg('#000')).to eq '#FFFFFF' end end - - describe 'unescape_html_entities' do - it 'decodes &, <, and > named entities' do - expect(unescape_html_entities('foo & bar < zoo > boo é')).to eq 'foo & bar < zoo > boo é' - end - end end -- cgit v1.2.1 From 5d11cf2e98156c7fff403c3d8375da6f9b7edbf3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 29 Jun 2016 17:47:37 -0300 Subject: Use CGI.unescapeHTML rather than doing the gsub with a map --- app/models/label.rb | 12 +----------- lib/banzai/filter/label_reference_filter.rb | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index b0e2cb448b8..dc5586f5756 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -10,12 +10,6 @@ class Label < ActiveRecord::Base DEFAULT_COLOR = '#428BCA' - TABLE_FOR_ESCAPE_HTML_ENTITIES = { - '&' => '&', - '<' => '<', - '>' => '>' - } - default_value_for :color, DEFAULT_COLOR belongs_to :project @@ -140,10 +134,6 @@ class Label < ActiveRecord::Base end def sanitize_title(value) - unescape_html_entities(Sanitize.clean(value.to_s)) - end - - def unescape_html_entities(value) - value.to_s.gsub(/(>)|(<)|(&)/, TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) + CGI.unescapeHTML(Sanitize.clean(value.to_s)) end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index fdd4afce606..e258dc8e2bf 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -68,7 +68,7 @@ module Banzai end def unescape_html_entities(text) - text.to_s.gsub(/(>)|(<)|(&)/, Label::TABLE_FOR_ESCAPE_HTML_ENTITIES.invert) + CGI.unescapeHTML(text.to_s) end end end -- cgit v1.2.1 From e87ed41876effa6c24cb521aeb8470d18bf52b12 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 29 Jun 2016 18:26:21 -0300 Subject: Render label name contains ?, & in the labels dropdown without escaping --- app/assets/javascripts/labels_select.js.coffee | 4 ++-- app/views/shared/_labels_row.html.haml | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index b88bc402801..3b826a6af97 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -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/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") -- cgit v1.2.1 From 998d4eb61fb933b9ca3108f3f627d41e05586ec9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 29 Jun 2016 17:19:31 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8ef934bf80d..8e0abbf1524 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.10.0 (unreleased) - Don't garbage collect commits that have related DB records like comments - More descriptive message for git hooks and file locks - Handle custom Git hook result in GitLab UI + - Allow '?', or '&' for label names v 8.9.5 (unreleased) - Improve the request / withdraw access button. !4860 -- cgit v1.2.1 From 4ea3d1785c635098b5630984ba78700aa6dac4d0 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 5 Jul 2016 19:44:40 -0500 Subject: Bump GITLAB_SHELL_VERSION to 3.2.0 --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.1 From e89a515ce953a6805d512e005cef4034b337c182 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 6 Jul 2016 04:40:02 -0300 Subject: Fix unescaped strings in the labels dropdown template --- app/assets/javascripts/labels_select.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 3b826a6af97..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){ %> - issues?label_name[]=<%= encodeURIComponent(label.title) %>"> + issues?label_name[]=<%- encodeURIComponent(label.title) %>"> - <%= label.title %> + <%- label.title %> <% }); %>' -- cgit v1.2.1 From e486bbfd34bc962ddc6e7a0433a5b0cce74603a7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Jul 2016 09:02:52 +0100 Subject: Fixed spelling Used variable for icon color --- app/assets/stylesheets/framework/blank.scss | 13 +++++-------- app/views/projects/issues/index.html.haml | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index 3e0ee4d66bd..d28cda6d62d 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -7,13 +7,6 @@ } } -.blank-state-welcome-title { - margin-top: 0; - margin-bottom: 5px; - font-size: 24px; - font-weight: normal; -} - .blank-state { padding-top: 20px; padding-bottom: 20px; @@ -29,7 +22,7 @@ padding-bottom: 20px; path { - fill: #ccc; + fill: $gray-darkest; } } @@ -45,3 +38,7 @@ margin-bottom: $gl-padding; font-size: 15px; } + +.blank-state-welcome-title { + font-size: 24px; +} diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index a4795cddf2a..312bd86ed04 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -26,7 +26,7 @@ = render "issues" - else .blank-state.blank-state-welcome - %h2.blank-state-welcome-title + %h2.blank-state-title.blank-state-welcome-title Welcome to GitLab Issues %p.blank-state-text Code, test, and deploy together @@ -36,7 +36,7 @@ %h3.blank-state-title You don't have any issues right now. %p.blank-state-text - Issues is the best way to track you project progress + 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 -- cgit v1.2.1 From 19e15ae244776d4d148e6a65a1443f94bb59398c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 6 Jul 2016 10:08:42 +0200 Subject: Use a more future-proof check for Note/LegacyDiffNote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/event.rb | 2 +- spec/models/event_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index e0c52fed6fb..fd736d12359 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -136,7 +136,7 @@ class Event < ActiveRecord::Base end def note? - %w[Note LegacyDiffNote].include?(target_type) + target.is_a?(Note) end def issue? diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 00925591a5e..6ac19756f15 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -55,8 +55,8 @@ describe Event, models: true do it { is_expected.to be_note } end - context 'merge request note event' do - let(:target) { create(:note_on_merge_request) } + context 'merge request diff note event' do + let(:target) { create(:note_on_merge_request_diff) } it { is_expected.to be_note } end @@ -129,10 +129,10 @@ describe Event, models: true do end end - context 'merge request note event' do + 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(:note_on_merge_request, noteable: merge_request, project: project) } + let(:note_on_merge_request) { create(:note_on_merge_request_diff, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } it { expect(event.visible_to_user?(non_member)).to eq true } -- cgit v1.2.1 From a91e4a904281c515825b5a08fa56001632aaa5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 6 Jul 2016 10:17:31 +0200 Subject: Add and update .gitignore & .gitlab-ci.yml templates for 8.10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- vendor/gitignore/Android.gitignore | 1 + vendor/gitignore/C++.gitignore | 3 + vendor/gitignore/C.gitignore | 3 + vendor/gitignore/Gradle.gitignore | 2 +- vendor/gitignore/LICENSE | 140 ++++++++++++++++++--- vendor/gitignore/Node.gitignore | 1 + vendor/gitignore/TeX.gitignore | 3 + vendor/gitlab-ci-yml/Maven.gitlab-ci.yml | 102 +++++++++++++++ vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml | 16 +++ vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml | 13 ++ vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml | 12 ++ vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml | 16 +++ vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml | 25 ++++ vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml | 11 ++ vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml | 25 ++++ vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml | 24 ++++ vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml | 12 ++ .../gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml | 17 +++ vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml | 27 ++++ vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml | 12 ++ vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml | 15 +++ vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml | 10 ++ vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml | 15 ++- vendor/gitlab-ci-yml/Rust.gitlab-ci.yml | 23 ++++ vendor/gitlab-ci-yml/Scala.gitlab-ci.yml | 22 ++++ 25 files changed, 526 insertions(+), 24 deletions(-) create mode 100644 vendor/gitlab-ci-yml/Maven.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Rust.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Scala.gitlab-ci.yml 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 new file mode 100644 index 00000000000..7fcc0b436b5 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml @@ -0,0 +1,16 @@ +# Full project: https://gitlab.com/pages/brunch +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules/ + + script: + - npm install -g brunch + - brunch build --production + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml new file mode 100644 index 00000000000..791afdd23f1 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml @@ -0,0 +1,13 @@ +# Full project: https://gitlab.com/pages/doxygen +image: alpine + +pages: + script: + - apk update && apk add doxygen + - doxygen doxygen/Doxyfile + - mv doxygen/documentation/html/ public/ + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml new file mode 100644 index 00000000000..249a168aa33 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/plain-html +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml new file mode 100644 index 00000000000..dd3ef149668 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml @@ -0,0 +1,16 @@ +# Full project: https://gitlab.com/pages/harp +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules + + script: + - npm install -g harp + - harp compile ./ public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml new file mode 100644 index 00000000000..b468d79bcad --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml @@ -0,0 +1,25 @@ +# Full project: https://gitlab.com/pages/hexo +image: python:2.7 + +cache: + paths: + - vendor/ + +test: + stage: test + script: + - pip install hyde + - hyde gen + except: + - master + +pages: + stage: deploy + script: + - pip install hyde + - hyde gen -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml new file mode 100644 index 00000000000..45df6975259 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml @@ -0,0 +1,11 @@ +# Full project: https://gitlab.com/pages/hugo +image: publysher/hugo + +pages: + script: + - hugo + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml new file mode 100644 index 00000000000..f5b40f2b9f1 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml @@ -0,0 +1,25 @@ +# Full project: https://gitlab.com/pages/hyde +image: python:2.7 + +cache: + paths: + - vendor/ + +test: + stage: test + script: + - pip install hyde + - hyde gen + except: + - master + +pages: + stage: deploy + script: + - pip install hyde + - hyde gen -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml new file mode 100644 index 00000000000..36918fc005a --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml @@ -0,0 +1,24 @@ +# Full project: https://gitlab.com/pages/jekyll +image: ruby:2.3 + +test: + stage: test + script: + - gem install jekyll + - jekyll build -d test + artifacts: + paths: + - test + except: + - master + +pages: + stage: deploy + script: + - gem install jekyll + - jekyll build -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml new file mode 100644 index 00000000000..c5c44a5d86c --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/hyde +image: python:2.7 + +pages: + script: + - pip install lektor + - lektor build --output-path public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml new file mode 100644 index 00000000000..50e8b7ccd46 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml @@ -0,0 +1,17 @@ +# Full project: https://gitlab.com/pages/metalsmith +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules/ + + script: + - npm install -g metalsmith + - npm install + - make build + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml new file mode 100644 index 00000000000..9f4cc0574d6 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml @@ -0,0 +1,27 @@ +# Full project: https://gitlab.com/pages/middleman +image: ruby:2.3 + +cache: + paths: + - vendor + +test: + script: + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build + except: + - master + +pages: + script: + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml new file mode 100644 index 00000000000..b469b316ba5 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/nanoc +image: ruby:2.3 + +pages: + script: + - bundle install -j4 + - nanoc + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml new file mode 100644 index 00000000000..4762ec9acfd --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml @@ -0,0 +1,15 @@ +# Full project: https://gitlab.com/pages/octopress +image: ruby:2.3 + +pages: + script: + - apt-get update -qq && apt-get install -qq nodejs + - bundle install -j4 + - bundle exec rake generate + - mv public .public + - mv .public/octopress public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml new file mode 100644 index 00000000000..c5f3154f587 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml @@ -0,0 +1,10 @@ +# Full project: https://gitlab.com/pages/pelican +image: python:2.7-alpine + +pages: + script: + - pip install -r requirements.txt + - pelican -s publishconf.py + artifacts: + paths: + - public/ 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 -- cgit v1.2.1 From 95f630daeb5eec330080095786f7ac6702ebcc3f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 6 Jul 2016 10:29:31 +0200 Subject: even more debug --- lib/gitlab/import_export/command_line_util.rb | 3 ++- lib/gitlab/import_export/saver.rb | 1 + lib/gitlab/sidekiq_middleware/memory_killer.rb | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 78664f076eb..3d0b23710cb 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(output.to_s) unless status_zero? status.zero? end diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb index f38229c6c59..dd4fdf37309 100644 --- a/lib/gitlab/import_export/saver.rb +++ b/lib/gitlab/import_export/saver.rb @@ -17,6 +17,7 @@ module Gitlab Rails.logger.info("Saved project export #{archive_file}") archive_file else + @shared.error("Unable to save #{archive_file} into #{@shared.export_path}") false end rescue => e 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']}" -- cgit v1.2.1 From 09452715d341268898308d996e8a5f4ce20feb72 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 6 Jul 2016 11:09:07 +0200 Subject: fix typo --- lib/gitlab/import_export/command_line_util.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 3d0b23710cb..62c736451b7 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -29,7 +29,7 @@ module Gitlab def execute(cmd) output, status = Gitlab::Popen.popen(cmd) - @shared.error(output.to_s) unless status_zero? + @shared.error(output.to_s) unless status.zero? status.zero? end -- cgit v1.2.1 From 2044af6ced27a28f87cf05ca6f2b0f7cb41031df Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Jul 2016 10:16:16 +0100 Subject: Fixed issue with build auto-refresh not working --- app/assets/javascripts/ci/build.coffee | 8 ++++---- app/views/projects/builds/show.html.haml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee index 2d515d7efa2..74691b2c1b5 100644 --- a/app/assets/javascripts/ci/build.coffee +++ b/app/assets/javascripts/ci/build.coffee @@ -2,7 +2,7 @@ class @CiBuild @interval: null @state: null - constructor: (@build_url, @build_status, @state) -> + constructor: (@page_url, @build_url, @build_status, @state) -> clearInterval(CiBuild.interval) # Init breakpoint checker @@ -41,7 +41,7 @@ class @CiBuild # Only valid for runnig build when output changes during time # CiBuild.interval = setInterval => - if window.location.href.split("#").first() is @build_url + 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/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index d1c468c4692..4e801cc72fe 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 CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}") -- cgit v1.2.1 From 10c446eaa2ca4b46f454bde7f9715dc839efa5b9 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 20 Jun 2016 13:11:54 +0530 Subject: Add wildcard protected branches to the CHANGELOG. --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 4fac555e12a..1da85c04512 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.10.0 (unreleased) - Exclude email check from the standard health check - Fix changing issue state columns in milestone view - Add notification settings dropdown for groups + - Wildcards for protected branches. !4665 - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - API: Todos !3188 (Robert Schilling) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) -- cgit v1.2.1 From 14a8c514efe745ffd95ebc261bec9135338ec8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 6 Jul 2016 16:54:20 +0200 Subject: Remove duplicate templates that are lowercase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml | 16 ------------- vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml | 13 ----------- vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml | 16 ------------- vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml | 25 -------------------- vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml | 12 ---------- vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml | 11 --------- vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml | 25 -------------------- vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml | 24 ------------------- vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml | 12 ---------- .../gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml | 17 -------------- vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml | 27 ---------------------- vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml | 12 ---------- vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml | 15 ------------ vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml | 10 -------- 14 files changed, 235 deletions(-) delete mode 100644 vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml delete mode 100644 vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml deleted file mode 100644 index 7fcc0b436b5..00000000000 --- a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Full project: https://gitlab.com/pages/brunch -image: node:4.2.2 - -pages: - cache: - paths: - - node_modules/ - - script: - - npm install -g brunch - - brunch build --production - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml deleted file mode 100644 index 791afdd23f1..00000000000 --- a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Full project: https://gitlab.com/pages/doxygen -image: alpine - -pages: - script: - - apk update && apk add doxygen - - doxygen doxygen/Doxyfile - - mv doxygen/documentation/html/ public/ - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml deleted file mode 100644 index dd3ef149668..00000000000 --- a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Full project: https://gitlab.com/pages/harp -image: node:4.2.2 - -pages: - cache: - paths: - - node_modules - - script: - - npm install -g harp - - harp compile ./ public - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml deleted file mode 100644 index b468d79bcad..00000000000 --- a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Full project: https://gitlab.com/pages/hexo -image: python:2.7 - -cache: - paths: - - vendor/ - -test: - stage: test - script: - - pip install hyde - - hyde gen - except: - - master - -pages: - stage: deploy - script: - - pip install hyde - - hyde gen -d public - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml deleted file mode 100644 index 249a168aa33..00000000000 --- a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Full project: https://gitlab.com/pages/plain-html -pages: - stage: deploy - script: - - mkdir .public - - cp -r * .public - - mv .public public - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml deleted file mode 100644 index 45df6975259..00000000000 --- a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Full project: https://gitlab.com/pages/hugo -image: publysher/hugo - -pages: - script: - - hugo - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml deleted file mode 100644 index f5b40f2b9f1..00000000000 --- a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Full project: https://gitlab.com/pages/hyde -image: python:2.7 - -cache: - paths: - - vendor/ - -test: - stage: test - script: - - pip install hyde - - hyde gen - except: - - master - -pages: - stage: deploy - script: - - pip install hyde - - hyde gen -d public - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml deleted file mode 100644 index 36918fc005a..00000000000 --- a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml +++ /dev/null @@ -1,24 +0,0 @@ -# Full project: https://gitlab.com/pages/jekyll -image: ruby:2.3 - -test: - stage: test - script: - - gem install jekyll - - jekyll build -d test - artifacts: - paths: - - test - except: - - master - -pages: - stage: deploy - script: - - gem install jekyll - - jekyll build -d public - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml deleted file mode 100644 index c5c44a5d86c..00000000000 --- a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Full project: https://gitlab.com/pages/hyde -image: python:2.7 - -pages: - script: - - pip install lektor - - lektor build --output-path public - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml deleted file mode 100644 index 50e8b7ccd46..00000000000 --- a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Full project: https://gitlab.com/pages/metalsmith -image: node:4.2.2 - -pages: - cache: - paths: - - node_modules/ - - script: - - npm install -g metalsmith - - npm install - - make build - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml deleted file mode 100644 index 9f4cc0574d6..00000000000 --- a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Full project: https://gitlab.com/pages/middleman -image: ruby:2.3 - -cache: - paths: - - vendor - -test: - script: - - apt-get update -yqqq - - apt-get install -y nodejs - - bundle install --path vendor - - bundle exec middleman build - except: - - master - -pages: - script: - - apt-get update -yqqq - - apt-get install -y nodejs - - bundle install --path vendor - - bundle exec middleman build - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml deleted file mode 100644 index b469b316ba5..00000000000 --- a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Full project: https://gitlab.com/pages/nanoc -image: ruby:2.3 - -pages: - script: - - bundle install -j4 - - nanoc - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml deleted file mode 100644 index 4762ec9acfd..00000000000 --- a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Full project: https://gitlab.com/pages/octopress -image: ruby:2.3 - -pages: - script: - - apt-get update -qq && apt-get install -qq nodejs - - bundle install -j4 - - bundle exec rake generate - - mv public .public - - mv .public/octopress public - artifacts: - paths: - - public - only: - - master diff --git a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml deleted file mode 100644 index c5f3154f587..00000000000 --- a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Full project: https://gitlab.com/pages/pelican -image: python:2.7-alpine - -pages: - script: - - pip install -r requirements.txt - - pelican -s publishconf.py - artifacts: - paths: - - public/ -- cgit v1.2.1 From bb3801268b0ff51543c8a6c727eb7574a2460d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 6 Jul 2016 17:52:59 +0200 Subject: Fix specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 -- cgit v1.2.1 From 4e5de3e6ee822c63e7d2278ef8449275ca1fc9f1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 6 Jul 2016 00:36:23 -0300 Subject: Doesn't trigger Git hooks when cleaning up restored branches from GitHub --- lib/gitlab/github_import/importer.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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) -- cgit v1.2.1 From 5f86a084f254859e551955e91803ce8faa30bda5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 6 Jul 2016 02:52:59 -0300 Subject: Consider that a GH exists only if both `ref`, and `sha` exist --- lib/gitlab/github_import/branch_formatter.rb | 12 ++++++++---- .../lib/gitlab/github_import/branch_formatter_spec.rb | 19 +++++++++++++------ .../github_import/pull_request_formatter_spec.rb | 18 ++++++++++-------- 3 files changed, 31 insertions(+), 18 deletions(-) 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/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/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index 120f59e6e71..9587252b990 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', + head_source_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + base_target_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', + head_source_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + base_target_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', + head_source_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + base_target_sha: target_sha, state: 'merged', milestone: nil, author_id: project.creator_id, -- cgit v1.2.1 From 075c899d82e6020f71608c1a97517973b5fa463e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 6 Jul 2016 03:27:14 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ff5996c1174..1350317ad64 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -44,6 +44,7 @@ v 8.10.0 (unreleased) - More descriptive message for git hooks and file locks - Handle custom Git hook result in GitLab UI - Allow '?', or '&' for label names + - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests v 8.9.5 (unreleased) - Improve the request / withdraw access button. !4860 -- cgit v1.2.1 From 47e5b5397e39433ca6ae8e1186b2673748fe73cf Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Jul 2016 11:09:28 -0500 Subject: Get rid of pluralize on stage names --- app/views/projects/commit/_ci_stage.html.haml | 2 +- app/views/projects/pipelines/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 28b475d5c2f..6a127afa410 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -50,7 +50,7 @@ - stages.each do |stage| %th.stage %span.has-tooltip{ title: "#{stage.titleize}" } - = stage.titleize.pluralize + = stage.titleize %th Duration %th = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages -- cgit v1.2.1 From d90d51f99d1955449cece6a60b79b6a7158ef1fc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Jul 2016 11:34:55 -0500 Subject: Remove plural from pipelines_spec --- spec/features/pipelines_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) -- cgit v1.2.1 From 5c4a2bff91e7ad02a675e5d0ce1c27a36bc4bee6 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 6 Jul 2016 11:48:06 -0500 Subject: Link to the user's profile in the abuse reports and add a link to the admin area view if the user viewing the profile is an admin --- app/views/admin/abuse_reports/_abuse_report.html.haml | 4 ++-- app/views/users/show.html.haml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) 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/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 -- cgit v1.2.1 From 3646eb165c1ed0bb64ec6259ea285f00e46180f0 Mon Sep 17 00:00:00 2001 From: Serg Date: Wed, 6 Jul 2016 08:01:32 +0000 Subject: Allow everyone to sort tags, not only those who has access --- CHANGELOG | 1 + app/views/projects/tags/index.html.haml | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1350317ad64..42a5e50be50 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.10.0 (unreleased) - 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 + - Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Add Spring EmojiOne updates. - Fix pagination when sorting by columns with lots of ties (like priority) - Updated project header design 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? -- cgit v1.2.1 From 3baed8cb6ddf9d46b653f17135cbffc8e662cedd Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 6 Jul 2016 16:26:59 +0300 Subject: Services: code style fixes, minor refactoring --- app/services/audit_event_service.rb | 2 +- .../auth/container_registry_authentication_service.rb | 2 ++ app/services/create_branch_service.rb | 10 +++++----- app/services/create_release_service.rb | 4 +--- app/services/create_snippet_service.rb | 10 +++++----- app/services/create_tag_service.rb | 2 ++ app/services/delete_branch_service.rb | 12 ++---------- app/services/delete_tag_service.rb | 9 ++------- app/services/files/base_service.rb | 7 +++---- app/services/files/create_service.rb | 2 +- app/services/git_tag_push_service.rb | 1 + app/services/issues/bulk_update_service.rb | 1 + app/services/merge_requests/post_merge_service.rb | 1 + app/services/notification_service.rb | 2 ++ app/services/projects/update_service.rb | 3 ++- app/services/system_note_service.rb | 7 ++++--- app/services/update_release_service.rb | 4 +--- app/services/update_snippet_service.rb | 1 + 18 files changed, 37 insertions(+), 43 deletions(-) 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/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 4bdb68a3698..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) @@ -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/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/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/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/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) -- cgit v1.2.1 From 0b34cac1c5f0115b7ea6ed0e1e08b57ee22671cd Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Thu, 23 Jun 2016 09:36:24 -0500 Subject: Show last push widget in upstream after push to fork --- CHANGELOG | 1 + app/helpers/projects_helper.rb | 6 +++++- app/views/projects/_last_push.html.haml | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1350317ad64..232685bd9a0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.10.0 (unreleased) - PipelinesFinder uses git cache data - Throttle the update of `project.pushes_since_gc` to 1 minute. - Check for conflicts with existing Project's wiki path when creating a new project. + - Show last push widget in upstream after push to fork - 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 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 88787576dd3..3bbbb26cff2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -293,7 +293,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/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 -- cgit v1.2.1 From e9bd8b615b9a0a88aaf7bcc13f5df73deef74805 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 6 Jul 2016 14:00:20 -0500 Subject: Update time format of duration --- app/helpers/time_helper.rb | 7 +++++++ app/views/projects/ci/pipelines/_pipeline.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index b04b0a5114c..d1086025ad5 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 < 3600 ? "%M:%S" : "%H:%M:%S" + + Time.at(diff_in_seconds).utc.strftime(time_format) + end end diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index e38d1ff5ff0..af8dd5cd02c 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -45,7 +45,7 @@ %td - if pipeline.started_at && pipeline.finished_at %p.duration - #{duration_in_words(pipeline.finished_at, pipeline.started_at)} + = duration_in_numbers(pipeline.finished_at, pipeline.started_at) %td .controls.hidden-xs.pull-right -- cgit v1.2.1 From a77f9c521c67f4bee1084e8af40868e56d1c5f5f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 Jul 2016 16:58:53 -0400 Subject: Update CHANGELOG for 8.9.5 [ci skip] --- CHANGELOG | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1350317ad64..9627c08d428 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,16 +46,20 @@ v 8.10.0 (unreleased) - Allow '?', or '&' for label names - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests -v 8.9.5 (unreleased) - - 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 - - Add index on the user and emoji name on AwardEmoji table !5061 - - 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. +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 - - Security: Update RedCloth to 4.3.2 (Takuya Noguchi) + - Add index on both Award Emoji user and name. !5061 + - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056 + - Re-enable import button when import process fails due to namespace already being taken. !5053 + - Fix snippets comments not displayed. !5045 + - Fix emoji paths in relative root configurations. !5027 + - Fix issues importing events in Import/Export. !4987 + - Fixed 'use shortcuts' button on docs. !4979 + - Admin should be able to turn shared runners into specific ones. !4961 + - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi) + - Improve the request / withdraw access button. !4860 v 8.9.4 - Fix privilege escalation issue with OAuth external users. -- cgit v1.2.1 From 1867d0d505baf518fe92d3c306fddfb56e68a810 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 6 Jul 2016 16:17:20 -0500 Subject: Added specs to check for the correct links. --- .../admin/abuse_reports/_abuse_report.html.haml | 2 +- app/views/users/show.html.haml | 2 +- spec/features/admin/admin_abuse_reports_spec.rb | 31 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 spec/features/admin/admin_abuse_reports_spec.rb diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index dd2e7ebd030..b54ca059a61 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -3,7 +3,7 @@ %tr %td - if user - = link_to user.name, user + = link_to user.name, user, id: 'abuser_profile_path' .light.small Joined #{time_ago_with_tooltip(user.created_at)} - else diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index db2b4885861..520f76eb062 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -31,7 +31,7 @@ = icon('rss') - if current_user.admin?   - = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', + = link_to [:admin, @user], id: 'admin_user_path', class: 'btn btn-gray', title: 'View user in admin area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('users') 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..2ff02a1c9a8 --- /dev/null +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -0,0 +1,31 @@ +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 + admin = create(:admin) + 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_selector '#abuser_profile_path' + 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_selector '#admin_user_path' + end + end + end + end +end -- cgit v1.2.1 From 18a5bb05204ee437902d82e5973a427b9aac6d53 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 18:43:55 +0200 Subject: Store diff head and start commit shas on MR diffs --- app/models/merge_request_diff.rb | 17 ++++++++++------- ...8202603_add_head_commit_id_to_merge_request_diffs.rb | 5 +++++ ...224534_add_start_commit_id_to_merge_request_diffs.rb | 5 +++++ 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb create mode 100644 db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0fcde6fc8f1..fd69f915bd2 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -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 def reload_content reload_commits @@ -147,12 +147,13 @@ 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) + + keep_around_commits end # Collect array of Git::Diff objects @@ -223,7 +224,9 @@ class MergeRequestDiff < ActiveRecord::Base reload end - def keep_around_commit - self.repository.keep_around(self.base_commit_sha) + def keep_around_commits + self.repository.keep_around(target_branch_sha) + self.repository.keep_around(source_branch_sha) + self.repository.keep_around(branch_base_sha) end end 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/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 -- cgit v1.2.1 From 6ce25e7b4caa9e94de74378729178c7060d640b2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 18:48:04 +0200 Subject: Rename MergeRequest methods that return commits or shas to be more clear and consistent --- .../projects/merge_requests_controller.rb | 18 ++-- app/helpers/merge_requests_helper.rb | 2 +- app/models/merge_request.rb | 120 ++++++++++++++------- app/models/merge_request_diff.rb | 43 ++++---- app/models/project_services/jira_service.rb | 2 +- app/services/merge_requests/merge_service.rb | 2 +- .../merge_when_build_succeeds_service.rb | 2 +- app/services/merge_requests/refresh_service.rb | 8 +- .../merge_requests/widget/_heading.html.haml | 10 +- .../merge_requests/widget/open/_accept.html.haml | 2 +- .../open/_merge_when_build_succeeds.html.haml | 2 +- features/steps/project/merge_requests.rb | 2 +- lib/api/merge_requests.rb | 4 +- lib/gitlab/github_import/pull_request_formatter.rb | 4 +- .../projects/merge_requests_controller_spec.rb | 6 +- .../merge_requests/created_from_fork_spec.rb | 2 +- .../merge_when_build_succeeds_spec.rb | 4 +- .../only_allow_merge_if_build_succeeds.rb | 2 +- .../github_import/pull_request_formatter_spec.rb | 12 +-- .../import_export/project_tree_saver_spec.rb | 2 +- spec/models/merge_request_spec.rb | 29 ++--- spec/models/project_spec.rb | 2 +- spec/models/repository_spec.rb | 4 +- spec/requests/api/merge_requests_spec.rb | 4 +- spec/services/system_note_service_spec.rb | 2 +- 25 files changed, 162 insertions(+), 128 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index dd86b940a08..a03eb8513b6 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -77,12 +77,8 @@ 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 + @commit = @merge_request.diff_head_commit + @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit @comments_target = { noteable_type: 'MergeRequest', @@ -134,7 +130,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 +208,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 +270,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 } diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 1dd07a2a220..c7dedfe9254 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) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4f7e1d2f302..cc85421a815 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -16,7 +16,7 @@ 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 @@ -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 @@ -169,28 +165,88 @@ 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 last_commit + merge_request_diff ? merge_request_diff.last_commit : compare_commits.last + 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 need the + # true base commit. + 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 last_commit_short_sha - last_commit.short_id + def diff_head_commit + if persisted? + merge_request_diff.head_commit + else + source_branch_head + end + end + + 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 validate_branches @@ -241,7 +297,7 @@ class MergeRequest < ActiveRecord::Base 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 @@ -293,7 +349,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 +387,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) @@ -510,22 +566,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 +598,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 +611,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 +622,13 @@ 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 diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index fd69f915bd2..60f4b44a5d1 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 @@ -40,8 +40,8 @@ class MergeRequestDiff < ActiveRecord::Base @diffs_no_whitespace ||= begin compare = Gitlab::Git::Compare.new( self.repository.raw_repository, - self.base, - self.head, + self.target_branch_sha, + self.source_branch_sha, ) compare.diffs(options) end @@ -63,13 +63,21 @@ 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) end - def last_commit_short_sha - @last_commit_short_sha ||= last_commit.short_id + def start_commit + return unless self.start_commit_sha + + merge_request.target_project.commit(self.start_commit_sha) + end + + def head_commit + return last_commit unless self.head_commit_sha + + merge_request.target_project.commit(self.head_commit_sha) end def dump_commits(commits) @@ -166,23 +174,14 @@ class MergeRequestDiff < ActiveRecord::Base merge_request.target_project.repository 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) - end - - def target_sha - merge_request.target_sha - end + def branch_base_commit + return unless self.source_branch_sha && self.target_branch_sha - def base - self.target_sha || self.target_branch + merge_request.target_project.merge_base_commit(self.source_branch_sha, self.target_branch_sha) end - def head - self.source_sha + def branch_base_sha + branch_base_commit.try(:sha) end def compare @@ -193,8 +192,8 @@ class MergeRequestDiff < ActiveRecord::Base Gitlab::Git::Compare.new( self.repository.raw_repository, - self.base, - self.head + self.target_branch_sha, + self.source_branch_sha ) 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/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/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index fe0579744b4..de79c024428 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| @@ -94,12 +94,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/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/_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/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 640f1720a6c..3611c187202 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -519,7 +519,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/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/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/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index eff74e12869..2d2fb87f14e 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -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 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/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/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index 9587252b990..79931ecd134 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -43,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: source_sha, + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: target_sha, + target_branch_sha: target_sha, state: 'opened', milestone: nil, author_id: project.creator_id, @@ -70,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: source_sha, + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: target_sha, + target_branch_sha: target_sha, state: 'closed', milestone: nil, author_id: project.creator_id, @@ -97,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: source_sha, + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: target_sha, + target_branch_sha: target_sha, state: 'merged', milestone: nil, author_id: project.creator_id, 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/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index ceb4d64698f..bb83676cddf 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -62,7 +62,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 +73,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 @@ -252,12 +252,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 +365,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 +375,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 +394,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 +465,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 +482,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 diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2e89d6de3a2..1b434a726dc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -312,7 +312,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 diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 7975fc64e59..24e49c8def3 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -12,8 +12,8 @@ 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 diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5d81844fb84..4a1b5600bdf 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -439,14 +439,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 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' -- cgit v1.2.1 From a9fa45f09e6b6188691f37d75883b22edce7bba1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 18:51:48 +0200 Subject: Represent DiffRefs as proper class instead of tuple array --- app/controllers/projects/blob_controller.rb | 2 +- app/controllers/projects/commit_controller.rb | 1 - app/controllers/projects/compare_controller.rb | 18 +++++++++++++----- app/helpers/diff_helper.rb | 4 ++-- app/models/commit.rb | 7 +++++++ app/models/merge_request.rb | 16 ++++++++++------ app/views/projects/commit/show.html.haml | 3 +-- app/views/projects/diffs/_diffs.html.haml | 2 +- app/views/projects/diffs/_image.html.haml | 11 +++++------ app/workers/emails_on_push_worker.rb | 16 ++++++++++++++-- lib/gitlab/diff/diff_refs.rb | 26 ++++++++++++++++++++++++++ lib/gitlab/diff/file.rb | 21 +++++++++------------ lib/gitlab/diff/highlight.rb | 10 ++++++---- lib/gitlab/email/message/repository_push.rb | 8 ++++++-- lib/gitlab/workhorse.rb | 6 ++---- spec/helpers/diff_helper_spec.rb | 2 +- spec/lib/gitlab/diff/file_spec.rb | 2 +- spec/lib/gitlab/diff/highlight_spec.rb | 6 +++--- spec/lib/gitlab/diff/parallel_diff_spec.rb | 3 +-- spec/mailers/notify_spec.rb | 4 ++-- 20 files changed, 111 insertions(+), 57 deletions(-) create mode 100644 lib/gitlab/diff/diff_refs.rb 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..37d6521026c 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -121,7 +121,6 @@ class Projects::CommitController < Projects::ApplicationController opts[:ignore_whitespace_change] = true if params[:format] == 'diff' @diffs = commit.diffs(opts) - @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count @statuses = CommitStatus.where(pipeline: pipelines) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index af0b69a2442..d240b9fe989 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -14,14 +14,22 @@ class Projects::CompareController < Projects::ApplicationController def show compare = CompareService.new. - execute(@project, @head_ref, @project, @base_ref, diff_options) + execute(@project, @head_ref, @project, @start_ref, diff_options) 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(@base_ref, @head_ref) + @base_commit = @project.merge_base_commit(@start_ref, @head_ref) + @diffs = compare.diffs(diff_options) - @diff_refs = [@base_commit, @commit] + @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 @@ -35,12 +43,12 @@ class Projects::CompareController < Projects::ApplicationController private def assign_ref_vars - @base_ref = Addressable::URI.unescape(params[:from]) + @start_ref = Addressable::URI.unescape(params[:from]) @ref = @head_ref = Addressable::URI.unescape(params[:to]) 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/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index e22dce59d0f..a7eedafe314 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -30,8 +30,8 @@ module DiffHelper options end - def safe_diff_files(diffs, diff_refs) - diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) } + 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 generate_line_code(file_path, line) 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/merge_request.rb b/app/models/merge_request.rb index cc85421a815..70ef275d3a5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -249,6 +249,16 @@ class MergeRequest < ActiveRecord::Base source_branch_head.try(:sha) end + def diff_refs + return nil 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 if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" @@ -622,12 +632,6 @@ class MergeRequest < ActiveRecord::Base end def pipeline - 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 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/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index f18bc8c41b3..151780addc5 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -2,7 +2,7 @@ - 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 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/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/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb new file mode 100644 index 00000000000..43489ae876b --- /dev/null +++ b/lib/gitlab/diff/diff_refs.rb @@ -0,0 +1,26 @@ +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 + + 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..e422c333341 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -1,39 +1,36 @@ 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 - def initialize(diff, diff_refs) + def initialize(diff, repository:, diff_refs: nil) @diff = diff + @repository = repository @diff_refs = diff_refs end def old_ref - diff_refs[0] if diff_refs + diff_refs.try(:base_sha) end def new_ref - diff_refs[1] if diff_refs + diff_refs.try(:head_sha) end - # Array of Gitlab::DIff::Line objects + # Array of Gitlab::Diff::Line objects def diff_lines - @lines ||= parser.parse(raw_diff.each_line).to_a - end - - def too_large? - diff.too_large? + @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a end def highlighted_diff_lines - Gitlab::Diff::Highlight.new(self).highlight + @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight end def parallel_diff_lines - Gitlab::Diff::ParallelDiff.new(self).parallelize + @parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize end def mode_changed? diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb index 9429b3ff88d..3ad68728d65 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 @@ -70,7 +72,7 @@ module Gitlab ref = send("diff_#{version}_ref") path = send("diff_#{version}_path") - [ref.project.repository, ref.id, path] + [self.repository, ref, path] 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/workhorse.rb b/lib/gitlab/workhorse.rb index ef1241f8600..41b6854cbe1 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 } [ diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 52764f41e0d..e2db33d8345 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 diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index a0cbef6e6a4..1cb513d5229 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -6,7 +6,7 @@ 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 let(:diff_lines) { diff_file.diff_lines } diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index d19bf4ac84b..fb5d50a5c68 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) @@ -41,7 +41,7 @@ describe Gitlab::Diff::Highlight, lib: true do 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/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/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) } -- cgit v1.2.1 From 17ab745e40bf89776ab16de9ba00ebb44d1c85ca Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 18:52:54 +0200 Subject: Add Timeless helper module to prevent updated_at from being updated --- app/models/merge_request.rb | 7 +------ lib/gitlab/timeless.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 lib/gitlab/timeless.rb diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 70ef275d3a5..04ec6d369e5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -85,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 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 -- cgit v1.2.1 From 9fc0e11e0dbd541c8c0bca60878178ca94ad34e1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 18:54:53 +0200 Subject: Add DiffFile#blob and #old_blob --- app/helpers/diff_helper.rb | 5 +++-- app/models/repository.rb | 10 --------- app/views/notify/repository_push_email.html.haml | 5 ++--- app/views/projects/diffs/_diffs.html.haml | 4 ++-- app/views/projects/diffs/_file.html.haml | 6 +++--- lib/gitlab/diff/file.rb | 26 +++++++++++++++++++----- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index a7eedafe314..346b04e40f0 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -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 = diff_file.content_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/models/repository.rb b/app/models/repository.rb index 078ca8f4e13..da2fcaa9cd1 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -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 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/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 151780addc5..8f252282692 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -23,8 +23,8 @@ .files - 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) + - diff_commit = diff_file.content_commit || commit_for_diff(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..8fc5237e935 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -1,4 +1,4 @@ -.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 @@ -52,7 +52,7 @@ - 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 + - old_blob = diff_file.old_blob(diff_commit) + = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob, index: i - else .nothing-here-block No preview for this file type diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index e422c333341..8a5c19609e4 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -12,6 +12,12 @@ module Gitlab @diff_refs = diff_refs end + def content_commit + return unless diff_refs + + repository.commit(deleted_file ? old_ref : new_ref) + end + def old_ref diff_refs.try(:base_sha) end @@ -56,11 +62,7 @@ module Gitlab 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.presence end def added_lines @@ -70,6 +72,20 @@ 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 -- cgit v1.2.1 From 4c1bf77c7fd3e62be4b9f6fd54520db0f7fc346b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 18:55:15 +0200 Subject: Remove unneeded div --- app/views/projects/notes/_diff_notes_with_reply.html.haml | 3 +-- app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml | 6 ++---- app/views/projects/notes/discussions/_notes.html.haml | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) 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/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) -- cgit v1.2.1 From 375193455aa5cb752f1035a6cc69160170a58477 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 18:57:10 +0200 Subject: Style diff and blob file headers the same way --- app/assets/stylesheets/framework/files.scss | 2 +- app/helpers/application_helper.rb | 11 ++++++ app/helpers/notes_helper.rb | 10 +++++ .../notify/note_merge_request_email.html.haml | 4 +- app/views/projects/diffs/_file.html.haml | 45 ++++++++-------------- app/views/projects/diffs/_file_header.html.haml | 25 ++++++++++++ .../notes/discussions/_diff_with_notes.html.haml | 19 ++++----- lib/gitlab/diff/file.rb | 15 +++----- 8 files changed, 77 insertions(+), 54 deletions(-) create mode 100644 app/views/projects/diffs/_file_header.html.haml 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/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/notes_helper.rb b/app/helpers/notes_helper.rb index e85ba76887d..1a97f884508 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -79,4 +79,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/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index a3643a00cfe..35c4b862bb7 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? %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/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 8fc5237e935..3b758a1ec4e 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_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 @@ -42,15 +21,21 @@ - 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_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_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 + - 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, index: i 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/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml index 6401245bf73..b924ed31b42 100644 --- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml +++ b/app/views/projects/notes/discussions/_diff_with_notes.html.haml @@ -1,16 +1,13 @@ - 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| diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 8a5c19609e4..eaa6fdff8aa 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -4,7 +4,8 @@ module Gitlab 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, repository:, diff_refs: nil) @diff = diff @@ -40,11 +41,7 @@ module Gitlab 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 @@ -56,13 +53,11 @@ module Gitlab end def prev_line(index) - if index > 0 - diff_lines[index - 1] - end + diff_lines[index - 1] if index > 0 end def file_path - new_path.presence || old_path.presence + new_path.presence || old_path end def added_lines -- cgit v1.2.1 From a27462a5c6da0182f6b3a55c9417e6405f2c0415 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:15:44 +0200 Subject: Extract parts of LegacyDiffNote into DiffOnNote concern and move part of responsibility to other classes --- app/helpers/diff_helper.rb | 4 -- app/helpers/notes_helper.rb | 7 +-- app/models/concerns/note_on_diff.rb | 53 +++++++++++++++++ app/models/legacy_diff_note.rb | 69 ++++------------------ app/models/note.rb | 4 +- app/models/sent_notification.rb | 16 +++-- app/views/projects/diffs/_line.html.haml | 1 + .../notes/discussions/_diff_with_notes.html.haml | 4 +- lib/api/entities.rb | 6 +- lib/gitlab/diff/file.rb | 10 ++++ lib/gitlab/diff/highlight.rb | 16 ++--- lib/gitlab/diff/line.rb | 16 +++++ lib/gitlab/diff/parallel_diff.rb | 16 ++--- 13 files changed, 120 insertions(+), 102 deletions(-) create mode 100644 app/models/concerns/note_on_diff.rb diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 346b04e40f0..c7c291516fc 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -34,10 +34,6 @@ module DiffHelper diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } end - def generate_line_code(file_path, line) - Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) - end - def unfold_bottom_class(bottom) bottom ? 'js-unfold-bottom' : '' end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 1a97f884508..721dfcf265f 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -60,10 +60,9 @@ 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', diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb new file mode 100644 index 00000000000..b511f33b8fa --- /dev/null +++ b/app/models/concerns/note_on_diff.rb @@ -0,0 +1,53 @@ +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 + + def truncated_diff_lines + prev_match_line = nil + prev_lines = [] + + highlighted_diff_lines.each do |line| + if line.meta? + prev_lines.clear + prev_match_line = line + 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/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 33d2a69ebaf..790dfd4d480 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) 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 @@ -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/note.rb b/app/models/note.rb index 81b5c47b738..0c265064630 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -193,7 +193,7 @@ class Note < ActiveRecord::Base end def award_emoji? - award_emoji_supported? && contains_emoji_only? + can_be_award_emoji? && contains_emoji_only? end def emoji_awardable? @@ -204,7 +204,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 diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index a2df899d012..928873fb5c3 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -20,7 +20,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 +31,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 +40,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 - record(note.noteable, recipient_id, reply_key, params) + attrs.merge!(note.diff_attributes) + end + + record(note.noteable, recipient_id, reply_key, attrs) end end diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index f1577e8a47b..dbdbb6eb754 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -1,3 +1,4 @@ +- line_code = diff_file.line_code(line) - type = line.type %tr.line_holder{ id: line_code, class: type } - case type 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 b924ed31b42..3866de0f7fa 100644 --- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml +++ b/app/views/projects/notes/discussions/_diff_with_notes.html.haml @@ -12,7 +12,7 @@ %table - note.truncated_diff_lines.each do |line| - type = line.type - - line_code = generate_line_code(note.diff_file_path, line) + - line_code = diff_file.line_code(line) %tr.line_holder{ id: line_code, class: "#{type}" } - if type == "match" %td.old_line.diff-line-num= "..." @@ -23,5 +23,5 @@ %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) - - if line_code == note.line_code + - if note.for_line?(line) = render "projects/notes/diff_notes_with_reply", notes: discussion_notes diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 8cc4368b5c2..db877d2eeb0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -240,9 +240,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/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index eaa6fdff8aa..c73208329d5 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -13,6 +13,16 @@ module Gitlab @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 content_commit return unless diff_refs diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb index 3ad68728d65..44ea6bf6102 100644 --- a/lib/gitlab/diff/highlight.rb +++ b/lib/gitlab/diff/highlight.rb @@ -42,10 +42,9 @@ module Gitlab line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' ' - case diff_line.type - when 'new', nil + if diff_line.unchanged? || diff_line.added? rich_line = new_lines[diff_line.new_pos - 1] - when 'old' + elsif diff_line.removed? rich_line = old_lines[diff_line.old_pos - 1] end @@ -60,19 +59,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") - - [self.repository, ref, path] + @new_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_new_ref, diff_new_path) end end end 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/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb index 74f9b3c050a..2d15b64fdb0 100644 --- a/lib/gitlab/diff/parallel_diff.rb +++ b/lib/gitlab/diff/parallel_diff.rb @@ -15,7 +15,7 @@ module Gitlab highlighted_diff_lines.each do |line| full_line = line.text type = line.type - line_code = generate_line_code(diff_file.file_path, line) + line_code = diff_file.line_code(line) line_new = line.new_pos line_old = line.old_pos @@ -23,9 +23,9 @@ module Gitlab if next_line next_line = highlighted_diff_lines[next_line.index] - next_line_code = generate_line_code(diff_file.file_path, next_line) + full_next_line = next_line.text + next_line_code = diff_file.line_code(next_line) next_type = next_line.type - next_line = next_line.text end case type @@ -59,8 +59,8 @@ module Gitlab right: { type: next_type, number: line_new, - text: next_line, - line_code: next_line_code + text: full_next_line, + line_code: next_line_code, } } skip_next = true @@ -108,12 +108,6 @@ module Gitlab end lines end - - private - - def generate_line_code(file_path, line) - Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) - end end end end -- cgit v1.2.1 From 9abcc0a98f76984fa843eb0db3cf66bc1107e3ed Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:17:25 +0200 Subject: Add Gitlab::Git::Position --- lib/gitlab/diff/file.rb | 32 ++++++++++ lib/gitlab/diff/position.rb | 150 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 lib/gitlab/diff/position.rb diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index c73208329d5..3941e963c03 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -13,6 +13,20 @@ module Gitlab @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, + base_sha: diff_refs.base_sha, + start_sha: diff_refs.start_sha, + head_sha: diff_refs.head_sha + ) + end + def line_code(line) return if line.meta? @@ -23,6 +37,20 @@ module Gitlab 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 @@ -66,6 +94,10 @@ module Gitlab diff_lines[index - 1] if index > 0 end + def paths + [old_path, new_path].compact + end + def file_path new_path.presence || old_path end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb new file mode 100644 index 00000000000..4eff71859c3 --- /dev/null +++ b/lib/gitlab/diff/position.rb @@ -0,0 +1,150 @@ +# 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 + + 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 -- cgit v1.2.1 From e9e06ca627d328fb67771949e36924f73b0067c9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:17:56 +0200 Subject: Add Gitlab::Diff::LineMapper --- lib/gitlab/diff/line_mapper.rb | 64 +++++++++++++++ spec/lib/gitlab/diff/line_mapper_spec.rb | 137 +++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 lib/gitlab/diff/line_mapper.rb create mode 100644 spec/lib/gitlab/diff/line_mapper_spec.rb diff --git a/lib/gitlab/diff/line_mapper.rb b/lib/gitlab/diff/line_mapper.rb new file mode 100644 index 00000000000..bde5b4eedaa --- /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 line number based on new 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/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 -- cgit v1.2.1 From db65954d78899f6d48a682e0c220cbb4e6f59cb8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:18:18 +0200 Subject: Add Gitlab::Git::PositionTracer --- lib/gitlab/diff/position_tracer.rb | 168 +++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 lib/gitlab/diff/position_tracer.rb 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 -- cgit v1.2.1 From 2f30d00432e9727581e814062ea6117e1946a981 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:20:39 +0200 Subject: Add DiffNote model --- app/models/diff_note.rb | 99 ++++++++++++++++++++++ app/models/note.rb | 8 +- .../20160508215920_add_positions_to_diff_notes.rb | 6 ++ features/steps/project/merge_requests.rb | 3 +- spec/factories/notes.rb | 33 +++++++- spec/lib/gitlab/note_data_builder_spec.rb | 4 +- spec/lib/gitlab/url_builder_spec.rb | 8 +- spec/models/legacy_diff_note_spec.rb | 16 ++-- 8 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 app/models/diff_note.rb create mode 100644 db/migrate/20160508215920_add_positions_to_diff_notes.rb diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb new file mode 100644 index 00000000000..94c07cc535c --- /dev/null +++ b/app/models/diff_note.rb @@ -0,0 +1,99 @@ +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, on: :create + before_validation :set_line_code + + 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? + 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 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 +end diff --git a/app/models/note.rb b/app/models/note.rb index 0c265064630..ffffd0c0838 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -56,7 +56,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 +82,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 +115,10 @@ class Note < ActiveRecord::Base false end + def new_diff_note? + false + end + def active? true 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/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 3611c187202..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 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/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/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb index b2d06853886..d64d89edbd3 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 @@ -17,7 +17,7 @@ describe LegacyDiffNote, models: true do describe '#active?' do it 'is always true when the note has no associated diff' do - note = build(:note_on_merge_request_diff) + note = build(:legacy_diff_note_on_merge_request) expect(note).to receive(:diff).and_return(nil) @@ -25,7 +25,7 @@ describe LegacyDiffNote, models: true do 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(:noteable).and_return(nil) @@ -34,7 +34,7 @@ describe LegacyDiffNote, models: true do 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,7 +45,7 @@ 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) expect(note).to receive(:find_noteable_diff).and_return(nil) @@ -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 -- cgit v1.2.1 From 8d7dc26d39b65b3ef6e8ec80ed5995ae307c3d3c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:22:08 +0200 Subject: Support new diff notes on MRs with diff_refs --- app/controllers/projects/merge_requests_controller.rb | 1 + app/models/deployment.rb | 6 ++++++ app/models/diff_note.rb | 2 +- app/models/merge_request.rb | 4 ++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a03eb8513b6..ae0660078f9 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -85,6 +85,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController 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( 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 index 94c07cc535c..70fbcb4c4e9 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -74,7 +74,7 @@ class DiffNote < Note private def supported? - !self.for_merge_request? + !self.for_merge_request? || self.noteable.support_new_diff_notes? end def set_original_position diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 04ec6d369e5..82e0f5a0b53 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -642,6 +642,10 @@ class MergeRequest < ActiveRecord::Base merge_commit end + def support_new_diff_notes? + diff_refs && diff_refs.complete? + end + def keep_around_commit project.repository.keep_around(self.merge_commit_sha) end -- cgit v1.2.1 From 710c4886911682742d439b15c5a7add7ca0bd898 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:22:39 +0200 Subject: Automatically update diff note positions when MR is pushed to --- app/models/diff_note.rb | 22 ++++++++++- app/models/merge_request.rb | 45 +++++++++++++++++++--- app/services/merge_requests/refresh_service.rb | 4 +- app/services/merge_requests/reopen_service.rb | 2 +- app/services/notes/diff_position_update_service.rb | 30 +++++++++++++++ 5 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 app/services/notes/diff_position_update_service.rb diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 70fbcb4c4e9..881ae5d1cad 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -12,7 +12,7 @@ class DiffNote < Note validate :positions_complete validate :verify_supported - before_validation :set_original_position, on: :create + before_validation :set_original_position, :update_position, on: :create before_validation :set_line_code class << self @@ -71,6 +71,26 @@ class DiffNote < Note self.position.diff_refs == diff_refs 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 update_position! + update_position && + Gitlab::Timeless.timeless(self, &:save) + end + private def supported? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 82e0f5a0b53..4624e9d36e9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -288,14 +288,23 @@ 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 @@ -646,6 +655,32 @@ class MergeRequest < ActiveRecord::Base 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/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index de79c024428..21490ac77ea 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -60,7 +60,7 @@ module MergeRequests merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? - merge_request.reload_code + merge_request.reload_diff merge_request.mark_as_unchecked else mr_commit_ids = merge_request.commits.map(&:id) @@ -68,7 +68,7 @@ module MergeRequests matches = mr_commit_ids & push_commit_ids if matches.any? - merge_request.reload_code + merge_request.reload_diff merge_request.mark_as_unchecked else merge_request.mark_as_unchecked 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/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 -- cgit v1.2.1 From 521a0a20f7b52a2fb6ea209b39824a752f2613bd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:23:10 +0200 Subject: Allow reply-by-email with diff notes --- app/models/sent_notification.rb | 29 +++++++++++++++++++++- ..._note_type_and_position_to_sent_notification.rb | 22 ++++++++++++++++ lib/gitlab/email/receiver.rb | 10 +------- 3 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 928873fb5c3..016172c6d7e 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 @@ -74,8 +76,33 @@ class SentNotification < ActiveRecord::Base 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/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/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 -- cgit v1.2.1 From 29d574868a044fbfdf7a2458fbb3d951cfe58171 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:23:46 +0200 Subject: Display new diff notes and allow creation through the web interface --- app/assets/javascripts/notes.js.coffee | 13 +- app/assets/stylesheets/pages/diff.scss | 10 - app/controllers/projects/notes_controller.rb | 21 +- app/helpers/notes_helper.rb | 56 ++- app/mailers/emails/projects.rb | 3 +- app/views/projects/diffs/_line.html.haml | 15 +- app/views/projects/diffs/_parallel_view.html.haml | 14 +- app/views/projects/diffs/_text_file.html.haml | 13 +- app/views/projects/notes/_form.html.haml | 1 + .../notes/discussions/_diff_with_notes.html.haml | 16 +- features/steps/shared/diff_note.rb | 12 +- lib/gitlab/diff/parallel_diff.rb | 16 +- spec/features/notes_on_merge_requests_spec.rb | 6 +- spec/fixtures/parallel_diff_result.yml | 504 ++++++++++++++++++++- 14 files changed, 622 insertions(+), 78 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 7c1d943667b..0b7d8f64456 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -240,12 +240,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 @@ -318,6 +322,7 @@ class @Notes form.addClass "js-main-target-form" form.find("#note_line_code").remove() + form.find("#note_position").remove() form.find("#note_type").remove() ### @@ -335,10 +340,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() ] ### @@ -512,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/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/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/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 721dfcf265f..2302e65c537 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -24,23 +24,55 @@ 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 link_to_new_diff_note(line_code, position, line_type = nil) + 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 } + 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 + button_tag(class: 'btn add-diff-note js-add-diff-note-button', data: data, title: 'Add a comment to this line') do @@ -65,8 +97,10 @@ module NotesHelper 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) diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index e0af7081411..236b6ab00d8 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -29,8 +29,7 @@ module Emails # used in notify layout @target_url = @message.target_url @project = Project.find(project_id) - @diff_notes_disabled = true - + add_project_headers headers['X-GitLab-Author'] = @message.author_username diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index dbdbb6eb754..2e7fdc832ed 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -1,4 +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 @@ -10,18 +12,19 @@ %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 } } + %td.old_line.diff-line-num{ class: type, data: { linenumber: line.old_pos } } - link_text = type == "new" ? " ".html_safe : line.old_pos - - if defined?(plain) && plain + - 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) + + - if !plain && !@diff_notes_disabled && can?(current_user, :create_note, @project) + = link_to_new_diff_note(line_code, position) %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 + - 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) + %td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code, position: position.to_json } }= diff_line_content(line.text, type) diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 4ecc9528bd2..59603f6071d 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -17,27 +17,25 @@ %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]) + = link_to_new_diff_note(left[:line_code], left[:position], '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], position: left[:position].to_json }}= diff_line_content(left[:text]) - if right[:type] == 'new' - new_line_class = 'new' - new_line_code = right[:line_code] + - new_position = right[:position] - else - new_line_class = 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]) + = link_to_new_diff_note(new_line_code, new_position, '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, position: new_position.to_json }}= 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..192093d1273 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -4,22 +4,17 @@ %a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show. %table.text-file.code.js-syntax-highlight{ 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/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/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml index 3866de0f7fa..4a69b8f8840 100644 --- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml +++ b/app/views/projects/notes/discussions/_diff_with_notes.html.haml @@ -11,17 +11,7 @@ .diff-content.code.js-syntax-highlight %table - note.truncated_diff_lines.each do |line| - - type = line.type - - line_code = diff_file.line_code(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 note.for_line?(line) - = 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/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index e8b1e4b4879..56ef44ec969 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -23,7 +23,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: "Typo, please fix" find(".js-comment-button").trigger("click") sleep 0.05 @@ -33,7 +33,7 @@ module SharedDiffNote 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 + page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Old comment" find(".js-comment-button").trigger("click") end @@ -41,7 +41,7 @@ module SharedDiffNote 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") end @@ -51,7 +51,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 +62,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 +91,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 diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb index 2d15b64fdb0..1c1fc148123 100644 --- a/lib/gitlab/diff/parallel_diff.rb +++ b/lib/gitlab/diff/parallel_diff.rb @@ -18,6 +18,7 @@ module Gitlab line_code = diff_file.line_code(line) line_new = line.new_pos line_old = line.old_pos + position = diff_file.position(line) next_line = diff_file.next_line(line.index) @@ -26,6 +27,7 @@ module Gitlab full_next_line = next_line.text next_line_code = diff_file.line_code(next_line) next_type = next_line.type + next_position = diff_file.position(next_line) end case type @@ -37,12 +39,14 @@ module Gitlab number: line_old, text: full_line, line_code: line_code, + position: position }, right: { type: type, number: line_new, text: full_line, - line_code: line_code + line_code: line_code, + position: position } } when 'old' @@ -55,12 +59,14 @@ module Gitlab number: line_old, text: full_line, line_code: line_code, + position: position }, right: { type: next_type, number: line_new, text: full_next_line, line_code: next_line_code, + position: next_position, } } skip_next = true @@ -73,12 +79,14 @@ module Gitlab number: line_old, text: full_line, line_code: line_code, + position: position }, right: { type: next_type, number: nil, text: "", - line_code: nil + line_code: nil, + position: nil } } end @@ -95,12 +103,14 @@ module Gitlab number: nil, text: "", line_code: line_code, + position: position }, right: { type: type, number: line_new, text: full_line, - line_code: line_code + line_code: line_code, + position: position } } end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 737efcef45d..5174168713c 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -166,12 +166,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 diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml index a8b7907d4ba..7d01183e3ef 100644 --- a/spec/fixtures/parallel_diff_result.yml +++ b/spec/fixtures/parallel_diff_result.yml @@ -3,322 +3,818 @@ :type: match :number: 6 :text: "@@ -6,12 +6,18 @@ module Popen" - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :line_code: + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: match :number: 6 :text: "@@ -6,12 +6,18 @@ module Popen" - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :line_code: + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 6 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 6 + :new_line: 6 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 6 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 6 + :new_line: 6 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 7 :text: |2 def popen(cmd, path=nil) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 7 + :new_line: 7 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 7 :text: |2 def popen(cmd, path=nil) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 7 + :new_line: 7 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 8 :text: |2 unless cmd.is_a?(Array) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 8 + :new_line: 8 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 8 :text: |2 unless cmd.is_a?(Array) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 8 + :new_line: 8 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: old :number: 9 :text: | - raise "System commands must be given as an array of strings" :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 9 + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 9 :text: | + raise RuntimeError, "System commands must be given as an array of strings" :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 9 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 10 :text: |2 end :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 10 + :new_line: 10 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 10 :text: |2 end :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 10 + :new_line: 10 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 11 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 11 + :new_line: 11 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 11 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 11 + :new_line: 11 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 12 :text: |2 path ||= Dir.pwd :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 12 + :new_line: 12 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 12 :text: |2 path ||= Dir.pwd :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 12 + :new_line: 12 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: old :number: 13 :text: | - vars = { "PWD" => path } :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_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: old :number: :text: '' :line_code: + :position: - :left: :type: old :number: 14 :text: | - options = { chdir: path } :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 14 + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 13 :text: | + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 13 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14 + :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 :right: :type: new :number: 14 :text: | + vars = { :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 14 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 15 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 15 :text: | + "PWD" => path :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 15 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 16 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 16 :text: | + } :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 16 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 17 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 17 :text: | + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 17 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 18 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 18 :text: | + options = { :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 18 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 19 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 19 :text: | + chdir: path :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 19 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 20 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 20 :text: | + } :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 20 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 15 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 15 + :new_line: 21 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 21 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 15 + :new_line: 21 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 16 :text: |2 unless File.directory?(path) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 16 + :new_line: 22 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 22 :text: |2 unless File.directory?(path) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 16 + :new_line: 22 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 17 :text: |2 FileUtils.mkdir_p(path) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 17 + :new_line: 23 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 23 :text: |2 FileUtils.mkdir_p(path) :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 17 + :new_line: 23 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: match :number: 19 :text: "@@ -19,6 +25,7 @@ module Popen" - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :line_code: + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: match :number: 25 :text: "@@ -19,6 +25,7 @@ module Popen" - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :line_code: + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 19 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 19 + :new_line: 25 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 25 :text: |2 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 19 + :new_line: 25 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 20 :text: |2 @cmd_output = "" :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 20 + :new_line: 26 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 26 :text: |2 @cmd_output = "" :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 20 + :new_line: 26 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 21 :text: |2 @cmd_status = 0 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 21 + :new_line: 27 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 27 :text: |2 @cmd_status = 0 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 21 + :new_line: 27 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 28 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 28 :text: | + :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 28 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 22 :text: |2 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 22 + :new_line: 29 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 29 :text: |2 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 22 + :new_line: 29 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 23 :text: |2 @cmd_output << stdout.read :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 23 + :new_line: 30 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 30 :text: |2 @cmd_output << stdout.read :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 23 + :new_line: 30 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 24 :text: |2 @cmd_output << stderr.read :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 24 + :new_line: 31 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 31 :text: |2 @cmd_output << stderr.read :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 24 + :new_line: 31 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d -- cgit v1.2.1 From e0ee6f085beac9aa1ec5d135305ca0dd0dc3cbb3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 20 Jun 2016 19:25:36 +0200 Subject: Update schema --- db/schema.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index f6465136e6a..68b9425253c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -593,6 +593,8 @@ ActiveRecord::Schema.define(version: 20160705163108) 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 @@ -689,10 +691,12 @@ ActiveRecord::Schema.define(version: 20160705163108) 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 @@ -881,6 +885,8 @@ ActiveRecord::Schema.define(version: 20160705163108) 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 -- cgit v1.2.1 From ebb5f591e3fc5d23bfd1c2707cacdeb76fa42fb6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 1 Jul 2016 16:52:26 -0400 Subject: Add tests for PositionTracer --- spec/lib/gitlab/diff/position_tracer_spec.rb | 1758 ++++++++++++++++++++++++++ 1 file changed, 1758 insertions(+) create mode 100644 spec/lib/gitlab/diff/position_tracer_spec.rb 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 -- cgit v1.2.1 From e0ecfe5bd17911b04514bd5601f848d21fa61dbd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 1 Jul 2016 16:52:43 -0400 Subject: Add tests for Position --- spec/lib/gitlab/diff/position_spec.rb | 341 ++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 spec/lib/gitlab/diff/position_spec.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("+ Created with Sketch.") + 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 -- cgit v1.2.1 From 213f646f9a00c6f00b4cdcf4c61bf1517972ebb0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 1 Jul 2016 16:52:55 -0400 Subject: Add tests for DiffPositionUpdateService --- .../notes/diff_position_update_service_spec.rb | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 spec/services/notes/diff_position_update_service_spec.rb 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..0bca3ea1a09 --- /dev/null +++ b/spec/services/notes/diff_position_update_service_spec.rb @@ -0,0 +1,71 @@ +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 + + 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 -- cgit v1.2.1 From 3dff2867b7f57a783897c6fd18257f85ff53307e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 1 Jul 2016 16:53:02 -0400 Subject: Add tests for DiffNote --- spec/models/diff_note_spec.rb | 186 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 spec/models/diff_note_spec.rb diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb new file mode 100644 index 00000000000..9443f7c13f8 --- /dev/null +++ b/spec/models/diff_note_spec.rb @@ -0,0 +1,186 @@ +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 "#update_position" do + context "when noteable is a commit" do + subject { create(:diff_note_on_commit, project: project, position: position) } + + it "doesn't use the DiffPositionUpdateService" do + expect(Notes::DiffPositionUpdateService).not_to receive(:new) + + subject.update_position + end + + it "doesn't update the position" do + subject.update_position + + expect(subject.original_position).to eq(position) + expect(subject.position).to eq(position) + end + end + + context "when noteable is a merge request" do + context "when the note is active" do + it "doesn't use the DiffPositionUpdateService" do + expect(Notes::DiffPositionUpdateService).not_to receive(:new) + + subject.update_position + end + + it "doesn't update the position" do + subject.update_position + + expect(subject.original_position).to eq(position) + expect(subject.position).to eq(position) + end + end + + context "when the note is outdated" do + before do + allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs) + end + + it "uses the DiffPositionUpdateService" do + expect(Notes::DiffPositionUpdateService).to receive(:new).with( + project, + nil, + old_diff_refs: position.diff_refs, + new_diff_refs: commit.diff_refs, + paths: [path] + ).and_call_original + expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(subject) + + subject.update_position + end + end + end + end +end -- cgit v1.2.1 From 5ffb848ee6ecb66d4ff1b2d2bb21968f44f33f7e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 1 Jul 2016 17:53:46 -0400 Subject: Add tests for MergeRequest#reload_diff --- spec/models/merge_request_spec.rb | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index bb83676cddf..a4b6ff8f8ad 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 @@ -609,4 +611,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 -- cgit v1.2.1 From ddec2ed0dfb0981bf1f022f705470402e20ef9bc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 3 Jul 2016 17:01:13 -0400 Subject: Add send_git_patch helper --- app/controllers/projects/merge_requests_controller.rb | 15 +++++++++------ app/helpers/workhorse_helper.rb | 9 ++++++++- lib/gitlab/workhorse.rb | 6 +++--- .../projects/merge_requests_controller_spec.rb | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ae0660078f9..df1943dd9bb 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -58,14 +58,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html - format.json { render json: @merge_request } + + 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 diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index 2bd0dbfd095..65598ad9ed3 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,6 +16,13 @@ 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)) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 41b6854cbe1..bc0193a6c32 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -50,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 } [ diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 2d2fb87f14e..c4b57e77804 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -103,7 +103,7 @@ 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 -- cgit v1.2.1 From d7e8479ee8c7a127ed9cd984755a4ea3a8f1c078 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jul 2016 12:26:28 -0400 Subject: Keep around DiffNote position commits --- app/models/diff_note.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 881ae5d1cad..cdd1c4b4aef 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -14,6 +14,7 @@ class DiffNote < Note 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) @@ -116,4 +117,16 @@ class DiffNote < Note 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 -- cgit v1.2.1 From 0a5e387284d2090172b17e6732cbf6b424768cbd Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jul 2016 12:35:56 -0400 Subject: On MRs being imported, commits aren't available yet --- app/models/merge_request_diff.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 60f4b44a5d1..b81ab817dfe 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -24,7 +24,7 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_diffs after_create :reload_content, unless: :importing? - after_save :keep_around_commits + after_save :keep_around_commits, unless: :importing? def reload_content reload_commits -- cgit v1.2.1 From d9c75aec3adf23428e4c85a21ce2c357da006b6c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 4 Jul 2016 13:11:33 -0400 Subject: Use stored start and head for MR diff without whitespace --- app/models/merge_request_diff.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index b81ab817dfe..7ad97a75d53 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -40,8 +40,8 @@ class MergeRequestDiff < ActiveRecord::Base @diffs_no_whitespace ||= begin compare = Gitlab::Git::Compare.new( self.repository.raw_repository, - self.target_branch_sha, - self.source_branch_sha, + self.start_commit_sha || self.target_branch_sha, + self.head_commit_sha || self.source_branch_sha, ) compare.diffs(options) end -- cgit v1.2.1 From 228d2a4cb1943c4eda751f80990eed06a3875864 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jul 2016 19:28:13 -0400 Subject: Add some more code comments. --- app/models/concerns/note_on_diff.rb | 1 + app/models/merge_request.rb | 5 +++-- lib/gitlab/diff/diff_refs.rb | 10 ++++++++++ lib/gitlab/diff/line_mapper.rb | 2 +- lib/gitlab/diff/position.rb | 5 +++++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb index b511f33b8fa..6d8b9b76c84 100644 --- a/app/models/concerns/note_on_diff.rb +++ b/app/models/concerns/note_on_diff.rb @@ -31,6 +31,7 @@ module NoteOnDiff false end + # Returns an array of at most 16 highlighted lines above a diff note def truncated_diff_lines prev_match_line = nil prev_lines = [] diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4624e9d36e9..ed99142902e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -186,8 +186,9 @@ class MergeRequest < ActiveRecord::Base # 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 need the - # true base 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 diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb index 43489ae876b..8406ca4269c 100644 --- a/lib/gitlab/diff/diff_refs.rb +++ b/lib/gitlab/diff/diff_refs.rb @@ -18,6 +18,16 @@ module Gitlab 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 diff --git a/lib/gitlab/diff/line_mapper.rb b/lib/gitlab/diff/line_mapper.rb index bde5b4eedaa..576a761423e 100644 --- a/lib/gitlab/diff/line_mapper.rb +++ b/lib/gitlab/diff/line_mapper.rb @@ -26,7 +26,7 @@ module Gitlab @diff_lines ||= @diff_file.diff_lines end - # Find old line number based on new line number. + # 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. diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index 4eff71859c3..989fff8918e 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -28,6 +28,11 @@ module Gitlab 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']) -- cgit v1.2.1 From 2db75f8debd96c48c6cb8548824dd72bd71fb176 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jul 2016 19:29:14 -0400 Subject: Make methods private that don't need to be public --- app/models/merge_request_diff.rb | 90 +++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 7ad97a75d53..9c898d9d7c0 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -65,42 +65,36 @@ class MergeRequestDiff < ActiveRecord::Base def base_commit return unless self.base_commit_sha - merge_request.target_project.commit(self.base_commit_sha) + project.commit(self.base_commit_sha) end def start_commit return unless self.start_commit_sha - merge_request.target_project.commit(self.start_commit_sha) + project.commit(self.start_commit_sha) end def head_commit return last_commit unless self.head_commit_sha - merge_request.target_project.commit(self.head_commit_sha) + project.commit(self.head_commit_sha) 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 + def compare + @compare ||= + begin + # Update ref for merge request + merge_request.fetch_ref - def dump_diffs(diffs) - if diffs.respond_to?(:map) - diffs.map(&:to_hash) - end + Gitlab::Git::Compare.new( + self.repository.raw_repository, + self.target_branch_sha, + self.source_branch_sha + ) + end end - def load_diffs(raw, options) - if raw.respond_to?(:each) - Gitlab::Git::DiffCollection.new(raw, options) - else - Gitlab::Git::DiffCollection.new([]) - end - end + private # Collect array of Git::Commit objects # between target and source branches @@ -114,6 +108,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 @@ -128,6 +130,26 @@ 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) + 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 @@ -164,42 +186,24 @@ class MergeRequestDiff < ActiveRecord::Base keep_around_commits end - # Collect array of Git::Diff objects - # between target and source branches - def unmerged_diffs - compare.diffs(Commit.max_diff_options) + def project + merge_request.target_project end def repository - merge_request.target_project.repository + project.repository end def branch_base_commit return unless self.source_branch_sha && self.target_branch_sha - merge_request.target_project.merge_base_commit(self.source_branch_sha, self.target_branch_sha) + project.merge_base_commit(self.source_branch_sha, self.target_branch_sha) end def branch_base_sha branch_base_commit.try(:sha) end - def compare - @compare ||= - begin - # Update ref for merge request - merge_request.fetch_ref - - Gitlab::Git::Compare.new( - self.repository.raw_repository, - self.target_branch_sha, - self.source_branch_sha - ) - end - end - - private - # # #save or #update_attributes providing changes on serialized attributes do a lot of # serialization and deserialization calls resulting in bad performance. -- cgit v1.2.1 From ac26b23712963b0471db9742fc340ea4fb8935a9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jul 2016 19:29:41 -0400 Subject: Remove duplication, unused methods, and some other style things --- app/helpers/diff_helper.rb | 4 +++- app/models/concerns/note_on_diff.rb | 2 -- app/models/merge_request.rb | 6 +----- app/views/projects/diffs/_diffs.html.haml | 2 +- lib/gitlab/diff/file.rb | 7 +++---- lib/gitlab/diff/highlight.rb | 11 ++++++----- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index c7c291516fc..eb57516247d 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -89,6 +89,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 @@ -97,7 +99,7 @@ module DiffHelper end def diff_file_html_data(project, diff_file) - commit = diff_file.content_commit || commit_for_diff(diff_file) + commit = commit_for_diff(diff_file) { blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, tree_join(commit.id, diff_file.file_path)) diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb index 6d8b9b76c84..2785fbb21c9 100644 --- a/app/models/concerns/note_on_diff.rb +++ b/app/models/concerns/note_on_diff.rb @@ -33,13 +33,11 @@ module NoteOnDiff # Returns an array of at most 16 highlighted lines above a diff note def truncated_diff_lines - prev_match_line = nil prev_lines = [] highlighted_diff_lines.each do |line| if line.meta? prev_lines.clear - prev_match_line = line else prev_lines << line diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ed99142902e..083e93f1ee7 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -164,10 +164,6 @@ class MergeRequest < ActiveRecord::Base merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end - def last_commit - merge_request_diff ? merge_request_diff.last_commit : compare_commits.last - end - def diff_size merge_request_diff.size end @@ -246,7 +242,7 @@ class MergeRequest < ActiveRecord::Base end def diff_refs - return nil unless diff_start_commit || diff_base_commit + return unless diff_start_commit || diff_base_commit Gitlab::Diff::DiffRefs.new( base_sha: diff_base_sha, diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 8f252282692..1975287faee 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -23,7 +23,7 @@ .files - diff_files.each_with_index do |diff_file, index| - - diff_commit = diff_file.content_commit || commit_for_diff(diff_file) + - diff_commit = commit_for_diff(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/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 3941e963c03..b0c50edba59 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -21,9 +21,7 @@ module Gitlab new_path: new_path, old_line: line.old_line, new_line: line.new_line, - base_sha: diff_refs.base_sha, - start_sha: diff_refs.start_sha, - head_sha: diff_refs.head_sha + diff_refs: diff_refs ) end @@ -53,7 +51,7 @@ module Gitlab def content_commit return unless diff_refs - + repository.commit(deleted_file ? old_ref : new_ref) end @@ -121,6 +119,7 @@ module Gitlab def blob(commit = content_commit) return unless commit + repository.blob_at(commit.id, file_path) end end diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb index 44ea6bf6102..649a265a02c 100644 --- a/lib/gitlab/diff/highlight.rb +++ b/lib/gitlab/diff/highlight.rb @@ -42,11 +42,12 @@ module Gitlab line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' ' - if diff_line.unchanged? || diff_line.added? - rich_line = new_lines[diff_line.new_pos - 1] - elsif diff_line.removed? - 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. -- cgit v1.2.1 From d8d5424d25c1738b170d58657ef71d4dbc89ca5e Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 30 Jun 2016 13:02:05 +0530 Subject: Use the `GLDropdown` component to select protected branches. 1. Modify the component to support a callback for every key press in the filter. We need this so we can update the "Create: ).first().click()` instead of `$(selector)[0].click()`, because the latter is non-standard, and doesn't work in PhantomJS. --- app/assets/javascripts/gl_dropdown.js.coffee | 4 ++- .../javascripts/protected_branch_select.js.coffee | 40 ++++++++++++++++++++++ .../projects/protected_branches_controller.rb | 2 +- .../protected_branches/_dropdown.html.haml | 17 +++++++++ .../projects/protected_branches/index.html.haml | 9 +---- spec/features/protected_branches_spec.rb | 4 ++- 6 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/protected_branch_select.js.coffee create mode 100644 app/views/projects/protected_branches/_dropdown.html.haml diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index ed9dfcc917e..1c65e833d47 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 @@ -530,7 +532,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/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/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index 026c5b74eb9..80dad758afa 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -9,7 +9,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController def index @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 } } }) + gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }) end def create 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/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 684cb175e68..5669713d9a1 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -21,7 +21,7 @@ .form-group = f.label :name, "Branch", class: "label-light" - = f.text_field(:name) + = render partial: "dropdown", locals: { f: f } %p.help-block = link_to "Wildcards", help_page_path(category: 'workflow', file: 'protected_branches', format: 'md', anchor: "wildcard-protected-branches") such as @@ -39,10 +39,3 @@ = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true %hr = render "branches_list" - -:javascript - $("#protected_branch_name").select2({ - placeholder: "Select branch", - createSearchChoice: function(term) { return { id: term, text: term }; }, - data: gon.open_branches - }) diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 9a552e93c24..d94dee0c797 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -7,7 +7,9 @@ feature 'Projected Branches', feature: true, js: true do before { login_as(user) } def set_protected_branch_name(branch_name) - page.execute_script("$('#protected_branch_name').val('#{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 -- cgit v1.2.1 From b1c81f849e5e5b03f56e89cdcefba029ed5c0543 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 6 Jul 2016 15:07:30 +0530 Subject: Have `Project#open_branches` return branches that are matched by a wildcard protected branch. 1. The `open_branches` method is used to provide a list of branches while creating a protected branch. 2. It makes sense to include branches which are matched by one or more wildcard protected branches, since the user might want to make exact protected branches from these as well. 3. This also provides a large performance improvement. On my machine, in a project with 5000 branches and 2000 protected branches, the `ProtectedBranches#index` page went from a 40 seconds load time to 4 seconds (10x speedup). --- app/models/project.rb | 6 +++++- app/models/protected_branch.rb | 2 +- spec/models/project_spec.rb | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index d5d57bafb98..087f4314565 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -802,8 +802,12 @@ class Project < ActiveRecord::Base @repo_exists = false end + # Branches that are not _exactly_ matched by a protected branch. def open_branches - repository.branches.reject { |branch| self.protected_branch?(branch.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) diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index d3d5e1d98b2..b7011d7afdf 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -34,7 +34,7 @@ class ProtectedBranch < ActiveRecord::Base # Checks if this protected branch contains a wildcard def wildcard? - self.name.include?('*') + self.name && self.name.include?('*') end protected diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 117ffd551e4..cd126027c95 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -438,12 +438,12 @@ 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 "does not include branches matching a protected branch wildcard" do + 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)).not_to include('feature') + expect(Project.find(project.id).open_branches.map(&:name)).to include('feature') end end -- cgit v1.2.1 From 7950fa48e8976507261f62d6694c1f618b478b8c Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 6 Jul 2016 18:57:45 +0300 Subject: Add data when member joined a team --- CHANGELOG | 1 + app/views/shared/members/_member.html.haml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index de5f1e8cda7..bc9bb7747a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,7 @@ v 8.10.0 (unreleased) - Handle custom Git hook result in GitLab UI - 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 v 8.9.5 - Add more debug info to import/export and memory killer. !5108 diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index a884e78e6e7..a137fde1c13 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -21,6 +21,10 @@ %span.cgray – Requested = time_ago_with_tooltip(member.requested_at) + - else + %span.cgray + = "(joined #{time_ago_with_tooltip(member.created_at)})" + - else = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' %strong= member.invite_email -- cgit v1.2.1 From 197b9793db4ff84d953dcfa9c79a4a2959c5c512 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 6 Jul 2016 19:34:47 +0300 Subject: Improve UI for member row on project/group members pages Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 9 +++ app/views/shared/members/_member.html.haml | 89 ++++++++++++++--------------- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index a12c0bba44a..aed0b44d91b 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; diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index a137fde1c13..8701c8deca0 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -3,75 +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 - %span.cgray - = "(joined #{time_ago_with_tooltip(member.created_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| -- cgit v1.2.1 From b37b7deedf253a37f4044b25e769f0de48947c55 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 7 Jul 2016 08:33:26 +0300 Subject: Fix invite user feature test and improve request member UI row Signed-off-by: Dmitriy Zaporozhets --- app/views/shared/members/_member.html.haml | 2 +- features/steps/admin/groups.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 8701c8deca0..5ae485f36ba 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -55,7 +55,7 @@ .cgray - if member.request? - – Requested + Requested = time_ago_with_tooltip(member.requested_at) - else Joined #{time_ago_with_tooltip(member.created_at)} 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 -- cgit v1.2.1 From b8c521fa6acf07f723529970865b42b536b8120f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Jul 2016 08:44:02 +0100 Subject: Fixed commit avatar alignment in compare view Closes #19567 --- app/assets/stylesheets/framework/lists.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index aed0b44d91b..2c40ec430ca 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -175,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 { -- cgit v1.2.1 From 91a183bc57e0fccd12d08e861bd8a37b402d81f3 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Jul 2016 09:49:46 +0200 Subject: fix log statements in import/export --- lib/gitlab/import_export/command_line_util.rb | 2 +- lib/gitlab/import_export/saver.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 62c736451b7..2249904145c 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -29,7 +29,7 @@ module Gitlab def execute(cmd) output, status = Gitlab::Popen.popen(cmd) - @shared.error(output.to_s) unless status.zero? + @shared.error(Gitlab::ImportExport::Error.new(output.to_s)) unless status.zero? status.zero? end diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb index dd4fdf37309..6a60b65071f 100644 --- a/lib/gitlab/import_export/saver.rb +++ b/lib/gitlab/import_export/saver.rb @@ -17,7 +17,7 @@ module Gitlab Rails.logger.info("Saved project export #{archive_file}") archive_file else - @shared.error("Unable to save #{archive_file} into #{@shared.export_path}") + @shared.error(Gitlab::ImportExport::Error.new("Unable to save #{archive_file} into #{@shared.export_path}")) false end rescue => e -- cgit v1.2.1 From b988644ad853ea00ed23bfc57f83883f96a12016 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Jul 2016 18:56:02 +0800 Subject: Use scope rather than class method --- app/models/ci/build.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 -- cgit v1.2.1 From 011e281604c3217591692bcfa20569eb6aef24cd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Jul 2016 19:25:05 +0800 Subject: Prefer ref rather than id because id is shadowing database id Some context: http://doc.gitlab.com/ce/api/repository_files.html#get-file-from-repository http://doc.gitlab.com/ce/api/repositories.html#list-repository-tree Slack: https://gitlab.slack.com/archives/questions/p1467890450002077 --- app/models/project.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 029026a4e56..735b9542d14 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -425,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) -- cgit v1.2.1 From 93dd8b0a088b51874c45bb69a5255529f7e37dd5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Jul 2016 19:33:54 +0800 Subject: Also use ref in Repository#commit --- app/models/repository.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 078ca8f4e13..3b956a30a77 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 -- cgit v1.2.1 From 82ba5f2bd339bb28ce1eeaaad5dda3f14ac67654 Mon Sep 17 00:00:00 2001 From: Sergey Gnuskov Date: Thu, 7 Jul 2016 15:41:15 +0300 Subject: Fix unarchive mistake --- doc/api/projects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index f5f195b97df..0425487ee58 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -713,7 +713,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 | -- cgit v1.2.1 From cd63d0a53844507fe7bbf0fabeb89dadecf85fe7 Mon Sep 17 00:00:00 2001 From: winniehell Date: Thu, 7 Jul 2016 14:28:28 +0200 Subject: Escape file extension when parsing search results (!5141) --- CHANGELOG | 1 + app/models/repository.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index bc9bb7747a4..7643193451d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.10.0 (unreleased) - Wrap code blocks on Activies and Todos page. !4783 (winniehell) - Align flash messages with left side of page content !4959 (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 - Add Sidekiq queue duration to transaction metrics. - Add a new column `artifacts_size` to table `ci_builds` !4964 diff --git a/app/models/repository.rb b/app/models/repository.rb index 078ca8f4e13..d232d422195 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -911,7 +911,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 -- cgit v1.2.1 From 664e4c125e4c2e096fcf8fd7cd538462e6eec841 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 7 Jul 2016 13:38:41 +0200 Subject: Avoid calculation of closes_issues. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We just need to get merge requests closes issues when we’re going to show them --- CHANGELOG | 1 + app/controllers/projects/merge_requests_controller.rb | 6 ------ app/helpers/merge_requests_helper.rb | 4 ++++ app/views/projects/merge_requests/widget/_open.html.haml | 6 +++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bc9bb7747a4..59182f6e339 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.10.0 (unreleased) - Bump Rinku to 2.0.0 - Remove unused front-end variable -> default_issues_tracker - 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 diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index dd86b940a08..c80d38a7889 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -9,7 +9,6 @@ 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] @@ -308,10 +307,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 @@ -377,7 +372,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_widget_vars @pipeline = @merge_request.pipeline @pipelines = [@pipeline].compact - closes_issues end def invalid_mr diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 1dd07a2a220..8ba42518401 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -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, 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 -- cgit v1.2.1 From dad8fc242c646161b353b5f65ab830ea689ff99e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Jul 2016 15:34:45 +0200 Subject: fix 404 error, redirect back with flash message --- app/controllers/import/gitlab_projects_controller.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 -- cgit v1.2.1 From cf46a88b8da55e015fa3af09f956880a3787bf50 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Jul 2016 15:49:16 +0200 Subject: added changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index bc9bb7747a4..937aefefd99 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -48,6 +48,7 @@ v 8.10.0 (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 v 8.9.5 - Add more debug info to import/export and memory killer. !5108 -- cgit v1.2.1 From f67984626b7422e6a110d343860f1079243dab07 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 7 Jul 2016 16:12:28 +0200 Subject: added test --- .../features/projects/import_export/import_file_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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? -- cgit v1.2.1 From a0a9494e4e6db3cfcdecae0a7e9c2877432fa30b Mon Sep 17 00:00:00 2001 From: Dravere Date: Wed, 6 Jul 2016 19:46:41 +0200 Subject: Added setting to set new users by default as external As requested by the issue #14508 this adds an option in the application settings to set newly registered users by default as external. The default setting is set to false to stay backward compatible. --- CHANGELOG | 1 + .../admin/application_settings_controller.rb | 1 + app/models/application_setting.rb | 1 + app/models/user.rb | 2 +- .../admin/application_settings/_form.html.haml | 7 +++++++ ...user_default_external_to_application_settings.rb | 13 +++++++++++++ db/schema.rb | 1 + doc/permissions/permissions.md | 3 +++ lib/gitlab/current_settings.rb | 1 + spec/models/user_spec.rb | 21 +++++++++++++++++++++ 10 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160608211215_add_user_default_external_to_application_settings.rb diff --git a/CHANGELOG b/CHANGELOG index 937aefefd99..266b085bad5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -49,6 +49,7 @@ v 8.10.0 (unreleased) - 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) v 8.9.5 - Add more debug info to import/export and memory killer. !5108 diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index cbdf2859898..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, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 7bf618d60b9..c6f77cc055f 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -142,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/user.rb b/app/models/user.rb index 695a47ba6eb..79c670cb35a 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 diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index eb325576e4f..8de28528cda 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -100,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/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/schema.rb b/db/schema.rb index f6465136e6a..97819731a4c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -84,6 +84,7 @@ ActiveRecord::Schema.define(version: 20160705163108) 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" diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 963b35de3a0..44f3f6d3b12 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -99,3 +99,6 @@ 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**. \ No newline at end of file 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/spec/models/user_spec.rb b/spec/models/user_spec.rb index 328254ed56b..3984b30ddf8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -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 -- cgit v1.2.1 From 6eb65651a71776725db35528d11f6cef4e0261bf Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 7 Jul 2016 14:14:13 -0400 Subject: We can't call private methods with `self.` --- app/models/merge_request_diff.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 9c898d9d7c0..ba235750aeb 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -39,7 +39,7 @@ class MergeRequestDiff < ActiveRecord::Base if options[:ignore_whitespace_change] @diffs_no_whitespace ||= begin compare = Gitlab::Git::Compare.new( - self.repository.raw_repository, + repository.raw_repository, self.start_commit_sha || self.target_branch_sha, self.head_commit_sha || self.source_branch_sha, ) @@ -87,7 +87,7 @@ class MergeRequestDiff < ActiveRecord::Base merge_request.fetch_ref Gitlab::Git::Compare.new( - self.repository.raw_repository, + repository.raw_repository, self.target_branch_sha, self.source_branch_sha ) @@ -228,8 +228,8 @@ class MergeRequestDiff < ActiveRecord::Base end def keep_around_commits - self.repository.keep_around(target_branch_sha) - self.repository.keep_around(source_branch_sha) - self.repository.keep_around(branch_base_sha) + repository.keep_around(target_branch_sha) + repository.keep_around(source_branch_sha) + repository.keep_around(branch_base_sha) end end -- cgit v1.2.1 From 338072cc4ba66cd7eb6fbd9541862946e4ae4b75 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 26 May 2016 17:55:49 -0500 Subject: Layout for Users Groups and Projects on admin area --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js.coffee | 2 +- app/assets/javascripts/namespace_select.js.coffee | 76 ++++++--- app/assets/stylesheets/framework/dropdowns.scss | 4 + app/assets/stylesheets/framework/nav.scss | 6 + app/assets/stylesheets/pages/admin.scss | 33 ++++ app/assets/stylesheets/pages/groups.scss | 33 ++++ app/assets/stylesheets/pages/projects.scss | 4 - app/assets/stylesheets/pages/search.scss | 2 +- app/controllers/admin/projects_controller.rb | 5 +- app/helpers/dropdowns_helper.rb | 2 +- app/views/admin/groups/_group.html.haml | 42 ++--- app/views/admin/groups/index.html.haml | 57 +++---- app/views/admin/projects/index.html.haml | 171 ++++++++++----------- app/views/admin/projects/show.html.haml | 8 +- app/views/admin/users/_user.html.haml | 42 +++++ app/views/admin/users/index.html.haml | 170 +++++++++----------- app/views/shared/projects/_dropdown.html.haml | 17 +- features/admin/projects.feature | 4 +- features/steps/admin/projects.rb | 9 +- spec/controllers/admin/projects_controller_spec.rb | 4 +- 21 files changed, 396 insertions(+), 296 deletions(-) create mode 100644 app/views/admin/users/_user.html.haml diff --git a/CHANGELOG b/CHANGELOG index cbda28824aa..063ddffb165 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ v 8.10.0 (unreleased) - Fix pagination when sorting by columns with lots of ties (like priority) - Updated project header design - 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 - Add notification settings dropdown for groups - Allow importing from Github using Personal Access Tokens. (Eric K Idema) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 9493a575801..a39df421832 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -127,7 +127,7 @@ class Dispatcher when 'groups' new UsersSelect() when 'projects' - new NamespaceSelect() + new NamespaceSelects() when 'dashboard', 'root' shortcut_handler = new ShortcutsDashboardNavigation() when 'profiles' diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index a02c4515ccc..3a4b64a18f7 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -1,25 +1,53 @@ class @NamespaceSelect - constructor: -> - namespaceFormatResult = (namespace) -> - markup = "
" - markup += "" + namespace.kind + "" - markup += "" + namespace.path + "" - markup += "
" - 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.data 'fieldName' + fieldName = @dropdown.data 'fieldName' + + @dropdown.glDropdown( + filterable: true + selectable: 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/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index f36736c475e..d4e900f80ef 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -68,6 +68,10 @@ color: $dropdown-toggle-hover-icon-color; } } + + &.large { + width: 200px; + } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 6e5f216c894..02ea98e9d94 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -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/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index e05f14e7496..1d34a7f79ae 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -71,3 +71,36 @@ @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: bold; + } + + .controls { + > .btn, > .dropdown { + margin-left: 5px; + } + } + + .dropdown { + .btn-block { + margin-bottom: 0; + line-height: inherit; + } + } +} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 3d79f4400e2..701b9388454 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -38,6 +38,39 @@ margin-right: 15px; } } + + &.group-admin { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + + .group-avatar, .group-details, .group-controls { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + } + + .group-details { + flex: 1 1 auto; + flex-direction: column; + min-width: 0; + } + + .group-controls { + align-items: center; + + a { + margin-left: 5px; + } + } + } + +} + +.ldap-group-links { + .form-actions { + margin-bottom: $gl-padding; + } } .groups-cover-block { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3325b586496..bce4aac3334 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -475,10 +475,6 @@ pre.light-well { a:hover { text-decoration: none; } - - > span { - margin-left: 10px; - } } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index ae524cd6bae..9e9b18fdbb8 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -208,7 +208,7 @@ margin-top: 5px; @media (min-width: $screen-sm-min) { - width: 160px; + width: 180px; margin-top: 0; } } 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/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/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 9025aaac097..59fd6c3fea0 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -1,28 +1,20 @@ - css_class = '' unless local_assigns[:css_class] -- 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' +%li.group-row.group-admin{ class: css_class } + .group-avatar + = image_tag group_icon(group), class: 'avatar hidden-xs' + .group-details + .title + = link_to [:admin, group], class: 'group-name' do + = group.name + .group-stats + %span>= pluralize(number_with_delimiter(group.projects.count), 'project') + , + %span= pluralize(number_with_delimiter(group.users.count), 'member') - .stats - %span - = icon('bookmark') - = number_with_delimiter(group.projects.count) - - %span - = icon('users') - = number_with_delimiter(group.users.count) - - %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' - .title - = link_to [:admin, group], class: 'group-name' do - = group.name - - - if group.description.present? - .description - = markdown(group.description, pipeline: :description) + - if group.description.present? + .description + = markdown(group.description, pipeline: :description) + .group-controls.hidden-xs + = 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' 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/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 7d2eb423223..7fbce25b2c4 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.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" + = 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..7e1f5a52c6f 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' }, { 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 diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml new file mode 100644 index 00000000000..463993d0c17 --- /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.pull-right + = 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! Maybe block 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/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/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/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/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 -- cgit v1.2.1 From 35224dbf5e88936b138af7e6215a2e4e0503ae97 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 6 Jul 2016 12:04:14 -0500 Subject: Do not show Any Namespace option when needed --- app/assets/javascripts/namespace_select.js.coffee | 6 ++++-- app/views/admin/projects/show.html.haml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index 3a4b64a18f7..70ceb4d9c7c 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -5,12 +5,14 @@ class @NamespaceSelect } = opts showAny = true - fieldName = 'namespace_id' - if @dropdown.data 'fieldName' + 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 diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 7e1f5a52c6f..2c5aba71699 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -100,7 +100,7 @@ = f.label :new_namespace_id, "Namespace", class: 'control-label' .col-sm-10 .dropdown - = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' }) + = 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") -- cgit v1.2.1 From 22191fcdd188b31bb1d3c2326d862019bd5b2457 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 6 Jul 2016 12:33:20 -0500 Subject: Rephrase deletion alert message --- app/views/admin/users/_user.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index 463993d0c17..d3519f616f6 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -37,6 +37,6 @@ - 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! Maybe block the user instead? Are you sure?" }, + = 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 -- cgit v1.2.1 From 140b0952a8b0ba1f9620a54f4ae2ea21abe02c18 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 7 Jul 2016 13:25:10 -0500 Subject: Namespaces should filter remotely --- app/assets/javascripts/namespace_select.js.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index 70ceb4d9c7c..3b419dff105 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -16,6 +16,7 @@ class @NamespaceSelect @dropdown.glDropdown( filterable: true selectable: true + filterRemote: true search: fields: ['path'] fieldName: fieldName -- cgit v1.2.1 From ea25e0918b77c2345585a968fbf5b73bb544aac7 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 7 Jul 2016 12:41:48 +0100 Subject: Exclude projects pending delete from notifications If the Sidekiq job fails for some reason, a project can be 'stuck' pending deletion. The project can't be viewed, so it shouldn't be available through the notification settings association as this will throw an exception when we try to show the link. --- CHANGELOG | 1 + app/models/notification_setting.rb | 9 ++++++++- spec/factories/notification_settings.rb | 8 ++++++++ spec/models/notification_setting_spec.rb | 17 +++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 spec/factories/notification_settings.rb diff --git a/CHANGELOG b/CHANGELOG index bc9bb7747a4..cc2a40ce409 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.10.0 (unreleased) - Fix MR-auto-close text added to description. !4836 - Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Add Spring EmojiOne updates. + - Fix viewing notification settings when a project is pending deletion - Fix pagination when sorting by columns with lots of ties (like priority) - Updated project header design - Exclude email check from the standard health check 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/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/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 -- cgit v1.2.1 From 70a64f6a57a2b4fd2874b0db03f4fe1f0c3794f8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 7 Jul 2016 15:57:38 -0400 Subject: Update test with new factory name --- spec/models/event_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 6ac19756f15..b5d0d79e14e 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -56,7 +56,7 @@ describe Event, models: true do end context 'merge request diff note event' do - let(:target) { create(:note_on_merge_request_diff) } + let(:target) { create(:legacy_diff_note_on_merge_request) } it { is_expected.to be_note } end @@ -132,7 +132,7 @@ describe Event, models: true do 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(:note_on_merge_request_diff, noteable: merge_request, project: project) } + 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 } -- cgit v1.2.1 From 6dd71888b310d2615d74ea129431fc6c9791b6b7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 7 Jul 2016 16:13:55 -0400 Subject: Make `DiffNote#update_position` private --- app/models/diff_note.rb | 33 ++++++++--------- spec/models/diff_note_spec.rb | 83 +++++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index cdd1c4b4aef..9671955db36 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -72,6 +72,20 @@ class DiffNote < Note 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? @@ -87,25 +101,6 @@ class DiffNote < Note ).execute(self) end - def update_position! - update_position && - Gitlab::Timeless.timeless(self, &:save) - 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 verify_supported return if supported? diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 9443f7c13f8..af8e890ca95 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -9,7 +9,7 @@ describe DiffNote, models: true do let(:path) { "files/ruby/popen.rb" } - let(:position) do + let!(:position) do Gitlab::Diff::Position.new( old_path: path, new_path: path, @@ -19,7 +19,7 @@ describe DiffNote, models: true do ) end - let(:new_position) do + let!(:new_position) do Gitlab::Diff::Position.new( old_path: path, new_path: path, @@ -129,56 +129,61 @@ describe DiffNote, models: true do end end - describe "#update_position" do - context "when noteable is a commit" do - subject { create(:diff_note_on_commit, project: project, position: position) } - - it "doesn't use the DiffPositionUpdateService" do - expect(Notes::DiffPositionUpdateService).not_to receive(:new) - - subject.update_position - end - - it "doesn't update the position" do - subject.update_position - - expect(subject.original_position).to eq(position) - expect(subject.position).to eq(position) - 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) } - context "when noteable is a merge request" do - context "when the note is active" do it "doesn't use the DiffPositionUpdateService" do expect(Notes::DiffPositionUpdateService).not_to receive(:new) - subject.update_position + diff_note end it "doesn't update the position" do - subject.update_position + diff_note - expect(subject.original_position).to eq(position) - expect(subject.position).to eq(position) + 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(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs) + 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 - it "uses the DiffPositionUpdateService" do - expect(Notes::DiffPositionUpdateService).to receive(:new).with( - project, - nil, - old_diff_refs: position.diff_refs, - new_diff_refs: commit.diff_refs, - paths: [path] - ).and_call_original - expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(subject) - - subject.update_position + 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 -- cgit v1.2.1 From 14c2b9683af73ffed6c9b544ff24353cf0d07e1a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 7 Jul 2016 16:14:18 -0400 Subject: Use HAML class syntax in diff line partials --- app/views/projects/diffs/_line.html.haml | 2 +- app/views/projects/diffs/_parallel_view.html.haml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 2e7fdc832ed..22cad00240a 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -27,4 +27,4 @@ = 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, position: position.to_json } }= diff_line_content(line.text, type) + %td.line_content.noteable_line{ class: [type, line_code], data: { line_code: line_code, position: position.to_json } }= diff_line_content(line.text, type) diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 59603f6071d..51f207dce94 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -14,11 +14,11 @@ %td.new_line.diff-line-num %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]}"} + %td.old_line.diff-line-num{id: left[:line_code], class: [left[:type], ('empty-cell' unless 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], left[:position], '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], position: left[:position].to_json }}= diff_line_content(left[:text]) + %td.line_content.parallel.noteable_line{class: [left[:type], left[:line_code], ('empty-cell' if left[:text].empty?)], data: { line_code: left[:line_code], position: left[:position].to_json }}= diff_line_content(left[:text]) - if right[:type] == 'new' - new_line_class = 'new' @@ -29,11 +29,11 @@ - 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] }} + %td.new_line.diff-line-num{id: new_line_code, class: [new_line_class, ('empty-cell' unless 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_position, '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, position: new_position.to_json }}= diff_line_content(right[:text]) + %td.line_content.parallel.noteable_line{class: [new_line_class, new_line_code, ('empty-cell' if right[:text].empty?)], data: { line_code: new_line_code, position: new_position.to_json }}= diff_line_content(right[:text]) - unless @diff_notes_disabled - notes_left, notes_right = organize_comments(left, right) -- cgit v1.2.1 From c66bcf34dd2ede698a784f55bd17346e85aeaf92 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 7 Jul 2016 16:14:57 -0400 Subject: Add comment with diff to DiffPositionUpdateService spec --- .../notes/diff_position_update_service_spec.rb | 104 +++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/spec/services/notes/diff_position_update_service_spec.rb b/spec/services/notes/diff_position_update_service_spec.rb index 0bca3ea1a09..110efb54fa0 100644 --- a/spec/services/notes/diff_position_update_service_spec.rb +++ b/spec/services/notes/diff_position_update_service_spec.rb @@ -32,6 +32,110 @@ describe Notes::DiffPositionUpdateService, services: true do ) 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) } -- cgit v1.2.1 From 4498bb78332ba5f03b23a4f8ddba2eb034830b39 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 7 Jul 2016 16:48:10 -0500 Subject: Change 3600 to 1.hour --- app/helpers/time_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index d1086025ad5..8cb82c2d5cc 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -26,7 +26,7 @@ module TimeHelper def duration_in_numbers(finished_at, started_at) diff_in_seconds = finished_at.to_i - started_at.to_i - time_format = diff_in_seconds < 3600 ? "%M:%S" : "%H:%M:%S" + time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S" Time.at(diff_in_seconds).utc.strftime(time_format) end -- cgit v1.2.1 From bf2a86b73cce332ff8f4392ffc8df501193f32ec Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 7 Jul 2016 18:25:05 -0400 Subject: Revert "Merge branch 'issue_3946' into 'master' " This reverts commit 68155ee73b549a4f79744bb325542c29d45c71ea, reversing changes made to 7ebd011ed1de7aee706f07a53c63c90f1c8aa5d4. --- CHANGELOG | 1 - app/assets/javascripts/dispatcher.js.coffee | 2 +- app/assets/javascripts/namespace_select.js.coffee | 79 +++------- app/assets/stylesheets/framework/dropdowns.scss | 4 - app/assets/stylesheets/framework/nav.scss | 6 - app/assets/stylesheets/pages/admin.scss | 33 ---- app/assets/stylesheets/pages/groups.scss | 33 ---- app/assets/stylesheets/pages/projects.scss | 4 + app/assets/stylesheets/pages/search.scss | 2 +- app/controllers/admin/projects_controller.rb | 5 +- app/helpers/dropdowns_helper.rb | 2 +- app/views/admin/groups/_group.html.haml | 42 +++-- app/views/admin/groups/index.html.haml | 57 ++++--- app/views/admin/projects/index.html.haml | 171 +++++++++++---------- app/views/admin/projects/show.html.haml | 8 +- app/views/admin/users/_user.html.haml | 42 ----- app/views/admin/users/index.html.haml | 170 +++++++++++--------- app/views/shared/projects/_dropdown.html.haml | 17 +- features/admin/projects.feature | 4 +- features/steps/admin/projects.rb | 9 +- spec/controllers/admin/projects_controller_spec.rb | 4 +- 21 files changed, 296 insertions(+), 399 deletions(-) delete mode 100644 app/views/admin/users/_user.html.haml diff --git a/CHANGELOG b/CHANGELOG index 491692204a6..1318d834a12 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,7 +22,6 @@ v 8.10.0 (unreleased) - Fix pagination when sorting by columns with lots of ties (like priority) - Updated project header design - 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 - Add notification settings dropdown for groups - Allow importing from Github using Personal Access Tokens. (Eric K Idema) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index a39df421832..9493a575801 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -127,7 +127,7 @@ class Dispatcher when 'groups' new UsersSelect() when 'projects' - new NamespaceSelects() + new NamespaceSelect() when 'dashboard', 'root' shortcut_handler = new ShortcutsDashboardNavigation() when 'profiles' diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index 3b419dff105..a02c4515ccc 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -1,56 +1,25 @@ class @NamespaceSelect - 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 - ) + constructor: -> + namespaceFormatResult = (namespace) -> + markup = "
" + markup += "" + namespace.kind + "" + markup += "" + namespace.path + "" + markup += "
" + 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 diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index d4e900f80ef..f36736c475e 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -68,10 +68,6 @@ color: $dropdown-toggle-hover-icon-color; } } - - &.large { - width: 200px; - } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 02ea98e9d94..6e5f216c894 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -134,11 +134,6 @@ margin-bottom: 0; border-bottom: none; - &.wide { - width: 100%; - display: block; - } - li a { padding: 16px 10px 11px; } @@ -169,7 +164,6 @@ > .btn { margin-right: $gl-padding-top; display: inline-block; - vertical-align: top; &:last-child { margin-right: 0; diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index 1d34a7f79ae..e05f14e7496 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -71,36 +71,3 @@ @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: bold; - } - - .controls { - > .btn, > .dropdown { - margin-left: 5px; - } - } - - .dropdown { - .btn-block { - margin-bottom: 0; - line-height: inherit; - } - } -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 701b9388454..3d79f4400e2 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -38,39 +38,6 @@ margin-right: 15px; } } - - &.group-admin { - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - - .group-avatar, .group-details, .group-controls { - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - } - - .group-details { - flex: 1 1 auto; - flex-direction: column; - min-width: 0; - } - - .group-controls { - align-items: center; - - a { - margin-left: 5px; - } - } - } - -} - -.ldap-group-links { - .form-actions { - margin-bottom: $gl-padding; - } } .groups-cover-block { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index bce4aac3334..3325b586496 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -475,6 +475,10 @@ pre.light-well { a:hover { text-decoration: none; } + + > span { + margin-left: 10px; + } } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 9e9b18fdbb8..ae524cd6bae 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -208,7 +208,7 @@ margin-top: 5px; @media (min-width: $screen-sm-min) { - width: 180px; + width: 160px; margin-top: 0; } } diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 0d2f4f6eb38..4c9c6362ffc 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -5,12 +5,11 @@ 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(visibility_level: params[:visibility_level]) if params[:visibility_level].present? + @projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].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[:archived].present? - @projects = @projects.personal(current_user) if params[:personal].present? + @projects = @projects.non_archived unless params[:with_archived].present? @projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]) diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 4566f3782cc..7c140538012 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/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 59fd6c3fea0..9025aaac097 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -1,20 +1,28 @@ - css_class = '' unless local_assigns[:css_class] +- css_class += ' no-description' if group.description.blank? -%li.group-row.group-admin{ class: css_class } - .group-avatar - = image_tag group_icon(group), class: 'avatar hidden-xs' - .group-details - .title - = link_to [:admin, group], class: 'group-name' do - = group.name - .group-stats - %span>= pluralize(number_with_delimiter(group.projects.count), 'project') - , - %span= pluralize(number_with_delimiter(group.users.count), 'member') +%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' - - if group.description.present? - .description - = markdown(group.description, pipeline: :description) - .group-controls.hidden-xs - = 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') + = number_with_delimiter(group.projects.count) + + %span + = icon('users') + = number_with_delimiter(group.users.count) + + %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' + .title + = link_to [:admin, group], class: 'group-name' do + = group.name + + - if group.description.present? + .description + = markdown(group.description, pipeline: :description) diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 794f910a61f..94aa5f5a942 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -3,32 +3,41 @@ = 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 - .prepend-top-default.append-bottom-default - = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f| + .nav-search + = form_tag admin_groups_path, method: :get, class: 'form-inline' do = hidden_field_tag :sort, @sort - .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 + = 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" + %ul.content-list = render @groups diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 7fbce25b2c4..7d2eb423223 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,94 +1,97 @@ - @no_container = true - page_title "Projects" -- params[:visibility_level] ||= [] - += render 'shared/show_aside' = render "admin/dashboard/head" %div{ class: container_class } - .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 + .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" - = 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 + = label_tag :namespace_id, "Namespace" + = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' - .nav-controls - = render 'shared/projects/dropdown' - = link_to new_project_path, class: 'btn btn-new' do - New Project + .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 - .projects-list-holder - - if @projects.any? - %ul.projects-list.content-list - - @projects.each_with_index do |project| - %li.project-row - .controls.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" - = 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 + %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 - - if project.description.present? - .description - = markdown(project.description, pipeline: :description) + = 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" - = paginate @projects, theme: 'gitlab' - - else - .nothing-here-block No projects found + %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" diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 2c5aba71699..82d3169c6f9 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -99,13 +99,7 @@ .form-group = f.label :new_namespace_id, "Namespace", class: 'control-label' .col-sm-10 - .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 + = namespace_select_tag :new_namespace_id, selected: params[:namespace_id], class: 'input-large' .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml deleted file mode 100644 index d3519f616f6..00000000000 --- a/app/views/admin/users/_user.html.haml +++ /dev/null @@ -1,42 +0,0 @@ -%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.pull-right - = 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 357123c2c13..21bb99a792c 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,78 +1,110 @@ - @no_container = true - page_title "Users" += render 'shared/show_aside' = render "admin/dashboard/head" %div{ class: container_class } - .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' + .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) - .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 + .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 - %ul.users-list.content-list - - if @users.empty? - %li - .nothing-here-block No users found. - - else - = render partial: 'admin/users/user', collection: @users + = 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 + + .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/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml index b7f8551153b..1169bed0382 100644 --- a/app/views/shared/projects/_dropdown.html.haml +++ b/app/views/shared/projects/_dropdown.html.haml @@ -1,30 +1,31 @@ - @sort ||= sort_value_recently_updated - personal = params[:personal] - archived = params[:archived] -- namespace_id = params[:namespace_id] .dropdown.inline - - toggle_text = projects_sort_options_hash[@sort] - = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light + = projects_sort_options_hash[@sort] + %b.caret %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(namespace_id: namespace_id, sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do + = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do = title %li.divider %li - = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do + = link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do Hide archived projects %li - = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do + = link_to filter_projects_path(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(namespace_id: namespace_id, sort: @sort, personal: nil), class: ("is-active" unless personal.present?) do + = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do Owned by anyone %li - = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: true), class: ("is-active" if personal.present?) do + = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do Owned by me diff --git a/features/admin/projects.feature b/features/admin/projects.feature index 8929bcf8d80..c5ee80136c8 100644 --- a/features/admin/projects.feature +++ b/features/admin/projects.feature @@ -10,11 +10,10 @@ 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 select "Show archived projects" + And I check "Show archived projects" Then I should see all projects And I should see "archived" label @@ -23,7 +22,6 @@ 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/steps/admin/projects.rb b/features/steps/admin/projects.rb index d77945a6b9c..a7a28755a6c 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 select "Show archived projects"' do - find(:css, '#sort-projects-dropdown').click - click_link 'Show archived projects' + step 'I check "Show archived projects"' do + page.check 'Show archived projects' + click_button "Search" end step 'I should see "archived" label' do @@ -45,8 +45,7 @@ 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) - click_button 'Search for Namespace' - click_link 'group: web' + find(:xpath, "//input[@id='new_namespace_id']").set group.id click_button 'Transfer' end diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 8eaacef2024..4cb8b8da150 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_level: [Gitlab::VisibilityLevel::PUBLIC] + get :index, visibility_levels: [Gitlab::VisibilityLevel::PUBLIC] expect(response.body).to match(project.name) end it 'does not retrieve the project' do - get :index, visibility_level: [Gitlab::VisibilityLevel::INTERNAL] + get :index, visibility_levels: [Gitlab::VisibilityLevel::INTERNAL] expect(response.body).not_to match(project.name) end end -- cgit v1.2.1 From be6c4fef40a937757a0e95ba758bf1b6da0155d7 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 7 Jul 2016 18:39:45 -0500 Subject: Removed unnecessary `id` from links and corrected tests to use the proper matcher. --- app/views/admin/abuse_reports/_abuse_report.html.haml | 2 +- app/views/users/show.html.haml | 2 +- spec/features/admin/admin_abuse_reports_spec.rb | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index b54ca059a61..dd2e7ebd030 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -3,7 +3,7 @@ %tr %td - if user - = link_to user.name, user, id: 'abuser_profile_path' + = link_to user.name, user .light.small Joined #{time_ago_with_tooltip(user.created_at)} - else diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 520f76eb062..db2b4885861 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -31,7 +31,7 @@ = icon('rss') - if current_user.admin?   - = link_to [:admin, @user], id: 'admin_user_path', class: 'btn btn-gray', title: 'View user in admin area', + = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('users') diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index 2ff02a1c9a8..16baf7e9516 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -6,16 +6,15 @@ describe "Admin::AbuseReports", feature: true, js: true do context 'as an admin' do describe 'if a user has been reported for abuse' do before do - admin = create(:admin) create(:abuse_report, user: user) - login_as admin + 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_selector '#abuser_profile_path' + expect(page).to have_link user.name, href: user_path(user) end end @@ -23,7 +22,7 @@ describe "Admin::AbuseReports", feature: true, js: true do it 'should show a link to the admin view of the user' do visit user_path(user) - expect(page).to have_selector '#admin_user_path' + expect(page).to have_link '', href: admin_user_path(user) end end end -- cgit v1.2.1 From 0de617772dfeb9bdcf3770e9acf7421db5023058 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 7 Jul 2016 23:48:02 -0400 Subject: Revert "Revert "Merge branch 'issue_3946' into 'master' "" This reverts commit bf2a86b73cce332ff8f4392ffc8df501193f32ec. --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js.coffee | 2 +- app/assets/javascripts/namespace_select.js.coffee | 79 +++++++--- app/assets/stylesheets/framework/dropdowns.scss | 4 + app/assets/stylesheets/framework/nav.scss | 6 + app/assets/stylesheets/pages/admin.scss | 33 ++++ app/assets/stylesheets/pages/groups.scss | 33 ++++ app/assets/stylesheets/pages/projects.scss | 4 - app/assets/stylesheets/pages/search.scss | 2 +- app/controllers/admin/projects_controller.rb | 5 +- app/helpers/dropdowns_helper.rb | 2 +- app/views/admin/groups/_group.html.haml | 42 ++--- app/views/admin/groups/index.html.haml | 57 +++---- app/views/admin/projects/index.html.haml | 171 ++++++++++----------- app/views/admin/projects/show.html.haml | 8 +- app/views/admin/users/_user.html.haml | 42 +++++ app/views/admin/users/index.html.haml | 170 +++++++++----------- app/views/shared/projects/_dropdown.html.haml | 17 +- features/admin/projects.feature | 4 +- features/steps/admin/projects.rb | 9 +- spec/controllers/admin/projects_controller_spec.rb | 4 +- 21 files changed, 399 insertions(+), 296 deletions(-) create mode 100644 app/views/admin/users/_user.html.haml diff --git a/CHANGELOG b/CHANGELOG index 3d79afbc39e..7fbfa5e7377 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.10.0 (unreleased) - Fix pagination when sorting by columns with lots of ties (like priority) - Updated project header design - 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 - Add notification settings dropdown for groups - Wildcards for protected branches. !4665 diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 9493a575801..a39df421832 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -127,7 +127,7 @@ class Dispatcher when 'groups' new UsersSelect() when 'projects' - new NamespaceSelect() + new NamespaceSelects() when 'dashboard', 'root' shortcut_handler = new ShortcutsDashboardNavigation() when 'profiles' 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 = "
" - markup += "" + namespace.kind + "" - markup += "" + namespace.path + "" - markup += "
" - 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/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index f36736c475e..d4e900f80ef 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -68,6 +68,10 @@ color: $dropdown-toggle-hover-icon-color; } } + + &.large { + width: 200px; + } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 6e5f216c894..02ea98e9d94 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -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/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index e05f14e7496..1d34a7f79ae 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -71,3 +71,36 @@ @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: bold; + } + + .controls { + > .btn, > .dropdown { + margin-left: 5px; + } + } + + .dropdown { + .btn-block { + margin-bottom: 0; + line-height: inherit; + } + } +} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 3d79f4400e2..701b9388454 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -38,6 +38,39 @@ margin-right: 15px; } } + + &.group-admin { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + + .group-avatar, .group-details, .group-controls { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + } + + .group-details { + flex: 1 1 auto; + flex-direction: column; + min-width: 0; + } + + .group-controls { + align-items: center; + + a { + margin-left: 5px; + } + } + } + +} + +.ldap-group-links { + .form-actions { + margin-bottom: $gl-padding; + } } .groups-cover-block { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3325b586496..bce4aac3334 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -475,10 +475,6 @@ pre.light-well { a:hover { text-decoration: none; } - - > span { - margin-left: 10px; - } } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index ae524cd6bae..9e9b18fdbb8 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -208,7 +208,7 @@ margin-top: 5px; @media (min-width: $screen-sm-min) { - width: 160px; + width: 180px; margin-top: 0; } } 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/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/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 9025aaac097..59fd6c3fea0 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -1,28 +1,20 @@ - css_class = '' unless local_assigns[:css_class] -- 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' +%li.group-row.group-admin{ class: css_class } + .group-avatar + = image_tag group_icon(group), class: 'avatar hidden-xs' + .group-details + .title + = link_to [:admin, group], class: 'group-name' do + = group.name + .group-stats + %span>= pluralize(number_with_delimiter(group.projects.count), 'project') + , + %span= pluralize(number_with_delimiter(group.users.count), 'member') - .stats - %span - = icon('bookmark') - = number_with_delimiter(group.projects.count) - - %span - = icon('users') - = number_with_delimiter(group.users.count) - - %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' - .title - = link_to [:admin, group], class: 'group-name' do - = group.name - - - if group.description.present? - .description - = markdown(group.description, pipeline: :description) + - if group.description.present? + .description + = markdown(group.description, pipeline: :description) + .group-controls.hidden-xs + = 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' 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/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 7d2eb423223..7fbce25b2c4 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.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" + = 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..2c5aba71699 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 diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml new file mode 100644 index 00000000000..d3519f616f6 --- /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.pull-right + = 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/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/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/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/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 -- cgit v1.2.1 From 5dcea406b97cbaf33afc9c7ad895dfbc1593401f Mon Sep 17 00:00:00 2001 From: kujiy Date: Fri, 8 Jul 2016 04:22:30 +0000 Subject: gitlab-org/gitlab-ci-multi-runner#1478 Fixed phpunit command in the official doc didn't work. Curl command has to follow redirects now. --- doc/ci/examples/php.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.1 From 8ab3ab9e0a21c087e90eda485486e4eff905b486 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 7 Jul 2016 10:32:01 +0200 Subject: Don't render discussion notes when requesting diff tab through AJAX --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 54 +++++++++++++--------- app/views/projects/merge_requests/_show.html.haml | 4 +- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7fbfa5e7377..907e1765aa7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.10.0 (unreleased) - 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 + - 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 diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index df1943dd9bb..8fda5618818 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -53,9 +53,6 @@ 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 @@ -80,6 +77,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs apply_diff_view_cookie! + @merge_request_diff = @merge_request.merge_request_diff + @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit @@ -109,7 +108,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController 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.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 @@ -340,14 +347,33 @@ 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 + + if request.format == :html || action_name == 'show' + define_show_html_vars + end + end + + # Discussion tab data is only required on html requests + def define_show_html_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,22 +382,6 @@ 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 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 -- cgit v1.2.1 From 0530ec5e6ec324c5b1dd6e450a534b204cae2118 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 8 Jul 2016 09:34:36 +0200 Subject: Expose shared groups for projects --- CHANGELOG | 1 + doc/api/groups.md | 72 ++++++++++++++++++++------------------ doc/api/projects.md | 37 ++++++++++++++++---- lib/api/entities.rb | 11 ++++++ spec/requests/api/projects_spec.rb | 40 +++++++++++++++++++-- 5 files changed, 118 insertions(+), 43 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 491692204a6..88ae6ed37a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.10.0 (unreleased) - Add notification settings dropdown for groups - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - API: Todos !3188 (Robert Schilling) + - API: Expose shared groups for projects !5148 (Robert Schilling) - Add "Enabled Git access protocols" to Application Settings - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - PipelinesFinder uses git cache data diff --git a/doc/api/groups.md b/doc/api/groups.md index 1ccb9715e96..eaff3fa044e 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": [] } ] ``` @@ -201,7 +204,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/projects.md b/doc/api/projects.md index f5f195b97df..bf30aa28a14 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -82,7 +82,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] }, { "id": 6, @@ -140,7 +141,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] } ] ``` @@ -262,7 +264,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 + } + ] } ``` @@ -553,7 +568,9 @@ Example response: "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": [] } ``` @@ -616,7 +633,9 @@ Example response: "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": [] } ``` @@ -699,7 +718,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": [] } ``` @@ -782,7 +803,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": [] } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index db877d2eeb0..90e51c29339 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -58,6 +58,14 @@ module API 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 +85,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 diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 611dd2a2a88..8a52725a893 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -392,11 +392,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 -- cgit v1.2.1 From 6d09e946d22727ce595aeb382685292a1ad8f5a8 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 8 Jul 2016 10:44:07 +0200 Subject: import_url migration performance improvements Nullifying empty import_urls upfront so the number of projects with import_url not NULL decreases to 1/5. Also, now processing batches in blocks of 1000, with a threaded process - a bit experimental. --- ...20160620110927_fix_no_validatable_import_url.rb | 34 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb index 82a616c62d9..02ff1962e3f 100644 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -11,7 +11,7 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration attr_reader :results, :query - def initialize(batch_size: 100, query:) + def initialize(batch_size: 1000, query:) @offset = 0 @batch_size = batch_size @query = query @@ -58,22 +58,40 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration return end + say('Nullifying empty import URLs') + + nullify_empty_urls + say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.') - invalid_import_url_project_ids.each { |project_id| cleanup_import_url(project_id) } + process_invalid_import_urls end - def invalid_import_url_project_ids - ids = [] + def process_invalid_import_urls + @threads = [] batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL") while batches.next? + project_ids = [] + batches.results.each do |result| - ids << result['id'] unless valid_url?(result['import_url']) + project_ids << result['id'] unless valid_url?(result['import_url']) end + + process_batch(project_ids) end - ids + @threads.each(&:join) + end + + def process_batch(project_ids) + @threads << Thread.new do + begin + project_ids.each { |project_id| cleanup_import_url(project_id) } + ensure + ActiveRecord::Base.connection.close + end + end end def valid_url?(url) @@ -83,4 +101,8 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration def cleanup_import_url(project_id) execute("UPDATE projects SET import_url = NULL WHERE id = #{project_id}") end + + def nullify_empty_urls + execute("UPDATE projects SET import_url = NULL WHERE import_url = ''") + end end -- cgit v1.2.1 From 2c6fe72265d250e47c03f27dc274b59d3e7e93f5 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 8 Jul 2016 11:00:30 +0200 Subject: fix thread join issue --- db/migrate/20160620110927_fix_no_validatable_import_url.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb index 02ff1962e3f..a3f5073d511 100644 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -68,7 +68,6 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration end def process_invalid_import_urls - @threads = [] batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL") while batches.next? @@ -81,17 +80,16 @@ class FixNoValidatableImportUrl < ActiveRecord::Migration process_batch(project_ids) end - @threads.each(&:join) end def process_batch(project_ids) - @threads << Thread.new do + Thread.new do begin project_ids.each { |project_id| cleanup_import_url(project_id) } ensure ActiveRecord::Base.connection.close end - end + end.join end def valid_url?(url) -- cgit v1.2.1 From c6f9a1c273beb7e427da1e384ed27e323d6f3b29 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 30 Jun 2016 13:20:15 +0200 Subject: Enable Style/IdenticalConditionalBranches Rubocop cop --- .rubocop.yml | 2 +- app/controllers/projects_controller.rb | 4 ++-- app/models/project_services/irker_service.rb | 4 ++-- app/services/merge_requests/refresh_service.rb | 11 +++-------- lib/rouge/formatters/html_gitlab.rb | 16 ++++++---------- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index cd13f581517..3aac8401848 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -284,7 +284,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. 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/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 58cb720c3c1..7fc33689952 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -114,13 +114,13 @@ class IrkerService < Service 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/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 21490ac77ea..b11ecd97a57 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -61,19 +61,14 @@ module MergeRequests merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? merge_request.reload_diff - merge_request.mark_as_unchecked 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_diff - 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 diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index 8c309efc7b8..3358ed6773e 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -143,18 +143,14 @@ module Rouge '' end end - lines.join("\n") - else - if @linenos == 'inline' - lines = lines.each_with_index.map do |line, index| - number = index + @linenostart - "#{number}#{line}" - end - lines.join("\n") - else - lines.join("\n") + elsif @linenos == 'inline' + lines = lines.each_with_index.map do |line, index| + number = index + @linenostart + "#{number}#{line}" end end + + lines.join("\n") end def span(tok, val) -- cgit v1.2.1 From 4c388fb86500a2691c7b584ffafcbac18d643cab Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 8 Jul 2016 11:06:54 +0200 Subject: Remove legacy conditional from irker service code --- app/models/project_services/irker_service.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 7fc33689952..ce7d1c5d5b1 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -112,14 +112,6 @@ 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 - else - # Authorize irc://domain.com/smthg#chan - # The irker daemon will deal with it by concatenating smthg and - # chan, thus sending messages on #smthgchan - end - uri.to_s end end -- cgit v1.2.1 From 52a89f20229285183eb6ecc9e9da444d004be5b3 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Fri, 8 Jul 2016 12:35:31 +0200 Subject: Memoize MR merged/closed events retrieval --- CHANGELOG | 1 + app/models/merge_request.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 09f2c44e02c..f6fb9b7d257 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.10.0 (unreleased) - 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 - 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 diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 083e93f1ee7..393d8a72657 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -318,11 +318,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 -- cgit v1.2.1 From 33124b4b500df904b91c74f3fdf4123fb27631a6 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 8 Jul 2016 10:59:52 +0200 Subject: API: Expose shared projects in a group --- CHANGELOG | 2 +- doc/api/groups.md | 175 ++++++++++++++++++++++++++++++++++++++- lib/api/entities.rb | 1 + spec/requests/api/groups_spec.rb | 19 ++++- 4 files changed, 193 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 88ae6ed37a6..c044246eeeb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,7 +27,7 @@ v 8.10.0 (unreleased) - Add notification settings dropdown for groups - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - API: Todos !3188 (Robert Schilling) - - API: Expose shared groups for projects !5148 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling) - Add "Enabled Git access protocols" to Application Settings - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - PipelinesFinder uses git cache data diff --git a/doc/api/groups.md b/doc/api/groups.md index eaff3fa044e..87480bebfc4 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -99,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 diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 90e51c29339..9076a0c3831 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -104,6 +104,7 @@ module API class GroupDetail < Group expose :projects, using: Entities::Project + expose :shared_projects, using: Entities::Project end class GroupMember < UserBasic 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 -- cgit v1.2.1 From 9a08fa082b862995568708ca529874788a00d86e Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Fri, 8 Jul 2016 11:10:04 +0000 Subject: Wrong gitlab-shell version --- doc/update/8.9-to-8.10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md index a51790b0bda..84065a84e50 100644 --- a/doc/update/8.9-to-8.10.md +++ b/doc/update/8.9-to-8.10.md @@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.1.0 +sudo -u git -H git checkout v3.2.0 ``` ### 5. Update gitlab-workhorse -- cgit v1.2.1 From 8e1a18f11f671249404b94f4c9e2639918ab3773 Mon Sep 17 00:00:00 2001 From: Andrey Krivko Date: Fri, 8 Apr 2016 22:36:15 +0600 Subject: Add min attribute to project_limit field on user's form --- CHANGELOG | 1 + app/views/admin/users/_form.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 12906398a94..3f26528942a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,6 +57,7 @@ v 8.10.0 (unreleased) - 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) v 8.9.5 - Add more debug info to import/export and memory killer. !5108 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' -- cgit v1.2.1 From 776632b40daf16aa3f7deb9405ad9ac6480a2d9d Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 8 Jul 2016 11:38:57 -0600 Subject: Run bundle install. --- Gemfile.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 055596b056f..a394e49e95b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -806,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) @@ -815,15 +815,15 @@ DEPENDENCIES awesome_print (~> 1.2.0) babosa (~> 1.0.2) base32 (~> 0.3.0) - benchmark-ips + benchmark-ips (~> 2.3.0) better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) brakeman (~> 3.3.0) browser (~> 2.2) - bullet - bundler-audit - byebug + bullet (~> 5.0.0) + bundler-audit (~> 0.5.0) + byebug (~> 8.2.1) capybara (~> 2.6.2) capybara-screenshot (~> 1.0.0) carrierwave (~> 0.10.0) @@ -844,8 +844,8 @@ DEPENDENCIES 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) @@ -854,7 +854,7 @@ 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) @@ -881,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) @@ -916,19 +916,19 @@ 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-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) @@ -936,7 +936,7 @@ DEPENDENCIES rouge (~> 1.11) rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) - rspec-retry + rspec-retry (~> 0.4.5) rubocop (~> 0.40.0) rubocop-rspec (~> 1.5.0) ruby-fogbugz (~> 0.2.1) @@ -948,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) -- cgit v1.2.1 From 443fdff1b43b5074321d0faf3a9d5396a4edcdab Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 29 Jun 2016 10:08:05 -0600 Subject: Update New Snippet buttons. No longer shows New Snippet button to users who aren't able to create a new snippet in the given context. Also removes the plus icon from the New Snippet buttons, as they're no longer used in other creation buttons. Fixes #14595. --- CHANGELOG | 1 + app/views/explore/snippets/index.html.haml | 1 - app/views/projects/snippets/_actions.html.haml | 42 ++++++++++++++------------ app/views/projects/snippets/index.html.haml | 6 ++-- app/views/snippets/_actions.html.haml | 39 ++++++++++++------------ 5 files changed, 46 insertions(+), 43 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b44ca54a39c..3e4a10bb5a3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.10.0 (unreleased) - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling) - Add "Enabled Git access protocols" to Application Settings - 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 - Throttle the update of `project.pushes_since_gc` to 1 minute. - Check for conflicts with existing Project's wiki path when creating a new project. 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/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..6c994ae486b 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -2,9 +2,9 @@ .row-content-block.top-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/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index a7769654b61..2957ff919e1 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 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 -- cgit v1.2.1 From 226bc5873a08f133bd3a3a2afe98559a0ebdcc4a Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 1 Jul 2016 14:01:54 -0600 Subject: Use btn-danger for delete button. --- app/views/snippets/_actions.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 2957ff919e1..160c6cd84da 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -6,7 +6,7 @@ = 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 - if current_user .visible-xs-block.dropdown -- cgit v1.2.1 From 9ac4c556eac857fc285838070ffc24650a1bab44 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 30 Jun 2016 11:51:07 +0200 Subject: Re-use queries in reference parsers This caches various queries to ensure that multiple reference extraction runs re-use any objects queried in previous runs. --- CHANGELOG | 1 + lib/banzai/reference_parser/base_parser.rb | 36 ++++++++++- lib/banzai/reference_parser/user_parser.rb | 5 +- .../banzai/reference_parser/base_parser_spec.rb | 75 ++++++++++++++++++++++ 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b44ca54a39c..25274fa8a6a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.10.0 (unreleased) - Add Spring EmojiOne updates. - Fix viewing notification settings when a project is pending deletion - 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 - Exclude email check from the standard health check - Updated layout for Projects, Groups, Users on Admin area !4424 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/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 -- cgit v1.2.1 From 3bae69aa5dba31941b3e46500e895fae835d6a71 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 8 Jul 2016 21:20:13 +0000 Subject: Update ui_guide.md with button capitalize rule --- doc/development/ui_guide.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md index 5893b7c219e..ce0aaa2fd25 100644 --- a/doc/development/ui_guide.md +++ b/doc/development/ui_guide.md @@ -52,5 +52,6 @@ information from database or file system * 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 +* 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" -- cgit v1.2.1 From 53697439cce04d5b1a75c3fcdb7c27bdc0fd2d2e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 9 Jul 2016 11:42:48 +0300 Subject: Make subnavigation a bit darker color Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/nav.scss | 4 ++-- app/assets/stylesheets/framework/variables.scss | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 02ea98e9d94..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; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 211a9af2348..4337fab5d87 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,10 +12,11 @@ $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; /* * Text @@ -153,9 +154,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; -- cgit v1.2.1 From 454e6c7b8c227f97d3158564273504067be05eab Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 9 Jul 2016 14:48:36 +0300 Subject: Add side shadow for unpinned sidebar Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/sidebar.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 188823054fd..78eefa538c3 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 0px 16px 0px #bbb); } } -- cgit v1.2.1 From f52e83a5c09badbfa713875184d2c52797f98f42 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 9 Jul 2016 15:05:05 +0300 Subject: Make color that highligh today issues more lightweight Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/issues.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 4e35ca329e4..05e1713d64a 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 { -- cgit v1.2.1 From c65e4af87e8e58996943ff98d71947fd45d6b2c7 Mon Sep 17 00:00:00 2001 From: Ingo Blechschmidt Date: Wed, 1 Jun 2016 11:54:42 +0200 Subject: Add reminder to not paste private SSH keys --- app/views/profiles/keys/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.1 From 47e20899c43a6a045726a55dcc4bfba47a4526b1 Mon Sep 17 00:00:00 2001 From: Ingo Blechschmidt Date: Sat, 9 Jul 2016 15:11:31 +0200 Subject: Add changelog entry for !4399 --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3e4a10bb5a3..e6aaaae202a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -60,6 +60,7 @@ v 8.10.0 (unreleased) - 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) + - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt) v 8.9.5 - Add more debug info to import/export and memory killer. !5108 -- cgit v1.2.1 From d9c49435a743fab3eb475cf2930245ea3f934fbd Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 9 Jul 2016 16:54:46 +0300 Subject: Fix typo and explain the precedence of STDERR and STDOUT --- doc/administration/custom_hooks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index 9fd7b71d2dc..e3306c22d3f 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -48,7 +48,8 @@ as appropriate. 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 and/or SDOUT message of the hook will be present in GitLab's UI. +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) -- cgit v1.2.1 From 6a477b9bfc5bf9b32c9a961269066694d1216dce Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 27 Apr 2016 23:38:33 +0200 Subject: Add blockquote fence syntax to Markdown --- doc/markdown/markdown.md | 34 +++++- lib/banzai/filter/blockquote_fence_filter.rb | 50 ++++++++ lib/banzai/pipeline/pre_process_pipeline.rb | 3 +- spec/fixtures/blockquote_fence_after.md | 115 ++++++++++++++++++ spec/fixtures/blockquote_fence_before.md | 131 +++++++++++++++++++++ .../banzai/filter/blockquote_fence_filter_spec.rb | 14 +++ 6 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 lib/banzai/filter/blockquote_fence_filter.rb create mode 100644 spec/fixtures/blockquote_fence_after.md create mode 100644 spec/fixtures/blockquote_fence_before.md create mode 100644 spec/lib/banzai/filter/blockquote_fence_filter_spec.rb 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 >>>. + +```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/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb new file mode 100644 index 00000000000..fb815c2d837 --- /dev/null +++ b/lib/banzai/filter/blockquote_fence_filter.rb @@ -0,0 +1,50 @@ +module Banzai + module Filter + class BlockquoteFenceFilter < HTML::Pipeline::TextFilter + REGEX = %r{ + (? + # Code blocks: + # ``` + # Anything, including ignored `>>>` blocks + # ``` + ^```.+?\n```$ + ) + | + (? + # HTML: + # + # Anything, including ignored `>>>` blocks + # + ^<[^>]+?>.+?\n<\/[^>]+?>$ + ) + | + ( + ^>>>\n(? + (?: + (?!^```|^<[^>]+?>). + | + \g + | + \g + ) + +?)\n>>>$ + ) + }mx.freeze + + def initialize(text, context = nil, result = nil) + super text, context, result + @text = @text.delete "\r" + end + + def call + @text.gsub(REGEX) do + if $~[:quote] + $~[:quote].gsub(/^/, "> ").gsub(/^> $/, ">") + else + $~[0] + end + end + end + end + end +end diff --git a/lib/banzai/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/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md new file mode 100644 index 00000000000..5ab136f76c3 --- /dev/null +++ b/spec/fixtures/blockquote_fence_after.md @@ -0,0 +1,115 @@ +Single `>>>` inside code block: + +``` +# Code +>>> +# Code +``` + +Double `>>>` inside code block: + +``` +# Code +>>> +# Code +>>> +# Code +``` + +Blockquote outside code block: + +> Quote + +Code block inside blockquote: + +> Quote +> +> ``` +> # Code +> ``` +> +> Quote + +Single `>>>` inside code block inside blockquote: + +> Quote +> +> ``` +> # Code +> >>> +> # Code +> ``` +> +> Quote + +Double `>>>` inside code block inside blockquote: + +> Quote +> +> ``` +> # Code +> >>> +> # Code +> >>> +> # Code +> ``` +> +> Quote + +Single `>>>` inside HTML: + +
+# Code
+>>>
+# Code
+
+ +Double `>>>` inside HTML: + +
+# Code
+>>>
+# Code
+>>>
+# Code
+
+ +Blockquote outside HTML: + +> Quote + +HTML inside blockquote: + +> Quote +> +>
+> # Code
+> 
+> +> Quote + +Single `>>>` inside HTML inside blockquote: + +> Quote +> +>
+> # Code
+> >>>
+> # Code
+> 
+> +> Quote + +Double `>>>` inside HTML inside blockquote: + +> Quote +> +>
+> # Code
+> >>>
+> # Code
+> >>>
+> # Code
+> 
+> +> Quote diff --git a/spec/fixtures/blockquote_fence_before.md b/spec/fixtures/blockquote_fence_before.md new file mode 100644 index 00000000000..e6689b6c5dd --- /dev/null +++ b/spec/fixtures/blockquote_fence_before.md @@ -0,0 +1,131 @@ +Single `>>>` inside code block: + +``` +# Code +>>> +# Code +``` + +Double `>>>` inside code block: + +``` +# Code +>>> +# Code +>>> +# Code +``` + +Blockquote outside code block: + +>>> +Quote +>>> + +Code block inside blockquote: + +>>> +Quote + +``` +# Code +``` + +Quote +>>> + +Single `>>>` inside code block inside blockquote: + +>>> +Quote + +``` +# Code +>>> +# Code +``` + +Quote +>>> + +Double `>>>` inside code block inside blockquote: + +>>> +Quote + +``` +# Code +>>> +# Code +>>> +# Code +``` + +Quote +>>> + +Single `>>>` inside HTML: + +
+# Code
+>>>
+# Code
+
+ +Double `>>>` inside HTML: + +
+# Code
+>>>
+# Code
+>>>
+# Code
+
+ +Blockquote outside HTML: + +>>> +Quote +>>> + +HTML inside blockquote: + +>>> +Quote + +
+# Code
+
+ +Quote +>>> + +Single `>>>` inside HTML inside blockquote: + +>>> +Quote + +
+# Code
+>>>
+# Code
+
+ +Quote +>>> + +Double `>>>` inside HTML inside blockquote: + +>>> +Quote + +
+# Code
+>>>
+# Code
+>>>
+# Code
+
+ +Quote +>>> diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb new file mode 100644 index 00000000000..19543bde838 --- /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 'convers 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 -- cgit v1.2.1 From 2fcb2b339bbaad7a04414363eed81d2af82674a6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 9 Jul 2016 21:25:23 -0400 Subject: Add changelog item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3e4a10bb5a3..4d606e66ed4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.10.0 (unreleased) - Fix MR-auto-close text added to description. !4836 - 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 - Fix pagination when sorting by columns with lots of ties (like priority) - Updated project header design -- cgit v1.2.1 From 24e7c3e3255111ee7a4907db26d4a37f5de9286d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 10 Jul 2016 14:47:53 -0500 Subject: Add more comments to regex --- lib/banzai/filter/blockquote_fence_filter.rb | 43 +++++++++++++++++++++------- spec/fixtures/blockquote_fence_after.md | 2 +- spec/fixtures/blockquote_fence_before.md | 2 +- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb index fb815c2d837..d2c4b1e4d76 100644 --- a/lib/banzai/filter/blockquote_fence_filter.rb +++ b/lib/banzai/filter/blockquote_fence_filter.rb @@ -5,35 +5,56 @@ module Banzai (? # Code blocks: # ``` - # Anything, including ignored `>>>` blocks + # Anything, including `>>>` blocks which are ignored by this filter # ``` - ^```.+?\n```$ + + ^``` + .+? + \n```$ ) | (? - # HTML: + # HTML block: # - # Anything, including ignored `>>>` blocks + # Anything, including `>>>` blocks which are ignored by this filter # - ^<[^>]+?>.+?\n<\/[^>]+?>$ + + ^<[^>]+?>\n + .+? + \n<\/[^>]+?>$ ) | - ( - ^>>>\n(? + (?: + # Blockquote: + # >>> + # Anything, including code and HTML blocks + # >>> + + ^>>>\n + (? (?: - (?!^```|^<[^>]+?>). + # Any character that doesn't introduce a code or HTML block + (?! + ^``` + | + ^<[^>]+?>\n + ) + . | + # A code block \g | + # An HTML block \g - ) - +?)\n>>>$ + )+? + ) + \n>>>$ ) }mx.freeze def initialize(text, context = nil, result = nil) super text, context, result - @text = @text.delete "\r" + @text = @text.delete("\r") end def call diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md index 5ab136f76c3..2652a842c0e 100644 --- a/spec/fixtures/blockquote_fence_after.md +++ b/spec/fixtures/blockquote_fence_after.md @@ -8,7 +8,7 @@ Single `>>>` inside code block: Double `>>>` inside code block: -``` +```txt # Code >>> # Code diff --git a/spec/fixtures/blockquote_fence_before.md b/spec/fixtures/blockquote_fence_before.md index e6689b6c5dd..d52eec72896 100644 --- a/spec/fixtures/blockquote_fence_before.md +++ b/spec/fixtures/blockquote_fence_before.md @@ -8,7 +8,7 @@ Single `>>>` inside code block: Double `>>>` inside code block: -``` +```txt # Code >>> # Code -- cgit v1.2.1 From e382ea8682bf246de006bcbf405d3017b12313f6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 10 Jul 2016 14:48:32 -0500 Subject: Update Gemfile.lock after versions were added in !5078 --- Gemfile.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 055596b056f..a394e49e95b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -806,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) @@ -815,15 +815,15 @@ DEPENDENCIES awesome_print (~> 1.2.0) babosa (~> 1.0.2) base32 (~> 0.3.0) - benchmark-ips + benchmark-ips (~> 2.3.0) better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) brakeman (~> 3.3.0) browser (~> 2.2) - bullet - bundler-audit - byebug + bullet (~> 5.0.0) + bundler-audit (~> 0.5.0) + byebug (~> 8.2.1) capybara (~> 2.6.2) capybara-screenshot (~> 1.0.0) carrierwave (~> 0.10.0) @@ -844,8 +844,8 @@ DEPENDENCIES 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) @@ -854,7 +854,7 @@ 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) @@ -881,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) @@ -916,19 +916,19 @@ 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-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) @@ -936,7 +936,7 @@ DEPENDENCIES rouge (~> 1.11) rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) - rspec-retry + rspec-retry (~> 0.4.5) rubocop (~> 0.40.0) rubocop-rspec (~> 1.5.0) ruby-fogbugz (~> 0.2.1) @@ -948,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) -- cgit v1.2.1 From 6ba884530f6d1132621e1050175ab3384ebdcbb5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 10 Jul 2016 14:59:36 -0500 Subject: Fix typo in spec --- spec/lib/banzai/filter/blockquote_fence_filter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb index 19543bde838..2799249ae3e 100644 --- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb +++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe Banzai::Filter::BlockquoteFenceFilter, lib: true do include FilterSpecHelper - it 'convers blockquote fences to blockquote lines' do + 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')) -- cgit v1.2.1 From 94e9d571c03932a0d71a5f10720d95ef014164c2 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 11 Jul 2016 08:56:45 +0200 Subject: remove fix validatable import url migration --- ...20160620110927_fix_no_validatable_import_url.rb | 106 --------------------- 1 file changed, 106 deletions(-) delete mode 100644 db/migrate/20160620110927_fix_no_validatable_import_url.rb diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb deleted file mode 100644 index a3f5073d511..00000000000 --- a/db/migrate/20160620110927_fix_no_validatable_import_url.rb +++ /dev/null @@ -1,106 +0,0 @@ -# Updates project records containing invalid URLs using the AddressableUrlValidator. -# This is optimized assuming the number of invalid records is low, but -# we still need to loop through all the projects with an +import_url+ -# so we use batching for the latter. -# -# This migration is non-reversible as we would have to keep the old data. - -class FixNoValidatableImportUrl < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - class SqlBatches - - attr_reader :results, :query - - def initialize(batch_size: 1000, query:) - @offset = 0 - @batch_size = batch_size - @query = query - @results = [] - end - - def next? - @results = ActiveRecord::Base.connection.exec_query(batched_sql) - @offset += @batch_size - @results.any? - end - - private - - def batched_sql - "#{@query} LIMIT #{@batch_size} OFFSET #{@offset}" - end - end - - # AddressableValidator - Snapshot of AddressableUrlValidator - module AddressableUrlValidatorSnap - extend self - - def valid_url?(value) - return false unless value - - valid_uri?(value) && valid_protocol?(value) - rescue Addressable::URI::InvalidURIError - false - end - - def valid_uri?(value) - Addressable::URI.parse(value).is_a?(Addressable::URI) - end - - def valid_protocol?(value) - value =~ /\A#{URI.regexp(%w(http https ssh git))}\z/ - end - end - - def up - unless defined?(Addressable::URI::InvalidURIError) - say('Skipping cleaning up invalid import URLs as class from Addressable is missing') - return - end - - say('Nullifying empty import URLs') - - nullify_empty_urls - - say('Cleaning up invalid import URLs... This may take a few minutes if we have a large number of imported projects.') - - process_invalid_import_urls - end - - def process_invalid_import_urls - batches = SqlBatches.new(query: "SELECT id, import_url FROM projects WHERE import_url IS NOT NULL") - - while batches.next? - project_ids = [] - - batches.results.each do |result| - project_ids << result['id'] unless valid_url?(result['import_url']) - end - - process_batch(project_ids) - end - - end - - def process_batch(project_ids) - Thread.new do - begin - project_ids.each { |project_id| cleanup_import_url(project_id) } - ensure - ActiveRecord::Base.connection.close - end - end.join - end - - def valid_url?(url) - AddressableUrlValidatorSnap.valid_url?(url) - end - - def cleanup_import_url(project_id) - execute("UPDATE projects SET import_url = NULL WHERE id = #{project_id}") - end - - def nullify_empty_urls - execute("UPDATE projects SET import_url = NULL WHERE import_url = ''") - end -end -- cgit v1.2.1 From 99f7b6d24684dcb9dbff79c8ff08f8c7580dcafe Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 11 Jul 2016 09:01:09 +0200 Subject: spec and fix for sanitize method --- lib/gitlab/url_sanitizer.rb | 2 ++ spec/lib/gitlab/url_sanitizer_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 86ed18fb50d..19dad699edf 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -4,6 +4,8 @@ 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) 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 -- cgit v1.2.1 From d794c96adea80dfcb36e4b202123f0d051efeceb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 11 Jul 2016 14:56:45 +0300 Subject: Lower case todya issue colors Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/issues.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 05e1713d64a..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: #F8FEEF; - border-color: #E1E8D5; + background: #f8feef; + border-color: #e1e8d5; } &.closed { -- cgit v1.2.1 From 8fa19571baa913a422aa8a7501e02c23c65e5402 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 11 Jul 2016 14:58:58 +0300 Subject: Refactor box-shadow style for sidebar to satisfy css lint Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/sidebar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 78eefa538c3..1a2220f3b40 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -17,7 +17,7 @@ height: 100%; overflow: hidden; transition: width $sidebar-transition-duration; - @include box-shadow(2px 0px 16px 0px #bbb); + @include box-shadow(2px 0 16px 0 #bbb); } } -- cgit v1.2.1