diff options
1089 files changed, 21104 insertions, 8786 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b917c645ff8..ff8aa351226 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,6 +18,7 @@ variables: SIMPLECOV: "true" USE_DB: "true" USE_BUNDLE_INSTALL: "true" + GIT_DEPTH: "20" before_script: - source ./scripts/prepare_build.sh diff --git a/.rubocop.yml b/.rubocop.yml index dbdabbb9d4c..3aac8401848 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,6 @@ -require: rubocop-rspec +require: + - rubocop-rspec + - ./rubocop/rubocop AllCops: TargetRubyVersion: 2.1 @@ -191,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: @@ -282,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. @@ -532,11 +534,11 @@ Style/SingleLineMethods: # Use spaces after colons. Style/SpaceAfterColon: - Enabled: false + Enabled: true # Use spaces after commas. Style/SpaceAfterComma: - Enabled: false + Enabled: true # Do not put a space between a method name and the opening parenthesis in a # method definition. @@ -679,7 +681,7 @@ Style/UnlessElse: # Checks for %W when interpolation is not needed. Style/UnneededCapitalW: - Enabled: false + Enabled: true # TODO: Enable UnneededInterpolation Cop. # Checks for strings that are just an interpolated expression. diff --git a/CHANGELOG b/CHANGELOG index 4a8188ea060..3e4a10bb5a3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,16 +1,116 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.10.0 (unreleased) + - Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Replace Haml with Hamlit to make view rendering faster. !3666 + - Refactor repository paths handling to allow multiple git mount points + - Add Application Setting to configure default Repository Path for new projects - 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 + - Let Workhorse serve format-patch diffs + - 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 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 - - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. + - 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 + - Allow importing from Github using Personal Access Tokens. (Eric K Idema) + - API: Todos !3188 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling) + - Add "Enabled Git access protocols" to Application Settings - 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. + - 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 + - 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 + - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab + - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. + - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) + - Set import_url validation to be more strict + - Memoize MR merged/closed events retrieval + - Don't render discussion notes when requesting diff tab through AJAX + - Add basic system information like memory and disk usage to the admin panel + - Don't garbage collect commits that have related DB records like comments + - More descriptive message for git hooks and file locks + - 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 + - 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 + - Fixed avatar alignment in new MR view. !5095 + - Fix diff comments not showing up in activity feed. !5069 + - Add index on both Award Emoji user and name. !5061 + - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056 + - Re-enable import button when import process fails due to namespace already being taken. !5053 + - Fix snippets comments not displayed. !5045 + - Fix emoji paths in relative root configurations. !5027 + - Fix issues importing events in Import/Export. !4987 + - Fixed 'use shortcuts' button on docs. !4979 + - Admin should be able to turn shared runners into specific ones. !4961 + - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi) + - Improve the request / withdraw access button. !4860 + +v 8.9.4 + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + - Fixed search field blur not removing focus. !4704 + - Resolve "Sub nav isn't showing on file view". !4890 + - Fixes middle click and double request when navigating through the file browser. !4891 + - Fixed URL on label button when filtering. !4897 + - Fixed commit avatar alignment. !4933 + - Do not show build retry link when build is active. !4967 + - Fix restore Rake task warning message output. !4980 + - Handle external issues in IssueReferenceFilter. !4988 + - Expiry date on pinned nav cookie. !5009 + - Updated breakpoint for sidebar pinning. !5019 + +v 8.9.3 + - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 + - Fix rendering of commit notes. !4953 + - Resolve "Pin should show up at 1280px min". !4947 + - Switched mobile button icons to ellipsis and angle. !4944 + - Correctly returns todo ID after creating todo. !4941 + - Better debugging for memory killer middleware. !4936 + - Remove duplicate new page btn from edit wiki. !4904 + - Use clock_gettime for all performance timestamps. !4899 + - Use memorized tags array when searching tags by name. !4859 + - Fixed avatar alignment in new MR view. !4901 + - Removed fade when filtering results. !4932 + - Fix missing avatar on system notes. !4954 + - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 + - Use update_columns to bypass all the dirty code on active_record. !4985 + - Fix restore Rake task warning message output !4980 + +v 8.9.2 + - Fix visibility of snippets when searching. + - Fix an information disclosure when requesting access to a group containing private projects. + - Update omniauth-saml to 1.6.0 !4951 v 8.9.1 - Refactor labels documentation. !3347 @@ -51,11 +151,12 @@ v 8.9.1 - Fix pagination when sorting by columns with lots of ties (like priority) - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - - Add API endpoint for a group issues !4520 (mahcsig) - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912 - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 + - Remove duplicate 'New Page' button on edit wiki page v 8.9.0 + - Fix group visibility form layout in application settings - Fix builds API response not including commit data - Fix error when CI job variables key specified but not defined - Fix pipeline status when there are no builds in pipeline @@ -151,6 +252,7 @@ v 8.9.0 - Add Application Setting to configure Container Registry token expire delay (default 5min) - Cache assigned issue and merge request counts in sidebar nav - Use Knapsack only in CI environment + - Updated project creation page to match new UI #2542 - Cache project build count in sidebar nav - Add milestone expire date to the right sidebar - Manually mark a issue or merge request as a todo @@ -200,12 +302,21 @@ v 8.9.0 - Filter parameters for request_uri value on instrumented transactions. - Remove duplicated keys add UNIQUE index to keys fingerprint column - ExtractsPath get ref_names from repository cache, if not there access git. + - Show a flash warning about the error detail of XHR requests which failed with status code 404 and 500 - Cache user todo counts from TodoService - Ensure Todos counters doesn't count Todos for projects pending delete - Add left/right arrows horizontal navigation - Add tooltip to pin/unpin navbar - Add new sub nav style to Wiki and Graphs sub navigation +v 8.8.7 + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +v 8.8.6 + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + v 8.8.5 - Import GitHub repositories respecting the API rate limit !4166 - Fix todos page throwing errors when you have a project pending deletion !4300 @@ -336,6 +447,14 @@ v 8.8.0 - When creating a .gitignore file a dropdown with templates will be provided - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 +v 8.7.9 + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +v 8.7.8 + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + v 8.7.7 - Fix import by `Any Git URL` broken if the URL contains a space - Prevent unauthorized access to other projects build traces diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 4a36342fcab..944880fa15e 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.0.0 +3.2.0 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 8bd6ba8c5c3..879be8a98fc 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.5 +0.7.7 @@ -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,15 +11,15 @@ gem 'responders', '~> 2.0' gem 'sprockets', '~> 3.6.0' # Default values for AR models -gem "default_value_for", "~> 3.0.0" +gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem "mysql2", '~> 0.3.16', group: :mysql -gem "pg", '~> 0.18.2', group: :postgres +gem 'mysql2', '~> 0.3.16', group: :mysql +gem 'pg', '~> 0.18.2', group: :postgres # Authentication libraries gem 'devise', '~> 4.0' -gem 'doorkeeper', '~> 3.1' +gem 'doorkeeper', '~> 4.0' gem 'omniauth', '~> 1.3.1' gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' @@ -30,7 +30,7 @@ gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos -gem 'omniauth-saml', '~> 1.5.0' +gem 'omniauth-saml', '~> 1.6.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' @@ -48,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' @@ -91,22 +91,23 @@ gem 'fog-core', '~> 1.40' gem 'fog-local', '~> 0.3' gem 'fog-google', '~> 0.3' gem 'fog-openstack', '~> 0.1' +gem 'fog-rackspace', '~> 0.1.1' # for aws storage -gem "unf", '~> 0.1.4' +gem 'unf', '~> 0.1.4' # Authorization -gem "six", '~> 0.2.0' +gem 'six', '~> 0.2.0' # Seed data -gem "seed-fu", '~> 2.3.5' +gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'github-markup', '~> 1.3.1' gem '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' @@ -123,29 +124,29 @@ gem 'diffy', '~> 3.0.3' # Application server group :unicorn do - gem "unicorn", '~> 4.9.0' + gem 'unicorn', '~> 4.9.0' gem 'unicorn-worker-killer', '~> 0.4.2' end # State machine -gem "state_machines-activerecord", '~> 0.4.0' +gem 'state_machines-activerecord', '~> 0.4.0' # Run events after state machine commits -gem 'after_commit_queue' +gem 'after_commit_queue', '~> 1.3.0' # Issue tags gem 'acts-as-taggable-on', '~> 3.4' # Background jobs -gem 'sinatra', '~> 1.4.4', require: nil +gem 'sinatra', '~> 1.4.4', require: false gem 'sidekiq', '~> 4.0' gem 'sidekiq-cron', '~> 0.4.0' -gem 'redis-namespace' +gem 'redis-namespace', '~> 1.5.2' # HTTP requests -gem "httparty", '~> 0.13.3' +gem 'httparty', '~> 0.13.3' # Colored output to console -gem "rainbow", '~> 2.1.0' +gem 'rainbow', '~> 2.1.0' # GitLab settings gem 'settingslogic', '~> 2.0.9' @@ -155,7 +156,7 @@ gem 'settingslogic', '~> 2.0.9' gem 'version_sorter', '~> 2.0.0' # Cache -gem "redis-rails", '~> 4.0.0' +gem 'redis-rails', '~> 4.0.0' # Redis gem 'redis', '~> 3.2' @@ -168,13 +169,13 @@ gem 'tinder', '~> 1.10.0' gem 'hipchat', '~> 1.5.0' # Flowdock integration -gem "gitlab-flowdock-git-hook", "~> 1.0.1" +gem 'gitlab-flowdock-git-hook', '~> 1.0.1' # Gemnasium integration -gem "gemnasium-gitlab-service", "~> 0.2" +gem 'gemnasium-gitlab-service', '~> 0.2' # Slack integration -gem "slack-notifier", "~> 1.2.0" +gem 'slack-notifier', '~> 1.2.0' # Asana integration gem 'asana', '~> 0.4.0' @@ -186,20 +187,20 @@ gem 'ruby-fogbugz', '~> 0.2.1' gem 'd3_rails', '~> 3.5.0' # underscore-rails -gem "underscore-rails", "~> 1.8.0" +gem 'underscore-rails', '~> 1.8.0' # Sanitize user input -gem "sanitize", '~> 2.0' +gem 'sanitize', '~> 2.0' gem 'babosa', '~> 1.0.2' # Sanitizes SVG input -gem "loofah", "~> 2.0.3" +gem 'loofah', '~> 2.0.3' # Working with license gem 'licensee', '~> 8.0.0' # Protect against bruteforcing -gem "rack-attack", '~> 4.3.1' +gem 'rack-attack', '~> 4.3.1' # Ace editor gem 'ace-rails-ap', '~> 4.0.2' @@ -213,16 +214,16 @@ gem 'charlock_holmes', '~> 0.7.3' # Parse duration gem 'chronic_duration', '~> 0.10.6' -gem "sass-rails", '~> 5.0.0' -gem "coffee-rails", '~> 4.1.0' -gem "uglifier", '~> 2.7.2' +gem 'sass-rails', '~> 5.0.0' +gem 'coffee-rails', '~> 4.1.0' +gem 'uglifier', '~> 2.7.2' gem 'turbolinks', '~> 2.5.0' gem 'jquery-turbolinks', '~> 2.1.0' gem 'addressable', '~> 2.3.8' gem 'bootstrap-sass', '~> 3.3.0' gem 'font-awesome-rails', '~> 4.6.1' -gem 'gitlab_emoji', '~> 0.3.0' +gem 'gemojione', '~> 2.6' gem 'gon', '~> 6.0.1' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' @@ -246,14 +247,13 @@ group :metrics do end group :development do - gem "foreman" + gem 'foreman', '~> 0.78.0' gem 'brakeman', '~> 3.3.0', require: false gem 'letter_opener_web', '~> 1.3.0' - gem 'quiet_assets', '~> 1.0.2' gem 'rerun', '~> 0.11.0' - gem 'bullet', require: false - gem 'rblineprof', platform: :mri, require: false + gem 'bullet', '~> 5.0.0', require: false + gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'web-console', '~> 2.0' # Better errors handler @@ -261,23 +261,23 @@ group :development do gem 'binding_of_caller', '~> 0.7.2' # Docs generator - gem "sdoc", '~> 0.3.20' + gem 'sdoc', '~> 0.3.20' # thin instead webrick - gem 'thin', '~> 1.6.1' + gem 'thin', '~> 1.7.0' end group :development, :test do - gem 'byebug', platform: :mri - gem 'pry-rails' + gem 'byebug', '~> 8.2.1', platform: :mri + gem 'pry-rails', '~> 0.3.4' gem 'awesome_print', '~> 1.2.0', require: false gem 'fuubar', '~> 2.0.0' gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails', '~> 4.6.0' - gem 'rspec-rails', '~> 3.4.0' - gem 'rspec-retry' + gem 'rspec-rails', '~> 3.5.0' + gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' @@ -302,16 +302,15 @@ group :development, :test do gem 'rubocop', '~> 0.40.0', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false - gem 'coveralls', '~> 0.8.2', require: false gem 'simplecov', '~> 0.11.0', require: false - gem 'flog', require: false - gem 'flay', require: false - gem 'bundler-audit', require: false + gem 'flog', '~> 4.3.2', require: false + gem 'flay', '~> 2.6.1', require: false + gem 'bundler-audit', '~> 0.5.0', require: false - gem 'benchmark-ips', require: false + gem 'benchmark-ips', '~> 2.3.0', require: false - gem "license_finder", require: false - gem 'knapsack' + gem 'license_finder', '~> 2.1.0', require: false + gem 'knapsack', '~> 1.11.0' end group :test do @@ -319,30 +318,34 @@ group :test do gem 'email_spec', '~> 1.6.0' gem 'webmock', '~> 1.21.0' gem 'test_after_commit', '~> 0.4.2' - gem 'sham_rack' + gem 'sham_rack', '~> 1.3.6' end group :production do - gem "gitlab_meta", '7.0' + gem 'gitlab_meta', '7.0' end -gem "newrelic_rpm", '~> 3.14' +gem 'newrelic_rpm', '~> 3.14' gem 'octokit', '~> 4.3.0' -gem "mail_room", "~> 0.8" +gem 'mail_room', '~> 0.8' gem 'email_reply_parser', '~> 0.5.8' ## CI gem 'activerecord-session_store', '~> 1.0.0' -gem "nested_form", '~> 0.3.2' +gem 'nested_form', '~> 0.3.2' # OAuth -gem 'oauth2', '~> 1.0.0' +gem 'oauth2', '~> 1.2.0' # Soft deletion -gem "paranoia", "~> 2.0" +gem 'paranoia', '~> 2.0' # Health check gem 'health_check', '~> 1.5.1' + +# System information +gem 'vmstat', '~> 2.1.0' +gem 'sys-filesystem', '~> 1.1.6' diff --git a/Gemfile.lock b/Gemfile.lock index 3f3ceb667b5..055596b056f 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) @@ -141,12 +141,6 @@ GEM colorize (0.7.7) concurrent-ruby (1.0.2) connection_pool (2.2.0) - coveralls (0.8.13) - json (~> 1.8) - simplecov (~> 0.11.0) - term-ansicolor (~> 1.3) - thor (~> 0.19.1) - tins (~> 1.6.0) crack (0.4.3) safe_yaml (~> 1.0.0) creole (0.5.0) @@ -177,8 +171,8 @@ GEM diff-lcs (1.2.5) diffy (3.0.7) docile (1.1.5) - doorkeeper (3.1.0) - railties (>= 3.2) + doorkeeper (4.0.0) + railties (>= 4.2) dropzonejs-rails (0.7.2) rails (> 3.1) email_reply_parser (0.5.8) @@ -243,6 +237,11 @@ GEM fog-core (>= 1.39) fog-json (>= 1.0) ipaddress (>= 0.8) + fog-rackspace (0.1.1) + fog-core (>= 1.35) + fog-json (>= 1.0) + fog-xml (>= 0.1) + ipaddress (>= 0.8) fog-xml (0.1.2) fog-core nokogiri (~> 1.5, >= 1.5.11) @@ -256,7 +255,7 @@ GEM ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) - gemojione (2.2.1) + gemojione (2.6.1) json get_process_mem (0.2.0) gherkin-ruby (0.3.2) @@ -275,8 +274,6 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_emoji (0.3.1) - gemojione (~> 2.2, >= 2.2.1) gitlab_git (10.2.3) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) @@ -356,7 +353,7 @@ GEM jquery-ui-rails (5.0.5) railties (>= 3.2.16) json (1.8.3) - jwt (1.5.2) + jwt (1.5.4) kaminari (0.17.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) @@ -396,7 +393,7 @@ GEM mini_portile2 (2.1.0) minitest (5.7.0) mousetrap-rails (1.4.6) - multi_json (1.11.2) + multi_json (1.12.1) multi_xml (0.5.5) multipart-post (2.0.0) mysql2 (0.3.20) @@ -409,12 +406,12 @@ GEM pkg-config (~> 1.1.7) numerizer (0.1.1) oauth (0.4.7) - oauth2 (1.0.0) + oauth2 (1.2.0) faraday (>= 0.8, < 0.10) jwt (~> 1.0) multi_json (~> 1.3) multi_xml (~> 0.5) - rack (~> 1.2) + rack (>= 1.2, < 3) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) omniauth (1.3.1) @@ -459,9 +456,9 @@ GEM omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-saml (1.5.0) + omniauth-saml (1.6.0) omniauth (~> 1.3) - ruby-saml (~> 1.1, >= 1.1.1) + ruby-saml (~> 1.3) omniauth-shibboleth (1.2.1) omniauth (>= 1.0.0) omniauth-twitter (1.2.1) @@ -500,8 +497,6 @@ GEM pry-rails (0.3.4) pry (>= 0.9.10) pyu-ruby-sasl (0.0.3.3) - quiet_assets (1.0.3) - railties (>= 3.1, < 5.0) rack (1.6.4) rack-accept (0.4.5) rack (>= 0.4) @@ -557,7 +552,7 @@ GEM recaptcha (3.0.0) json redcarpet (3.3.3) - redis (3.3.0) + redis (3.2.2) redis-actionpack (4.0.1) actionpack (~> 4) redis-rack (~> 1.5.0) @@ -581,36 +576,36 @@ 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) 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) @@ -622,9 +617,8 @@ GEM ruby-fogbugz (0.2.1) crack (~> 0.4) ruby-progressbar (1.8.1) - ruby-saml (1.1.2) + ruby-saml (1.3.0) nokogiri (>= 1.5.10) - uuid (~> 2.3) ruby_parser (3.8.2) sexp_processor (~> 4.1) rubyntlm (0.5.2) @@ -636,8 +630,8 @@ GEM sanitize (2.1.0) nokogiri (>= 1.4.4) sass (3.4.22) - sass-rails (5.0.4) - railties (>= 4.0.0, < 5.0) + sass-rails (5.0.5) + railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) @@ -651,9 +645,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) @@ -664,10 +658,11 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.1.2) + sidekiq (4.1.4) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) redis (~> 3.2, >= 3.2.1) + sinatra (>= 1.4.7) sidekiq-cron (0.4.0) redis-namespace (>= 1.5.2) rufus-scheduler (>= 2.0.24) @@ -678,8 +673,8 @@ GEM json (~> 1.8) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - sinatra (1.4.6) - rack (~> 1.4) + sinatra (1.4.7) + rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) six (0.2.0) @@ -695,17 +690,17 @@ GEM spinach (>= 0.4) spinach-rerun-reporter (0.0.2) spinach (~> 0.8) - spring (1.7.1) + spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.1.0) spring (>= 0.9.1) spring-commands-teaspoon (0.0.2) spring (>= 0.9.1) - sprockets (3.6.0) + sprockets (3.6.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) @@ -717,6 +712,8 @@ GEM activerecord (>= 4.1, < 5.1) state_machines-activemodel (>= 0.3.0) stringex (2.5.2) + sys-filesystem (1.1.6) + ffi systemu (2.6.5) task_list (1.0.2) html-pipeline @@ -725,14 +722,12 @@ GEM teaspoon-jasmine (2.2.0) teaspoon (>= 1.0.0) temple (0.7.7) - term-ansicolor (1.3.2) - tins (~> 1.0) 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) @@ -747,7 +742,6 @@ GEM mime-types multi_json (~> 1.7) twitter-stream (~> 0.1) - tins (1.6.0) turbolinks (2.5.3) coffee-rails twitter-stream (0.1.16) @@ -781,6 +775,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) + vmstat (2.1.0) warden (1.2.6) rack (>= 1.0) web-console (2.3.0) @@ -806,7 +801,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) @@ -836,7 +831,6 @@ DEPENDENCIES chronic_duration (~> 0.10.6) coffee-rails (~> 4.1.0) connection_pool (~> 2.0) - coveralls (~> 0.8.2) creole (~> 0.5.0) d3_rails (~> 3.5.0) database_cleaner (~> 1.4.0) @@ -844,7 +838,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) @@ -858,14 +852,15 @@ DEPENDENCIES fog-google (~> 0.3) fog-local (~> 0.3) fog-openstack (~> 0.1) + fog-rackspace (~> 0.1.1) font-awesome-rails (~> 4.6.1) 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) @@ -900,7 +895,7 @@ DEPENDENCIES net-ssh (~> 3.0.1) newrelic_rpm (~> 3.14) nokogiri (~> 1.6.7, >= 1.6.7.2) - oauth2 (~> 1.0.0) + oauth2 (~> 1.2.0) octokit (~> 4.3.0) omniauth (~> 1.3.1) omniauth-auth0 (~> 1.4.1) @@ -912,7 +907,7 @@ DEPENDENCIES omniauth-gitlab (~> 1.0.0) omniauth-google-oauth2 (~> 0.2.0) omniauth-kerberos (~> 0.3.0) - omniauth-saml (~> 1.5.0) + omniauth-saml (~> 1.6.0) omniauth-shibboleth (~> 1.2.0) omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) @@ -922,7 +917,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) @@ -941,7 +935,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) @@ -970,11 +964,12 @@ DEPENDENCIES spring-commands-teaspoon (~> 0.0.2) sprockets (~> 3.6.0) state_machines-activerecord (~> 0.4.0) + sys-filesystem (~> 1.1.6) task_list (~> 1.0.2) teaspoon (~> 1.1.0) teaspoon-jasmine (~> 2.2.0) test_after_commit (~> 0.4.2) - thin (~> 1.6.1) + thin (~> 1.7.0) tinder (~> 1.10.0) turbolinks (~> 2.5.0) u2f (~> 0.2.1) @@ -985,6 +980,7 @@ DEPENDENCIES unicorn-worker-killer (~> 0.4.2) version_sorter (~> 2.0.0) virtus (~> 1.0.1) + vmstat (~> 2.1.0) web-console (~> 2.0) webmock (~> 1.21.0) wikicloth (= 0.8.1) diff --git a/app/assets/images/auth_buttons/azure_64.png b/app/assets/images/auth_buttons/azure_64.png Binary files differindex a82c751e001..85de7793440 100644 --- a/app/assets/images/auth_buttons/azure_64.png +++ b/app/assets/images/auth_buttons/azure_64.png diff --git a/app/assets/images/auth_buttons/bitbucket_64.png b/app/assets/images/auth_buttons/bitbucket_64.png Binary files differindex 4b90a57bc7d..b3d022a5a70 100644 --- a/app/assets/images/auth_buttons/bitbucket_64.png +++ b/app/assets/images/auth_buttons/bitbucket_64.png diff --git a/app/assets/images/auth_buttons/facebook_64.png b/app/assets/images/auth_buttons/facebook_64.png Binary files differindex 1f1a80d7368..71ffb1c6a1f 100644 --- a/app/assets/images/auth_buttons/facebook_64.png +++ b/app/assets/images/auth_buttons/facebook_64.png diff --git a/app/assets/images/auth_buttons/github_64.png b/app/assets/images/auth_buttons/github_64.png Binary files differindex 182a1a3f734..1fa19c55d2f 100644 --- a/app/assets/images/auth_buttons/github_64.png +++ b/app/assets/images/auth_buttons/github_64.png diff --git a/app/assets/images/auth_buttons/gitlab_64.png b/app/assets/images/auth_buttons/gitlab_64.png Binary files differindex 99a40583b3a..f675678dc9d 100644 --- a/app/assets/images/auth_buttons/gitlab_64.png +++ b/app/assets/images/auth_buttons/gitlab_64.png diff --git a/app/assets/images/auth_buttons/google_64.png b/app/assets/images/auth_buttons/google_64.png Binary files differindex fb64f8bee68..720824230a5 100644 --- a/app/assets/images/auth_buttons/google_64.png +++ b/app/assets/images/auth_buttons/google_64.png diff --git a/app/assets/images/auth_buttons/twitter_64.png b/app/assets/images/auth_buttons/twitter_64.png Binary files differindex e3bd9169a34..a4f14de57ae 100644 --- a/app/assets/images/auth_buttons/twitter_64.png +++ b/app/assets/images/auth_buttons/twitter_64.png diff --git a/app/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png Binary files differindex e5fe659ba63..5c55bc79dec 100644 --- a/app/assets/images/bg_fallback.png +++ b/app/assets/images/bg_fallback.png diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png Binary files differindex 2ef58e52549..8855babf147 100644 --- a/app/assets/images/dark-scheme-preview.png +++ b/app/assets/images/dark-scheme-preview.png diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png Binary files differindex 1e7cf79ea45..6bacb0e92b6 100644 --- a/app/assets/images/emoji.png +++ b/app/assets/images/emoji.png diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png Binary files differindex 74d67f7520d..99588b56616 100644 --- a/app/assets/images/emoji@2x.png +++ b/app/assets/images/emoji@2x.png diff --git a/app/assets/images/gitlab_logo.png b/app/assets/images/gitlab_logo.png Binary files differindex 0c157546b9c..ca30b459019 100644 --- a/app/assets/images/gitlab_logo.png +++ b/app/assets/images/gitlab_logo.png diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png Binary files differindex 78f17a9af79..4a55fdc225a 100644 --- a/app/assets/images/gitorious-logo-black.png +++ b/app/assets/images/gitorious-logo-black.png diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png Binary files differindex 4962cffba31..5eaa327d3df 100644 --- a/app/assets/images/gitorious-logo-blue.png +++ b/app/assets/images/gitorious-logo-blue.png diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png Binary files differindex 7d89da97e11..5b55e12571c 100644 --- a/app/assets/images/icon-link.png +++ b/app/assets/images/icon-link.png diff --git a/app/assets/images/images.png b/app/assets/images/images.png Binary files differindex ad146246caf..bd60de994c4 100644 --- a/app/assets/images/images.png +++ b/app/assets/images/images.png diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png Binary files differindex fbb339c6a91..d9c7d2d8041 100644 --- a/app/assets/images/monokai-scheme-preview.png +++ b/app/assets/images/monokai-scheme-preview.png diff --git a/app/assets/images/msapplication-tile.png b/app/assets/images/msapplication-tile.png Binary files differindex 58bbf2b20cb..1e0e2ed73ce 100644 --- a/app/assets/images/msapplication-tile.png +++ b/app/assets/images/msapplication-tile.png diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png Binary files differindex 8287acbce13..5383d687b53 100644 --- a/app/assets/images/no_avatar.png +++ b/app/assets/images/no_avatar.png diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png Binary files differindex bfb31bb2184..71612d55286 100644 --- a/app/assets/images/no_group_avatar.png +++ b/app/assets/images/no_group_avatar.png diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png Binary files differindex 884378ec96a..52ad11ab7a1 100644 --- a/app/assets/images/slider_handles.png +++ b/app/assets/images/slider_handles.png diff --git a/app/assets/images/touch-icon-ipad-retina.png b/app/assets/images/touch-icon-ipad-retina.png Binary files differindex feb32b48ec9..516dc2f4710 100644 --- a/app/assets/images/touch-icon-ipad-retina.png +++ b/app/assets/images/touch-icon-ipad-retina.png diff --git a/app/assets/images/touch-icon-ipad.png b/app/assets/images/touch-icon-ipad.png Binary files differindex a6ddc543509..b2093d015b8 100644 --- a/app/assets/images/touch-icon-ipad.png +++ b/app/assets/images/touch-icon-ipad.png diff --git a/app/assets/images/touch-icon-iphone-retina.png b/app/assets/images/touch-icon-iphone-retina.png Binary files differindex 8bf7ccb7534..438654e0d20 100644 --- a/app/assets/images/touch-icon-iphone-retina.png +++ b/app/assets/images/touch-icon-iphone-retina.png diff --git a/app/assets/images/touch-icon-iphone.png b/app/assets/images/touch-icon-iphone.png Binary files differindex 87da550f8be..e5f87fbbcf6 100644 --- a/app/assets/images/touch-icon-iphone.png +++ b/app/assets/images/touch-icon-iphone.png diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 5c5a4ca7670..64da503c35f 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) -> @@ -185,6 +184,15 @@ $ -> else buttons.enable() + $(document).ajaxError (e, xhrObj, xhrSetting, xhrErrorText) -> + + if xhrObj.status is 401 + new Flash 'You need to be logged in.', 'alert' + + else if xhrObj.status in [ 404, 500 ] + new Flash 'Something went wrong on our end.', 'alert' + + # Show/Hide the profile menu when hovering the account box $('.account-box').hover -> $(@).toggleClass('hover') @@ -199,7 +207,6 @@ $ -> $('.header-content .header-logo').toggle() $('.header-content .navbar-collapse').toggle() $('.navbar-toggle').toggleClass('active') - $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") # Show/hide comments on diff $body.on "click", ".js-toggle-diff-comments", (e) -> @@ -261,8 +268,8 @@ $ -> new Aside() # Sidenav pinning - if $window.width() < 1440 and $.cookie('pin_nav') is 'true' - $.cookie('pin_nav', 'false', { path: '/' }) + if $window.width() < 1024 and $.cookie('pin_nav') is 'true' + $.cookie('pin_nav', 'false', { path: '/', expires: 365 * 10 }) $('.page-with-sidebar') .toggleClass('page-sidebar-collapsed page-sidebar-expanded') .removeClass('page-sidebar-pinned') @@ -293,7 +300,7 @@ $ -> .toggleClass('header-collapsed header-expanded') # Save settings - $.cookie 'pin_nav', doPinNav, { path: '/' } + $.cookie 'pin_nav', doPinNav, { path: '/', expires: 365 * 10 } if $.cookie('pin_nav') is 'true' or doPinNav tooltipText = 'Unpin navigation' diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 030f1564862..37d0adaa625 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -341,7 +341,9 @@ class @AwardsHandler for emoji in frequentlyUsedEmojis $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul) - $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used')) + $('.emoji-menu-content') + .prepend(ul) + .prepend($('<h5>').text('Frequently used')) @frequentEmojiBlockRendered = true @@ -356,7 +358,7 @@ class @AwardsHandler if term # Generate a search result block - h5 = $('<h5>').text('Search results').addClass('emoji-search') + h5 = $('<h5>').text('Search results') found_emojis = @searchEmojis(term).show() ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis) $('.emoji-menu-content ul, .emoji-menu-content h5').hide() 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/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 7fbff9214cf..74fd77cf7ab 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -84,6 +84,8 @@ class Dispatcher new Activities() when 'groups:show' shortcut_handler = new ShortcutsNavigation() + new NotificationsForm() + new NotificationsDropdown() when 'groups:group_members:index' new GroupMembers() new UsersSelect() @@ -125,11 +127,10 @@ class Dispatcher when 'groups' new UsersSelect() when 'projects' - new NamespaceSelect() + new NamespaceSelects() when 'dashboard', 'root' shortcut_handler = new ShortcutsDashboardNavigation() when 'profiles' - new Profile() new NotificationsForm() new NotificationsDropdown() when 'projects' diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index e2194589b38..665246e2a7d 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -70,12 +70,12 @@ class @DropzoneInput pasteText response.link.markdown return - error: (temp, errorMessage) -> + error: (temp) -> errorAlert = $(form).find('.error-alert') checkIfMsgExists = errorAlert.children().length if checkIfMsgExists is 0 errorAlert.append divAlert - $(".div-dropzone-alert").append btnAlert + errorMessage + $(".div-dropzone-alert").append "#{btnAlert}Attaching the file failed." return totaluploadprogress: (totalUploadProgress) -> diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index 4f73d215b85..b76d214790a 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -4,11 +4,19 @@ class @Flash @flash.html("") innerDiv = $('<div/>', - class: "flash-#{type}", - text: message + class: "flash-#{type}" ) innerDiv.appendTo(".flash-container") + textDiv = $("<div/>", + class: "flash-text", + text: message + ) + textDiv.appendTo(innerDiv) + + if @flash.parent().hasClass('content-wrapper') + textDiv.addClass('container-fluid container-limited') + @flash.click -> $(@).fadeOut() @flash.show() diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 190bb38504c..4a851d9c9fb 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 @@ -188,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) @@ -205,6 +207,7 @@ GitLab.GfmAutoComplete = $.getJSON(dataSource) loadData: (data) -> + @cachedData = data @dataLoaded = true # load members diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 703128fecb3..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 @@ -186,6 +187,8 @@ class GitLabDropdown @fullData = data @parseData @fullData + + @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input } # Init filterable @@ -193,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 @@ -218,6 +222,13 @@ class GitLabDropdown @dropdown.on 'keyup', (e) => if e.which is 27 # Escape key $('.dropdown-menu-close', @dropdown).trigger 'click' + @dropdown.on 'blur', 'a', (e) => + if e.relatedTarget? + $relatedTarget = $(e.relatedTarget) + $dropdownMenu = $relatedTarget.closest('.dropdown-menu') + + if $dropdownMenu.length is 0 + @dropdown.removeClass('open') if @dropdown.find(".dropdown-toggle-page").length @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => @@ -521,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/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee index b0edc895649..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() diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee index 6a108c033ea..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){ %> - <span class="label-row btn-group" role="group" aria-label="<%= _.escape(label.title) %>" style="color: <%= label.text_color %>;"> - <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%= label.color %>;" title="<%= _.escape(label.description) %>" data-container="body"> - <%= _.escape(label.title) %> + <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> + <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> + <%- label.title %> </a> - <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%= label.color %>;" data-label="<%= _.escape(label.title) %>"> + <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> @@ -59,13 +59,12 @@ issuable_created = false filterResults: (form) => formData = form.serialize() - $('.issues-holder, .merge-requests-holder').css('opacity', '0.5') formAction = form.attr('action') issuesUrl = formAction issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}") issuesUrl += formData - Turbolinks.visit(issuesUrl); + Turbolinks.visit(issuesUrl) initChecks: -> @issuableBulkActions = $('.bulk-update').data('bulkActions') diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 157361404e0..f446aa49cde 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -99,7 +99,7 @@ class @Issue # If the user doesn't have the required permissions the container isn't # rendered at all. - return unless $container + return if $container.length is 0 $.getJSON($container.data('path')) .error -> diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index e95fd96a83f..7688609b301 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -32,9 +32,9 @@ class @LabelsSelect if issueUpdateURL labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> - <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%= _.escape(label.title) %>"> - <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;"> - <%= _.escape(label.title) %> + <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> + <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> + <%- label.title %> </span> </a> <% }); %>' @@ -261,7 +261,7 @@ class @LabelsSelect $a.attr('data-label-id', label.id) $a.addClass(selectedClass.join(' ')) - .html("#{colorEl} #{_.escape(label.title)}") + .html("#{colorEl} #{label.title}") # Return generated html $li.html($a).prop('outerHTML') @@ -288,7 +288,7 @@ class @LabelsSelect fieldName: $dropdown.data('field-name') id: (label) -> if $dropdown.hasClass("js-filter-submit") and not label.isAny? - _.escape label.title + label.title else label.id diff --git a/app/assets/javascripts/lib/cropper.js.coffee b/app/assets/javascripts/lib/cropper.js.coffee new file mode 100644 index 00000000000..32536d23fe3 --- /dev/null +++ b/app/assets/javascripts/lib/cropper.js.coffee @@ -0,0 +1 @@ +#= require cropper diff --git a/app/assets/javascripts/lib/utils/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee index e39dcb2daa9..d4dd3dc329a 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.coffee +++ b/app/assets/javascripts/lib/utils/common_utils.js.coffee @@ -5,12 +5,12 @@ w.gl.utils.isInGroupsPage = -> - return $('body').data('page').split(':')[0] is 'groups' + return gl.utils.getPagePath() is 'groups' w.gl.utils.isInProjectPage = -> - return $('body').data('page').split(':')[0] is 'projects' + return gl.utils.getPagePath() is 'projects' w.gl.utils.getProjectSlug = -> @@ -40,6 +40,9 @@ e.stopImmediatePropagation() return false + gl.utils.getPagePath = -> + return $('body').data('page').split(':')[0] + jQuery.timefor = (time, suffix, expiredLabel) -> diff --git a/app/assets/javascripts/lib/utils/text_utility.js.coffee b/app/assets/javascripts/lib/utils/text_utility.js.coffee index bb2772dfed2..2e1407f8738 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js.coffee +++ b/app/assets/javascripts/lib/utils/text_utility.js.coffee @@ -10,23 +10,48 @@ gl.text.selectedText = (text, textarea) -> text.substring(textarea.selectionStart, textarea.selectionEnd) - gl.text.insertText = (textArea, text, tag, selected, wrap) -> + gl.text.lineBefore = (text, textarea) -> + split = text.substring(0, textarea.selectionStart).trim().split('\n') + split[split.length - 1] + + gl.text.lineAfter = (text, textarea) -> + text.substring(textarea.selectionEnd).trim().split('\n')[0] + + gl.text.blockTagText = (text, textArea, blockTag, selected) -> + lineBefore = @lineBefore(text, textArea) + lineAfter = @lineAfter(text, textArea) + + if lineBefore is blockTag and lineAfter is blockTag + # To remove the block tag we have to select the line before & after + if blockTag? + textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1) + textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1) + + selected + else + "#{blockTag}\n#{selected}\n#{blockTag}" + + gl.text.insertText = (textArea, text, tag, blockTag, selected, wrap) -> selectedSplit = selected.split('\n') startChar = if not wrap and textArea.selectionStart > 0 then '\n' else '' - if selectedSplit.length > 1 and not wrap - insertText = selectedSplit.map((val) -> - if val.indexOf(tag) is 0 - "#{val.replace(tag, '')}" - else - "#{tag}#{val}" - ).join('\n') + if selectedSplit.length > 1 and (not wrap or blockTag?) + if blockTag? + insertText = @blockTagText(text, textArea, blockTag, selected) + else + insertText = selectedSplit.map((val) -> + if val.indexOf(tag) is 0 + "#{val.replace(tag, '')}" + else + "#{tag}#{val}" + ).join('\n') else 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") @@ -51,7 +76,7 @@ textArea.setSelectionRange pos, pos - gl.text.updateText = (textArea, tag, wrap) -> + gl.text.updateText = (textArea, tag, blockTag, wrap) -> $textArea = $(textArea) oldVal = $textArea.val() textArea = $textArea.get(0) @@ -59,7 +84,7 @@ selected = @selectedText(text, textArea) $textArea.focus() - @insertText(textArea, text, tag, selected, wrap) + @insertText(textArea, text, tag, blockTag, selected, wrap) gl.text.init = (form) -> self = @ @@ -70,6 +95,7 @@ self.updateText( $this.closest('.md-area').find('textarea'), $this.data('md-tag'), + $this.data('md-block'), not $this.data('md-prepend') ) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 02480f3a025..8ab03ed93ee 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -24,14 +24,14 @@ class @MilestoneSelect if issueUpdateURL milestoneLinkTemplate = _.template( - '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>" class="bold has-tooltip" data-container="body" title="<%= remaining %>"><%= _.escape(title) %></a>' + '<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>' ) milestoneLinkNoneTemplate = '<span class="no-value">None</span>' collapsedSidebarLabelTemplate = _.template( - '<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left"> - <%= _.escape(title) %> + '<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> + <%- title %> </span>' ) diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index a02c4515ccc..3b419dff105 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -1,25 +1,56 @@ class @NamespaceSelect - constructor: -> - namespaceFormatResult = (namespace) -> - markup = "<div class='namespace-result'>" - markup += "<span class='namespace-kind'>" + namespace.kind + "</span>" - markup += "<span class='namespace-path'>" + namespace.path + "</span>" - markup += "</div>" - markup - - formatSelection = (namespace) -> - namespace.kind + ": " + namespace.path - - $('.ajax-namespace-select').each (i, select) -> - $(select).select2 - placeholder: "Search for namespace" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.namespaces query.term, (namespaces) -> - data = { results: namespaces } - query.callback(data) - - dropdownCssClass: "ajax-namespace-dropdown" - formatResult: namespaceFormatResult - formatSelection: formatSelection + constructor: (opts) -> + { + @dropdown + } = opts + + showAny = true + fieldName = 'namespace_id' + + if @dropdown.attr 'data-field-name' + fieldName = @dropdown.data 'fieldName' + + if @dropdown.attr 'data-show-any' + showAny = @dropdown.data 'showAny' + + @dropdown.glDropdown( + filterable: true + selectable: true + filterRemote: true + search: + fields: ['path'] + fieldName: fieldName + toggleLabel: (selected) -> + return if not selected.id? then selected.text else "#{selected.kind}: #{selected.path}" + data: (term, dataCallback) -> + Api.namespaces term, (namespaces) -> + if showAny + anyNamespace = + text: 'Any namespace' + id: null + + namespaces.unshift(anyNamespace) + namespaces.splice 1, 0, 'divider' + + dataCallback(namespaces) + text: (namespace) -> + return if not namespace.id? then namespace.text else "#{namespace.kind}: #{namespace.path}" + renderRow: @renderRow + clicked: @onSelectItem + ) + + onSelectItem: (item, el, e) => + e.preventDefault() + +class @NamespaceSelects + constructor: (opts = {}) -> + { + @$dropdowns = $('.js-namespace-select') + } = opts + + @$dropdowns.each (i, dropdown) -> + $dropdown = $(dropdown) + + new NamespaceSelect( + dropdown: $dropdown + ) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 17f7e180127..0b7d8f64456 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -100,13 +100,40 @@ class @Notes $('.note .js-task-list-container').taskList('disable') $(document).off 'tasklist:changed', '.note .js-task-list-container' - keydownNoteText: (e) -> - $this = $(this) - if $this.val() is '' and e.which is 38 and not isMetaKey e - myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") - if myLastNote.length - myLastNoteEditBtn = myLastNote.find('.js-note-edit') - myLastNoteEditBtn.trigger('click', [true, myLastNote]) + keydownNoteText: (e) => + return if isMetaKey e + + $textarea = $(e.target) + + # Edit previous note when UP arrow is hit + switch e.which + when 38 + return unless $textarea.val() is '' + + myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") + if myLastNote.length + myLastNoteEditBtn = myLastNote.find('.js-note-edit') + myLastNoteEditBtn.trigger('click', [true, myLastNote]) + + # Cancel creating diff note or editing any note when ESCAPE is hit + when 27 + discussionNoteForm = $textarea.closest('.js-discussion-note-form') + if discussionNoteForm.length + if $textarea.val() isnt '' + return unless confirm('Are you sure you want to cancel creating this comment?') + + @removeDiscussionNoteForm(discussionNoteForm) + return + + editNote = $textarea.closest('.note') + if editNote.length + originalText = $textarea.closest('form').data('original-note') + newText = $textarea.val() + if originalText isnt newText + return unless confirm('Are you sure you want to cancel editing this comment?') + + @removeNoteEditForm(editNote) + isMetaKey = (e) -> (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey) @@ -213,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 @@ -291,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() ### @@ -308,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() ] ### @@ -401,9 +435,12 @@ class @Notes Hides edit form and restores the original note text to the editor textarea. ### - cancelEdit: (e) -> + cancelEdit: (e) => e.preventDefault() - note = $(this).closest(".note") + note = $(e.target).closest('.note') + @removeNoteEditForm(note) + + removeNoteEditForm: (note) -> form = note.find(".current-note-edit-form") note.removeClass "is-editting" form.removeClass("current-note-edit-form") @@ -482,10 +519,12 @@ class @Notes setupDiscussionNoteForm: (dataHolder, form) => # setup note target form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}" + form.attr "data-line-code", dataHolder.data("lineCode") form.find("#note_type").val dataHolder.data("noteType") form.find("#line_type").val dataHolder.data("lineType") form.find("#note_commit_id").val dataHolder.data("commitId") form.find("#note_line_code").val dataHolder.data("lineCode") + form.find("#note_position").val dataHolder.attr("data-position") form.find("#note_noteable_type").val dataHolder.data("noteableType") form.find("#note_noteable_id").val dataHolder.data("noteableId") form.find('.js-note-discard') diff --git a/app/assets/javascripts/profile/application.js.coffee b/app/assets/javascripts/profile/application.js.coffee new file mode 100644 index 00000000000..91cacfece46 --- /dev/null +++ b/app/assets/javascripts/profile/application.js.coffee @@ -0,0 +1,2 @@ +# +#= require_tree . diff --git a/app/assets/javascripts/gl_crop.js.coffee b/app/assets/javascripts/profile/gl_crop.js.coffee index df9bfdfa6cc..df9bfdfa6cc 100644 --- a/app/assets/javascripts/gl_crop.js.coffee +++ b/app/assets/javascripts/profile/gl_crop.js.coffee diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee index 1583d1ba6f9..f3b05f2c646 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile/profile.js.coffee @@ -78,3 +78,6 @@ $ -> if comment && comment.length > 1 && $title.val() == '' $title.val(comment[1]).change() + + if gl.utils.getPagePath() == 'profiles' + new Profile() diff --git a/app/assets/javascripts/protected_branch_select.js.coffee b/app/assets/javascripts/protected_branch_select.js.coffee new file mode 100644 index 00000000000..6d45770ace9 --- /dev/null +++ b/app/assets/javascripts/protected_branch_select.js.coffee @@ -0,0 +1,40 @@ +class @ProtectedBranchSelect + constructor: (currentProject) -> + $('.dropdown-footer').hide(); + @dropdown = $('.js-protected-branch-select').glDropdown( + data: @getProtectedBranches + filterable: true + remote: false + search: + fields: ['title'] + selectable: true + toggleLabel: (selected) -> if (selected and 'id' of selected) then selected.title else 'Protected Branch' + fieldName: 'protected_branch[name]' + text: (protected_branch) -> _.escape(protected_branch.title) + id: (protected_branch) -> _.escape(protected_branch.id) + onFilter: @toggleCreateNewButton + clicked: () -> $('.protect-branch-btn').attr('disabled', false) + ) + + $('.create-new-protected-branch').on 'click', (event) => + # Refresh the dropdown's data, which ends up calling `getProtectedBranches` + @dropdown.data('glDropdown').remote.execute() + @dropdown.data('glDropdown').selectRowAtIndex(event, 0) + + getProtectedBranches: (term, callback) => + if @selectedBranch + callback(gon.open_branches.concat(@selectedBranch)) + else + callback(gon.open_branches) + + toggleCreateNewButton: (branchName) => + @selectedBranch = { title: branchName, id: branchName, text: branchName } + + if branchName is '' + $('.protected-branch-select-footer-list').addClass('hidden') + $('.dropdown-footer').hide(); + else + $('.create-new-protected-branch').text("Create Protected Branch: #{branchName}") + $('.protected-branch-select-footer-list').removeClass('hidden') + $('.dropdown-footer').show(); + diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee index 5753c9d4e72..79c2306e4d2 100644 --- a/app/assets/javascripts/protected_branches.js.coffee +++ b/app/assets/javascripts/protected_branches.js.coffee @@ -11,7 +11,8 @@ $ -> dataType: "json" data: id: id - developers_can_push: checked + protected_branch: + developers_can_push: checked success: -> row = $(e.target) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index 421328554b8..72b1d3dfb1e 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -171,22 +171,15 @@ class @SearchAutocomplete } bindEvents: -> - $(document).on 'click', @onDocumentClick @searchInput.on 'keydown', @onSearchInputKeyDown @searchInput.on 'keyup', @onSearchInputKeyUp @searchInput.on 'click', @onSearchInputClick @searchInput.on 'focus', @onSearchInputFocus + @searchInput.on 'blur', @onSearchInputBlur @clearInput.on 'click', @onClearInputClick @locationBadgeEl.on 'click', => @searchInput.focus() - onDocumentClick: (e) => - # If clicking outside the search box - # And search input is not focused - # And we are not clicking inside a suggestion - if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).closest('.search-form').length - @onSearchInputBlur() - enableAutocomplete: -> # No need to enable anything if user is not logged in return if !gon.current_user_id @@ -287,8 +280,6 @@ class @SearchAutocomplete value: @originalState._location ) - @dropdown.removeClass 'open' - badgePresent: -> @locationBadgeEl.length diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee index c03877e9b06..3319a67a79d 100644 --- a/app/assets/javascripts/shortcuts.js.coffee +++ b/app/assets/javascripts/shortcuts.js.coffee @@ -9,12 +9,12 @@ class @Shortcuts onToggleHelp: (e) => e.preventDefault() - @toggleHelp(@enabledHelp) + Shortcuts.toggleHelp(@enabledHelp) - toggleMarkdownPreview: (e) => + toggleMarkdownPreview: (e) -> $(document).triggerHandler('markdown-preview:toggle', [e]) - toggleHelp: (location) -> + @toggleHelp: (location) -> $modal = $('#modal-shortcuts') if $modal.length diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index de8eebcd0b2..83de584f2d9 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -5,9 +5,15 @@ class @TreeView # Code browser tree slider # Make the entire tree-item row clickable, but not if clicking another link (like a commit message) $(".tree-content-holder .tree-item").on 'click', (e) -> - if (e.target.nodeName != "A") - path = $('.tree-item-file-name a', this).attr('href') - Turbolinks.visit(path) + $clickedEl = $(e.target) + path = $('.tree-item-file-name a', this).attr('href') + + if not $clickedEl.is('a') and not $clickedEl.is('.str-truncated') + if e.metaKey or e.which is 2 + e.preventDefault() + window.open path, '_blank' + else + Turbolinks.visit path # Show the "Loading commit data" for only the first element $('span.log_loading:first').removeClass('hide') 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 ) { %> - <a class="author_link" href="/u/<%= username %>"> - <img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>"> + <a class="author_link" href="/u/<%- username %>"> + <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> <span class="author">Toni Boehm</span> </a> <% } else { %> @@ -72,13 +72,13 @@ class @UsersSelect assigneeTemplate = _.template( '<% if (username) { %> - <a class="author_link bold" href="/u/<%= username %>"> + <a class="author_link bold" href="/u/<%- username %>"> <% if( avatar ) { %> - <img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>"> + <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> - <span class="author"><%= name %></span> + <span class="author"><%- name %></span> <span class="username"> - @<%= username %> + @<%- username %> </span> </a> <% } else { %> diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index 40b5171a8c6..d28cda6d62d 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -1,3 +1,12 @@ +.blank-state-welcome { + text-align: center; + border-bottom: 1px solid $border-color; + + .blank-state-text { + margin-bottom: 0; + } +} + .blank-state { padding-top: 20px; padding-bottom: 20px; @@ -6,7 +15,15 @@ .blank-state-no-icon { padding-top: 40px; - padding-bottom: 40px; + padding-bottom: 40px; +} + +.blank-state-icon { + padding-bottom: 20px; + + path { + fill: $gray-darkest; + } } .blank-state-title { @@ -21,3 +38,7 @@ margin-bottom: $gl-padding; font-size: 15px; } + +.blank-state-welcome-title { + font-size: 24px; +} diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 38023818709..41e77a4ac68 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -137,7 +137,7 @@ margin: 0; font-size: 24px; font-weight: normal; - margin-bottom: 5px; + margin-bottom: 10px; color: #4c4e54; font-size: 23px; line-height: 1.1; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 1e3083cce55..590b8f54363 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -281,3 +281,21 @@ color: $gl-icon-color; } } + +.clone-dropdown-btn a { + color: $dropdown-link-color; + &:hover { + text-decoration: none; + } +} + +.btn-static { + background-color: $background-color !important; + border: 1px solid lightgrey; + cursor: default; + &:active { + -moz-box-shadow: inset 0 0 0 white; + -webkit-box-shadow: inset 0 0 0 white; + box-shadow: inset 0 0 0 white; + } +} diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 00111dfa706..d4e900f80ef 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -20,8 +20,12 @@ } .open { - .dropdown-menu { + .dropdown-menu, + .dropdown-menu-nav { display: block; + @media (max-width: $screen-xs-max) { + width: 100%; + } } .dropdown-menu-toggle { @@ -64,9 +68,14 @@ color: $dropdown-toggle-hover-icon-color; } } + + &.large { + width: 200px; + } } -.dropdown-menu { +.dropdown-menu, +.dropdown-menu-nav { display: none; position: absolute; top: 100%; @@ -77,7 +86,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; @@ -101,12 +110,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; } @@ -122,7 +131,7 @@ a { display: block; position: relative; - padding: 5px 10px; + padding: 5px 8px; color: $dropdown-link-color; line-height: initial; text-overflow: ellipsis; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 71a9f79be3e..71e4b50f2af 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -50,7 +50,7 @@ } a:not(.btn) { - color: $gl-dark-link-color; + color: $gl-text-color; } .left-options { diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 1bfd0213995..a951a2b97fe 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -16,4 +16,11 @@ @extend .alert-danger; margin: 0; } + + .flash-notice, .flash-alert { + .container-fluid.flash-text { + background: transparent; + } + } } + 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/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/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/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index fd8eaa8a691..5d3273ea64d 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -125,7 +125,8 @@ border: 0; outline: 0; - &:hover { + &:hover, + &:focus { color: $gl-link-color; } } diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index c74682dfef4..367c7d01944 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -71,6 +71,10 @@ display: none; } + .group-right-buttons { + display: none; + } + .container .title { padding-left: 15px !important; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 6211bc04597..364952d3b4a 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -21,9 +21,8 @@ .fa { position: relative; - top: 3px; - font-size: 13px; - color: $btn-placeholder-gray; + top: 5px; + font-size: 18px; } } @@ -78,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; } @@ -135,6 +134,11 @@ margin-bottom: 0; border-bottom: none; + &.wide { + width: 100%; + display: block; + } + li a { padding: 16px 10px 11px; } @@ -165,6 +169,7 @@ > .btn { margin-right: $gl-padding-top; display: inline-block; + vertical-align: top; &:last-child { margin-right: 0; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 98f917ce69b..188823054fd 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -1,5 +1,6 @@ .page-with-sidebar { padding-top: $header-height; + padding-bottom: 25px; transition: padding $sidebar-transition-duration; .sidebar-wrapper { @@ -39,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: 0; width: $sidebar_width; overflow-y: auto; overflow-x: hidden; @media (min-width: $sidebar-breakpoint) { - bottom: 115px; + bottom: 50px; } &.navbar-collapse { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index c37574ca7a1..4337fab5d87 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -7,15 +7,16 @@ $gutter_collapsed_width: 62px; $gutter_width: 290px; $gutter_inner_width: 258px; $sidebar-transition-duration: .15s; -$sidebar-breakpoint: 1440px; +$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; 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/awards.scss b/app/assets/stylesheets/pages/awards.scss index 6211f3a52eb..5faedfedd66 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -8,8 +8,9 @@ .emoji-menu { position: absolute; margin-top: 3px; - z-index: 1000; - min-width: 160px; + padding: $gl-padding; + z-index: 9; + width: 300px; font-size: 14px; background-color: $award-emoji-menu-bg; border: 1px solid $award-emoji-menu-border; @@ -33,20 +34,18 @@ } .emoji-menu-content { - padding: $gl-padding; - width: 300px; height: 300px; overflow-y: scroll; - - input.emoji-search { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC"); - background-repeat: no-repeat; - background-position: right 5px center; - background-size: 16px; - } } } +.emoji-search { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC"); + background-repeat: no-repeat; + background-position: right 5px center; + background-size: 16px; +} + .emoji-menu-list { list-style: none; padding-left: 0; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index de534d28421..85bbf70e188 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -83,11 +83,7 @@ position: relative; @media (min-width: $screen-sm-min) { - padding-left: 20px; - - .commit-info-block { - padding-left: 44px; - } + padding-left: 46px; } &:not(:last-child) { @@ -102,9 +98,7 @@ .avatar { - position: absolute; - top: 10px; - left: 16px; + margin-left: -46px; } .item-title { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 5286b73cc50..21b1c223c88 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -434,13 +434,3 @@ } } } - -.discussion { - .diff-content { - .diff-line-num { - &:before { - content: attr(data-linenumber); - } - } - } -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index ac7721cbe15..701b9388454 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -38,21 +38,53 @@ 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; + } + } + } + } -.groups-cover-block { +.ldap-group-links { + .form-actions { + margin-bottom: $gl-padding; + } +} +.groups-cover-block { .container-fluid { position: relative; } - .access-request-button { - @include btn-gray; + .group-right-buttons { position: absolute; right: 16px; - bottom: 32px; - padding: 3px 10px; - text-transform: none; - background-color: $background-color; + .btn { + @include btn-gray; + padding: 3px 10px; + background-color: $background-color; + } } } diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 0b710ef168b..00ab42bec5c 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -63,5 +63,6 @@ border: 1px solid $table-border-gray; padding: 5px; margin: 5px; + max-height: calc(100vh - 100px); } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 21ff6ab71f0..542fa244689 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -10,6 +10,7 @@ border: 1px solid $table-border-gray; padding: 5px; margin: 5px; + max-height: calc(100vh - 100px); } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index aca82f7f7bf..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; @@ -264,8 +265,15 @@ margin-bottom: 4px; } + .item-title { + @media (min-width: $screen-sm-min) { + width: 49%; + } + } + .avatar { - margin-left: 0; + left: 0; + top: 2px; } .commit-row-info { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index ffba3dc5bc6..ac8c02b59dc 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -41,6 +41,10 @@ ul.notes { .timeline-icon { .avatar { visibility: hidden; + + .discussion-body & { + visibility: visible; + } } } } @@ -113,6 +117,7 @@ ul.notes { border: 1px solid $table-border-gray; padding: 5px; margin: 5px 0; + max-height: calc(100vh - 100px); } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index d3e59d7fdb9..bce4aac3334 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -13,94 +13,100 @@ .new_project, .edit-project { - fieldset.features { - .control-label { + fieldset { + &.features .control-label { font-weight: normal; } + .form-group { + margin-bottom: 5px; + } + &> .form-group { + padding-left: 0; + } } -} - -.project-home-panel { - background: $white-light; - text-align: left; - padding: 24px 0; - - .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; - } + .help-block { + margin-bottom: 10px; + } + .project-path { + padding-right: 0; + .form-control { + border-radius: $border-radius-base; } } - - .cover-controls { - .project-settings-dropdown { - margin-left: 10px; - display: inline-block; - - .dropdown-menu { - left: auto; - width: auto; - right: 0; - max-width: 240px; + .input-group > div { + &:last-child { + padding-right: 0; + } + } + @media (max-width: $screen-xs-max) { + .input-group > div { + margin-bottom: 14px; + &:last-child { + margin-bottom: 0; } } + fieldset > .form-group:first-child { + padding-right: 0; + } } - .cover-title { - margin-bottom: 0; + .input-group-addon { + &.static-namespace { + height: 35px; + border-radius: 3px; + border: 1px solid #e5e5e5; + } + &+ .select2 a { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } } +} - .project-image-container { - @include make-sm-column(1); - max-width: 86px; - min-width: 86px; - padding-right: 0; - - @media (max-width: $screen-md-max) { - padding-left: 0; - margin: 0 0 10px; - max-width: none; - min-width: none; +.project-home-panel { + padding-top: 24px; + padding-bottom: 24px; - .avatar.s70 { - margin: auto; - } - } + @media (min-width: $screen-sm-min) { + border-bottom: 1px solid $border-color; } - .project-info { - @include make-sm-column(10); + .project-avatar { + float: none; + margin-left: auto; + margin-right: auto; - h1 { - font-size: 24px; - font-weight: normal; - margin: 0; + &.identicon { + border-radius: 50%; } + } - .project-home-desc { - p { - margin: 0; - } + .project-title { + margin-top: 10px; + margin-bottom: 10px; + font-size: 24px; + font-weight: 400; + line-height: 1; + + .fa { + margin-left: 2px; + font-size: 12px; + vertical-align: middle; } } - .identicon { - float: left; - @include border-radius(50%); - } + .project-home-desc { + margin-left: auto; + margin-right: auto; + margin-bottom: 15px; + max-width: 480px; - .avatar { - float: none; + > p { + margin-bottom: 0; + } } .notifications-btn { - .fa-bell, .fa-spinner { margin-right: 6px; @@ -110,139 +116,106 @@ margin-left: 6px; } } +} - .project-repo-buttons { - font-size: 0; +.project-repo-buttons { + font-size: 0; - .btn { - @include btn-gray; - padding: 3px 10px; - text-transform: none; - background-color: $background-color; + .btn { + @include btn-gray; + padding: 3px 10px; - .fa { - color: $layout-link-gray; - } - - .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; - .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; - 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; - cursor: pointer; - background-image: none; - white-space: nowrap; - margin: 0 10px 0 4px; - - a { - color: inherit; - } - - &:hover { - background: #fff; - } - } + input { + height: 29px; } } - .project-right-buttons { - position: absolute; - right: 16px; - bottom: 0; - - @media (max-width: $screen-md-max) { - top: 0; - } + .count-with-arrow { + display: inline-block; + position: relative; + margin-left: 4px; - .access-request-button { - position: absolute; - right: 0; - bottom: 61px; + .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; + pointer-events: none; + } - @media (max-width: $screen-md-max) { - position: relative; - bottom: 0; - margin-right: 10px; + &: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; + } } } } @@ -365,43 +338,67 @@ a.deploy-project-label { } } -.project-import .btn { - float: left; - margin-bottom: 10px; - margin-right: 10px; +.project-import { + .form-group { + margin-bottom: 0; + } + .import-buttons { + padding-left: 0; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + .btn { + margin-right: 10px; + padding: 8px 12px; + } + &> div { + margin-bottom: 14px; + padding-left: 0; + &:last-child { + margin-bottom: 0; + } + } + } } .project-stats { - margin-top: $gl-padding; - margin-bottom: 0; - padding: 0; - background-color: $white-light; font-size: 0; + border-bottom: 1px solid $border-color; - ul.nav { - display: inline-block; + .nav { + padding-top: 12px; + padding-bottom: 12px; } - .nav li { + .nav > li { display: inline-block; - margin: 16px 0; - margin-right: 16px; + + &:not(:last-child) { + margin-right: $gl-padding; + } + + &.project-repo-buttons-right { + margin-top: 10px; + + @media (min-width: $screen-md-min) { + float: right; + margin-top: 0; + } + } } .nav > li > a { + padding: 0; background-color: transparent; - padding: 5px 10px; font-size: 15px; + line-height: 29px; color: $notes-light-color; - } - - li { - display: inline; - } - a { - float: left; - font-size: 17px; + &:hover, + &:focus { + color: darken($notes-light-color, 15%); + } } li.missing { @@ -409,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; } @@ -417,10 +416,6 @@ a.deploy-project-label { background-color: $gray-normal; } } - - &.row-content-block.second-block { - margin-top: 0; - } } pre.light-well { @@ -480,10 +475,6 @@ pre.light-well { a:hover { text-decoration: none; } - - > span { - margin-left: 10px; - } } } @@ -508,8 +499,32 @@ pre.light-well { } .project-last-commit { + @media (min-width: $screen-sm-min) { + margin-top: $gl-padding; + } + + &.container-fluid { + padding-top: 12px; + padding-bottom: 12px; + background-color: $background-color; + border: 1px solid $border-color; + border-right-width: 0; + border-left-width: 0; + + @media (min-width: $screen-sm-min) { + border-right-width: 1px; + border-left-width: 1px; + } + } + + &.container-limited { + @media (min-width: 1281px) { + border-radius: $border-radius-base; + } + } + .ci-status { - margin-right: 16px; + margin-right: $gl-padding; } .commit-row-message { @@ -517,19 +532,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; } @@ -552,15 +560,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/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/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 99c9e81ddb9..5b61270daa8 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -101,7 +101,8 @@ margin: 0; .commit { - padding: 0 0 0 55px; + padding-top: 0; + padding-bottom: 0; .commit-row-title { .commit-row-message { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index f4eda864aac..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, @@ -109,6 +110,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :metrics_packet_size, :send_user_confirmation_email, :container_registry_token_expire_delay, + :repository_storage, + :enabled_git_access_protocol, restricted_visibility_levels: [], import_sources: [], disabled_oauth_sign_in_sources: [] diff --git a/app/controllers/admin/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/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/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 87986fdf8b1..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]) @@ -20,7 +21,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/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb index bf20c5305a7..bc65dcc33d3 100644 --- a/app/controllers/admin/runner_projects_controller.rb +++ b/app/controllers/admin/runner_projects_controller.rb @@ -4,8 +4,6 @@ class Admin::RunnerProjectsController < Admin::ApplicationController def create @runner = Ci::Runner.find(params[:runner_project][:runner_id]) - return head(403) if @runner.is_shared? || @runner.locked? - runner_project = @runner.assign_to(@project, current_user) if runner_project.persisted? diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb new file mode 100644 index 00000000000..e4c73008826 --- /dev/null +++ b/app/controllers/admin/system_info_controller.rb @@ -0,0 +1,59 @@ +class Admin::SystemInfoController < Admin::ApplicationController + EXCLUDED_MOUNT_OPTIONS = [ + 'nobrowse', + 'read-only', + 'ro' + ] + + EXCLUDED_MOUNT_TYPES = [ + 'autofs', + 'binfmt_misc', + 'cgroup', + 'debugfs', + 'devfs', + 'devpts', + 'devtmpfs', + 'efivarfs', + 'fuse.gvfsd-fuse', + 'fuseblk', + 'fusectl', + 'hugetlbfs', + 'mqueue', + 'proc', + 'pstore', + 'securityfs', + 'sysfs', + 'tmpfs', + 'tracefs', + 'vfat' + ] + + def show + system_info = Vmstat.snapshot + mounts = Sys::Filesystem.mounts + + @disks = [] + mounts.each do |mount| + mount_options = mount.options.split(',') + + next if (EXCLUDED_MOUNT_OPTIONS & mount_options).any? + next if (EXCLUDED_MOUNT_TYPES & [mount.mount_type]).any? + + begin + disk = Sys::Filesystem.stat(mount.mount_point) + @disks.push({ + bytes_total: disk.bytes_total, + bytes_used: disk.bytes_used, + disk_name: mount.name, + mount_path: disk.path + }) + rescue Sys::Filesystem::Error + end + end + + @cpus = system_info.cpus.length + + @mem_used = system_info.memory.active_bytes + @mem_total = system_info.memory.total_bytes + end +end diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index 8bf71a1adbb..aa894fde36b 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -25,7 +25,7 @@ module Ci return render_404 unless @project image = Ci::ImageForBuildService.new.execute(@project, params) - send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" + send_file image.path, filename: image.name, disposition: 'inline', type: "image/svg+xml" end protected 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/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/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb index 71ba6153021..de6bc689bb7 100644 --- a/app/controllers/dashboard/groups_controller.rb +++ b/app/controllers/dashboard/groups_controller.rb @@ -1,5 +1,5 @@ class Dashboard::GroupsController < Dashboard::ApplicationController def index - @group_members = current_user.group_members.page(params[:page]) + @group_members = current_user.group_members.includes(:source).page(params[:page]) end end 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/groups_controller.rb b/app/controllers/groups_controller.rb index ee4fcc4e360..a04bf7df722 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -37,15 +37,12 @@ class GroupsController < Groups::ApplicationController end def show - @last_push = current_user.recent_push if current_user - - @projects = @projects.includes(:namespace) - @projects = @projects.sorted_by_activity - @projects = filter_projects(@projects) - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]) if params[:filter_projects].blank? + if current_user + @last_push = current_user.recent_push + @notification_setting = current_user.notification_settings_for(group) + end - @shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user) + setup_projects respond_to do |format| format.html @@ -97,6 +94,16 @@ class GroupsController < Groups::ApplicationController protected + def setup_projects + @projects = @projects.includes(:namespace) + @projects = @projects.sorted_by_activity + @projects = filter_projects(@projects) + @projects = @projects.sort(@sort = params[:sort]) + @projects = @projects.page(params[:page]) if params[:filter_projects].blank? + + @shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user) + end + def authorize_create_group! unless can?(current_user, :create_group, nil) return render_404 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/github_controller.rb b/app/controllers/import/github_controller.rb index 67bf4190e7e..9c1b0eb20f4 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -1,14 +1,29 @@ class Import::GithubController < Import::BaseController before_action :verify_github_import_enabled - before_action :github_auth, except: :callback + before_action :github_auth, only: [:status, :jobs, :create] rescue_from Octokit::Unauthorized, with: :github_unauthorized + helper_method :logged_in_with_github? + + def new + if logged_in_with_github? + go_to_github_for_permissions + elsif session[:github_access_token] + redirect_to status_import_github_url + end + end + def callback session[:github_access_token] = client.get_token(params[:code]) redirect_to status_import_github_url end + def personal_access_token + session[:github_access_token] = params[:personal_access_token] + redirect_to status_import_github_url + end + def status @repos = client.repos @already_added_projects = current_user.created_projects.where(import_type: "github") @@ -57,10 +72,14 @@ class Import::GithubController < Import::BaseController end def github_unauthorized - go_to_github_for_permissions + session[:github_access_token] = nil + redirect_to new_import_github_url, + alert: 'Access denied to your GitHub account.' end - private + def logged_in_with_github? + current_user.identities.exists?(provider: 'github') + end def access_params { github_access_token: session[:github_access_token] } diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 513348c39af..30df1fb2fec 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -27,10 +27,7 @@ class Import::GitlabProjectsController < Import::BaseController notice: "Project '#{@project.name}' is being imported." ) else - redirect_to( - new_import_gitlab_project_path, - alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" - ) + redirect_back_or_default(options: { alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" }) end end diff --git a/app/controllers/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/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index eddd03cc229..8ec4bb1233f 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -2,11 +2,9 @@ class NotificationSettingsController < ApplicationController before_action :authenticate_user! def create - project = Project.find(params[:project][:id]) + return render_404 unless can_read?(resource) - return render_404 unless can?(current_user, :read_project, project) - - @notification_setting = current_user.notification_settings_for(project) + @notification_setting = current_user.notification_settings_for(resource) @saved = @notification_setting.update_attributes(notification_setting_params) render_response @@ -21,6 +19,22 @@ class NotificationSettingsController < ApplicationController private + def resource + @resource ||= + if params[:project_id].present? + Project.find(params[:project_id]) + elsif params[:namespace_id].present? + Group.find(params[:namespace_id]) + end + end + + def can_read?(resource) + ability_name = resource.class.name.downcase + ability_name = "read_#{ability_name}".to_sym + + can?(current_user, ability_name, resource) + end + def render_response render json: { html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting), 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 6751737d15e..37d6521026c 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -18,9 +18,16 @@ class Projects::CommitController < Projects::ApplicationController apply_diff_view_cookie! @grouped_diff_notes = commit.notes.grouped_diff_notes + @notes = commit.notes.non_diff_notes.fresh + + Banzai::NoteRenderer.render( + @grouped_diff_notes.values.flatten + @notes, + @project, + current_user, + ) @note = @project.build_commit_note(commit) - @notes = commit.notes.non_diff_notes.fresh + @noteable = @commit @comments_target = { noteable_type: 'Commit', @@ -114,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/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index f907d63258b..40a8b7940d9 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 @@ -14,6 +19,8 @@ class Projects::GitHttpController < Projects::ApplicationController render_ok elsif receive_pack? && receive_pack_allowed? render_ok + elsif http_blocked? + render_not_allowed else render_not_found end @@ -40,9 +47,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 +63,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 +153,11 @@ 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 render_not_allowed + render plain: download_access.message, status: :forbidden end def ci? @@ -131,12 +168,28 @@ class Projects::GitHttpController < Projects::ApplicationController return false unless Gitlab.config.gitlab_shell.upload_pack if user - Gitlab::GitAccess.new(user, project).download_access_check.allowed? + download_access.allowed? else ci? || project.public? end end + def access + return @access if defined?(@access) + + @access = Gitlab::GitAccess.new(user, project, 'http') + end + + def download_access + return @download_access if defined?(@download_access) + + @download_access = access.check('git-upload-pack') + end + + def http_blocked? + !access.protocol_allowed? + end + def receive_pack_allowed? return false unless Gitlab.config.gitlab_shell.receive_pack diff --git a/app/controllers/projects/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/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 39c8ba40ca2..5678a4015b6 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] @@ -53,13 +52,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def show - @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)). - group(:commit_id).count - respond_to do |format| format.html - format.json { render json: @merge_request } - format.patch { render text: @merge_request.to_patch } + + format.json do + render json: @merge_request + end + + format.patch do + 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 @@ -71,18 +76,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs apply_diff_view_cookie! - @commit = @merge_request.last_commit - @base_commit = @merge_request.diff_base_commit + @merge_request_diff = @merge_request.merge_request_diff - # 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', 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( @@ -103,7 +107,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 @@ -128,7 +140,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 @@ -206,7 +218,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 @@ -268,16 +280,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 } @@ -302,10 +314,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 @@ -334,14 +342,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, @@ -350,28 +377,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController @project_wiki, @ref ) - - @noteable = @merge_request - - # Get commits from repository - # or from cache if already merged - @commits = @merge_request.commits - - @merge_request_diff = @merge_request.merge_request_diff - - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses if @pipeline - - if @merge_request.locked_long_ago? - @merge_request.unlock_mr - @merge_request.close - end end def define_widget_vars @pipeline = @merge_request.pipeline @pipelines = [@pipeline].compact - closes_issues end def invalid_mr 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/notes_controller.rb b/app/controllers/projects/notes_controller.rb index e14fe26dde7..3eacdbbd067 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -128,7 +128,7 @@ class Projects::NotesController < Projects::ApplicationController elsif note.valid? Banzai::NoteRenderer.render([note], @project, current_user) - { + attrs = { valid: true, id: note.id, discussion_id: note.discussion_id, @@ -138,6 +138,23 @@ class Projects::NotesController < Projects::ApplicationController discussion_html: note_to_discussion_html(note), discussion_with_diff_html: note_to_discussion_with_diff_html(note) } + + # The discussion_id is used to add the comment to the correct discussion + # element on the merge request page. Among other things, the discussion_id + # contains the sha of head commit of the merge request. + # When new commits are pushed into the merge request after the initial + # load of the merge request page, the discussion elements will still have + # the old discussion_ids, with the old head commit sha. The new comment, + # however, will have the new discussion_id with the new commit sha. + # To ensure that these new comments will still end up in the correct + # discussion element, we also send the original discussion_id, with the + # old commit sha, along, and fall back on this value when no discussion + # element with the new discussion_id could be found. + if note.new_diff_note? && note.position != note.original_position + attrs[:original_discussion_id] = note.original_discussion_id + end + + attrs else { valid: false, @@ -154,7 +171,7 @@ class Projects::NotesController < Projects::ApplicationController def note_params params.require(:note).permit( :note, :noteable, :noteable_id, :noteable_type, :project_id, - :attachment, :line_code, :commit_id, :type + :attachment, :line_code, :commit_id, :type, :position ) end diff --git a/app/controllers/projects/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/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index efa7bf14d0f..80dad758afa 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -2,12 +2,14 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController # Authorize before_action :require_non_empty_project before_action :authorize_admin_project! + before_action :load_protected_branch, only: [:show, :update, :destroy] layout "project_settings" def index - @branches = @project.protected_branches.to_a + @protected_branches = @project.protected_branches.order(:name).page(params[:page]) @protected_branch = @project.protected_branches.new + gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }) end def create @@ -16,26 +18,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController @project) end - def update - protected_branch = @project.protected_branches.find(params[:id]) - - if protected_branch && - protected_branch.update_attributes( - developers_can_push: params[:developers_can_push] - ) + def show + @matching_branches = @protected_branch.matching(@project.repository.branches) + end + def update + if @protected_branch && @protected_branch.update_attributes(protected_branch_params) respond_to do |format| - format.json { render json: protected_branch, status: :ok } + format.json { render json: @protected_branch, status: :ok } end else respond_to do |format| - format.json { render json: protected_branch.errors, status: :unprocessable_entity } + format.json { render json: @protected_branch.errors, status: :unprocessable_entity } end end end def destroy - @project.protected_branches.find(params[:id]).destroy + @protected_branch.destroy respond_to do |format| format.html { redirect_to namespace_project_protected_branches_path } @@ -45,6 +45,10 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController private + def load_protected_branch + @protected_branch = @project.protected_branches.find(params[:id]) + end + def protected_branch_params params.require(:protected_branch).permit(:name, :developers_can_push) end diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb index dc1a18f8d42..8267b14941d 100644 --- a/app/controllers/projects/runner_projects_controller.rb +++ b/app/controllers/projects/runner_projects_controller.rb @@ -6,8 +6,7 @@ class Projects::RunnerProjectsController < Projects::ApplicationController def create @runner = Ci::Runner.find(params[:runner_project][:runner_id]) - return head(403) if @runner.is_shared? || @runner.locked? - return head(403) unless current_user.ci_authorized_runners.include?(@runner) + return head(403) unless can?(current_user, :assign_runner, @runner) path = runners_path(project) runner_project = @runner.assign_to(project, current_user) 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 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/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2b1f50fd01e..1803aa8eab4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -4,7 +4,8 @@ class ProjectsController < Projects::ApplicationController before_action :authenticate_user!, except: [:show, :activity, :refs] before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] - before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? + before_action :assign_ref_vars, only: [:show], if: :repo_exists? + before_action :tree, only: [:show], if: :project_view_files? # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] @@ -52,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 @@ -303,6 +304,10 @@ class ProjectsController < Projects::ApplicationController project.repository_exists? && !project.empty_repo? end + def project_view_files? + current_user && current_user.project_view == 'files' + end + # Override extract_ref from ExtractsPath, which returns the branch and file path # for the blob/tree, which in this case is just the root of the default branch. # This way we avoid to access the repository.ref_names. diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index c19a795d467..641fbf838f1 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -29,10 +29,10 @@ class PipelinesFinder end def branches - project.repository.branches.map(&:name) + project.repository.branch_names end def tags - project.repository.tags.map(&:name) + project.repository.tag_names end end 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/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/helpers/application_helper.rb b/app/helpers/application_helper.rb index 62d13a4b4f3..03495cf5ec4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -306,4 +306,15 @@ module ApplicationHelper def truncate_first_line(message, length = 50) truncate(message.each_line.first.chomp, length: length) if message end + + # While similarly named to Rails's `link_to_if`, this method behaves quite differently. + # If `condition` is truthy, a link will be returned with the result of the block + # as its body. If `condition` is falsy, only the result of the block will be returned. + def conditional_link_to(condition, options, html_options = {}, &block) + if condition + link_to options, html_options, &block + else + capture(&block) + end + end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 55313fd8357..78c0b79d2bd 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -31,6 +31,28 @@ module ApplicationSettingsHelper current_application_settings.akismet_enabled? end + def allowed_protocols_present? + current_application_settings.enabled_git_access_protocol.present? + end + + def enabled_protocol + case current_application_settings.enabled_git_access_protocol + when 'http' + gitlab_config.protocol + when 'ssh' + 'ssh' + end + end + + def enabled_project_button(project, protocol) + case protocol + when 'ssh' + ssh_clone_button(project, 'bottom', append_link: false) + else + http_clone_button(project, 'bottom', append_link: false) + end + end + # Return a group of checkboxes that use Bootstrap's button plugin for a # toggle button effect. def restricted_level_checkboxes(help_block_id) @@ -78,4 +100,12 @@ module ApplicationSettingsHelper end end end + + def repository_storage_options_for_select + options = Gitlab.config.repositories.storages.map do |name, path| + ["#{name} - #{path}", name] + end + + options_for_select(options, @application_setting.repository_storage) + end end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 4b4bc3d4276..428a42266d0 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,10 +1,10 @@ module BlobHelper - def highlighter(blob_name, blob_content, nowrap: false) - Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap) + def highlighter(blob_name, blob_content, repository: nil, nowrap: false) + Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap, repository: repository) end - def highlight(blob_name, blob_content, nowrap: false, plain: false) - Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain) + def highlight(blob_name, blob_content, repository: nil, nowrap: false, plain: false) + Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain, repository: repository) end def no_highlight_files diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index c533659b600..601df5c18df 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -12,7 +12,7 @@ module BranchesHelper def can_push_branch?(project, branch_name) return false unless project.repository.branch_exists?(branch_name) - ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) + ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(branch_name) end def project_branches diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 9051a493b9b..0f097f86816 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -40,33 +40,33 @@ module ButtonHelper type: :button end - def http_clone_button(project) + def http_clone_button(project, placement = 'right', append_link: true) klass = 'http-selector' klass << ' has-tooltip' if current_user.try(:require_password?) protocol = gitlab_config.protocol.upcase - content_tag :a, protocol, + content_tag (append_link ? :a : :span), protocol, class: klass, - href: project.http_url_to_repo, + href: (project.http_url_to_repo if append_link), data: { html: true, - placement: 'right', + placement: placement, container: 'body', title: "Set a password on your account<br>to pull or push via #{protocol}" } end - def ssh_clone_button(project) + def ssh_clone_button(project, placement = 'right', append_link: true) klass = 'ssh-selector' klass << ' has-tooltip' if current_user.try(:require_ssh_key?) - content_tag :a, 'SSH', + content_tag (append_link ? :a : :span), 'SSH', class: klass, - href: project.ssh_url_to_repo, + href: (project.ssh_url_to_repo if append_link), data: { html: true, - placement: 'right', + placement: placement, container: 'body', title: 'Add an SSH key to your profile<br>to pull or push via SSH.' } diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index e22dce59d0f..eb57516247d 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -30,12 +30,8 @@ module DiffHelper options end - def safe_diff_files(diffs, diff_refs) - diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) } - end - - def generate_line_code(file_path, line) - Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) + def safe_diff_files(diffs, diff_refs: nil, repository: nil) + diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } end def unfold_bottom_class(bottom) @@ -93,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 @@ -100,10 +98,11 @@ module DiffHelper end end - def diff_file_html_data(project, diff_commit, diff_file) + def diff_file_html_data(project, diff_file) + commit = commit_for_diff(diff_file) { blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, - tree_join(diff_commit.id, diff_file.file_path)) + tree_join(commit.id, diff_file.file_path)) } end diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 6b617e1730a..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') @@ -69,7 +69,7 @@ module DropdownsHelper def dropdown_filter(placeholder, search_id: nil) content_tag :div, class: "dropdown-input" do - filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder + filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off' filter_output << icon('search', class: "dropdown-input-search") filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button") 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/issues_helper.rb b/app/helpers/issues_helper.rb index 72bd1fbbd81..2b0defd1dda 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -118,7 +118,7 @@ module IssuesHelper end def emoji_icon(name, unicode = nil, aliases = [], sprite: true) - unicode ||= Emoji.emoji_filename(name) rescue "" + unicode ||= Gitlab::Emoji.emoji_filename(name) rescue "" data = { aliases: aliases.join(" "), diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb index 5109356941d..0e456214d37 100644 --- a/app/helpers/javascript_helper.rb +++ b/app/helpers/javascript_helper.rb @@ -1,5 +1,5 @@ module JavascriptHelper def page_specific_javascript_tag(js) - javascript_include_tag asset_path(js), { integrity: true, "data-turbolinks-track" => true } + javascript_include_tag asset_path(js), { "data-turbolinks-track" => true } end end 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/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 5074e645769..5e9f5837101 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -34,10 +34,7 @@ module LabelsHelper # Returns a String def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block) project ||= @project || label.project - link = send("namespace_project_#{type.to_s.pluralize}_path", - project.namespace, - project, - label_name: [label.name]) + link = label_filter_path(project, label, type: type) if block_given? link_to link, class: css_class, &block @@ -46,6 +43,13 @@ module LabelsHelper end end + def label_filter_path(project, label, type: issue) + send("namespace_project_#{type.to_s.pluralize}_path", + project.namespace, + project, + label_name: [label.name]) + end + def project_label_names @project.labels.pluck(:title) end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 1dd07a2a220..4da1f4865a4 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -27,7 +27,7 @@ module MergeRequestsHelper end def ci_build_details_path(merge_request) - build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) + build_url = merge_request.source_project.ci_service.build_page(merge_request.diff_head_sha, merge_request.source_branch) return nil unless build_url parsed_url = URI.parse(build_url) @@ -55,6 +55,10 @@ module MergeRequestsHelper end.sort.to_sentence end + def mr_closes_issues + @mr_closes_issues ||= @merge_request.closes_issues + end + def mr_change_branches_path(merge_request) new_namespace_project_merge_request_path( @project.namespace, @project, diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index b401c8385be..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 @@ -60,13 +92,34 @@ 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 + + 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 - button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', - data: data, title: 'Add a reply' + def note_max_access_for_user(note) + @max_access_by_user_id ||= Hash.new do |hash, key| + project = key[:project] + hash[key] = project.team.human_max_access(key[:user_id]) + end + + full_key = { project: note.project, user_id: note.author_id } + @max_access_by_user_id[full_key] + end + + def diff_note_path(note) + return unless note.diff_note? + + if note.for_merge_request? && note.active? + diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) + elsif note.for_commit? + namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) + end end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 77783cd7640..7e8369d0a05 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -72,6 +72,6 @@ module NotificationsHelper # Create hidden field to send notification setting source to controller def hidden_setting_source_input(notification_setting) return unless notification_setting.source_type - hidden_field_tag "#{notification_setting.source_type.downcase}[id]", notification_setting.source_id + hidden_field_tag "#{notification_setting.source_type.downcase}_id", notification_setting.source_id end end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index e4e8b934bc8..22387d66451 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -52,7 +52,7 @@ module PageLayoutHelper raise ArgumentError, 'cannot provide more than two attributes' if map.length > 2 @page_card_attributes ||= {} - @page_card_attributes = map.reject { |_,v| v.blank? } if map.present? + @page_card_attributes = map.reject { |_, v| v.blank? } if map.present? @page_card_attributes end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d91e3332e48..3bbbb26cff2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -15,7 +15,7 @@ module ProjectsHelper def link_to_member_avatar(author, opts = {}) default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } opts = default_opts.merge(opts) - image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] + image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar] end def link_to_member(project, author, opts = {}, &block) @@ -27,7 +27,7 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] + author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar] # Build name span tag if opts[:by_username] @@ -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 @@ -289,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 @@ -327,9 +335,9 @@ module ProjectsHelper end end - def sanitize_repo_path(message) + def sanitize_repo_path(project, message) return '' unless message.present? - message.strip.gsub(Gitlab.config.gitlab_shell.repos_path.chomp('/'), "[REPOS PATH]") + message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") end 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/helpers/time_helper.rb b/app/helpers/time_helper.rb index b04b0a5114c..8cb82c2d5cc 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -23,4 +23,11 @@ module TimeHelper def date_from_to(from, to) "#{from.to_s(:short)} - #{to.to_s(:short)}" end + + def duration_in_numbers(finished_at, started_at) + diff_in_seconds = finished_at.to_i - started_at.to_i + time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S" + + Time.at(diff_in_seconds).utc.strftime(time_format) + end end diff --git a/app/helpers/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/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/models/ability.rb b/app/models/ability.rb index f5950879ccb..eeb0ceba081 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,5 +1,6 @@ class Ability class << self + # rubocop: disable Metrics/CyclomaticComplexity def allowed(user, subject) return anonymous_abilities(user, subject) if user.nil? return [] unless user.is_a?(User) @@ -19,6 +20,7 @@ class Ability when ProjectMember then project_member_abilities(user, subject) when User then user_abilities when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) + when Ci::Runner then runner_abilities(user, subject) else [] end.concat(global_abilities(user)) end @@ -155,10 +157,11 @@ class Ability # Push abilities on the users team role rules.push(*project_team_rules(project.team, user)) - if project.owner == user || - (project.group && project.group.has_owner?(user)) || - user.admin? + owner = user.admin? || + project.owner == user || + (project.group && project.group.has_owner?(user)) + if owner rules.push(*project_owner_rules) end @@ -167,6 +170,10 @@ class Ability # Allow to read builds for internal projects rules << :read_build if project.public_builds? + + unless owner || project.team.member?(user) || project_group_member?(project, user) + rules << :request_access + end end if project.archived? @@ -343,8 +350,11 @@ class Ability rules = [] rules << :read_group if can_read_group?(user, group) + owner = user.admin? || group.has_owner?(user) + master = owner || group.has_master?(user) + # Only group masters and group owners can create new projects - if group.has_master?(user) || group.has_owner?(user) || user.admin? + if master rules += [ :create_projects, :admin_milestones @@ -352,7 +362,7 @@ class Ability end # Only group owner and administrators can admin group - if group.has_owner?(user) || user.admin? + if owner rules += [ :admin_group, :admin_namespace, @@ -361,6 +371,10 @@ class Ability ] end + if group.public? || (group.internal? && !user.external?) + rules << :request_access unless group.users.include?(user) + end + rules.flatten end @@ -512,6 +526,18 @@ class Ability rules end + def runner_abilities(user, runner) + if user.is_admin? + [:assign_runner] + elsif runner.is_shared? || runner.locked? + [] + elsif user.ci_authorized_runners.include?(runner) + [:assign_runner] + else + [] + end + end + def user_abilities [:read_user] end @@ -550,5 +576,13 @@ class Ability rules end + + def project_group_member?(project, user) + project.group && + ( + project.group.members.exists?(user_id: user.id) || + project.group.requesters.exists?(user_id: user.id) + ) + end end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index d914b0b26eb..c6f77cc055f 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -55,6 +55,13 @@ class ApplicationSetting < ActiveRecord::Base presence: true, numericality: { only_integer: true, greater_than: 0 } + validates :repository_storage, + presence: true, + inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } + + validates :enabled_git_access_protocol, + inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true } + validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? value.each do |level| @@ -134,6 +141,8 @@ class ApplicationSetting < ActiveRecord::Base disabled_oauth_sign_in_sources: [], send_user_confirmation_email: false, container_registry_token_expire_delay: 5, + repository_storage: 'default', + user_default_external: false, ) end diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb index 59c7d87f5df..46b17479d6d 100644 --- a/app/models/award_emoji.rb +++ b/app/models/award_emoji.rb @@ -8,7 +8,7 @@ class AwardEmoji < ActiveRecord::Base belongs_to :user validates :awardable, :user, presence: true - validates :name, presence: true, inclusion: { in: Emoji.emojis_names } + validates :name, presence: true, inclusion: { in: Gitlab::Emoji.emojis_names } validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] } participant :user diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 2b0bec33131..e189dbac285 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -13,21 +13,19 @@ 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 acts_as_taggable + before_save :update_artifacts_size, if: :artifacts_file_changed? before_destroy { project } 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 @@ -90,7 +88,7 @@ module Ci end def retryable? - project.builds_enabled? && commands.present? + project.builds_enabled? && commands.present? && complete? end def retried? @@ -329,7 +327,12 @@ module Ci end def artifacts_metadata_entry(path, **options) - Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry + metadata = Gitlab::Ci::Build::Artifacts::Metadata.new( + artifacts_metadata.path, + path, + **options) + + metadata.to_entry end def erase_artifacts! @@ -375,6 +378,14 @@ module Ci private + def update_artifacts_size + self.artifacts_size = if artifacts_file.exists? + artifacts_file.size + else + nil + end + end + def erase_trace! self.trace = nil end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 0c9a5e42eec..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] @@ -163,7 +164,7 @@ module Ci end def skip_ci? - git_commit_message =~ /(\[ci skip\])/ if git_commit_message + git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message end def environments @@ -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/ci/variable.rb b/app/models/ci/variable.rb index f8d5d4486fd..c9c47ec7419 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -13,6 +13,7 @@ module Ci attr_encrypted :value, mode: :per_attribute_iv_and_salt, + insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, algorithm: 'aes-256-cbc' end diff --git a/app/models/commit.rb b/app/models/commit.rb index 174ccbaea6c..2ef3973c160 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -214,6 +214,13 @@ class Commit @raw.short_id(7) end + def diff_refs + Gitlab::Diff::DiffRefs.new( + base_sha: self.parent_id || self.sha, + head_sha: self.sha + ) + end + def pipelines @pipeline ||= project.pipelines.where(sha: sha) end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 4066958f67c..630ee9601e0 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -23,7 +23,7 @@ class CommitRange attr_reader :commit_from, :notation, :commit_to attr_reader :ref_from, :ref_to - # Optional Project model + # The Project model attr_accessor :project # The beginning and ending refs can be named or SHAs, and @@ -56,7 +56,7 @@ class CommitRange # Initialize a CommitRange # # range_string - The String commit range. - # project - An optional Project model. + # project - The Project model. # # Raises ArgumentError if `range_string` does not match `PATTERN`. def initialize(range_string, project) diff --git a/app/models/concerns/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/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/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb new file mode 100644 index 00000000000..2785fbb21c9 --- /dev/null +++ b/app/models/concerns/note_on_diff.rb @@ -0,0 +1,52 @@ +module NoteOnDiff + extend ActiveSupport::Concern + + NUMBER_OF_TRUNCATED_DIFF_LINES = 16 + + included do + delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true + end + + def diff_note? + true + end + + def diff_file + raise NotImplementedError + end + + def diff_line + raise NotImplementedError + end + + def for_line?(line) + raise NotImplementedError + end + + def diff_attributes + raise NotImplementedError + end + + def can_be_award_emoji? + false + end + + # Returns an array of at most 16 highlighted lines above a diff note + def truncated_diff_lines + prev_lines = [] + + highlighted_diff_lines.each do |line| + if line.meta? + prev_lines.clear + else + prev_lines << line + + break if for_line?(line) + + prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES + end + end + + prev_lines + end +end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index e498ca96e3c..520026c18dd 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,6 +11,8 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true + after_save :keep_around_commit + def commit project.commit(sha) end @@ -26,4 +28,8 @@ class Deployment < ActiveRecord::Base def last? self == environment.last_deployment end + + def keep_around_commit + project.repository.keep_around(self.sha) + end end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb new file mode 100644 index 00000000000..9671955db36 --- /dev/null +++ b/app/models/diff_note.rb @@ -0,0 +1,127 @@ +class DiffNote < Note + include NoteOnDiff + + serialize :original_position, Gitlab::Diff::Position + serialize :position, Gitlab::Diff::Position + + validates :original_position, presence: true + validates :position, presence: true + validates :diff_line, presence: true + validates :line_code, presence: true, line_code: true + validates :noteable_type, inclusion: { in: ['Commit', 'MergeRequest'] } + validate :positions_complete + validate :verify_supported + + before_validation :set_original_position, :update_position, on: :create + before_validation :set_line_code + after_save :keep_around_commits + + class << self + def build_discussion_id(noteable_type, noteable_id, position) + [super(noteable_type, noteable_id), *position.key].join("-") + end + end + + def new_diff_note? + true + end + + def diff_attributes + { position: position.to_json } + end + + def discussion_id + @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, position) + end + + def original_discussion_id + @original_discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, original_position) + end + + def position=(new_position) + if new_position.is_a?(String) + new_position = JSON.parse(new_position) rescue nil + end + + if new_position.is_a?(Hash) + new_position = new_position.with_indifferent_access + new_position = Gitlab::Diff::Position.new(new_position) + end + + super(new_position) + end + + def diff_file + @diff_file ||= self.original_position.diff_file(self.project.repository) + end + + def diff_line + @diff_line ||= diff_file.line_for_position(self.original_position) if diff_file + end + + def for_line?(line) + diff_file.position(line) == self.original_position + end + + def active?(diff_refs = nil) + return false unless supported? + return true if for_commit? + + diff_refs ||= self.noteable.diff_refs + + self.position.diff_refs == diff_refs + end + + private + + def supported? + !self.for_merge_request? || self.noteable.support_new_diff_notes? + end + + def set_original_position + self.original_position = self.position.dup + end + + def set_line_code + self.line_code = self.position.line_code(self.project.repository) + end + + def update_position + return unless supported? + return if for_commit? + + return if active? + + Notes::DiffPositionUpdateService.new( + self.project, + nil, + old_diff_refs: self.position.diff_refs, + new_diff_refs: self.noteable.diff_refs, + paths: self.position.paths + ).execute(self) + end + + def verify_supported + return if supported? + + errors.add(:noteable, "doesn't support new-style diff notes") + end + + def positions_complete + return if self.original_position.complete? && self.position.complete? + + errors.add(:position, "is invalid") + end + + def keep_around_commits + project.repository.keep_around(self.original_position.base_sha) + project.repository.keep_around(self.original_position.start_sha) + project.repository.keep_around(self.original_position.head_sha) + + if self.position != self.original_position + project.repository.keep_around(self.position.base_sha) + project.repository.keep_around(self.position.start_sha) + project.repository.keep_around(self.position.head_sha) + end + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 716039fb54b..fd736d12359 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -67,7 +67,7 @@ class Event < ActiveRecord::Base elsif issue? || issue_note? Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target) else - ((merge_request? || note?) && target) || milestone? + ((merge_request? || note?) && target.present?) || milestone? end end @@ -136,7 +136,7 @@ class Event < ActiveRecord::Base end def note? - target_type == "Note" + target.is_a?(Note) end def issue? @@ -315,7 +315,7 @@ class Event < ActiveRecord::Base def body? if push? - push_with_commits? + push_with_commits? || rm_ref? elsif note? true else diff --git a/app/models/group.rb b/app/models/group.rb index e66e04371b2..37631b99701 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: { 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 @@ -89,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/issue.rb b/app/models/issue.rb index 3c5859194b4..60abd47409e 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -19,6 +19,8 @@ class Issue < ActiveRecord::Base belongs_to :project belongs_to :moved_to, class_name: 'Issue' + has_many :events, as: :target, dependent: :destroy + validates :project, presence: true scope :cared, ->(user) { where(assignee_id: user) } diff --git a/app/models/label.rb b/app/models/label.rb index 49c352cc239..dc5586f5756 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 @@ -58,8 +58,8 @@ class Label < ActiveRecord::Base (?: (?<label_id>\d+) | # Integer-based label ID, or (?<label_name> - [A-Za-z0-9_-]+ | # String-based single-word label title, or - "[^&\?,]+" # String-based multi-word label surrounded in quotes + [A-Za-z0-9_\-\?&]+ | # String-based single-word label title, or + "[^,]+" # String-based multi-word label surrounded in quotes ) ) }x @@ -114,7 +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,8 @@ class Label < ActiveRecord::Base def nullify_priority self.priority = nil if priority.blank? end + + def sanitize_title(value) + CGI.unescapeHTML(Sanitize.clean(value.to_s)) + end end diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 33d2a69ebaf..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/member.rb b/app/models/member.rb index c74a16367db..44db3d977fa 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -30,8 +30,7 @@ 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) } scope :reporters, -> { where(access_level: REPORTER) } 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/merge_request.rb b/app/models/merge_request.rb index f5c5b7c1306..393d8a72657 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -12,9 +12,11 @@ class MergeRequest < ActiveRecord::Base has_one :merge_request_diff, dependent: :destroy + has_many :events, as: :target, dependent: :destroy + 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 @@ -27,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 @@ -87,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 @@ -115,6 +108,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 @@ -165,10 +160,6 @@ 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 @@ -178,15 +169,86 @@ class MergeRequest < ActiveRecord::Base end def diff_base_commit - if merge_request_diff + if persisted? merge_request_diff.base_commit - elsif source_sha - self.target_project.merge_base_commit(self.source_sha, self.target_branch) + elsif diff_start_commit && diff_head_commit + self.target_project.merge_base_commit(diff_start_sha, diff_head_sha) + end + end + + # MRs created before 8.4 don't store a MergeRequestDiff#base_commit_sha, + # but we need to get a commit for the "View file @ ..." link by deleted files, + # so we find the likely one if we can't get the actual one. + # This will not be the actual base commit if the target branch was merged into + # the source branch after the merge request was created, but it is good enough + # for the specific purpose of linking to a commit. + # It is not good enough for use in `Gitlab::Git::DiffRefs`, which needs the + # true base commit, so we can't simply have `#diff_base_commit` fall back on + # this method. + def likely_diff_base_commit + first_commit.parent || first_commit + end + + def diff_start_commit + if persisted? + merge_request_diff.start_commit + else + target_branch_head end end - def 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 diff_refs + return unless diff_start_commit || diff_base_commit + + Gitlab::Diff::DiffRefs.new( + base_sha: diff_base_sha, + start_sha: diff_start_sha, + head_sha: diff_head_sha + ) end def validate_branches @@ -223,21 +285,30 @@ class MergeRequest < ActiveRecord::Base def update_merge_request_diff if source_branch_changed? || target_branch_changed? - reload_code + reload_diff end end - def reload_code - if merge_request_diff && open? - merge_request_diff.reload_content - end + def reload_diff + return unless merge_request_diff && open? + + old_diff_refs = self.diff_refs + + merge_request_diff.reload_content + + new_diff_refs = self.diff_refs + + update_diff_notes_positions( + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs + ) end def check_if_can_be_merged return unless unchecked? can_be_merged = - !broken? && project.repository.can_be_merged?(source_sha, target_branch) + !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch) if can_be_merged mark_as_mergeable @@ -247,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 @@ -289,7 +360,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? @@ -319,13 +390,6 @@ class MergeRequest < ActiveRecord::Base ) end - # Returns the commit as a series of email patches. - # - # see "git format-patch" - def to_patch - target_project.repository.format_patch(diff_base_commit.sha, source_sha) - end - def hook_attrs attrs = { source: source_project.try(:hook_attrs), @@ -334,8 +398,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) @@ -484,7 +548,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? @@ -513,22 +577,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, @@ -541,12 +589,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 @@ -561,10 +609,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) @@ -574,9 +622,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 @@ -585,13 +633,7 @@ class MergeRequest < ActiveRecord::Base end def pipeline - @pipeline ||= source_project.pipeline(last_commit.id, source_branch) if last_commit && source_project - end - - def diff_refs - return nil unless diff_base_commit - - [diff_base_commit, last_commit] + @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project end def merge_commit @@ -605,4 +647,38 @@ class MergeRequest < ActiveRecord::Base def can_be_cherry_picked? merge_commit end + + def support_new_diff_notes? + diff_refs && diff_refs.complete? + end + + def update_diff_notes_positions(old_diff_refs:, new_diff_refs:) + return unless support_new_diff_notes? + return if new_diff_refs == old_diff_refs + + active_diff_notes = self.notes.diff_notes.select do |note| + note.new_diff_note? && note.active?(old_diff_refs) + end + + return if active_diff_notes.empty? + + paths = active_diff_notes.flat_map { |n| n.diff_file.paths }.uniq + + service = Notes::DiffPositionUpdateService.new( + self.project, + nil, + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs, + paths: paths + ) + + active_diff_notes.each do |note| + service.execute(note) + Gitlab::Timeless.timeless(note, &:save) + end + end + + def keep_around_commit + project.repository.keep_around(self.merge_commit_sha) + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index aca377cc600..ba235750aeb 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request - delegate :head_source_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil + delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil state_machine :state, initial: :empty do state :collected @@ -24,6 +24,7 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_diffs after_create :reload_content, unless: :importing? + after_save :keep_around_commits, unless: :importing? def reload_content reload_commits @@ -38,9 +39,9 @@ class MergeRequestDiff < ActiveRecord::Base if options[:ignore_whitespace_change] @diffs_no_whitespace ||= begin compare = Gitlab::Git::Compare.new( - self.repository.raw_repository, - self.base, - self.head, + repository.raw_repository, + self.start_commit_sha || self.target_branch_sha, + self.head_commit_sha || self.source_branch_sha, ) compare.diffs(options) end @@ -62,37 +63,39 @@ class MergeRequestDiff < ActiveRecord::Base end def base_commit - return nil unless self.base_commit_sha + return unless self.base_commit_sha - merge_request.target_project.commit(self.base_commit_sha) + project.commit(self.base_commit_sha) end - def last_commit_short_sha - @last_commit_short_sha ||= last_commit.short_id - end + def start_commit + return unless self.start_commit_sha - def dump_commits(commits) - commits.map(&:to_hash) + project.commit(self.start_commit_sha) end - def load_commits(array) - array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } - end + def head_commit + return last_commit unless self.head_commit_sha - def dump_diffs(diffs) - if diffs.respond_to?(:map) - diffs.map(&:to_hash) - end + project.commit(self.head_commit_sha) end - def load_diffs(raw, options) - if raw.respond_to?(:each) - Gitlab::Git::DiffCollection.new(raw, options) - else - Gitlab::Git::DiffCollection.new([]) - end + def compare + @compare ||= + begin + # Update ref for merge request + merge_request.fetch_ref + + Gitlab::Git::Compare.new( + repository.raw_repository, + self.target_branch_sha, + self.source_branch_sha + ) + end end + private + # Collect array of Git::Commit objects # between target and source branches def unmerged_commits @@ -105,89 +108,128 @@ 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 + new_attributes = {} + commit_objects = unmerged_commits if commit_objects.present? - self.st_commits = dump_commits(commit_objects) + new_attributes[:st_commits] = dump_commits(commit_objects) end - save + 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 + new_attributes = {} new_diffs = [] if commits.size.zero? - self.state = :empty + new_attributes[:state] = :empty else diff_collection = unmerged_diffs if diff_collection.overflow? # Set our state to 'overflow' to make the #empty? and #collected? # methods (generated by StateMachine) return false. - self.state = :overflow + new_attributes[:state] = :overflow end - self.real_size = diff_collection.real_size + new_attributes[:real_size] = diff_collection.real_size if diff_collection.any? new_diffs = dump_diffs(diff_collection) - self.state = :collected + new_attributes[:state] = :collected end end - self.st_diffs = new_diffs + new_attributes[:st_diffs] = new_diffs + + 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 - self.base_commit_sha = self.repository.merge_base(self.head, self.base) + update_columns_serialized(new_attributes) - self.save + 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 source_sha - return head_source_sha if head_source_sha.present? + def branch_base_commit + return unless self.source_branch_sha && self.target_branch_sha - source_commit = merge_request.source_project.commit(source_branch) - source_commit.try(:sha) + project.merge_base_commit(self.source_branch_sha, self.target_branch_sha) end - def target_sha - merge_request.target_sha + def branch_base_sha + branch_base_commit.try(:sha) end - def base - self.target_sha || self.target_branch - end + # + # #save or #update_attributes providing changes on serialized attributes do a lot of + # serialization and deserialization calls resulting in bad performance. + # Using #update_columns solves the problem with just one YAML.dump per serialized attribute that we provide. + # As a tradeoff we need to reload the current instance to properly manage time objects on those serialized + # attributes. So to keep the same behaviour as the attribute assignment we reload the instance. + # The difference is in the usage of + # #write_attribute= (#update_attributes) and #raw_write_attribute= (#update_columns) + # + # Ex: + # + # new_attributes[:st_commits].first.slice(:committed_date) + # => {:committed_date=>2014-02-27 11:01:38 +0200} + # YAML.load(YAML.dump(new_attributes[:st_commits].first.slice(:committed_date))) + # => {:committed_date=>2014-02-27 10:01:38 +0100} + # + def update_columns_serialized(new_attributes) + return unless new_attributes.any? - def head - self.source_sha + update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone)) + reload end - def compare - @compare ||= - begin - # Update ref for merge request - merge_request.fetch_ref - - Gitlab::Git::Compare.new( - self.repository.raw_repository, - self.base, - self.head - ) - end + def keep_around_commits + repository.keep_around(target_branch_sha) + repository.keep_around(source_branch_sha) + repository.keep_around(branch_base_sha) end end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index e0c8454a998..2bd7f198030 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -17,6 +17,7 @@ class Milestone < ActiveRecord::Base has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :merge_requests has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee + has_many :events, as: :target, dependent: :destroy scope :active, -> { with_state(:active) } scope :closed, -> { with_state(:closed) } diff --git a/app/models/namespace.rb b/app/models/namespace.rb index da19462f265..8b52cc824cd 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -21,8 +21,10 @@ class Namespace < ActiveRecord::Base delegate :name, to: :owner, allow_nil: true, prefix: true - after_create :ensure_dir_exist after_update :move_dir, if: :path_changed? + + # Save the storage paths before the projects are destroyed to use them on after destroy + before_destroy(prepend: true) { @old_repository_storage_paths = repository_storage_paths } after_destroy :rm_dir scope :root, -> { where('type IS NULL') } @@ -87,51 +89,35 @@ class Namespace < ActiveRecord::Base owner_name end - def ensure_dir_exist - gitlab_shell.add_namespace(path) - end - - def rm_dir - # Move namespace directory into trash. - # We will remove it later async - new_path = "#{path}+#{id}+deleted" - - if gitlab_shell.mv_namespace(path, new_path) - message = "Namespace directory \"#{path}\" moved to \"#{new_path}\"" - Gitlab::AppLogger.info message - - # Remove namespace directroy async with delay so - # GitLab has time to remove all projects first - GitlabShellWorker.perform_in(5.minutes, :rm_namespace, new_path) - end - end - def move_dir - # Ensure old directory exists before moving it - gitlab_shell.add_namespace(path_was) - if any_project_has_container_registry_tags? raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry') end - if gitlab_shell.mv_namespace(path_was, path) - Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) - - # If repositories moved successfully we need to - # send update instructions to users. - # However we cannot allow rollback since we moved namespace dir - # So we basically we mute exceptions in next actions - begin - send_update_instructions - rescue - # Returning false does not rollback after_* transaction but gives - # us information about failing some of tasks - false + # Move the namespace directory in all storages paths used by member projects + repository_storage_paths.each do |repository_storage_path| + # Ensure old directory exists before moving it + gitlab_shell.add_namespace(repository_storage_path, path_was) + + unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) + # if we cannot move namespace directory we should rollback + # db changes in order to prevent out of sync between db and fs + raise Exception.new('namespace directory cannot be moved') end - else - # if we cannot move namespace directory we should rollback - # db changes in order to prevent out of sync between db and fs - raise Exception.new('namespace directory cannot be moved') + end + + Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) + + # If repositories moved successfully we need to + # send update instructions to users. + # However we cannot allow rollback since we moved namespace dir + # So we basically we mute exceptions in next actions + begin + send_update_instructions + rescue + # Returning false does not rollback after_* transaction but gives + # us information about failing some of tasks + false end end @@ -152,4 +138,33 @@ class Namespace < ActiveRecord::Base def find_fork_of(project) projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) end + + private + + def repository_storage_paths + # We need to get the storage paths for all the projects, even the ones that are + # pending delete. Unscoping also get rids of the default order, which causes + # problems with SELECT DISTINCT. + Project.unscoped do + projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path) + end + end + + def rm_dir + # Remove the namespace directory in all storages paths used by member projects + @old_repository_storage_paths.each do |repository_storage_path| + # Move namespace directory into trash. + # We will remove it later async + new_path = "#{path}+#{id}+deleted" + + if gitlab_shell.mv_namespace(repository_storage_path, path, new_path) + message = "Namespace directory \"#{path}\" moved to \"#{new_path}\"" + Gitlab::AppLogger.info message + + # Remove namespace directroy async with delay so + # GitLab has time to remove all projects first + GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) + end + end + end end diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index a2aee2f925b..345041a6ad1 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -54,7 +54,7 @@ module Network @map = {} @reserved = {} - @commits.each_with_index do |c,i| + @commits.each_with_index do |c, i| c.time = i days[i] = c.committed_date @map[c.id] = c @@ -116,7 +116,7 @@ module Network end def commits_sort_by_ref - @commits.sort do |a,b| + @commits.sort do |a, b| if include_ref?(a) -1 elsif include_ref?(b) diff --git a/app/models/note.rb b/app/models/note.rb index 8db500a5219..ffffd0c0838 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -21,6 +21,7 @@ class Note < ActiveRecord::Base belongs_to :updated_by, class_name: "User" has_many :todos, dependent: :destroy + has_many :events, as: :target, dependent: :destroy delegate :gfm_reference, :local_reference, to: :noteable delegate :name, to: :project, prefix: true @@ -55,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 @@ -65,6 +66,7 @@ class Note < ActiveRecord::Base end before_validation :clear_blank_line_code! + after_save :keep_around_commit class << self def model_name @@ -80,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. @@ -113,6 +115,10 @@ class Note < ActiveRecord::Base false end + def new_diff_note? + false + end + def active? true end @@ -191,7 +197,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? @@ -202,7 +208,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 @@ -214,4 +220,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/notification_setting.rb b/app/models/notification_setting.rb index d41fc7073c6..121b598b8f3 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -5,6 +5,7 @@ class NotificationSetting < ActiveRecord::Base belongs_to :user belongs_to :source, polymorphic: true + belongs_to :project, foreign_key: 'source_id' validates :user, presence: true validates :level, presence: true @@ -13,7 +14,13 @@ class NotificationSetting < ActiveRecord::Base allow_nil: true } scope :for_groups, -> { where(source_type: 'Namespace') } - scope :for_projects, -> { where(source_type: 'Project') } + + # Exclude projects not included by the Project model's default scope (those that are + # pending delete). + # + scope :for_projects, -> do + includes(:project).references(:projects).where(source_type: 'Project').where.not(projects: { id: nil }) + end EMAIL_EVENTS = [ :new_note, diff --git a/app/models/project.rb b/app/models/project.rb index ca3bc04e2dd..a66b750cd48 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -24,8 +24,12 @@ class Project < ActiveRecord::Base default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :container_registry_enabled, gitlab_config_features.container_registry + default_value_for(:repository_storage) { current_application_settings.repository_storage } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } + after_create :ensure_dir_exist + after_save :ensure_dir_exist, if: :namespace_id_changed? + # set last_activity_at to the same as created_at after_create :set_last_activity_at def set_last_activity_at @@ -81,6 +85,7 @@ class Project < ActiveRecord::Base has_one :jira_service, dependent: :destroy has_one :redmine_service, dependent: :destroy has_one :custom_issue_tracker_service, dependent: :destroy + has_one :bugzilla_service, dependent: :destroy has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project has_one :external_wiki_service, dependent: :destroy @@ -103,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 @@ -153,9 +162,7 @@ class Project < ActiveRecord::Base validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id - validates :import_url, - url: { protocols: %w(ssh git http https) }, - if: :external_import? + validates :import_url, addressable_url: true, if: :external_import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, @@ -163,6 +170,10 @@ class Project < ActiveRecord::Base validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validate :visibility_level_allowed_by_group validate :visibility_level_allowed_as_fork + validate :check_wiki_path_conflict + validates :repository_storage, + presence: true, + inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } add_authentication_token_field :runners_token before_save :ensure_runners_token @@ -374,6 +385,10 @@ class Project < ActiveRecord::Base end end + def repository_storage_path + Gitlab.config.repositories.storages[repository_storage] + end + def team @team ||= ProjectTeam.new(self) end @@ -410,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) @@ -446,6 +461,8 @@ 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) @@ -539,6 +556,16 @@ class Project < ActiveRecord::Base self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.") end + def check_wiki_path_conflict + return if path.blank? + + path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki" + + if Project.where(namespace_id: namespace_id, path: path_to_check).exists? + errors.add(:name, 'has already been taken') + end + end + def to_param path end @@ -674,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) @@ -775,18 +802,12 @@ class Project < ActiveRecord::Base @repo_exists = false end + # Branches that are not _exactly_ matched by a protected branch. def open_branches - # We're using a Set here as checking values in a large Set is faster than - # checking values in a large Array. - protected_set = Set.new(protected_branch_names) - - repository.branches.reject do |branch| - protected_set.include?(branch.name) - end - end - - def protected_branch_names - @protected_branch_names ||= protected_branches.pluck(:name) + exact_protected_branch_names = protected_branches.reject(&:wildcard?).map(&:name) + branch_names = repository.branches.map(&:name) + non_open_branch_names = Set.new(exact_protected_branch_names).intersection(Set.new(branch_names)) + repository.branches.reject { |branch| non_open_branch_names.include? branch.name } end def root_ref?(branch) @@ -803,11 +824,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? @@ -830,12 +852,12 @@ class Project < ActiveRecord::Base raise Exception.new('Project cannot be renamed, because tags are present in its container registry') end - if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) + if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace) # If repository moved successfully we need to send update instructions to users. # However we cannot allow rollback since we moved repository # So we basically we mute exceptions in next actions begin - gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") + gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") send_move_instructions(old_path_with_namespace) reset_events_cache @@ -976,7 +998,7 @@ class Project < ActiveRecord::Base def create_repository # Forked import is handled asynchronously unless forked? - if gitlab_shell.add_repository(path_with_namespace) + if gitlab_shell.add_repository(repository_storage_path, path_with_namespace) repository.after_create true else @@ -1128,4 +1150,8 @@ class Project < ActiveRecord::Base _, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) status.zero? end + + def ensure_dir_exist + gitlab_shell.add_namespace(repository_storage_path, namespace.path) + end end diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index ca8a9b4217b..331123a5a5b 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -7,6 +7,7 @@ class ProjectImportData < ActiveRecord::Base marshal: true, encode: true, mode: :per_attribute_iv_and_salt, + insecure_mode: true, algorithm: 'aes-256-cbc' serialize :data, JSON diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb new file mode 100644 index 00000000000..81af55aa29a --- /dev/null +++ b/app/models/project_services/bugzilla_service.rb @@ -0,0 +1,23 @@ +class BugzillaService < IssueTrackerService + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def title + if self.properties && self.properties['title'].present? + self.properties['title'] + else + 'Bugzilla' + end + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'Bugzilla issue tracker' + end + end + + 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 6b2b1daa724..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,8 +30,4 @@ class CustomIssueTrackerService < IssueTrackerService { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' } ] end - - def initialize_properties - self.properties = {} if properties.nil? - 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/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 0ff4f4c8dd2..23e5b16221b 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -106,7 +106,7 @@ class HipchatService < Service else message << "pushed to #{ref_type} <a href=\""\ "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> " - message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> " + message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/, '')}</a> " message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)" push[:commits].take(MAX_COMMITS).each do |commit| diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 58cb720c3c1..ce7d1c5d5b1 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -112,15 +112,7 @@ class IrkerService < Service # Authorize both irc://domain.com/#chan and irc://domain.com/chan if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? - # Do not authorize irc://domain.com/ - if uri.fragment.nil? && uri.path.length > 1 - uri.to_s - else - # Authorize irc://domain.com/smthg#chan - # The irker daemon will deal with it by concatenating smthg and - # chan, thus sending messages on #smthgchan - uri.to_s - end + uri.to_s end end end diff --git a/app/models/project_services/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 beda89d3963..97bcbacf2b9 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -124,7 +124,7 @@ class JiraService < IssueTrackerService def build_api_url_from_project_url server = URI(project_url) - default_ports = [["http",80],["https",443]].include?([server.scheme,server.port]) + default_ports = [["http", 80], ["https", 443]].include?([server.scheme, server.port]) server_url = "#{server.scheme}://#{server.host}" server_url.concat(":#{server.port}") unless default_ports "#{server_url}/rest/api/#{DEFAULT_API_VERSION}" @@ -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) @@ -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/project_team.rb b/app/models/project_team.rb index 73e736820af..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,20 +137,10 @@ class ProjectTeam def max_member_access(user_id) access = [] - project.members.non_request.each do |member| - if member.user_id == user_id - access << member.access_field if member.access_field - break - end - end + access += project.members.where(user_id: user_id).has_access.pluck(:access_level) if group - group.members.non_request.each do |member| - if member.user_id == user_id - access << member.access_field if member.access_field - break - end - end + 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? @@ -178,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/models/project_wiki.rb b/app/models/project_wiki.rb index 25d82929c0b..a255710f577 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -159,7 +159,7 @@ class ProjectWiki private def init_repo(path_with_namespace) - gitlab_shell.add_repository(path_with_namespace) + gitlab_shell.add_repository(project.repository_storage_path, path_with_namespace) end def commit_details(action, message = nil, title = nil) @@ -173,7 +173,7 @@ class ProjectWiki end def path_to_repo - @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") + @path_to_repo ||= File.join(project.repository_storage_path, "#{path_with_namespace}.git") end def update_project_activity diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 33cf046fa75..b7011d7afdf 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -8,4 +8,51 @@ class ProtectedBranch < ActiveRecord::Base def commit project.commit(self.name) end + + # Returns all protected branches that match the given branch name. + # This realizes all records from the scope built up so far, and does + # _not_ return a relation. + # + # This method optionally takes in a list of `protected_branches` to search + # through, to avoid calling out to the database. + def self.matching(branch_name, protected_branches: nil) + (protected_branches || all).select { |protected_branch| protected_branch.matches?(branch_name) } + end + + # Returns all branches (among the given list of branches [`Gitlab::Git::Branch`]) + # that match the current protected branch. + def matching(branches) + branches.select { |branch| self.matches?(branch.name) } + end + + # Checks if the protected branch matches the given branch name. + def matches?(branch_name) + return false if self.name.blank? + + exact_match?(branch_name) || wildcard_match?(branch_name) + end + + # Checks if this protected branch contains a wildcard + def wildcard? + self.name && self.name.include?('*') + end + + protected + + def exact_match?(branch_name) + self.name == branch_name + end + + def wildcard_match?(branch_name) + wildcard_regex === branch_name + end + + def wildcard_regex + @wildcard_regex ||= begin + name = self.name.gsub('*', 'STAR_DONT_ESCAPE') + quoted_name = Regexp.quote(name) + regex_string = quoted_name.gsub('STAR_DONT_ESCAPE', '.*?') + /\A#{regex_string}\z/ + end + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index acc720ccfa3..5b670cb4b8f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -39,7 +39,7 @@ class Repository # Return absolute path to repository def path_to_repo @path_to_repo ||= File.expand_path( - File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git") + File.join(@project.repository_storage_path, path_with_namespace + ".git") ) end @@ -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 @@ -203,6 +203,26 @@ class Repository branch_names.include?(branch_name) end + def ref_exists?(ref) + 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) + + 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 tag_names cache.fetch(:tag_names) { raw_repository.tag_names } end @@ -246,24 +266,26 @@ class Repository end end + # Keys for data that can be affected for any commit push. def cache_keys - %i(size branch_names tag_names branch_count tag_count commit_count + %i(size commit_count readme version contribution_guide changelog license_blob license_key gitignore) end + # Keys for data on branch/tag operations. + def cache_keys_for_branches_and_tags + %i(branch_names tag_names branch_count tag_count) + end + def build_cache - cache_keys.each do |key| + (cache_keys + cache_keys_for_branches_and_tags).each do |key| unless cache.exist?(key) send(key) end end end - def expire_gitignore - cache.expire(:gitignore) - end - def expire_tags_cache cache.expire(:tag_names) @tags = nil @@ -286,8 +308,6 @@ class Repository # This ensures this particular cache is flushed after the first commit to a # new repository. expire_emptiness_caches if empty? - expire_branch_count_cache - expire_tag_count_cache end def expire_branch_cache(branch_name = nil) @@ -633,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 @@ -875,7 +885,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}) @@ -892,7 +901,7 @@ class Repository if line =~ /^.*:.*:\d+:/ ref, filename, startline = line.split(':') startline = startline.to_i - index - extname = File.extname(filename) + extname = Regexp.escape(File.extname(filename)) basename = filename.sub(/#{extname}$/, '') break end @@ -978,6 +987,10 @@ class Repository raw_repository.ls_files(actual_ref) end + def gitattribute(path, name) + raw_repository.attributes(path)[name] + end + def copy_gitattributes(ref) actual_ref = ref || root_ref begin @@ -1015,4 +1028,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 diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 375f195dba7..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,9 @@ 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 class << self def reply_key @@ -18,7 +22,7 @@ class SentNotification < ActiveRecord::Base find_by(reply_key: reply_key) end - def record(noteable, recipient_id, reply_key, params = {}) + def record(noteable, recipient_id, reply_key, attrs = {}) return unless reply_key noteable_id = nil @@ -29,7 +33,7 @@ class SentNotification < ActiveRecord::Base noteable_id = noteable.id end - params.reverse_merge!( + attrs.reverse_merge!( project: noteable.project, noteable_type: noteable.class.name, noteable_id: noteable_id, @@ -38,13 +42,17 @@ class SentNotification < ActiveRecord::Base reply_key: reply_key ) - create(params) + create(attrs) end - def record_note(note, recipient_id, reply_key, params = {}) - params[:line_code] = note.line_code + def record_note(note, recipient_id, reply_key, attrs = {}) + if note.diff_note? + attrs[:note_type] = note.type + + attrs.merge!(note.diff_attributes) + end - record(note.noteable, recipient_id, reply_key, params) + record(note.noteable, recipient_id, reply_key, attrs) end end @@ -67,4 +75,35 @@ class SentNotification < ActiveRecord::Base def to_param self.reply_key end + + def note_attributes + { + project: self.project, + author: self.recipient, + type: self.note_type, + noteable_type: self.noteable_type, + noteable_id: self.noteable_id, + commit_id: self.commit_id, + line_code: self.line_code, + position: self.position.to_json + } + end + + def create_note(note) + Notes::CreateService.new( + self.project, + self.recipient, + self.note_attributes.merge(note: note) + ).execute + end + + private + + def note_valid + Note.new(note_attributes.merge(note: "Test")).valid? + end + + def keep_around_commit + project.repository.keep_around(self.commit_id) + end end diff --git a/app/models/service.rb b/app/models/service.rb index 40d39933ad8..d7a32c28267 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -170,6 +170,7 @@ class Service < ActiveRecord::Base bamboo buildkite builds_email + bugzilla campfire custom_issue_tracker drone_ci diff --git a/app/models/snippet.rb b/app/models/snippet.rb index f8034cb5e6b..5ec933601ac 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -20,6 +20,7 @@ class Snippet < ActiveRecord::Base length: { within: 0..255 }, format: { with: Gitlab::Regex.file_name_regex, message: Gitlab::Regex.file_name_regex_message } + validates :content, presence: true validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } @@ -81,6 +82,11 @@ class Snippet < ActiveRecord::Base 0 end + # alias for compatibility with blobs and highlighting + def path + file_name + end + def name file_name end @@ -135,7 +141,16 @@ class Snippet < ActiveRecord::Base end def accessible_to(user) - where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user) + return are_public unless user.present? + return all if user.admin? + + where( + 'visibility_level IN (:visibility_levels) + OR author_id = :author_id + OR project_id IN (:project_ids)', + visibility_levels: [Snippet::PUBLIC, Snippet::INTERNAL], + author_id: user.id, + project_ids: user.authorized_projects.select(:id)) end end end diff --git a/app/models/todo.rb b/app/models/todo.rb index 2792fa9b9a8..ac3fdbc7f3b 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 @@ -30,10 +37,16 @@ class Todo < ActiveRecord::Base state :done end + after_save :keep_around_commit + def build_failed? action == BUILD_FAILED end + def action_name + ACTION_NAMES[action] + end + def body if note.present? note.note @@ -62,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/app/models/user.rb b/app/models/user.rb index 04b220ee13c..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 @@ -25,6 +25,7 @@ class User < ActiveRecord::Base attr_encrypted :otp_secret, key: Gitlab::Application.config.secret_key_base, mode: :per_attribute_iv_and_salt, + insecure_mode: true, algorithm: 'aes-256-cbc' devise :two_factor_authenticatable, @@ -57,7 +58,7 @@ class User < ActiveRecord::Base # Groups has_many :members, dependent: :destroy - has_many :group_members, dependent: :destroy, source: 'GroupMember' + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' has_many :groups, through: :group_members has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group @@ -65,7 +66,7 @@ class User < ActiveRecord::Base # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects - has_many :project_members, dependent: :destroy, class_name: 'ProjectMember' + has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, class_name: 'ProjectMember' has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :users_star_projects, dependent: :destroy @@ -652,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) @@ -763,7 +764,7 @@ class User < ActiveRecord::Base unless email_domains.blank? match_found = email_domains.any? do |domain| - escaped = Regexp.escape(domain).gsub('\*','.*?') + escaped = Regexp.escape(domain).gsub('\*', '.*?') regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE email_domain = Mail::Address.new(self.email).domain email_domain =~ regexp @@ -851,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/audit_event_service.rb b/app/services/audit_event_service.rb index a7f090655e1..8a000585e89 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -7,7 +7,7 @@ class AuditEventService @details = { with: @details[:with], target_id: @author.id, - target_type: "User", + target_type: 'User', target_details: @author.name, } diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index e57b95f21ec..e294a962352 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -20,9 +20,11 @@ module Auth token.issuer = registry.issuer token.audience = AUDIENCE token.expire_time = token_expire_at + token[:access] = names.map do |name| { type: 'repository', name: name, actions: %w(*) } end + token.encoded end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 6b69cb53b2c..c578097376a 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -23,7 +23,7 @@ module Commits private def check_push_permissions - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch) unless allowed raise ValidationError.new('You are not allowed to push into this branch') diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 9f4481a8153..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,18 +32,15 @@ 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') end - rescue GitHooksService::PreReceiveError - error('Branch creation was rejected by Git hook') + rescue GitHooksService::PreReceiveError => ex + error(ex.message) 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 e06a6f2f47a..d6d4afcf29a 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) @@ -24,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 91ed0e354d0..c0e7ecf6a96 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -9,12 +9,13 @@ 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 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 @@ -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 fae069ee4a5..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,36 +13,29 @@ 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') 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) - out = super(message) - out[:return_code] = return_code - out + super(message).merge(return_code: return_code) end def success(message) - out = super() - out[:message] = message - out + super().merge(message: message) end def build_push_data(branch) diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index de3352a6756..1e41fbe34b6 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -5,7 +5,6 @@ class DeleteTagService < BaseService repository = project.repository tag = repository.find_tag(tag_name) - # No such tag unless tag return error('No such tag', 404) end @@ -26,15 +25,11 @@ class DeleteTagService < BaseService end def error(message, return_code = 400) - out = super(message) - out[:return_code] = return_code - out + super(message).merge(return_code: return_code) end def success(message) - out = super() - out[:message] = message - out + super().merge(message: message) end def build_push_data(tag) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 0326a8823e9..37c5e321b39 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -15,7 +15,6 @@ module Files params[:file_content] end - # Validate parameters validate # Create new branch if it different from source_branch @@ -26,7 +25,7 @@ module Files if commit success else - error("Something went wrong. Your changes were not committed") + error('Something went wrong. Your changes were not committed') end rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex error(ex.message) @@ -43,7 +42,7 @@ module Files end def validate - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + allowed = ::Gitlab::GitAccess.new(current_user, project, 'web').can_push_to_branch?(@target_branch) unless allowed raise_error("You are not allowed to push into this branch") @@ -51,12 +50,12 @@ module Files unless project.empty_repo? unless @source_project.repository.branch_names.include?(@source_branch) - raise_error("You can only create or edit files when you are on a branch") + raise_error('You can only create or edit files when you are on a branch') end if different_branch? if repository.branch_names.include?(@target_branch) - raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes") + raise_error('Branch with such name already exists. You need to switch to this branch in order to make changes') end end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index e4cde4a2fd8..8eaf6db8012 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -29,7 +29,7 @@ module Files blob = repository.blob_at_branch(@source_branch, @file_path) if blob - raise_error("Your changes could not be committed because a file with the same name already exists") + raise_error('Your changes could not be committed because a file with the same name already exists') end end end diff --git a/app/services/git_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/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/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/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/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_service.rb b/app/services/merge_requests/merge_service.rb index 9aaf5a5e561..f1b1d90c457 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -34,12 +34,15 @@ 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) + 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/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 12edfb2d671..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 @@ -40,6 +40,5 @@ module MergeRequests error("Can't cancel the automatic merge", 406) end end - end end diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index 064910f81f7..8437d9b8b43 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -20,6 +20,7 @@ module MergeRequests return unless merge_request.target_branch == project.default_branch closed_issues = merge_request.closes_issues(current_user) + closed_issues.each do |issue| if can?(current_user, :update_issue, issue) Issues::CloseService.new(project, current_user, {}).execute(issue, commit: merge_request) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index fe0579744b4..b11ecd97a57 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -34,10 +34,10 @@ module MergeRequests def close_merge_requests commit_ids = @commits.map(&:id) merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a - merge_requests = merge_requests.select(&:last_commit) + merge_requests = merge_requests.select(&:diff_head_commit) merge_requests = merge_requests.select do |merge_request| - commit_ids.include?(merge_request.last_commit.id) + commit_ids.include?(merge_request.diff_head_sha) end merge_requests.uniq.select(&:source_project).each do |merge_request| @@ -60,20 +60,15 @@ module MergeRequests merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? - merge_request.reload_code - merge_request.mark_as_unchecked + merge_request.reload_diff else mr_commit_ids = merge_request.commits.map(&:id) push_commit_ids = @commits.map(&:id) matches = mr_commit_ids & push_commit_ids - - if matches.any? - merge_request.reload_code - merge_request.mark_as_unchecked - else - merge_request.mark_as_unchecked - end + merge_request.reload_diff if matches.any? end + + merge_request.mark_as_unchecked end end @@ -94,12 +89,10 @@ module MergeRequests merge_request = merge_requests_for_source_branch.first return unless merge_request - last_commit = merge_request.last_commit - begin # Since any number of commits could have been made to the restored branch, # find the common root to see what has been added. - common_ref = @project.repository.merge_base(last_commit.id, @newrev) + common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @newrev) # If the a commit no longer exists in this repo, gitlab_git throws # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index 8279ad2001b..eb88ae9d11c 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -6,7 +6,7 @@ module MergeRequests create_note(merge_request) notification_service.reopen_mr(merge_request, current_user) execute_hooks(merge_request, 'reopen') - merge_request.reload_code + merge_request.reload_diff merge_request.mark_as_unchecked end diff --git a/app/services/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/notes/diff_position_update_service.rb b/app/services/notes/diff_position_update_service.rb new file mode 100644 index 00000000000..0cb731f5bc3 --- /dev/null +++ b/app/services/notes/diff_position_update_service.rb @@ -0,0 +1,30 @@ +module Notes + class DiffPositionUpdateService < BaseService + def execute(note) + new_position = tracer.trace(note.position) + + # Don't update the position if the type doesn't match, since that means + # the diff line commented on was changed, and the comment is now outdated + old_position = note.position + if new_position && + new_position != old_position && + new_position.type == old_position.type + + note.position = new_position + end + + note + end + + private + + def tracer + @tracer ||= Gitlab::Diff::PositionTracer.new( + repository: project.repository, + old_diff_refs: params[:old_diff_refs], + new_diff_refs: params[:new_diff_refs], + paths: params[:paths] + ) + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 590350a11e5..ab6e51209ee 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -153,6 +153,7 @@ class NotificationService else mentioned_users end + recipients = recipients.concat(participants) # Merge project watchers @@ -176,6 +177,7 @@ class NotificationService # build notify method like 'note_commit_email' notify_method = "note_#{note.noteable_type.underscore}_email".to_sym + recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index f09072975c3..882606e38d0 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -51,13 +51,13 @@ module Projects return true if params[:skip_repo] == true # There is a possibility project does not have repository or wiki - return true unless gitlab_shell.exists?(path + '.git') + return true unless gitlab_shell.exists?(project.repository_storage_path, path + '.git') new_path = removal_path(path) - if gitlab_shell.mv_repository(path, new_path) + if gitlab_shell.mv_repository(project.repository_storage_path, path, new_path) log_info("Repository \"#{path}\" moved to \"#{new_path}\"") - GitlabShellWorker.perform_in(5.minutes, :remove_repository, new_path) + GitlabShellWorker.perform_in(5.minutes, :remove_repository, project.repository_storage_path, new_path) else false end 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/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index 43db29315a1..752c11d7ae6 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -24,10 +24,10 @@ module Projects def execute raise LeaseTaken unless try_obtain_lease - GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace) + 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) diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 80c7193efcb..6afc048576d 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 @@ -39,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 @@ -46,6 +47,8 @@ module Projects end def notify_success + Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported") + notification_service.project_exported(@project, @current_user) end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 9159ec08959..163ebf26c84 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -42,7 +42,7 @@ module Projects def import_repository begin - gitlab_shell.import_repository(project.path_with_namespace, project.import_url) + gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) rescue Gitlab::Shell::Error => e raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 03b57dea51e..bc7f8bf433b 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -50,12 +50,12 @@ module Projects project.send_move_instructions(old_path) # Move main repository - unless gitlab_shell.mv_repository(old_path, new_path) + unless gitlab_shell.mv_repository(project.repository_storage_path, old_path, new_path) raise TransferError.new('Cannot move project') end # Move wiki repo also if present - gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki") + gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki") # clear project cached events project.reset_events_cache diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 0c88022276e..f06311511cc 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -3,6 +3,7 @@ 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) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 4e8fa0818b9..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 @@ -293,7 +294,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/todo_service.rb b/app/services/todo_service.rb index 540bf54b920..6bb0a72d30e 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -159,8 +159,9 @@ class TodoService def create_todos(users, attributes) Array(users).map do |user| next if pending_todos(user, attributes).exists? - Todo.create(attributes.merge(user_id: user.id)) + todo = Todo.create(attributes.merge(user_id: user.id)) user.update_todos_count_cache + todo end end @@ -236,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/app/services/update_release_service.rb b/app/services/update_release_service.rb index 25eb13ef09a..0ee1ff2d7d9 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) @@ -22,8 +21,6 @@ class UpdateReleaseService < BaseService end def success(release) - out = super() - out[:release] = release - out + super().merge(release: release) end end diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb index 93af8f21972..a6bb36821c3 100644 --- a/app/services/update_snippet_service.rb +++ b/app/services/update_snippet_service.rb @@ -9,6 +9,7 @@ class UpdateSnippetService < BaseService def execute # check that user is allowed to set specified visibility_level new_visibility = params[:visibility_level] + if new_visibility && new_visibility.to_i != snippet.visibility_level unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) deny_visibility_level(snippet, new_visibility) diff --git a/app/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/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb index 28085b31083..046a1d641a9 100644 --- a/app/uploaders/lfs_object_uploader.rb +++ b/app/uploaders/lfs_object_uploader.rb @@ -4,7 +4,7 @@ class LfsObjectUploader < CarrierWave::Uploader::Base storage :file def store_dir - "#{Gitlab.config.lfs.storage_path}/#{model.oid[0,2]}/#{model.oid[2,2]}" + "#{Gitlab.config.lfs.storage_path}/#{model.oid[0, 2]}/#{model.oid[2, 2]}" end def cache_dir diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb new file mode 100644 index 00000000000..09bfa613cbe --- /dev/null +++ b/app/validators/addressable_url_validator.rb @@ -0,0 +1,45 @@ +# AddressableUrlValidator +# +# Custom validator for URLs. This is a stricter version of UrlValidator - it also checks +# for using the right protocol, but it actually parses the URL checking for any syntax errors. +# The regex is also different from `URI` as we use `Addressable::URI` here. +# +# By default, only URLs for http, https, ssh, and git protocols will be considered valid. +# Provide a `:protocols` option to configure accepted protocols. +# +# Example: +# +# class User < ActiveRecord::Base +# validates :personal_url, addressable_url: true +# +# validates :ftp_url, addressable_url: { protocols: %w(ftp) } +# +# validates :git_url, addressable_url: { protocols: %w(http https ssh git) } +# end +# +class AddressableUrlValidator < ActiveModel::EachValidator + DEFAULT_OPTIONS = { protocols: %w(http https ssh git) } + + def validate_each(record, attribute, value) + unless valid_url?(value) + record.errors.add(attribute, "must be a valid URL") + end + end + + private + + def valid_url?(value) + return false unless value + + valid_protocol?(value) && valid_uri?(value) + end + + def valid_uri?(value) + Gitlab::UrlSanitizer.valid?(value) + end + + def valid_protocol?(value) + options = DEFAULT_OPTIONS.merge(self.options) + value =~ /\A#{URI.regexp(options[:protocols])}\z/ + end +end diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index 862b86d9d4a..dd2e7ebd030 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -3,14 +3,14 @@ %tr %td - if user - = link_to user.name, [:admin, user] + = link_to user.name, user .light.small Joined #{time_ago_with_tooltip(user.created_at)} - else (removed) %td - if reporter - = link_to reporter.name, [:admin, reporter] + = link_to reporter.name, reporter - else (removed) .light.small diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index c883e8f97da..8de28528cda 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -15,7 +15,7 @@ = f.label :default_snippet_visibility, class: 'control-label col-sm-2' .col-sm-10 = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new) - .form-group.group-visibility-level-holder + .form-group.project-visibility-level-holder = f.label :default_group_visibility, class: 'control-label col-sm-2' .col-sm-10 = render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new) @@ -44,6 +44,12 @@ 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_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control') + %span.help-block#clone-protocol-help + Allow only the selected protocols to be used for Git access. + .form-group .col-sm-offset-2.col-sm-10 .checkbox = f.label :version_check_enabled do @@ -94,6 +100,13 @@ = f.label :user_oauth_applications do = f.check_box :user_oauth_applications Allow users to register any application to use GitLab as an OAuth provider + .form-group + = f.label :user_default_external, 'New users set to external', class: 'control-label col-sm-2' + .col-sm-10 + .checkbox + = f.label :user_default_external do + = f.check_box :user_default_external + Newly registered users will by default be external %fieldset %legend Sign-in Restrictions @@ -311,6 +324,15 @@ = f.text_field :sentry_dsn, class: 'form-control' %fieldset + %legend Repository Storage + .form-group + = f.label :repository_storage, 'Storage path for new projects', class: 'control-label col-sm-2' + .col-sm-10 + = f.select :repository_storage, repository_storage_options_for_select, {}, class: 'form-control' + .help-block + You can manage the repository storage paths in your gitlab.yml configuration file + + %fieldset %legend Repository Checks .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml index d78682532ed..9d722bd7382 100644 --- a/app/views/admin/background_jobs/_head.html.haml +++ b/app/views/admin/background_jobs/_head.html.haml @@ -1,5 +1,9 @@ .nav-links.sub-nav %ul{ class: (container_class) } + = nav_link(controller: :system_info) do + = link_to admin_system_info_path, title: 'System Info' do + %span + System Info = nav_link(controller: :background_jobs) do = link_to admin_background_jobs_path, title: 'Background Jobs' do %span diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 654d261aa99..4f680b507c4 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -2,7 +2,7 @@ - page_title "Background Jobs" = render 'admin/background_jobs/head' -%div{ class: (container_class) } +%div{ class: container_class } %h3.page-title Background Jobs %p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index efd5b12cfeb..1e60205f91a 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -1,7 +1,7 @@ - @no_container = true = render "admin/dashboard/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area %ul.nav-links diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 4682016a886..a2ac407c159 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,7 +1,7 @@ - @no_container = true = render "admin/dashboard/head" -%div{ class: (container_class) } +%div{ class: container_class } .admin-dashboard.prepend-top-default .row .col-md-4 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 4f1996ef7ab..794f910a61f 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -2,44 +2,34 @@ - page_title "Groups" = 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. - +%div{ class: container_class } .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 - - @groups.each do |group| - = render 'group', group: group + = render @groups = paginate @groups, theme: "gitlab" 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/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index 7b8407f9152..e79303240f0 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -2,7 +2,7 @@ - page_title "Health Check" = render 'admin/background_jobs/head' -%div{ class: (container_class) } +%div{ class: container_class } %h3.page-title Health Check .bs-callout.clearfix diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 5ddc3b9ea85..676812121d7 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -5,7 +5,7 @@ Gitlab::RepositoryCheckLogger] = render 'admin/background_jobs/head' -%div{ class: (container_class) } +%div{ class: container_class } %ul.nav-links.log-tabs - loggers.each do |klass| %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 4822cb693c2..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" +%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 - .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 461d588415d..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 @@ -137,16 +143,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 +162,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/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 36b21eefdee..64893b38c58 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -4,6 +4,8 @@ %span.label.label-success shared - else %span.label.label-info specific + - if runner.locked? + %span.label.label-warning locked - unless runner.active? %span.label.label-danger paused diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 5eff77aff2d..a53876d6757 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -1,7 +1,7 @@ - @no_container = true = render "admin/dashboard/head" -%div{ class: (container_class) } +%div{ class: container_class } %p.prepend-top-default %span @@ -40,6 +40,9 @@ %span.label.label-info specific \- run builds from assigned projects %li + %span.label.label-warning locked + \- runner cannot be assigned to other projects + %li %span.label.label-danger paused \- runner will not receive any new builds diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml new file mode 100644 index 00000000000..6956e5ab795 --- /dev/null +++ b/app/views/admin/system_info/show.html.haml @@ -0,0 +1,25 @@ +- @no_container = true +- page_title "System Info" += render 'admin/background_jobs/head' + +%div{ class: container_class } + .prepend-top-default + .row + .col-sm-4 + .light-well + %h4 CPU + .data + %h1= "#{@cpus} cores" + .col-sm-4 + .light-well + %h4 Memory + .data + %h1= "#{number_to_human_size(@mem_used)} / #{number_to_human_size(@mem_total)}" + .col-sm-4 + .light-well + %h4 Disks + .data + - @disks.each do |disk| + %h1= "#{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}" + %p= "#{disk[:disk_name]}" + %p= "#{disk[:mount_path]}" diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index fe0b9d3a491..3145212728f 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -44,7 +44,7 @@ %legend Access .form-group = f.label :projects_limit, class: 'control-label' - .col-sm-10= f.number_field :projects_limit, class: 'form-control' + .col-sm-10= f.number_field :projects_limit, min: 0, class: 'form-control' .form-group = f.label :can_create_group, class: 'control-label' diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml new file mode 100644 index 00000000000..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/groups.html.haml b/app/views/admin/users/groups.html.haml index b0a709a568a..8f6d13b881a 100644 --- a/app/views/admin/users/groups.html.haml +++ b/app/views/admin/users/groups.html.haml @@ -1,11 +1,12 @@ - page_title "Groups", @user.name, "Users" = render 'admin/users/head' -- if @user.group_members.present? +- group_members = @user.group_members.includes(:source) +- if group_members.any? .panel.panel-default .panel-heading Groups: %ul.well-list - - @user.group_members.each do |group_member| + - group_members.each do |group_member| - group = group_member.group %li.group_member %span{class: ("list-item-name" unless group_member.owner?)} diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index d0a696da64b..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) +%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' - .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/emojis/index.html.haml b/app/views/emojis/index.html.haml index 97401a2e618..8b38b4c2bd4 100644 --- a/app/views/emojis/index.html.haml +++ b/app/views/emojis/index.html.haml @@ -1,6 +1,6 @@ .emoji-menu + = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Seach emojis" .emoji-menu-content - = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control" - Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis| %h5.emoji-menu-title = Gitlab::AwardEmoji::CATEGORIES[category] diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index dc4ff17e31a..ea54ef226ec 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -1,3 +1,5 @@ +- project = event.project + .event-title %span.author_name= link_to_author event %span.event_label.pushed #{event.action_name} #{event.ref_type} @@ -5,19 +7,18 @@ %strong= event.ref_name - else %strong - = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), title: h(event.target_title) + = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title) at - = link_to_project event.project + = link_to_project project - if event.push_with_commits? - - project = event.project .event-body %ul.well-list.event_commits - few_commits = event.commits[0...2] - few_commits.each do |commit| = render "events/commit", commit: commit, project: project, event: event - - create_mr = event.new_ref? && create_mr_button?(event.project.default_branch, event.ref_name, event.project) + - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) - if event.commits_count > 1 %li.commits-stat - if event.commits_count > 2 @@ -27,18 +28,26 @@ - from = event.commit_from - from_label = truncate_sha(from) - else - - from = event.project.default_branch + - from = project.default_branch - from_label = from - = link_to namespace_project_compare_path(event.project.namespace, event.project, from: from, to: event.commit_to) do + = link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do Compare #{from_label}...#{truncate_sha(event.commit_to)} - if create_mr %span{"data-user-is" => event.author_id, "data-display" => "inline"} or - = link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do + = link_to create_mr_path(project.default_branch, event.ref_name, project) do create a merge request - elsif create_mr %li.commits-stat{"data-user-is" => event.author_id} - = link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do + = link_to create_mr_path(project.default_branch, event.ref_name, project) do Create Merge Request +- elsif event.rm_ref? + - repository = project.repository + - last_commit = repository.commit(event.commit_from) + - if last_commit + .event-body + %ul.well-list.event_commits + = render "events/commit", commit: last_commit, project: project, event: event + diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 9b838b9f3b7..6306fe6d0bf 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -10,7 +10,6 @@ - if current_user .pull-right = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do - = icon('plus') New Snippet .oneline diff --git a/app/views/groups/group_members/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/groups/show.html.haml b/app/views/groups/show.html.haml index aecefbc6e8f..a83eb7e88bb 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -5,7 +5,7 @@ = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") .cover-block.groups-cover-block - %div{ class: (container_class) } + %div{ class: container_class } = link_to group_icon(@group), target: '_blank' do = image_tag group_icon(@group), class: "avatar group-avatar s70" .group-info @@ -15,13 +15,15 @@ %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) } = visibility_level_icon(@group.visibility_level, fw: false) + .group-right-buttons.btn-group + - if current_user + .pull-left.append-right-10= render 'shared/members/access_request_buttons', source: @group + = render 'shared/notifications/button', notification_setting: @notification_setting + - if @group.description.present? .cover-desc.description = markdown(@group.description, pipeline: :description) - - if current_user - = render 'shared/members/access_request_buttons', source: @group - %div{ class: container_class } .top-area %ul.nav-links diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 57bc91ea5a9..57601ae9be0 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -36,6 +36,6 @@ %ul.well-list %li= link_to 'See our website for getting help', promo_url + '/getting-help/' %li= link_to 'Use the search bar on the top of this page', '#', onclick: 'Shortcuts.focusSearch(event)' - %li= link_to 'Use shortcuts', '#', onclick: 'Shortcuts.showHelp(event)' + %li= link_to 'Use shortcuts', '#', onclick: 'Shortcuts.toggleHelp()' %li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/' %li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare' 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}") diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml new file mode 100644 index 00000000000..435ed7bd4cb --- /dev/null +++ b/app/views/import/github/new.html.haml @@ -0,0 +1,43 @@ +- page_title "GitHub Import" +- header_title "Projects", root_path + +%h3.page-title + = icon 'github', text: 'Import Projects from GitHub' + +- if github_import_configured? + %p + To import a GitHub project, you first need to authorize GitLab to access + the list of your GitHub repositories: + + = link_to 'List Your GitHub Repositories', status_import_github_path, class: 'btn btn-success' + + %hr + +%p + - if github_import_configured? + Alternatively, + - else + To import a GitHub project, + you can use a + = succeed '.' do + = link_to 'Personal Access Token', 'https://github.com/settings/tokens' + When you create your Personal Access Token, + you will need to select the <code>repo</code> scope, so we can display a + list of your public and private repositories which are available for import. + += form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do + .form-group + = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40 + = submit_tag 'List Your GitHub Repositories', class: 'btn btn-success' + +- unless github_import_configured? + %hr + %p + Note: + - if current_user.admin? + As an administrator you may like to configure + - else + Consider asking your GitLab administrator to configure + = link_to 'GitHub integration', help_page_path("integration", "github") + which will allow login via GitHub and allow importing projects without + generating a Personal Access Token. diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 2d020e9c222..757de92d6d4 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -25,10 +25,10 @@ = favicon_link_tag 'favicon.ico' - = stylesheet_link_tag "application", media: "all", integrity: true - = stylesheet_link_tag "print", media: "print", integrity: true + = stylesheet_link_tag "application", media: "all" + = stylesheet_link_tag "print", media: "print" - = javascript_include_tag "application", integrity: true + = javascript_include_tag "application" - if content_for?(:page_specific_javascripts) = yield :page_specific_javascripts 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(); 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') diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 40a2c81eebd..11cee421a99 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -6,7 +6,7 @@ = icon('bars') %button.navbar-toggle{type: 'button'} %span.sr-only Toggle navigation - = icon('angle-left') + = icon('ellipsis-v') .navbar-collapse.collapse %ul.nav.navbar-nav @@ -38,9 +38,19 @@ = link_to sherlock_transactions_path, title: 'Sherlock Transactions', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('tachometer fw') - %li - = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do - = icon('sign-out') + %li.header-user.dropdown + = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do + = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar" + %span.caret + .dropdown-menu-nav.dropdown-menu-align-right + %ul + %li + = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } + %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' - else %li %div diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 0f264cd2e06..5ee8772882e 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -1,16 +1,16 @@ .scrolling-tabs-container{ class: nav_control_class } = render 'layouts/nav/admin_settings' .fade-left - = icon('arrow-left') + = icon('angle-left') .fade-right - = icon('arrow-right') + = icon('angle-right') %ul.nav-links.scrolling-tabs = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do %span Overview - = nav_link(controller: %w(background_jobs logs health_check)) do - = link_to admin_background_jobs_path, title: 'Monitoring' do + = nav_link(controller: %w(system_info background_jobs logs health_check)) do + = link_to admin_system_info_path, title: 'Monitoring' do %span Monitoring = nav_link(controller: :broadcast_messages) do diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 5d657a9ac84..d7d36c84b6c 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,9 +1,9 @@ .scrolling-tabs-container{ class: nav_control_class } = render 'layouts/nav/group_settings' .fade-left - = icon('arrow-left') + = icon('angle-left') .fade-right - = icon('arrow-right') + = icon('angle-right') %ul.nav-links.scrolling-tabs = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do 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/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index f37f9b0f5a3..96fe62c39c3 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,8 +1,8 @@ .scrolling-tabs-container .fade-left - = icon('arrow-left') + = icon('angle-left') .fade-right - = icon('arrow-right') + = icon('angle-right') %ul.nav-links.scrolling-tabs = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index a4bb56aa56f..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 @@ -26,9 +26,9 @@ .scrolling-tabs-container{ class: nav_control_class } .fade-left - = icon('arrow-left') + = icon('angle-left') .fade-right - = icon('arrow-right') + = icon('angle-right') %ul.nav-links.scrolling-tabs = nav_link(path: 'projects#show', html_options: {class: 'home'}) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do 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/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index f1532371b2e..c161ecc3463 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -72,12 +72,11 @@ The diff for this file was not included because it is too large. - else %hr - - diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last - - blob = @message.project.repository.blob_for_diff(diff_commit, diff_file) + - blob = diff_file.blob - if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob) %table.code.white - diff_file.highlighted_diff_lines.each do |line| - = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true} + = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true - else No preview for this file type %br diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml new file mode 100644 index 00000000000..003884a5bd9 --- /dev/null +++ b/app/views/profiles/_head.html.haml @@ -0,0 +1,3 @@ +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/cropper.js') + = page_specific_javascript_tag('profile/application.js') diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 8efe486e01b..57d16d29158 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,4 +1,5 @@ - page_title "Account" += render 'profiles/head' - if current_user.ldap_user? .alert.alert-info diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index 9c404b6935f..9fe86e6b291 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,4 +1,5 @@ - page_title "Audit Log" += render 'profiles/head' .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 6f7fefdb46d..dc499be885b 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,4 +1,5 @@ - page_title "Emails" += render 'profiles/head' .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/keys/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 eef50d887c7..d9fa74fad90 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,3 +1,5 @@ += render 'profiles/head' + = form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| = form_errors(@user) diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 593be2617c1..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 diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml deleted file mode 100644 index 46ad1559356..00000000000 --- a/app/views/projects/_github_import_modal.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -%div#github_import_modal.modal - .modal-dialog - .modal-content - .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 Import projects from GitHub - .modal-body - To enable importing projects from GitHub, - - if current_user.admin? - as administrator you need to configure - - else - ask your Gitlab administrator to configure - == #{link_to 'OAuth integration', help_page_path("integration", "github")}. diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 86ea08dd229..cf11723dc8e 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,41 +1,29 @@ - empty_repo = @project.empty_repo? -.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)} - %div{ class: (container_class) } - .row - .project-image-container - = project_icon(@project, alt: '', class: 'project-avatar avatar s70') - .project-info - .cover-title.project-home-desc - %h1 - = @project.name - %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)} - = visibility_level_icon(@project.visibility_level, fw: false) +.project-home-panel.text-center{ class: ("empty-project" if empty_repo) } + %div{ class: container_class } + = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70') + %h1.project-title + = @project.name + %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)} + = visibility_level_icon(@project.visibility_level, fw: false) - - if @project.description.present? - .cover-desc.project-home-desc - = markdown(@project.description, pipeline: :description) + .project-home-desc + - if @project.description.present? + = markdown(@project.description, pipeline: :description) - - if forked_from_project = @project.forked_from_project - .cover-desc - Forked from - = link_to project_path(forked_from_project) do - = forked_from_project.namespace.try(:name) + - if forked_from_project = @project.forked_from_project + %p + Forked from + = link_to project_path(forked_from_project) do + = forked_from_project.namespace.try(:name) - .project-repo-buttons.project-action-buttons - .count-buttons - = render 'projects/buttons/star' - = render 'projects/buttons/fork' + .project-repo-buttons.project-action-buttons + .count-buttons + = render 'projects/buttons/star' + = render 'projects/buttons/fork' - .project-clone-holder - = render "shared/clone_panel" - - .project-repo-buttons.btn-group.project-right-buttons - - if current_user - = render 'shared/members/access_request_buttons', source: @project - - = render "projects/buttons/download" - = render 'projects/buttons/dropdown' - = render 'shared/notifications/button', notification_setting: @notification_setting + .project-clone-holder + = render "shared/clone_panel" :javascript new Star(); diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 66c30283c7a..630ae7d6140 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -1,11 +1,10 @@ -.project-last-commit - - if commit.status - = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do - = ci_icon_for_status(commit.status) - = ci_label_for_status(commit.status) +- if commit.status + = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do + = ci_icon_for_status(commit.status) + = ci_label_for_status(commit.status) - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" - · - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by - = commit_author_link(commit, avatar: true, size: 24) += link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" += link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" +· +#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by += commit_author_link(commit, avatar: true, size: 24) diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index e0ca2a3109c..3c6b931f41a 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,13 +1,15 @@ - if event = last_push_event - if show_last_push_widget?(event) .row-content-block.top-block.clear-block.hidden-xs - %div{ class: (container_class) } + %div{ class: container_class } .event-last-push .event-last-push-text %span You pushed to = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do %strong= event.ref_name - branch + - if @project && event.project != @project + %span at + %strong= link_to_project event.project #{time_ago_with_tooltip(event.created_at)} .pull-right diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index ca6714ef42b..58d961d93ca 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -12,13 +12,13 @@ %li.confidential-issue-warning = icon('warning') %span This is a confidential issue. Your comment will not be visible to the public. - + %li.pull-right .toolbar-group = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" }) = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" }) - = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`" }, title: "Insert code" }) + = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" }) = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index b1769759dce..58524418a67 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -16,4 +16,4 @@ .file-content.code .nothing-here-block Empty file - else - = render 'shared/file_highlight', blob: blob + = render 'shared/file_highlight', blob: blob, repository: @repository diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index e4f04ca7764..b1c9895f43e 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -4,12 +4,10 @@ %ul.nav-links.no-bottom.js-edit-mode %li.active = link_to '#editor' do - = icon('edit') Edit File %li = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do - = icon('eye') = editing_preview_title(@blob.name) = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index ed670dae88d..0ab78a39cf9 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,12 +1,15 @@ +- @no_container = true - page_title @blob.path, @ref += render "projects/commits/head" -= render 'projects/last_push' +%div{ class: container_class } + = render 'projects/last_push' -%div#tree-holder.tree-holder - = render 'blob', blob: @blob + %div#tree-holder.tree-holder + = render 'blob', blob: @blob -- if can_edit_blob?(@blob) - = render 'projects/blob/remove' + - if can_edit_blob?(@blob) + = render 'projects/blob/remove' - - title = "Replace #{@blob.name}" - = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put + - title = "Replace #{@blob.name}" + = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index e0367c40272..77b405f1f39 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -2,7 +2,7 @@ - page_title "Branches" = render "projects/commits/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area .nav-text Protected branches can be managed in project settings @@ -27,7 +27,7 @@ = sort_title_recently_updated = link_to namespace_project_branches_path(sort: 'last_updated') do = sort_title_oldest_updated - - unless @branches.empty? + - if @branches.any? %ul.content-list.all-branches - @branches.each do |branch| = render "projects/branches/branch", branch: branch diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 181547316aa..a131289ee97 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -2,7 +2,7 @@ - page_title "Builds" = render "projects/pipelines/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area %ul.nav-links %li{class: ('active' if @scope.nil?)} diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 4e2702c2e44..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)}", "#{@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]}") diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 34ad9fe2c43..a9eaed4c5f6 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -14,6 +14,5 @@ Fork %div.count-with-arrow %span.arrow - %span.count - = link_to namespace_project_forks_path(@project.namespace, @project) do - = @project.forks_count + = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do + = @project.forks_count diff --git a/app/views/projects/ci/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 diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml index ae7bb01223e..9d925cacc0d 100644 --- a/app/views/projects/commit/_ci_stage.html.haml +++ b/app/views/projects/commit/_ci_stage.html.haml @@ -7,7 +7,7 @@ = ci_icon_for_status(status) - if stage - = stage.titleize.pluralize + = stage.titleize = render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true = render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true %tr diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 401cb4f7e30..d0da2606587 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -7,8 +7,7 @@ = render "ci_menu" - else %div.block-connector -= render "projects/diffs/diffs", diffs: @diffs, project: @project, - diff_refs: @diff_refs += render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @commit.diff_refs = render "projects/notes/notes_with_form" - if can_collaborate_with_project? - %w(revert cherry-pick).each do |type| diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index b11b6c24ccd..61152649907 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,8 +1,8 @@ .scrolling-tabs-container.sub-nav-scroll .fade-left - = icon('arrow-left') + = icon('angle-left') .fade-right - = icon('arrow-right') + = icon('angle-right') .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 51ca4eb903e..9a44ba94970 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -7,7 +7,7 @@ = render "head" -%div{ class: (container_class) } +%div{ class: container_class } .row-content-block.second-block.content-component-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'commits' diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index b22285c11e0..e9ff8e90dd5 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -2,7 +2,7 @@ - page_title "Compare" = render "projects/commits/head" -%div{ class: (container_class) } +%div{ class: container_class } .sub-header-block Compare branches, tags or commit ranges. %br diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index f4ec7b767f6..28a50e7031a 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -2,7 +2,7 @@ - page_title "#{params[:from]}...#{params[:to]}" = render "projects/commits/head" -%div{ class: (container_class) } +%div{ class: container_class } .sub-header-block.no-bottom-space = render "form" diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index f18bc8c41b3..1975287faee 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 @@ -24,7 +24,7 @@ .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) + - 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..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_commit, diff_file)} +.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_file)} .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"} - - if diff_file.diff.submodule? - %span - = icon('archive fw') - %span - = submodule_link(blob, @commit.id, project.repository) - - else - = blob_icon blob.mode, blob.name - - = link_to "#diff-#{i}" do - - if diff_file.renamed_file - - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) - = old_path - → - = new_path - - else - %span - = diff_file.new_path - - if diff_file.deleted_file - deleted - - - if diff_file.mode_changed? - %small - = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" + = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{i}" + - unless diff_file.submodule? .file-actions.hidden-xs - if blob_text_viewable?(blob) = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do @@ -42,17 +21,23 @@ - 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_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/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml new file mode 100644 index 00000000000..95a2772fd0b --- /dev/null +++ b/app/views/projects/diffs/_file_header.html.haml @@ -0,0 +1,25 @@ +- if defined?(blob) && blob && diff_file.submodule? + %span + = icon('archive fw') + %span + = submodule_link(blob, diff_commit.id, project.repository) +- else + = conditional_link_to url.present?, url do + = blob_icon diff_file.b_mode, diff_file.file_path + + - if diff_file.renamed_file + - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) + %strong + = old_path + → + %strong + = new_path + - else + %strong + = diff_file.new_path + - if diff_file.deleted_file + deleted + + - if diff_file.mode_changed? + %small + = "#{diff_file.a_mode} → #{diff_file.b_mode}" diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml index 2731219ccad..9ec6a7aa5cd 100644 --- a/app/views/projects/diffs/_image.html.haml +++ b/app/views/projects/diffs/_image.html.haml @@ -1,9 +1,8 @@ - diff = diff_file.diff -- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path)) +- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) // diff_refs will be nil for orphaned commits (e.g. first commit in repo) -- if diff_refs - - old_commit_id = diff_refs.first.id - - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path)) +- if diff_file.old_ref + - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) - if diff.renamed_file || diff.new_file || diff.deleted_file .image @@ -16,7 +15,7 @@ %div.two-up.view %span.wrap .frame.deleted - %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))} + %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))} %img{src: old_file_raw_path} %p.image-info.hide %span.meta-filesize= "#{number_to_human_size old_file.size}" @@ -28,7 +27,7 @@ %span.meta-height %span.wrap .frame.added - %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))} + %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))} %img{src: file_raw_path} %p.image-info.hide %span.meta-filesize= "#{number_to_human_size file.size}" diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index f1577e8a47b..22cad00240a 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -1,3 +1,6 @@ +- plain = local_assigns.fetch(:plain, false) +- line_code = diff_file.line_code(line) +- position = diff_file.position(line) - type = line.type %tr.line_holder{ id: line_code, class: type } - case type @@ -9,18 +12,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.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 4ecc9528bd2..51f207dce94 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -14,30 +14,28 @@ %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], '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.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' - 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] }} + %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') - %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.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) - 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/environments/index.html.haml b/app/views/projects/environments/index.html.haml index a03f117291f..5242021243e 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -2,7 +2,7 @@ - page_title "Environments" = render "projects/pipelines/head" -%div{ class: (container_class) } +%div{ class: container_class } - if can?(current_user, :create_environment, @project) && !@environments.blank? .top-area .nav-controls diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 4c15e2759d6..53c62ef234d 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -2,7 +2,7 @@ - page_title "Environments" = render "projects/pipelines/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area .col-md-9 %h3.page-title= @environment.name.titleize diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml index e695d3ae369..6be4273b6ab 100644 --- a/app/views/projects/graphs/ci.html.haml +++ b/app/views/projects/graphs/ci.html.haml @@ -2,7 +2,7 @@ - page_title "Continuous Integration", "Graphs" = render 'head' -%div{ class: (container_class) } +%div{ class: container_class } .sub-header-block .oneline A collection of graphs for Continuous Integration diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 0daffe68f6f..65db8af494d 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -2,7 +2,7 @@ - page_title "Commits", "Graphs" = render 'head' -%div{ class: (container_class) } +%div{ class: container_class } .sub-header-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs_commits' diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml index 6d97f552a8e..fcfcae0be20 100644 --- a/app/views/projects/graphs/languages.html.haml +++ b/app/views/projects/graphs/languages.html.haml @@ -2,7 +2,7 @@ - page_title "Languages", "Graphs" = render 'head' -%div{ class: (container_class) } +%div{ class: container_class } .sub-header-block .oneline Programming languages used in this repository diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 9f7e2a361ff..a985b442b2d 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -2,7 +2,7 @@ - page_title "Contributors", "Graphs" = render 'head' -%div{ class: (container_class) } +%div{ class: container_class } .sub-header-block .tree-ref-holder = render 'shared/ref_switcher', destination: 'graphs' diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index a8a8caf7280..2cd8d03e30e 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -10,7 +10,7 @@ .panel-body %pre :preserve - #{sanitize_repo_path(@project.import_error)} + #{sanitize_repo_path(@project, @project.import_error)} = form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| = render "shared/import_form", f: f diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index cd876b5ea62..312bd86ed04 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -7,20 +7,36 @@ = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") %div{ class: (container_class) } - .top-area - = render 'shared/issuable/nav', type: :issues - .nav-controls - - if current_user - = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do - = icon('rss') - %span.icon-label - Subscribe - = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) + - if @project.issues.any? + .top-area + = render 'shared/issuable/nav', type: :issues + .nav-controls + - if current_user + = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do + = icon('rss') + %span.icon-label + Subscribe + = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) + - if can? current_user, :create_issue, @project + = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do + New Issue + = render 'shared/issuable/filter', type: :issues + + .issues-holder + = render "issues" + - else + .blank-state.blank-state-welcome + %h2.blank-state-title.blank-state-welcome-title + Welcome to GitLab Issues + %p.blank-state-text + Code, test, and deploy together + .blank-state + .blank-state-icon + = navbar_icon("issues", size: 50) + %h3.blank-state-title + You don't have any issues right now. + %p.blank-state-text + Issues are the best way to track your project progress - if can? current_user, :create_issue, @project - = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do New Issue - - = render 'shared/issuable/filter', type: :issues - - .issues-holder - = render "issues" diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index aa4d69550ec..db66a0edbd8 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -3,7 +3,7 @@ - hide_class = '' = render "projects/issues/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area.adjust .nav-text Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 2ec96308fd7..873ed9b59ee 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -42,7 +42,7 @@ = succeed '.' do = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - - if @commits.present? + - if @commits_count.nonzero? %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do @@ -51,7 +51,7 @@ %li.commits-tab = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do Commits - %span.badge= @commits.size + %span.badge= @commits_count - if @pipeline %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 9f948d41dda..ace275c689b 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -3,7 +3,7 @@ = render "projects/issues/head" = render 'projects/last_push' -%div{ class: (container_class) } +%div{ class: container_class } .top-area = render 'shared/issuable/nav', type: :merge_requests .nav-controls diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 08a38d283d2..489c632ae22 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -7,7 +7,7 @@ CI build = ci_label_for_status(status) for - - commit = @merge_request.last_commit + - commit = @merge_request.diff_head_commit = succeed "." do = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" %span.ci-coverage @@ -24,7 +24,7 @@ CI build = ci_label_for_status(status) for - - commit = @merge_request.last_commit + - commit = @merge_request.diff_head_commit = succeed "." do = link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace" %span.ci-coverage @@ -33,12 +33,12 @@ .ci_widget = icon("spinner spin") - Checking CI status for #{@merge_request.last_commit_short_sha}… + Checking CI status for #{@merge_request.diff_head_commit.short_id}… .ci_widget.ci-not_found{style: "display:none"} = icon("times-circle") - Could not find CI status for #{@merge_request.last_commit_short_sha}. + Could not find CI status for #{@merge_request.diff_head_commit.short_id}. .ci_widget.ci-error{style: "display:none"} = icon("times-circle") - Could not connect to the CI server. Please check your settings and try again.
\ No newline at end of file + Could not connect to the CI server. Please check your settings and try again. diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 0e0af57d76e..dc18f715f25 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -22,10 +22,10 @@ - elsif @merge_request.can_be_merged? = render 'projects/merge_requests/widget/open/accept' - - if @closes_issues.present? + - if mr_closes_issues.present? .mr-widget-footer %span %i.fa.fa-check - Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)} + Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)} = succeed '.' do - != markdown issues_sentence(@closes_issues), pipeline: :gfm, author: @merge_request.author + != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 941513febbd..bf2e76f0083 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -2,7 +2,7 @@ = form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-quick-submit js-requires-input' } do |f| = hidden_field_tag :authenticity_token, form_authenticity_token - = hidden_field_tag :sha, @merge_request.source_sha + = hidden_field_tag :sha, @merge_request.diff_head_sha .accept-merge-holder.clearfix.js-toggle-container .clearfix .accept-action diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index ad898ff153b..2b6b5e05e86 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -16,7 +16,7 @@ - if remove_source_branch_button || user_can_cancel_automatic_merge .clearfix.prepend-top-10 - if remove_source_branch_button - = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.source_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do + = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.diff_head_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do = icon('times') Remove Source Branch When Merged diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index b0e0bdfff5a..ad2bfbec915 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -2,7 +2,7 @@ - page_title "Milestones" = render "projects/issues/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area = render 'shared/milestones_filter' diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index 86295a3d011..8f6805268d5 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -1,6 +1,6 @@ - @no_container = true -%div{ class: (container_class) } +%div{ class: container_class } .row-content-block.second-block.content-component-block .tree-ref-holder = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 3ca30b4ba6b..091af4df4a1 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -4,7 +4,7 @@ = page_specific_javascript_tag('network/application.js') = render "projects/commits/head" = render "head" -%div{ class: (container_class) } +%div{ class: container_class } .project-network .controls = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 3c1c6060504..05f33b78a47 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,110 +1,115 @@ - page_title 'New Project' - header_title "Projects", dashboard_projects_path -%h3.page-title - New Project -%hr - .project-edit-container .project-edit-errors = render 'projects/errors' - .project-edit-content - - = form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f| - .form-group - = f.label :path, class: 'control-label' do - Project owner - .col-sm-10 - = f.select :namespace_id, namespaces_options(:current_user), {}, {class: 'select2 js-select-namespace', tabindex: 1} - - - if current_user.can_create_group? - .help-block - Want to house several dependent projects under the same namespace? - = link_to "Create a group", new_group_path - - .form-group - = f.label :path, class: 'control-label' do - Project name - .col-sm-10 - = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true - - - if import_sources_enabled? - .project-import.js-toggle-container - .form-group - %label.control-label Import project from - .col-sm-10 - - if github_import_enabled? - - if github_import_configured? - = link_to status_import_github_path, class: 'btn import_github' do - %i.fa.fa-github - GitHub - - else - = link_to '#', class: 'how_to_import_link btn import_github' do - %i.fa.fa-github - GitHub - = render 'github_import_modal' + .row.prepend-top-default + .col-lg-3.profile-settings-sidebar + %h4.prepend-top-0 + New project + %p + Create or Import your project from popular Git services + .col-lg-9 + = form_for @project, html: { class: 'new_project' } do |f| + %fieldset.append-bottom-0 + .form-group.col-xs-12.col-sm-6 + = f.label :namespace_id, class: 'label-light' do + %span + Project path + .form-group + .input-group + - if current_user.can_select_namespace? + .input-group-addon + = root_url + = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} - - if bitbucket_import_enabled? - - if bitbucket_import_configured? - = link_to status_import_bitbucket_path, class: 'btn import_bitbucket', "data-no-turbolink" => "true" do - %i.fa.fa-bitbucket - Bitbucket - else - = link_to status_import_bitbucket_path, class: 'how_to_import_link btn import_bitbucket', "data-no-turbolink" => "true" do - %i.fa.fa-bitbucket - Bitbucket - = render 'bitbucket_import_modal' - - - if gitlab_import_enabled? - - if gitlab_import_configured? - = link_to status_import_gitlab_path, class: 'btn import_gitlab' do - %i.fa.fa-heart - GitLab.com - - else - = link_to status_import_gitlab_path, class: 'how_to_import_link btn import_gitlab' do - %i.fa.fa-heart - GitLab.com - = render 'gitlab_import_modal' - - - if gitorious_import_enabled? - = link_to new_import_gitorious_path, class: 'btn import_gitorious' do - %i.icon-gitorious.icon-gitorious-small - Gitorious.org - - - if google_code_import_enabled? - = link_to new_import_google_code_path, class: 'btn import_google_code' do - %i.fa.fa-google - Google Code - - - if fogbugz_import_enabled? - = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do - %i.fa.fa-bug - Fogbugz - - - if git_import_enabled? - = link_to "#", class: 'btn js-toggle-button import_git' do - %i.fa.fa-git - %span Repo by URL - - - if gitlab_project_import_enabled? - = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do - %i.fa.fa-gitlab - %span GitLab export - - .js-toggle-content.hide - = render "shared/import_form", f: f - - .prepend-botton-10 - - .form-group - = f.label :description, class: 'control-label' do - Description - %span.light (optional) - .col-sm-10 - = f.text_area :description, class: "form-control", rows: 3, maxlength: 250, tabindex: 3 - = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project + .input-group-addon.static-namespace + #{root_url}#{current_user.username}/ + .form-group.col-xs-12.col-sm-6.project-path + = f.label :namespace_id, class: 'label-light' do + %span + Project name + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true + - if current_user.can_create_group? + .help-block + Want to house several dependent projects under the same namespace? + = link_to "Create a group", new_group_path + + - if import_sources_enabled? + .project-import.js-toggle-container + .form-group.clearfix + = f.label :visibility_level, class: 'label-light' do + Import project from + .col-sm-12.import-buttons + %div + - if github_import_enabled? + = link_to new_import_github_path, class: 'btn import_github' do + = icon 'github', text: 'GitHub' + %div + - if bitbucket_import_enabled? + - if bitbucket_import_configured? + = link_to status_import_bitbucket_path, class: 'btn import_bitbucket', "data-no-turbolink" => "true" do + %i.fa.fa-bitbucket + Bitbucket + - else + = link_to status_import_bitbucket_path, class: 'how_to_import_link btn import_bitbucket', "data-no-turbolink" => "true" do + %i.fa.fa-bitbucket + Bitbucket + = render 'bitbucket_import_modal' + %div + - if gitlab_import_enabled? + - if gitlab_import_configured? + = link_to status_import_gitlab_path, class: 'btn import_gitlab' do + %i.fa.fa-heart + GitLab.com + - else + = link_to status_import_gitlab_path, class: 'how_to_import_link btn import_gitlab' do + %i.fa.fa-heart + GitLab.com + = render 'gitlab_import_modal' + %div + - if gitorious_import_enabled? + = link_to new_import_gitorious_path, class: 'btn import_gitorious' do + %i.icon-gitorious.icon-gitorious-small + Gitorious.org + %div + - if google_code_import_enabled? + = link_to new_import_google_code_path, class: 'btn import_google_code' do + %i.fa.fa-google + Google Code + %div + - if fogbugz_import_enabled? + = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do + %i.fa.fa-bug + Fogbugz + %div + - if git_import_enabled? + = link_to "#", class: 'btn js-toggle-button import_git' do + %i.fa.fa-git + %span Repo by URL + %div + - if gitlab_project_import_enabled? + = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do + %i.fa.fa-gitlab + %span GitLab export + + .js-toggle-content.hide + = render "shared/import_form", f: f + + .form-group + = f.label :description, class: 'label-light' do + Project description + %span.light (optional) + = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250 + + .form-group.project-visibility-level-holder + = f.label :visibility_level, class: 'label-light' do + Visibility Level + = link_to "(?)", help_page_path("public_access", "public_access") + = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project) - .form-actions = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel' diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml index 8144c1ba49e..ec6c4938efc 100644 --- a/app/views/projects/notes/_diff_notes_with_reply.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml @@ -4,5 +4,4 @@ %td.notes_content %ul.notes{ data: { discussion_id: note.discussion_id } } = render partial: "projects/notes/note", collection: notes, as: :note - .discussion-reply-holder - = link_to_reply_discussion(note) + = link_to_reply_discussion(note) diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml index 45986b0d1e8..e50a4f86d03 100644 --- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -8,8 +8,7 @@ %ul.notes{ data: { discussion_id: note_left.discussion_id } } = render partial: "projects/notes/note", collection: notes_left, as: :note - .discussion-reply-holder - = link_to_reply_discussion(note_left, 'old') + = link_to_reply_discussion(note_left, 'old') - else %td.notes_line.old= "" %td.notes_content.parallel.old= "" @@ -20,8 +19,7 @@ %ul.notes{ data: { discussion_id: note_right.discussion_id } } = render partial: "projects/notes/note", collection: notes_right, as: :note - .discussion-reply-holder - = link_to_reply_discussion(note_right, 'new') + = link_to_reply_discussion(note_right, 'new') - else %td.notes_line.new= "" %td.notes_content.parallel.new= "" diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 03b3f6935d1..7c61ba750fe 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -7,6 +7,7 @@ = f.hidden_field :noteable_id = f.hidden_field :noteable_type = f.hidden_field :type + = f.hidden_field :position = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..." diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index a5e163b91e9..af0046886fb 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -17,7 +17,7 @@ %a{ href: "##{dom_id(note)}" } = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') .note-actions - - access = note.project.team.human_max_access(note.author.id) + - access = note_max_access_for_user(note) - if access and not note.system %span.note-role.hidden-xs= access - if current_user and not note.system diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml index 6401245bf73..4a69b8f8840 100644 --- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml +++ b/app/views/projects/notes/discussions/_diff_with_notes.html.haml @@ -1,30 +1,17 @@ - note = discussion_notes.first -- diff = note.diff -- return unless diff +- diff_file = note.diff_file +- return unless diff_file + +- blob = note.blob + +.diff-file.file-holder + .file-title + = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: note.project, url: diff_note_path(note) -.diff-file - .diff-header - %span - - if diff.deleted_file - = diff.old_path - - else - = diff.new_path - - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" .diff-content.code.js-syntax-highlight %table - note.truncated_diff_lines.each do |line| - - type = line.type - - line_code = generate_line_code(note.diff_file_path, line) - %tr.line_holder{ id: line_code, class: "#{type}" } - - if type == "match" - %td.old_line.diff-line-num= "..." - %td.new_line.diff-line-num= "..." - %td.line_content.match= line.text - - else - %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? " ".html_safe : line.old_pos } } - %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? " ".html_safe : line.new_pos } } - %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type) + = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true - - if line_code == note.line_code - = render "projects/notes/diff_notes_with_reply", notes: discussion_notes + - if note.for_line?(line) + = render "projects/notes/diff_notes_with_reply", notes: discussion_notes diff --git a/app/views/projects/notes/discussions/_notes.html.haml b/app/views/projects/notes/discussions/_notes.html.haml index e598e3c7c63..a785149549d 100644 --- a/app/views/projects/notes/discussions/_notes.html.haml +++ b/app/views/projects/notes/discussions/_notes.html.haml @@ -3,5 +3,4 @@ .notes{ data: { discussion_id: note.discussion_id } } %ul.notes.timeline = render partial: "projects/notes/note", collection: discussion_notes, as: :note - .discussion-reply-holder - = link_to_reply_discussion(note) + = link_to_reply_discussion(note) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index b70693eeb62..6a127afa410 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -2,7 +2,7 @@ - page_title "Pipelines" = render "projects/pipelines/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area %ul.nav-links %li{class: ('active' if @scope.nil?)} @@ -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 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/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 565905cbe7b..97cb1a9052b 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -1,6 +1,6 @@ %h5.prepend-top-0 - Already Protected (#{@branches.size}) -- if @branches.empty? + Already Protected (#{@protected_branches.size}) +- if @protected_branches.empty? %p.settings-message.text-center No branches are protected, protect a branch with the form above. - else @@ -9,33 +9,18 @@ %table.table.protected-branches-list %colgroup %col{ width: "30%" } - %col{ width: "30%" } + %col{ width: "25%" } %col{ width: "25%" } - if can_admin_project %col %thead %tr - %th Branch - %th Last commit - %th Developers can push + %th Protected Branch + %th Commit + %th Developers Can Push - if can_admin_project %th %tbody - - @branches.each do |branch| - - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch) - %tr - %td - = link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name)) - - if @project.root_ref?(branch.name) - %span.label.label-info.prepend-left-5 default - %td - - if commit = branch.commit - = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id') - #{time_ago_with_tooltip(commit.committed_date)} - - else - (branch was removed from repository) - %td - = check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url }) - - if can_admin_project - %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm" + = render partial: @protected_branches, locals: { can_admin_project: can_admin_project } + + = paginate @protected_branches, theme: 'gitlab' diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/_dropdown.html.haml new file mode 100644 index 00000000000..b803d932e67 --- /dev/null +++ b/app/views/projects/protected_branches/_dropdown.html.haml @@ -0,0 +1,17 @@ += f.hidden_field(:name) + += dropdown_tag("Protected Branch", + options: { title: "Pick protected branch", toggle_class: 'js-protected-branch-select js-filter-submit', + filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches", + footer_content: true, + data: { show_no: true, show_any: true, show_upcoming: true, + selected: params[:protected_branch_name], + project_id: @project.try(:id) } }) do + + %ul.dropdown-footer-list.hidden.protected-branch-select-footer-list + %li + = link_to '#', title: "New Protected Branch", class: "create-new-protected-branch" do + Create new + +:javascript + new ProtectedBranchSelect(); diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/_matching_branch.html.haml new file mode 100644 index 00000000000..8a5332ca5bb --- /dev/null +++ b/app/views/projects/protected_branches/_matching_branch.html.haml @@ -0,0 +1,9 @@ +%tr + %td + = link_to matching_branch.name, namespace_project_tree_path(@project.namespace, @project, matching_branch.name) + - if @project.root_ref?(matching_branch.name) + %span.label.label-info.prepend-left-5 default + %td + - commit = @project.commit(matching_branch.name) + = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id') + = time_ago_with_tooltip(commit.committed_date) diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml new file mode 100644 index 00000000000..474aec3a97c --- /dev/null +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -0,0 +1,21 @@ +- url = namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) +%tr + %td + = protected_branch.name + - if @project.root_ref?(protected_branch.name) + %span.label.label-info.prepend-left-5 default + %td + - if protected_branch.wildcard? + - matching_branches = protected_branch.matching(repository.branches) + = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) + - else + - if commit = protected_branch.commit + = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id') + = time_ago_with_tooltip(commit.committed_date) + - else + (branch was removed from repository) + %td + = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url }) + - if can_admin_project + %td + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right" diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index c7d317dbaee..5669713d9a1 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -4,30 +4,38 @@ .col-lg-3 %h4.prepend-top-0 = page_title - %p Keep stable branches secure and force developers to use Merge Requests - .col-lg-9 - %h5.prepend-top-0 - Protect a branch - .account-well.append-bottom-default - %p.light-header.append-bottom-0 Protected branches are designed to + %p Keep stable branches secure and force developers to use merge requests. + %p.prepend-top-20 + Protected branches are designed to: %ul %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} %li prevent 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) .form-group = f.label :name, "Branch", class: "label-light" - = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}}) + = render partial: "dropdown", locals: { f: f } + %p.help-block + = link_to "Wildcards", help_page_path(category: 'workflow', file: 'protected_branches', format: 'md', anchor: "wildcard-protected-branches") + such as + %code *-stable + or + %code production/* + are supported. + .form-group = f.check_box :developers_can_push, class: "pull-left" .prepend-left-20 = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0" %p.light.append-bottom-0 Allow developers to push to this branch - = f.submit "Protect", class: "btn-create btn" + = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true %hr = render "branches_list" diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml new file mode 100644 index 00000000000..4d8169815b3 --- /dev/null +++ b/app/views/projects/protected_branches/show.html.haml @@ -0,0 +1,25 @@ +- page_title @protected_branch.name, "Protected Branches" + +.row.prepend-top-default.append-bottom-default + .col-lg-3 + %h4.prepend-top-0 + = @protected_branch.name + + .col-lg-9 + %h5 Matching Branches + - if @matching_branches.present? + .table-responsive + %table.table.protected-branches-list + %colgroup + %col{ width: "30%" } + %col{ width: "30%" } + %thead + %tr + %th Branch + %th Last commit + %tbody + - @matching_branches.each do |matching_branch| + = render partial: "matching_branch", object: matching_branch + - else + %p.settings-message.text-center + Couldn't find any matching branches. diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 15f0d85194b..58d8e068754 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -12,60 +12,67 @@ = render 'projects/last_push' = render "home_panel" -.project-stats.row-content-block.second-block - %div{ class: (container_class) } - %ul.nav - %li - = link_to project_files_path(@project) do - Files (#{repository_size}) - %li - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do - #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) - %li - = link_to namespace_project_branches_path(@project.namespace, @project) do - #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) +%nav.project-stats{ class: (container_class) } + %ul.nav + %li + = link_to project_files_path(@project) do + Files (#{repository_size}) + %li + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) + %li + = link_to namespace_project_branches_path(@project.namespace, @project) do + #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) + %li + = link_to namespace_project_tags_path(@project.namespace, @project) do + #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) + + - if default_project_view != 'readme' && @repository.readme %li - = link_to namespace_project_tags_path(@project.namespace, @project) do - #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) + = link_to 'Readme', readme_path(@project) - - if default_project_view != 'readme' && @repository.readme - %li - = link_to 'Readme', readme_path(@project) + - if @repository.changelog + %li + = link_to 'Changelog', changelog_path(@project) - - if @repository.changelog - %li - = link_to 'Changelog', changelog_path(@project) + - if @repository.license_blob + %li + = link_to license_short_name(@project), license_path(@project) - - if @repository.license_blob - %li - = link_to license_short_name(@project), license_path(@project) + - if @repository.contribution_guide + %li + = link_to 'Contribution guide', contribution_guide_path(@project) - - if @repository.contribution_guide - %li - = link_to 'Contribution guide', contribution_guide_path(@project) + - if current_user && can_push_branch?(@project, @project.default_branch) + - unless @repository.changelog + %li.missing + = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do + Add Changelog + - unless @repository.license_blob + %li.missing + = link_to add_special_file_path(@project, file_name: 'LICENSE') do + Add License + - unless @repository.contribution_guide + %li.missing + = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do + Add Contribution guide + - unless @repository.gitlab_ci_yml + %li.missing + = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do + Set Up CI + %li.project-repo-buttons-right + .project-repo-buttons.project-right-buttons + - if current_user + = render 'shared/members/access_request_buttons', source: @project - - if current_user && can_push_branch?(@project, @project.default_branch) - - unless @repository.changelog - %li.missing - = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do - Add Changelog - - unless @repository.license_blob - %li.missing - = link_to add_special_file_path(@project, file_name: 'LICENSE') do - Add License - - unless @repository.contribution_guide - %li.missing - = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do - Add Contribution guide - - unless @repository.gitlab_ci_yml - %li.missing - = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do - Set Up CI + .btn-group.project-repo-btn-group + = render "projects/buttons/download" + = render 'projects/buttons/dropdown' + = render 'shared/notifications/button', notification_setting: @notification_setting - if @repository.commit - .content-block.second-block.white - %div{ class: container_class } - = render 'projects/last_commit', commit: @repository.commit, project: @project + .project-last-commit{ class: container_class } + = render 'projects/last_commit', commit: @repository.commit, project: @project %div{ class: container_class } - if @project.archived? 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/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 4ca1f58ac5c..368231e73fe 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -2,30 +2,30 @@ - page_title "Tags" = render "projects/commits/head" -%div{ class: (container_class) } +%div{ class: container_class } .top-area .nav-text Tags give the ability to mark specific points in history as being important - - if can? current_user, :push_code, @project - .nav-controls + .nav-controls + - if can? current_user, :push_code, @project = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do New tag - .dropdown.inline - %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } - %span.light= @sort.humanize - %b.caret - %ul.dropdown-menu.dropdown-menu-align-right - %li - = link_to namespace_project_tags_path(sort: nil) do - Name - = link_to namespace_project_tags_path(sort: sort_value_recently_updated) do - = sort_title_recently_updated - = link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do - = sort_title_oldest_updated + .dropdown.inline + %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } + %span.light= @sort.humanize + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right + %li + = link_to namespace_project_tags_path(sort: nil) do + Name + = link_to namespace_project_tags_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated .tags - - unless @tags.empty? + - if @tags.any? %ul.content-list = render partial: 'tag', collection: @tags diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 2abcfcdd7b2..bf5360b4dee 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -7,7 +7,7 @@ = render 'projects/last_push' = render "projects/commits/head" -%div{ class: (container_class) } +%div{ class: container_class } .tree-controls = render 'projects/find_file_link' - if can? current_user, :download_code, @project diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index 4f8abcdc8e1..c32cb122c26 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -1,6 +1,6 @@ - @no_container = true -%div{ class: (container_class) } +%div{ class: container_class } %div#modal-new-wiki.modal .modal-dialog .modal-content diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index bf5d09d50c2..233538bb488 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -2,7 +2,7 @@ - page_title "Edit", @page.title.capitalize, "Wiki" = render 'nav' -%div{ class: (container_class) } +%div{ class: container_class } .top-area .nav-text %strong @@ -15,9 +15,10 @@ Edit Page .nav-controls - - if can?(current_user, :create_wiki, @project) - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - New Page + - if !(@page && @page.persisted?) + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + New Page = render 'main_links' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index 6caf7230f35..b8811a28dd6 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -2,7 +2,7 @@ - page_title "Git Access", "Wiki" = render 'nav' -%div{ class: (container_class) } +%div{ class: container_class } .sub-header-block %span.oneline Git access for diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 630ee35b70b..4c0b14e2c42 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,6 +1,6 @@ - page_title "History", @page.title.capitalize, "Wiki" = render 'nav' -%div{ class: (container_class) } +%div{ class: container_class } .top-area .nav-text %strong diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 81d9f391c1c..9c10acd4cb6 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -3,7 +3,7 @@ = render 'nav' -%div{ class: (container_class) } +%div{ class: container_class } %ul.content-list - @wiki_pages.each do |wiki_page| %li diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 76f9b1ecd76..5cebb538cf5 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -2,7 +2,7 @@ - page_title @page.title.capitalize, "Wiki" = render 'nav' -%div{ class: (container_class) } +%div{ class: container_class } .top-area .nav-text %strong= @page.title.capitalize diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 84b3f44c0ad..3b82d8e686f 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -2,15 +2,20 @@ .git-clone-holder.input-group .input-group-btn - %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'} - %span - = default_clone_protocol.upcase - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown - %li - = ssh_clone_button(project) - %li - = http_clone_button(project) + -if allowed_protocols_present? + .clone-dropdown-btn.btn.btn-static + %span + = enabled_project_button(project, enabled_protocol) + - else + %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', data: { toggle: 'dropdown' }} + %span + = default_clone_protocol.upcase + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown + %li + = ssh_clone_button(project) + %li + = http_clone_button(project) = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true .input-group-btn diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index ad944a19ca1..e26693bf5b9 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,3 +1,5 @@ +- repository = nil unless local_assigns.key?(:repository) + .file-content.code.js-syntax-highlight .line-numbers - if blob.data.present? @@ -11,4 +13,4 @@ = link_icon = i .blob-content{data: {blob_id: blob.id}} - = highlight(blob.name, blob.data, plain: blob.no_highlighting?) + = highlight(blob.path, blob.data, repository: repository, plain: blob.no_highlighting?) diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml index 87028ececd4..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 namespace_project_label_path(@project.namespace, @project, label), + %span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" } + = link_to label.name, label_filter_path(@project, label, type: controller.controller_name), class: "btn btn-transparent has-tooltip", style: "background-color: #{label.color};", title: escape_once(label.description), - data: { container: "body" } do - = escape_once label.name + data: { container: "body" } %button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } } = icon("times") diff --git a/app/views/shared/icons/_issues.svg b/app/views/shared/icons/_issues.svg deleted file mode 100644 index 2682c27ade9..00000000000 --- a/app/views/shared/icons/_issues.svg +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> - <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch --> - <title>Group</title> - <desc>Created with Sketch.</desc> - <defs></defs> - <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> - <g id="Group" fill="#7E7C7C"> - <path d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2" id="Fill-1"></path> - <path d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z" id="Combined-Shape"></path> - </g> - </g> -</svg>
\ No newline at end of file diff --git a/app/views/shared/icons/_issues.svg.erb b/app/views/shared/icons/_issues.svg.erb new file mode 100644 index 00000000000..fa8655b5609 --- /dev/null +++ b/app/views/shared/icons/_issues.svg.erb @@ -0,0 +1,4 @@ +<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16" class="gitlab-icon"> + <path fill="#7E7C7C" d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2"></path> + <path fill="#7E7C7C" d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z"></path> +</svg> diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index 480e8ba6c85..eff914398bb 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,14 +1,10 @@ -- 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 access-request-button hidden-xs' +- 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, + data: { confirm: remove_member_message(requester) }, + class: 'btn' - else = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), method: :post, - class: 'btn access-request-button hidden-xs' + class: 'btn' diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index a884e78e6e7..5ae485f36ba 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -3,71 +3,74 @@ - user = member.user %li.js-toggle-container{ class: dom_class(member), id: dom_id(member) } - %span{ class: ("list-item-name" if show_controls) } - - if user - = image_tag avatar_icon(user, 24), class: "avatar s24", alt: '' - %strong - = link_to user.name, user_path(user) - %span.cgray= user.username - - - if user == current_user - %span.label.label-success It's you - - - if user.blocked? - %label.label.label-danger - %strong Blocked - - - if member.request? - %span.cgray - – Requested - = time_ago_with_tooltip(member.requested_at) - - else - = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' - %strong= member.invite_email - %span.cgray - – Invited - - if member.created_by - by - = link_to member.created_by.name, user_path(member.created_by) - = time_ago_with_tooltip(member.created_at) - - - if show_controls && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn-xs btn' - - if show_roles - %span.pull-right - %strong= member.human_access + .controls + %strong.control-text= member.human_access - if show_controls + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), + method: :post, + class: 'btn' + - if can?(current_user, action_member_permission(:update, member), member) = button_tag icon('pencil'), type: 'button', - class: 'btn-xs btn btn-grouped inline js-toggle-button', + class: 'btn inline js-toggle-button', title: 'Edit access level' - if member.request? - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, - class: 'btn-xs btn btn-success', + class: 'btn btn-success', title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) - - if current_user == user = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), method: :delete, data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn-xs btn btn-remove' + class: 'btn btn-remove' - else = link_to icon('trash'), member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, - class: 'btn-xs btn btn-remove', + class: 'btn btn-remove', title: remove_member_title(member) + + %span{ class: ("list-item-name" if show_controls) } + - if user + = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' + %strong + = link_to user.name, user_path(user) + %span.cgray= user.username + + - if user == current_user + %span.label.label-success It's you + + - if user.blocked? + %label.label.label-danger + %strong Blocked + + .cgray + - if member.request? + Requested + = time_ago_with_tooltip(member.requested_at) + - else + Joined #{time_ago_with_tooltip(member.created_at)} + + - else + = image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: '' + %strong= member.invite_email + .cgray + Invited + - if member.created_by + by + = link_to member.created_by.name, user_path(member.created_by) + = time_ago_with_tooltip(member.created_at) + + - if show_roles .edit-member.hide.js-toggle-content %br = form_for member, remote: true do |f| diff --git a/app/views/shared/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/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml index 1169bed0382..b7f8551153b 100644 --- a/app/views/shared/projects/_dropdown.html.haml +++ b/app/views/shared/projects/_dropdown.html.haml @@ -1,31 +1,30 @@ - @sort ||= sort_value_recently_updated - personal = params[:personal] - archived = params[:archived] +- namespace_id = params[:namespace_id] .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} - %span.light - = projects_sort_options_hash[@sort] - %b.caret + - toggle_text = projects_sort_options_hash[@sort] + = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable %li.dropdown-header Sort by - projects_sort_options_hash.each do |value, title| %li - = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do = title %li.divider %li - = link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do Hide archived projects %li - = link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do Show archived projects - if current_user %li.divider %li - = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: nil), class: ("is-active" unless personal.present?) do Owned by anyone %li - = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do + = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: true), class: ("is-active" if personal.present?) do Owned by me diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index a7769654b61..160c6cd84da 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,27 +1,28 @@ .hidden-xs - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do - = icon('plus') - New Snippet + - if current_user + = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do + New Snippet - if can?(current_user, :update_personal_snippet, @snippet) = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do Edit - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete -.visible-xs-block.dropdown - %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } - Options - %span.caret - .dropdown-menu.dropdown-menu-full-width - %ul - %li - = link_to new_snippet_path, title: "New Snippet" do - New Snippet - - if can?(current_user, :update_personal_snippet, @snippet) +- if current_user + .visible-xs-block.dropdown + %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } + Options + %span.caret + .dropdown-menu.dropdown-menu-full-width + %ul %li - = link_to edit_snippet_path(@snippet) do - Edit - - if can?(current_user, :admin_personal_snippet, @snippet) - %li - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do - Delete + = link_to new_snippet_path, title: "New Snippet" do + New Snippet + - if can?(current_user, :update_personal_snippet, @snippet) + %li + = link_to edit_snippet_path(@snippet) do + Edit + - if can?(current_user, :admin_personal_snippet, @snippet) + %li + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do + Delete diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml index 630d97e339d..f51599212db 100644 --- a/app/views/users/calendar_activities.html.haml +++ b/app/views/users/calendar_activities.html.haml @@ -14,7 +14,7 @@ - else = event_action_name(event) - if event.target - %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target] + %strong= link_to "#{event.target.to_reference}", [event.project.namespace.becomes(Namespace), event.project, event.target] at %strong diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 68665858c3e..db2b4885861 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -29,6 +29,11 @@ = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do = icon('rss') + - if current_user.admin? + + = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', + data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = icon('users') .avatar-holder = link_to avatar_icon(@user, 400), target: '_blank' do diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 971f969e25e..8551288e2f2 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -28,18 +28,30 @@ class EmailsOnPushWorker :push end + merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha) + diff_refs = nil compare = nil reverse_compare = false if action == :push compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) - diff_refs = [project.merge_base_commit(before_sha, after_sha), project.commit(after_sha)] + + diff_refs = Gitlab::Diff::DiffRefs.new( + base_sha: merge_base_sha, + start_sha: before_sha, + head_sha: after_sha + ) return false if compare.same if compare.commits.empty? compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) - diff_refs = [project.merge_base_commit(after_sha, before_sha), project.commit(before_sha)] + + diff_refs = Gitlab::Diff::DiffRefs.new( + base_sha: merge_base_sha, + start_sha: after_sha, + head_sha: before_sha + ) reverse_compare = true diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index f3327ca9e61..09035a7cf2d 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -4,10 +4,10 @@ class PostReceive sidekiq_options queue: :post_receive def perform(repo_path, identifier, changes) - if repo_path.start_with?(Gitlab.config.gitlab_shell.repos_path.to_s) - repo_path.gsub!(Gitlab.config.gitlab_shell.repos_path.to_s, "") + if path = Gitlab.config.repositories.storages.find { |p| repo_path.start_with?(p[1].to_s) } + repo_path.gsub!(path[1].to_s, "") else - log("Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"") + log("Check gitlab.yml config for correct repositories.storages values. No repository storage path matches \"#{repo_path}\"") end post_received = Gitlab::GitPostReceive.new(repo_path, identifier, changes) diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index d947f105516..f7604e48f83 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -12,7 +12,7 @@ class RepositoryForkWorker return end - result = gitlab_shell.fork_repository(source_path, target_path) + result = gitlab_shell.fork_repository(project.repository_storage_path, source_path, target_path) unless result logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}") project.mark_import_as_failed('The project could not be forked.') 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" diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index 436a2c5e17a..293f2b71d65 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -181,3 +181,9 @@ :why: Equivalent to LGPLv2 :versions: [] :when: 2016-06-07 17:14:10.907682000 Z +- - :whitelist + - Artistic 2.0 + - :who: Josh Frye + :why: Disk/mount information display on Admin pages + :versions: [] + :when: 2016-06-29 16:32:45.432113000 Z 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 diff --git a/config/gitlab.teatro.yml b/config/gitlab.teatro.yml index 01c8dc5ff98..75b79b837e0 100644 --- a/config/gitlab.teatro.yml +++ b/config/gitlab.teatro.yml @@ -47,11 +47,13 @@ production: &base backup: path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/) + repositories: + storages: # REPO PATHS MUST NOT BE A SYMLINK!!! + default: /apps/repositories/ + gitlab_shell: path: /apps/gitlab-shell/ - # REPOS_PATH MUST NOT BE A SYMLINK!!! - repos_path: /apps/repositories/ hooks_path: /apps/gitlab-shell/hooks/ upload_pack: true diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 75e1a3c1093..325eca72862 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -428,6 +428,13 @@ production: &base satellites: path: /home/git/gitlab-satellites/ + ## Repositories settings + repositories: + # Paths where repositories can be stored. Give the canonicalized absolute pathname. + # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!! + storages: # You must have at least a `default` storage path. + default: /home/git/repositories/ + ## Backup settings backup: path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/) @@ -452,9 +459,6 @@ production: &base ## GitLab Shell settings gitlab_shell: path: /home/git/gitlab-shell/ - - # REPOS_PATH MUST NOT BE A SYMLINK!!! - repos_path: /home/git/repositories/ hooks_path: /home/git/gitlab-shell/hooks/ # File that contains the secret key for verifying access for gitlab-shell. @@ -528,11 +532,13 @@ test: # user: YOUR_USERNAME satellites: path: tmp/tests/gitlab-satellites/ + repositories: + storages: + default: tmp/tests/repositories/ backup: path: tmp/tests/backups gitlab_shell: path: tmp/tests/gitlab-shell/ - repos_path: tmp/tests/repositories/ hooks_path: tmp/tests/gitlab-shell/hooks/ issues_tracker: redmine: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c6dc1e4ab38..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 # @@ -304,7 +301,6 @@ Settings.gitlab_shell['hooks_path'] ||= Settings.gitlab['user_home'] + '/gitla Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret') Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil? Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil? -Settings.gitlab_shell['repos_path'] ||= Settings.gitlab['user_home'] + '/repositories/' Settings.gitlab_shell['ssh_host'] ||= Settings.gitlab.ssh_host Settings.gitlab_shell['ssh_port'] ||= 22 Settings.gitlab_shell['ssh_user'] ||= Settings.gitlab.user @@ -312,6 +308,14 @@ Settings.gitlab_shell['owner_group'] ||= Settings.gitlab.user Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_ssh_path_prefix) # +# Repositories +# +Settings['repositories'] ||= Settingslogic.new({}) +Settings.repositories['storages'] ||= {} +# Setting gitlab_shell.repos_path is DEPRECATED and WILL BE REMOVED in version 9.0 +Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'] || Settings.gitlab['user_home'] + '/repositories/' + +# # Backup # Settings['backup'] ||= Settingslogic.new({}) @@ -341,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/initializers/6_validations.rb b/config/initializers/6_validations.rb new file mode 100644 index 00000000000..3ba9e36c567 --- /dev/null +++ b/config/initializers/6_validations.rb @@ -0,0 +1,24 @@ +def storage_name_valid?(name) + !!(name =~ /\A[a-zA-Z0-9\-_]+\z/) +end + +def find_parent_path(name, path) + Gitlab.config.repositories.storages.detect do |n, p| + name != n && path.chomp('/').start_with?(p.chomp('/')) + end +end + +def error(message) + raise "#{message}. Please fix this in your gitlab.yml before starting GitLab." +end + +error('No repository storage path defined') if Gitlab.config.repositories.storages.empty? + +Gitlab.config.repositories.storages.each do |name, path| + error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name) + + parent_name, _parent_path = find_parent_path(name, path) + if parent_name + error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages") + end +end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 021bdb11251..73977341b73 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -212,7 +212,7 @@ Devise.setup do |config| if Gitlab::LDAP::Config.enabled? Gitlab.config.ldap.servers.values.each do |server| if server['allow_username_or_email_login'] - email_stripping_proc = ->(name) {name.gsub(/@.*\z/,'')} + email_stripping_proc = ->(name) {name.gsub(/@.*\z/, '')} else email_stripping_proc = ->(name) {name} end diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb index 751fccead07..7454c33c9dd 100644 --- a/config/initializers/gitlab_shell_secret_token.rb +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -1,19 +1 @@ -# Be sure to restart your server when you modify this file. - -require 'securerandom' - -# Your secret key for verifying the gitlab_shell. - - -secret_file = Gitlab.config.gitlab_shell.secret_file - -unless File.exist? secret_file - # Generate a new token of 16 random hexadecimal characters and store it in secret_file. - token = SecureRandom.hex(16) - File.write(secret_file, token) -end - -link_path = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') -if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(link_path) - FileUtils.symlink(secret_file, link_path) -end +Gitlab::Shell.new.generate_and_link_secret_token diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 75f89d524e7..c4266ab8ba5 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -132,6 +132,11 @@ if Gitlab::Metrics.enabled? config.instrument_instance_methods(API::Helpers) config.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker) + + config.instrument_instance_methods(Rouge::Plugins::Redcarpet) + config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab) + + config.instrument_methods(Rinku) end GC::Profiler.enable diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example index 30d05f16153..69052c029f2 100644 --- a/config/initializers/rack_attack.rb.example +++ b/config/initializers/rack_attack.rb.example @@ -10,7 +10,8 @@ paths_to_be_protected = [ "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session", "#{Rails.application.config.relative_url_root}/users", "#{Rails.application.config.relative_url_root}/users/confirmation", - "#{Rails.application.config.relative_url_root}/unsubscribes/" + "#{Rails.application.config.relative_url_root}/unsubscribes/", + "#{Rails.application.config.relative_url_root}/import/github/personal_access_token" ] diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 7a2b9a7f6c1..593c14a289f 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -13,7 +13,7 @@ Sidekiq.configure_server do |config| # UGLY Hack to get nested hash from settingslogic cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json) # UGLY hack: Settingslogic doesn't allow 'class' key - cron_jobs.each { |k,v| cron_jobs[k]['class'] = cron_jobs[k].delete('job_class') } + cron_jobs.each { |k, v| cron_jobs[k]['class'] = cron_jobs[k].delete('job_class') } Sidekiq::Cron::Job.load_from_hash! cron_jobs # Database pool should be at least `sidekiq_concurrency` + 2 diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb index d256a16d42b..df4a933e22f 100644 --- a/config/initializers/trusted_proxies.rb +++ b/config/initializers/trusted_proxies.rb @@ -1,3 +1,16 @@ +# Override Rack::Request to make use of the same list of trusted_proxies +# as the ActionDispatch::Request object. This is necessary for libraries +# like rack_attack where they don't use ActionDispatch, and we want them +# to block/throttle requests on private networks. +# Rack Attack specific issue: https://github.com/kickstarter/rack-attack/issues/145 +module Rack + class Request + def trusted_proxy?(ip) + Rails.application.config.action_dispatch.trusted_proxies.any? { |proxy| proxy === ip } + end + end +end + Rails.application.config.action_dispatch.trusted_proxies = ( [ '127.0.0.1', '::1' ] + Array(Gitlab.config.gitlab.trusted_proxies) ).map { |proxy| IPAddr.new(proxy) } diff --git a/config/routes.rb b/config/routes.rb index e45293cdf7f..18a4ead2b37 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -133,12 +133,12 @@ Rails.application.routes.draw do # resources :notification_settings, only: [:create, :update] - # # Import # namespace :import do resource :github, only: [:create, :new], controller: :github do + post :personal_access_token get :status get :callback get :jobs @@ -280,6 +280,7 @@ Rails.application.routes.draw do resource :logs, only: [:show] resource :health_check, controller: 'health_check', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show] + resource :system_info, controller: 'system_info', only: [:show] resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do root to: 'projects#index', as: :projects @@ -464,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_\-]+(?<!\.atom)/ }, except: [:new, :create, :index], path: "/") do - member do put :transfer delete :remove_fork @@ -652,7 +652,7 @@ Rails.application.routes.draw do get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/markdown_preview', to:'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview' + post '/wikis/*id/markdown_preview', to: 'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview' end resource :repository, only: [:show, :create] do @@ -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] diff --git a/db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb b/db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb new file mode 100644 index 00000000000..1c4d60e7234 --- /dev/null +++ b/db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb @@ -0,0 +1,5 @@ +class AddHeadCommitIdToMergeRequestDiffs < ActiveRecord::Migration + def change + add_column :merge_request_diffs, :head_commit_sha, :string + end +end diff --git a/db/migrate/20160508215920_add_positions_to_diff_notes.rb b/db/migrate/20160508215920_add_positions_to_diff_notes.rb new file mode 100644 index 00000000000..2952c25004e --- /dev/null +++ b/db/migrate/20160508215920_add_positions_to_diff_notes.rb @@ -0,0 +1,6 @@ +class AddPositionsToDiffNotes < ActiveRecord::Migration + def change + add_column :notes, :position, :text + add_column :notes, :original_position, :text + end +end diff --git a/db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb b/db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb new file mode 100644 index 00000000000..b7fd76ee84b --- /dev/null +++ b/db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb @@ -0,0 +1,5 @@ +class AddStartCommitIdToMergeRequestDiffs < ActiveRecord::Migration + def change + add_column :merge_request_diffs, :start_commit_sha, :string + end +end diff --git a/db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb b/db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb new file mode 100644 index 00000000000..4eef16c9408 --- /dev/null +++ b/db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb @@ -0,0 +1,22 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddNoteTypeAndPositionToSentNotification < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :sent_notifications, :note_type, :string + add_column :sent_notifications, :position, :text + end +end diff --git a/db/migrate/20160608195742_add_repository_storage_to_projects.rb b/db/migrate/20160608195742_add_repository_storage_to_projects.rb new file mode 100644 index 00000000000..c700d2b569d --- /dev/null +++ b/db/migrate/20160608195742_add_repository_storage_to_projects.rb @@ -0,0 +1,12 @@ +class AddRepositoryStorageToProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def up + add_column_with_default(:projects, :repository_storage, :string, default: 'default') + end + + def down + remove_column(:projects, :repository_storage) + end +end diff --git a/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb b/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb new file mode 100644 index 00000000000..34c702e3fa6 --- /dev/null +++ b/db/migrate/20160608211215_add_user_default_external_to_application_settings.rb @@ -0,0 +1,13 @@ +class AddUserDefaultExternalToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def up + add_column_with_default(:application_settings, :user_default_external, :boolean, + default: false, allow_null: false) + end + + def down + remove_column(:application_settings, :user_default_external) + end +end diff --git a/db/migrate/20160614182521_add_repository_storage_to_application_settings.rb b/db/migrate/20160614182521_add_repository_storage_to_application_settings.rb new file mode 100644 index 00000000000..6dae91b700b --- /dev/null +++ b/db/migrate/20160614182521_add_repository_storage_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddRepositoryStorageToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :repository_storage, :string, default: 'default' + end +end diff --git a/db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb b/db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb new file mode 100644 index 00000000000..013904b3f4f --- /dev/null +++ b/db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb @@ -0,0 +1,11 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. +# rubocop:disable all + +class AddEnabledGitAccessProtocolsToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + add_column :application_settings, :enabled_git_access_protocol, :string + end +end diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb index 00a45d7fe73..180a75e0998 100644 --- a/db/migrate/20160616102642_remove_duplicated_keys.rb +++ b/db/migrate/20160616102642_remove_duplicated_keys.rb @@ -4,12 +4,12 @@ class RemoveDuplicatedKeys < ActiveRecord::Migration select_all("SELECT fingerprint FROM #{quote_table_name(:keys)} GROUP BY fingerprint HAVING COUNT(*) > 1").each do |row| fingerprint = connection.quote(row['fingerprint']) execute(%Q{ - DELETE FROM keys + DELETE FROM #{quote_table_name(:keys)} WHERE fingerprint = #{fingerprint} AND id != ( SELECT id FROM ( SELECT max(id) AS id - FROM keys + FROM #{quote_table_name(:keys)} WHERE fingerprint = #{fingerprint} ) max_ids ) diff --git a/db/migrate/20160620110927_fix_no_validatable_import_url.rb b/db/migrate/20160620110927_fix_no_validatable_import_url.rb new file mode 100644 index 00000000000..a3f5073d511 --- /dev/null +++ b/db/migrate/20160620110927_fix_no_validatable_import_url.rb @@ -0,0 +1,106 @@ +# 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 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..61dd726fac7 --- /dev/null +++ b/db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb @@ -0,0 +1,7 @@ +class AddArtifactsSizeToCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + add_column(:ci_builds, :artifacts_size, :integer) + end +end diff --git a/db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb b/db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb new file mode 100644 index 00000000000..0c25f87dfb4 --- /dev/null +++ b/db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb @@ -0,0 +1,11 @@ +# rubocop:disable all +# Migration type: online without errors + +class AddIndexOnAwardEmojiUserAndName < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def change + add_concurrent_index(:award_emoji, [:user_id, :name]) + end +end diff --git a/db/migrate/20160705163108_remove_requesters_that_are_owners.rb b/db/migrate/20160705163108_remove_requesters_that_are_owners.rb new file mode 100644 index 00000000000..1fca230c019 --- /dev/null +++ b/db/migrate/20160705163108_remove_requesters_that_are_owners.rb @@ -0,0 +1,40 @@ +class RemoveRequestersThatAreOwners < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def up + # Delete requesters that are owner of their projects and actually requested + # access to it + execute <<-SQL + DELETE FROM members + WHERE members.source_type = 'Project' + AND members.type = 'ProjectMember' + AND members.requested_at IS NOT NULL + AND members.user_id = ( + SELECT namespaces.owner_id + FROM namespaces + JOIN projects ON namespaces.id = projects.namespace_id + WHERE namespaces.type IS NULL + AND projects.id = members.source_id + AND namespaces.owner_id = members.user_id); + SQL + + # Delete requesters that are owner of their project's group and actually requested + # access to it + execute <<-SQL + DELETE FROM members + WHERE members.source_type = 'Project' + AND members.type = 'ProjectMember' + AND members.requested_at IS NOT NULL + AND members.user_id = ( + SELECT namespaces.owner_id + FROM namespaces + JOIN projects ON namespaces.id = projects.namespace_id + WHERE namespaces.type = 'Group' + AND projects.id = members.source_id + AND namespaces.owner_id = members.user_id); + SQL + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a8377f687c..a5eea3a697c 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: 20160705163108) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -84,7 +84,10 @@ ActiveRecord::Schema.define(version: 20160620115026) do t.string "health_check_access_token" t.boolean "send_user_confirmation_email", default: false t.integer "container_registry_token_expire_delay", default: 5 + t.boolean "user_default_external", default: false, null: false t.text "after_sign_up_text" + t.string "repository_storage", default: "default" + t.string "enabled_git_access_protocol" end create_table "audit_events", force: :cascade do |t| @@ -111,6 +114,7 @@ ActiveRecord::Schema.define(version: 20160620115026) 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| @@ -163,6 +167,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 @@ -589,6 +594,8 @@ ActiveRecord::Schema.define(version: 20160620115026) 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 @@ -685,10 +692,12 @@ ActiveRecord::Schema.define(version: 20160620115026) 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 @@ -796,38 +805,39 @@ ActiveRecord::Schema.define(version: 20160620115026) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.boolean "snippets_enabled", default: true, null: false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false t.string "avatar" t.string "import_status" t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false + t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "public_builds", default: true, null: false t.integer "pushes_since_gc", default: 0 t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" + t.string "repository_storage", default: "default", null: false end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -876,6 +886,8 @@ ActiveRecord::Schema.define(version: 20160620115026) do t.string "commit_id" t.string "reply_key", null: false t.string "line_code" + t.string "note_type" + t.text "position" end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree diff --git a/doc/README.md b/doc/README.md index be0d17084c7..cf7a828d91e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -21,9 +21,10 @@ ## Administrator documentation +- [Access restrictions](administration/access_restrictions.md) Define which Git access protocols can be used to talk to GitLab - [Authentication/Authorization](administration/auth/README.md) Configure external authentication with LDAP, SAML, CAS and additional Omniauth providers. -- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when webhooks aren't enough. +- [Custom Git hooks](administration/custom_hooks.md) Custom Git hooks (on the filesystem) for when webhooks aren't enough. - [Install](install/README.md) Requirements, directory structures and installation from source. - [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter. @@ -34,6 +35,7 @@ - [Operations](operations/README.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Repository checks](administration/repository_checks.md) Periodic Git repository checks. +- [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. diff --git a/doc/administration/access_restrictions.md b/doc/administration/access_restrictions.md new file mode 100644 index 00000000000..51d7996effd --- /dev/null +++ b/doc/administration/access_restrictions.md @@ -0,0 +1,38 @@ +# Access Restrictions + +> **Note:** This feature is only available on versions 8.10 and above. + +With GitLab's Access restrictions you can choose which Git access protocols you +want your users to use to communicate with GitLab. This feature can be enabled +via the `Application Settings` in the Admin interface. + +The setting is called `Enabled Git access protocols`, and it gives you the option +to choose between: + +- Both SSH and HTTP(S) +- Only SSH +- Only HTTP(s) + +![Settings Overview](img/access_restrictions.png) + +## Enabled Protocol + +When both SSH and HTTP(S) are enabled, GitLab will behave as usual, it will give +your users the option to choose which protocol they would like to use. + +When you choose to allow only one of the protocols, a couple of things will happen: + +- The project page will only show the allowed protocol's URL, with no option to + change it. +- A tooltip will be shown when you hover over the URL's protocol, if an action + on the user's part is required, e.g. adding an SSH key, or setting a password. + +![Project URL with SSH only access](img/restricted_url.png) + +On top of these UI restrictions, GitLab will deny all Git actions on the protocol +not selected. + +> **Note:** Please keep in mind that disabling an access protocol does not actually + block access to the server itself. The ports used for the protocol, be it SSH or + HTTP, will still be accessible. What GitLab does is restrict access on the + application level.
\ No newline at end of file diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 10096779844..7186f707ad6 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -130,27 +130,27 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server first_name: 'givenName' last_name: 'sn' - ## EE only - - # Base where we can search for groups - # - # Ex. ou=groups,dc=gitlab,dc=example - # - group_base: '' - - # The CN of a group containing GitLab administrators - # - # Ex. administrators - # - # Note: Not `cn=administrators` or the full DN - # - admin_group: '' - - # The LDAP attribute containing a user's public SSH key - # - # Ex. ssh_public_key - # - sync_ssh_keys: false + ## EE only + + # Base where we can search for groups + # + # Ex. ou=groups,dc=gitlab,dc=example + # + group_base: '' + + # The CN of a group containing GitLab administrators + # + # Ex. administrators + # + # Note: Not `cn=administrators` or the full DN + # + admin_group: '' + + # The LDAP attribute containing a user's public SSH key + # + # Ex. ssh_public_key + # + sync_ssh_keys: false # GitLab EE only: add more LDAP servers # Choose an ID made of a-z and 0-9 . This ID will be stored in the database diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md new file mode 100644 index 00000000000..e3306c22d3f --- /dev/null +++ b/doc/administration/custom_hooks.md @@ -0,0 +1,57 @@ +# Custom Git Hooks + +> +**Note:** Custom Git hooks must be configured on the filesystem of the GitLab +server. Only GitLab server administrators will be able to complete these tasks. +Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not +have filesystem access. For a user configurable Git hook interface, please see +[GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html). + +Git natively supports hooks that are executed on different actions. +Examples of server-side git hooks include pre-receive, post-receive, and update. +See [Git SCM Server-Side Hooks][hooks] for more information about each hook type. + +As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab +administrators can add custom git hooks to any GitLab project. + +## Setup + +Normally, Git hooks are placed in the repository or project's `hooks` directory. +GitLab creates a symlink from each project's `hooks` directory to the +gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell +upgrades. As such, custom hooks are implemented a little differently. Behavior +is exactly the same once the hook is created, though. + +Follow the steps below to set up a custom hook: + +1. Pick a project that needs a custom Git hook. +1. On the GitLab server, navigate to the project's repository directory. + For an installation from source the path is usually + `/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is + usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`. +1. Create a new directory in this location called `custom_hooks`. +1. Inside the new `custom_hooks` directory, create a file with a name matching + the hook type. For a pre-receive hook the file name should be `pre-receive` + with no extension. +1. Make the hook file executable and make sure it's owned by git. +1. Write the code to make the Git hook function as expected. Hooks can be + in any language. Ensure the 'shebang' at the top properly reflects the language + type. For example, if the script is in Ruby the shebang will probably be + `#!/usr/bin/env ruby`. + +That's it! Assuming the hook code is properly implemented the hook will fire +as appropriate. + +## Custom error messages + +>**Note:** +This feature was [introduced][5073] in GitLab 8.10. + +If the commit is declined or an error occurs during the Git hook check, +the STDERR or STDOUT message of the hook will be present in GitLab's UI. +STDERR takes precedence over STDOUT. + +![Custom message from custom Git hook](img/custom_hooks_error_msg.png) + +[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks +[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073 diff --git a/doc/administration/img/access_restrictions.png b/doc/administration/img/access_restrictions.png Binary files differnew file mode 100644 index 00000000000..66fd9491e85 --- /dev/null +++ b/doc/administration/img/access_restrictions.png diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png Binary files differnew file mode 100644 index 00000000000..92e87e15fb3 --- /dev/null +++ b/doc/administration/img/custom_hooks_error_msg.png diff --git a/doc/administration/img/housekeeping_settings.png b/doc/administration/img/housekeeping_settings.png Binary files differindex f7c5bc44367..f72ad9a45d5 100644 --- a/doc/administration/img/housekeeping_settings.png +++ b/doc/administration/img/housekeeping_settings.png diff --git a/doc/administration/img/restricted_url.png b/doc/administration/img/restricted_url.png Binary files differnew file mode 100644 index 00000000000..0a677433dcf --- /dev/null +++ b/doc/administration/img/restricted_url.png diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md new file mode 100644 index 00000000000..81bfe173151 --- /dev/null +++ b/doc/administration/repository_storages.md @@ -0,0 +1,18 @@ +# Repository storages + +GitLab allows you to define repository storage paths to enable distribution of +storage load between several mount points. + +## For installations from source + +Add your repository storage paths in your `gitlab.yml` under repositories -> storages, using key -> value pairs. + +>**Notes:** +- You must have at least one storage path called `default`. +- In order for backups to work correctly the storage path must **not** be a +mount point and the GitLab user should have correct permissions for the parent +directory of the path. + +## For omnibus installations + +Follow the instructions at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/configuration.md#storing-git-data-in-an-alternative-directory 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 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/builds.md b/doc/api/builds.md index de998944352..2adea11247e 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -107,6 +107,11 @@ Example of response Get a list of builds for specific commit in a project. +This endpoint will return all builds, from all pipelines for a given commit. +If the commit SHA is not found, it will respond with 404, otherwise it will +return an array of builds (an empty array if there are no builds for this +particular commit). + ``` GET /projects/:id/repository/commits/:sha/builds ``` diff --git a/doc/api/groups.md b/doc/api/groups.md index 1ccb9715e96..87480bebfc4 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -42,46 +42,49 @@ Parameters: ```json
[
{
- "id": 4,
- "description": null,
+ "id": 9,
+ "description": "foo",
"default_branch": "master",
+ "tag_list": [],
"public": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
- "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
- "web_url": "http://example.com/diaspora/diaspora-client",
- "tag_list": [
- "example",
- "disapora client"
- ],
- "owner": {
- "id": 3,
- "name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
- },
- "name": "Diaspora Client",
- "name_with_namespace": "Diaspora / Diaspora Client",
- "path": "diaspora-client",
- "path_with_namespace": "diaspora/diaspora-client",
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
+ "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "Experimental / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
"issues_enabled": true,
"merge_requests_enabled": true,
- "builds_enabled": true,
"wiki_enabled": true,
- "snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
- "creator_id": 3,
+ "builds_enabled": true,
+ "snippets_enabled": true,
+ "created_at": "2016-04-05T21:40:50.169Z",
+ "last_activity_at": "2016-04-06T16:52:08.432Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
- "description": "",
- "id": 3,
- "name": "Diaspora",
- "owner_id": 1,
- "path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "id": 5,
+ "name": "Experimental",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-04-05T21:40:49.152Z",
+ "updated_at": "2016-04-07T08:07:48.466Z",
+ "description": "foo",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 10
},
- "archived": false,
- "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
+ "avatar_url": null,
+ "star_count": 1,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
}
]
```
@@ -96,7 +99,180 @@ GET /groups/:id Parameters:
-- `id` (required) - The ID or path of a group
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or path of a group |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4
+```
+
+Example response:
+
+```json
+{
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "visibility_level": 20,
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/twitter",
+ "projects": [
+ {
+ "id": 7,
+ "description": "Voluptas veniam qui et beatae voluptas doloremque explicabo facilis.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/typeahead-js.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/typeahead-js.git",
+ "web_url": "https://gitlab.example.com/twitter/typeahead-js",
+ "name": "Typeahead.Js",
+ "name_with_namespace": "Twitter / Typeahead.Js",
+ "path": "typeahead-js",
+ "path_with_namespace": "twitter/typeahead-js",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:25.578Z",
+ "last_activity_at": "2016-06-17T07:47:25.881Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
+ },
+ {
+ "id": 6,
+ "description": "Aspernatur omnis repudiandae qui voluptatibus eaque.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/flight.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/flight.git",
+ "web_url": "https://gitlab.example.com/twitter/flight",
+ "name": "Flight",
+ "name_with_namespace": "Twitter / Flight",
+ "path": "flight",
+ "path_with_namespace": "twitter/flight",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:24.661Z",
+ "last_activity_at": "2016-06-17T07:47:24.838Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 8,
+ "public_builds": true,
+ "shared_with_groups": []
+ }
+ ],
+ "shared_projects": [
+ {
+ "id": 8,
+ "description": "Velit eveniet provident fugiat saepe eligendi autem.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@gitlab.example.com:h5bp/html5-boilerplate.git",
+ "http_url_to_repo": "https://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "https://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "H5bp / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:27.089Z",
+ "last_activity_at": "2016-06-17T07:47:27.310Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "H5bp",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:26.621Z",
+ "updated_at": "2016-06-17T07:47:26.621Z",
+ "description": "Id consequatur rem vel qui doloremque saepe.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 4,
+ "public_builds": true,
+ "shared_with_groups": [
+ {
+ "group_id": 4,
+ "group_name": "Twitter",
+ "group_access_level": 30
+ },
+ {
+ "group_id": 3,
+ "group_name": "Gitlab Org",
+ "group_access_level": 10
+ }
+ ]
+ }
+ ]
+}
+```
## New group
@@ -201,7 +377,8 @@ Example response: "star_count": 1,
"forks_count": 0,
"open_issues_count": 3,
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
}
]
}
diff --git a/doc/api/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 169bfb913bf..816f09e1007 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 @@ -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/doc/api/oauth2.md b/doc/api/oauth2.md index d416a826f79..31902e145f6 100644 --- a/doc/api/oauth2.md +++ b/doc/api/oauth2.md @@ -65,6 +65,13 @@ curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user ## Resource Owner Password Credentials +## Deprecation Notice + +1. Starting in GitLab 9.0, the Resource Owner Password Credentials will be *disabled* for users with two-factor authentication turned on. +2. These users can access the API using [personal access tokens] instead. + +--- + In this flow, a token is requested in exchange for the resource owner credentials (username and password). The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the client is part of the device operating system or a highly privileged application), and when other authorization grant types are not @@ -100,3 +107,5 @@ client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http access_token = client.password.get_token('user@example.com', 'sekret') puts access_token.token ``` + +[personal access tokens]: ./README.md#personal-access-tokens diff --git a/doc/api/projects.md b/doc/api/projects.md index a15f3df498e..dceee7b4ea7 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -83,7 +83,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] }, { "id": 6, @@ -142,7 +143,8 @@ Parameters: "forks_count": 0, "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", - "public_builds": true + "public_builds": true, + "shared_with_groups": [] } ] ``` @@ -265,7 +267,20 @@ Parameters: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [ + { + "group_id": 4, + "group_name": "Twitter", + "group_access_level": 30 + }, + { + "group_id": 3, + "group_name": "Gitlab Org", + "group_access_level": 10 + } + ] } ``` @@ -428,6 +443,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -452,6 +468,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `import_url` (optional) @@ -478,6 +495,7 @@ Parameters: - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `container_registry_enabled` (optional) +- `shared_runners_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `public_builds` (optional) @@ -557,7 +575,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": [] } ``` @@ -621,7 +641,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": [] } ``` @@ -705,7 +727,9 @@ Example response: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [] } ``` @@ -719,7 +743,7 @@ have the proper access rights, code 403 is returned. Status 404 is returned if t doesn't exist, or is hidden to the user. ``` -POST /projects/:id/archive +POST /projects/:id/unarchive ``` | Attribute | Type | Required | Description | @@ -789,7 +813,9 @@ Example response: "shared_runners_enabled": true, "forks_count": 0, "star_count": 0, - "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b" + "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", + "public_builds": true, + "shared_with_groups": [] } ``` diff --git a/doc/api/services.md b/doc/api/services.md index ccfc0fccb7f..f821a614047 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 @@ -374,40 +374,6 @@ Get Gemnasium service settings for a project. GET /projects/:id/services/gemnasium ``` -## GitLab CI - -Continuous integration server from GitLab - -### Create/Edit GitLab CI service - -Set GitLab CI service for a project. - -``` -PUT /projects/:id/services/gitlab-ci -``` - -Parameters: - -- `token` (**required**) - GitLab CI project specific token -- `project_url` (**required**) - http://ci.gitlabhq.com/projects/3 -- `enable_ssl_verification` (optional) - Enable SSL verification - -### Delete GitLab CI service - -Delete GitLab CI service for a project. - -``` -DELETE /projects/:id/services/gitlab-ci -``` - -### Get GitLab CI service settings - -Get GitLab CI service settings for a project. - -``` -GET /projects/:id/services/gitlab-ci -``` - ## HipChat Private group chat and IM diff --git a/doc/api/session.md b/doc/api/session.md index 71e93d0bb0a..066a055702d 100644 --- a/doc/api/session.md +++ b/doc/api/session.md @@ -1,5 +1,12 @@ # Session +## Deprecation Notice + +1. Starting in GitLab 9.0, this feature will be *disabled* for users with two-factor authentication turned on. +2. These users can access the API using [personal access tokens] instead. + +--- + You can login with both GitLab and LDAP credentials in order to obtain the private token. @@ -45,3 +52,5 @@ Example response: "private_token": "9koXpg98eAheJpvBs5tK" } ``` + +[personal access tokens]: ./README.md#personal-access-tokens diff --git a/doc/api/settings.md b/doc/api/settings.md index 43a0fe35e42..d9b68eaeadf 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -38,7 +38,8 @@ Example response: "default_project_visibility" : 0, "gravatar_enabled" : true, "sign_in_text" : null, - "container_registry_token_expire_delay": 5 + "container_registry_token_expire_delay": 5, + "repository_storage": "default" } ``` @@ -56,7 +57,7 @@ PUT /application/settings | `gravatar_enabled` | boolean | no | Enable Gravatar | | `sign_in_text` | string | no | Text on login page | | `home_page_url` | string | no | Redirect to this URL when not logged in | -| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. | +| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `2`. | | `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. | | `max_attachment_size` | integer | no | Limit attachment size in MB | | `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes | @@ -66,6 +67,8 @@ PUT /application/settings | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider | | `after_sign_out_path` | string | no | Where to redirect users after logout | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes | +| `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml | +| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. ```bash curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1 @@ -93,6 +96,7 @@ Example response: "restricted_signup_domains": [], "user_oauth_applications": true, "after_sign_out_path": "", - "container_registry_token_expire_delay": 5 + "container_registry_token_expire_delay": 5, + "repository_storage": "default" } ``` diff --git a/doc/api/todos.md b/doc/api/todos.md new file mode 100644 index 00000000000..29e73664410 --- /dev/null +++ b/doc/api/todos.md @@ -0,0 +1,444 @@ +# 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 precise the request. + +``` +GET /todos +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `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 a todo. Can be either `Issue` or `MergeRequest` | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos +``` + +Example Response: + +```json +[ + { + "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": "pending", + "created_at": "2016-06-17T07:52:35.225Z" + }, + { + "id": 98, + "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": "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" + }, + "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-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 +todo 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": 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" +} +``` + +## 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": 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" + }, + { + "id": 98, + "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": "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" + }, + "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-06-17T07:49:24.624Z" + }, +] +``` + +[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188 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/ci/build_artifacts/img/build_artifacts_browser.png b/doc/ci/build_artifacts/img/build_artifacts_browser.png Binary files differindex 73ed4eeb927..59cf2b8746b 100644 --- a/doc/ci/build_artifacts/img/build_artifacts_browser.png +++ b/doc/ci/build_artifacts/img/build_artifacts_browser.png diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png Binary files differindex f5d15bc3e7d..7801c2e6fa6 100644 --- a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png +++ b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png 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) diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index 17e1c64bb8a..bfafcc44d66 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -49,7 +49,7 @@ apt-get update -yqq apt-get install git -yqq # Install phpunit, the tool that we will use for testing -curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar +curl -Lo /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar chmod +x /usr/local/bin/phpunit # Install mysql driver diff --git a/doc/ci/img/builds_tab.png b/doc/ci/img/builds_tab.png Binary files differindex d088b8b329d..35780e277ae 100644 --- a/doc/ci/img/builds_tab.png +++ b/doc/ci/img/builds_tab.png diff --git a/doc/ci/img/features_settings.png b/doc/ci/img/features_settings.png Binary files differindex 17aba5d14d8..38d7036f606 100644 --- a/doc/ci/img/features_settings.png +++ b/doc/ci/img/features_settings.png diff --git a/doc/ci/quick_start/img/build_log.png b/doc/ci/quick_start/img/build_log.png Binary files differindex 89e6cd40cb6..b53a6cd86b0 100644 --- a/doc/ci/quick_start/img/build_log.png +++ b/doc/ci/quick_start/img/build_log.png diff --git a/doc/ci/quick_start/img/builds_status.png b/doc/ci/quick_start/img/builds_status.png Binary files differindex b8e6c2a361a..47862761ffe 100644 --- a/doc/ci/quick_start/img/builds_status.png +++ b/doc/ci/quick_start/img/builds_status.png diff --git a/doc/ci/quick_start/img/new_commit.png b/doc/ci/quick_start/img/new_commit.png Binary files differindex 3d3c9d5c0bd..a53562ce328 100644 --- a/doc/ci/quick_start/img/new_commit.png +++ b/doc/ci/quick_start/img/new_commit.png diff --git a/doc/ci/quick_start/img/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png Binary files differindex eafcfd6ecd5..23261123b18 100644 --- a/doc/ci/quick_start/img/runners_activated.png +++ b/doc/ci/quick_start/img/runners_activated.png diff --git a/doc/ci/quick_start/img/single_commit_status_pending.png b/doc/ci/quick_start/img/single_commit_status_pending.png Binary files differindex 23b3bb5acfc..ccf3ac957bb 100644 --- a/doc/ci/quick_start/img/single_commit_status_pending.png +++ b/doc/ci/quick_start/img/single_commit_status_pending.png diff --git a/doc/ci/quick_start/img/status_pending.png b/doc/ci/quick_start/img/status_pending.png Binary files differindex a049ec2a5ba..9feacf0c961 100644 --- a/doc/ci/quick_start/img/status_pending.png +++ b/doc/ci/quick_start/img/status_pending.png diff --git a/doc/ci/runners/project_specific.png b/doc/ci/runners/project_specific.png Binary files differindex f51ea694e78..c812defa67b 100644 --- a/doc/ci/runners/project_specific.png +++ b/doc/ci/runners/project_specific.png diff --git a/doc/ci/runners/shared_runner.png b/doc/ci/runners/shared_runner.png Binary files differindex 9755144eb08..31574a17764 100644 --- a/doc/ci/runners/shared_runner.png +++ b/doc/ci/runners/shared_runner.png diff --git a/doc/ci/runners/shared_to_specific_admin.png b/doc/ci/runners/shared_to_specific_admin.png Binary files differindex 44a4bef22f7..8f4010a5849 100644 --- a/doc/ci/runners/shared_to_specific_admin.png +++ b/doc/ci/runners/shared_to_specific_admin.png diff --git a/doc/ci/triggers/img/builds_page.png b/doc/ci/triggers/img/builds_page.png Binary files differindex e78794fbee7..2dee8ee6107 100644 --- a/doc/ci/triggers/img/builds_page.png +++ b/doc/ci/triggers/img/builds_page.png diff --git a/doc/ci/triggers/img/trigger_single_build.png b/doc/ci/triggers/img/trigger_single_build.png Binary files differindex c25f27409d6..baf3fc183d8 100644 --- a/doc/ci/triggers/img/trigger_single_build.png +++ b/doc/ci/triggers/img/trigger_single_build.png diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png Binary files differindex 2207e8b34cb..908355c33a5 100644 --- a/doc/ci/triggers/img/trigger_variables.png +++ b/doc/ci/triggers/img/trigger_variables.png diff --git a/doc/ci/triggers/img/triggers_page.png b/doc/ci/triggers/img/triggers_page.png Binary files differindex 268368dc3c5..69cec5cdebf 100644 --- a/doc/ci/triggers/img/triggers_page.png +++ b/doc/ci/triggers/img/triggers_page.png diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 1892acda29b..eb81267242e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -811,7 +811,7 @@ deploy: It's possible to overwrite globally defined `before_script` and `after_script`: ```yaml -before_script +before_script: - global before script job: @@ -1034,8 +1034,8 @@ You can find the link under `/ci/lint` of your gitlab instance. ## Skipping builds -If your commit message contains `[ci skip]`, the commit will be created but the -builds will be skipped. +If your commit message contains `[ci skip]` or `[skip ci]`, using any +capitalization, the commit will be created but the builds will be skipped. ## Examples diff --git a/doc/container_registry/img/container_registry.png b/doc/container_registry/img/container_registry.png Binary files differindex e9505a73b40..57d6f9f22c5 100644 --- a/doc/container_registry/img/container_registry.png +++ b/doc/container_registry/img/container_registry.png diff --git a/doc/container_registry/img/project_feature.png b/doc/container_registry/img/project_feature.png Binary files differindex 57a73d253c0..a59b4f82b56 100644 --- a/doc/container_registry/img/project_feature.png +++ b/doc/container_registry/img/project_feature.png diff --git a/doc/customization/branded_login_page/appearance.png b/doc/customization/branded_login_page/appearance.png Binary files differindex 6bce1f0a287..023dc5599b4 100644 --- a/doc/customization/branded_login_page/appearance.png +++ b/doc/customization/branded_login_page/appearance.png diff --git a/doc/customization/branded_login_page/custom_sign_in.png b/doc/customization/branded_login_page/custom_sign_in.png Binary files differindex d6020b029a2..7d99e0a2b3b 100644 --- a/doc/customization/branded_login_page/custom_sign_in.png +++ b/doc/customization/branded_login_page/custom_sign_in.png diff --git a/doc/customization/branded_login_page/default_login_page.png b/doc/customization/branded_login_page/default_login_page.png Binary files differindex 795c7954d8e..0cfa9da202e 100644 --- a/doc/customization/branded_login_page/default_login_page.png +++ b/doc/customization/branded_login_page/default_login_page.png diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 12e33406cb6..33fd50f4c11 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -52,7 +52,9 @@ To serve repositories over SSH there's an add-on application called gitlab-shell ### Components -![GitLab Diagram Overview](gitlab_diagram_overview.png) +![GitLab Diagram Overview](gitlab_architecture_diagram.png) + +_[edit diagram (for GitLab team members only)](https://docs.google.com/drawings/d/1fBzAyklyveF-i-2q-OHUIqDkYfjjxC4mq5shwKSZHLs/edit)_ A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs. 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/development/gitlab_architecture_diagram.png b/doc/development/gitlab_architecture_diagram.png Binary files differnew file mode 100644 index 00000000000..80e975718e0 --- /dev/null +++ b/doc/development/gitlab_architecture_diagram.png diff --git a/doc/development/gitlab_diagram_overview.png b/doc/development/gitlab_diagram_overview.png Binary files differdeleted file mode 100644 index d9b9eed3d8f..00000000000 --- a/doc/development/gitlab_diagram_overview.png +++ /dev/null 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" diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md index 3625c4191b8..a6d22e5a04a 100644 --- a/doc/downgrade_ee_to_ce/README.md +++ b/doc/downgrade_ee_to_ce/README.md @@ -44,13 +44,13 @@ to avoid getting this error, you need to remove all instances of the **Omnibus Installation** ``` -$ sudo gitlab-rails runner "Service.where(type: 'JenkinsService').delete_all" +$ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" ``` **Source Installation** ``` -$ bundle exec rails runner "Service.where(type: 'JenkinsService').delete_all" production +$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production ``` ## Downgrade to CE diff --git a/doc/gitlab-basics/basicsimages/add_new_merge_request.png b/doc/gitlab-basics/basicsimages/add_new_merge_request.png Binary files differindex 9d93b217a59..e60992c4c6a 100644 --- a/doc/gitlab-basics/basicsimages/add_new_merge_request.png +++ b/doc/gitlab-basics/basicsimages/add_new_merge_request.png diff --git a/doc/gitlab-basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png Binary files differindex 2dede97aa40..89c86018629 100644 --- a/doc/gitlab-basics/basicsimages/add_sshkey.png +++ b/doc/gitlab-basics/basicsimages/add_sshkey.png diff --git a/doc/gitlab-basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png Binary files differindex c5e38b552a5..2264f3c5bf2 100644 --- a/doc/gitlab-basics/basicsimages/branch_info.png +++ b/doc/gitlab-basics/basicsimages/branch_info.png diff --git a/doc/gitlab-basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png Binary files differindex 06e77f5eea9..75fe8313611 100644 --- a/doc/gitlab-basics/basicsimages/branch_name.png +++ b/doc/gitlab-basics/basicsimages/branch_name.png diff --git a/doc/gitlab-basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png Binary files differindex c18fa83b968..8621bc05776 100644 --- a/doc/gitlab-basics/basicsimages/branches.png +++ b/doc/gitlab-basics/basicsimages/branches.png diff --git a/doc/gitlab-basics/basicsimages/button-create-mr.png b/doc/gitlab-basics/basicsimages/button-create-mr.png Binary files differindex 457af459bb9..b52ab148839 100644 --- a/doc/gitlab-basics/basicsimages/button-create-mr.png +++ b/doc/gitlab-basics/basicsimages/button-create-mr.png diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png Binary files differindex 94b6d5756d3..6450deec6fc 100644 --- a/doc/gitlab-basics/basicsimages/click-on-new-group.png +++ b/doc/gitlab-basics/basicsimages/click-on-new-group.png diff --git a/doc/gitlab-basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png Binary files differindex 81588336f37..a88809c5a3f 100644 --- a/doc/gitlab-basics/basicsimages/commit_changes.png +++ b/doc/gitlab-basics/basicsimages/commit_changes.png diff --git a/doc/gitlab-basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png Binary files differindex 0df2c32653c..4abe4517f98 100644 --- a/doc/gitlab-basics/basicsimages/commit_message.png +++ b/doc/gitlab-basics/basicsimages/commit_message.png diff --git a/doc/gitlab-basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png Binary files differindex 7e606539077..2bfcaf75f01 100644 --- a/doc/gitlab-basics/basicsimages/commits.png +++ b/doc/gitlab-basics/basicsimages/commits.png diff --git a/doc/gitlab-basics/basicsimages/compare_branches.png b/doc/gitlab-basics/basicsimages/compare_branches.png Binary files differindex 7eebaed9075..8a18453dd05 100644 --- a/doc/gitlab-basics/basicsimages/compare_branches.png +++ b/doc/gitlab-basics/basicsimages/compare_branches.png diff --git a/doc/gitlab-basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png Binary files differindex 688e355cca2..5ebe1b227dd 100644 --- a/doc/gitlab-basics/basicsimages/create_file.png +++ b/doc/gitlab-basics/basicsimages/create_file.png diff --git a/doc/gitlab-basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png Binary files differindex 57da898abdc..7ecc3baa990 100644 --- a/doc/gitlab-basics/basicsimages/create_group.png +++ b/doc/gitlab-basics/basicsimages/create_group.png diff --git a/doc/gitlab-basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png Binary files differindex afa68760108..9d3e817d036 100644 --- a/doc/gitlab-basics/basicsimages/edit_file.png +++ b/doc/gitlab-basics/basicsimages/edit_file.png diff --git a/doc/gitlab-basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png Binary files differindex 1def489d16b..e357cb5c6ab 100644 --- a/doc/gitlab-basics/basicsimages/file_located.png +++ b/doc/gitlab-basics/basicsimages/file_located.png diff --git a/doc/gitlab-basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png Binary files differindex 9ac2f1c355f..01639c77d0d 100644 --- a/doc/gitlab-basics/basicsimages/file_name.png +++ b/doc/gitlab-basics/basicsimages/file_name.png diff --git a/doc/gitlab-basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png Binary files differindex 98639149a39..6f26d26ae18 100644 --- a/doc/gitlab-basics/basicsimages/find_file.png +++ b/doc/gitlab-basics/basicsimages/find_file.png diff --git a/doc/gitlab-basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png Binary files differindex 5ac33c7e953..1211510aae9 100644 --- a/doc/gitlab-basics/basicsimages/find_group.png +++ b/doc/gitlab-basics/basicsimages/find_group.png diff --git a/doc/gitlab-basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png Binary files differindex b1f94938613..13ff8345616 100644 --- a/doc/gitlab-basics/basicsimages/fork.png +++ b/doc/gitlab-basics/basicsimages/fork.png diff --git a/doc/gitlab-basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png Binary files differindex e78d84e4d80..2507d6c295b 100644 --- a/doc/gitlab-basics/basicsimages/group_info.png +++ b/doc/gitlab-basics/basicsimages/group_info.png diff --git a/doc/gitlab-basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png Binary files differindex b8104343afa..ef3dca60cc8 100644 --- a/doc/gitlab-basics/basicsimages/groups.png +++ b/doc/gitlab-basics/basicsimages/groups.png diff --git a/doc/gitlab-basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png Binary files differindex 2a31b4cf751..e74dbc13f9a 100644 --- a/doc/gitlab-basics/basicsimages/https.png +++ b/doc/gitlab-basics/basicsimages/https.png diff --git a/doc/gitlab-basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png Binary files differindex 1061d9c5082..7f304b8e1f2 100644 --- a/doc/gitlab-basics/basicsimages/image_file.png +++ b/doc/gitlab-basics/basicsimages/image_file.png diff --git a/doc/gitlab-basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png Binary files differindex 7b69c705392..60a6f7973be 100644 --- a/doc/gitlab-basics/basicsimages/issue_title.png +++ b/doc/gitlab-basics/basicsimages/issue_title.png diff --git a/doc/gitlab-basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png Binary files differindex 9354d05319e..14e9cdb64e1 100644 --- a/doc/gitlab-basics/basicsimages/issues.png +++ b/doc/gitlab-basics/basicsimages/issues.png diff --git a/doc/gitlab-basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png Binary files differindex 321805cda98..04400173ce8 100644 --- a/doc/gitlab-basics/basicsimages/key.png +++ b/doc/gitlab-basics/basicsimages/key.png diff --git a/doc/gitlab-basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png Binary files differindex 7601d40de47..570164df18b 100644 --- a/doc/gitlab-basics/basicsimages/merge_requests.png +++ b/doc/gitlab-basics/basicsimages/merge_requests.png diff --git a/doc/gitlab-basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png Binary files differindex 9120d2b1ab1..842f5ebed74 100644 --- a/doc/gitlab-basics/basicsimages/new_merge_request.png +++ b/doc/gitlab-basics/basicsimages/new_merge_request.png diff --git a/doc/gitlab-basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png Binary files differindex ac255270a66..421e8bc247b 100644 --- a/doc/gitlab-basics/basicsimages/new_project.png +++ b/doc/gitlab-basics/basicsimages/new_project.png diff --git a/doc/gitlab-basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png Binary files differindex da1a6b604ea..d5fcf33c4ea 100644 --- a/doc/gitlab-basics/basicsimages/newbranch.png +++ b/doc/gitlab-basics/basicsimages/newbranch.png diff --git a/doc/gitlab-basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png Binary files differindex 9880ddfead1..578ebee4440 100644 --- a/doc/gitlab-basics/basicsimages/paste_sshkey.png +++ b/doc/gitlab-basics/basicsimages/paste_sshkey.png diff --git a/doc/gitlab-basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png Binary files differindex 5f2e7a7e10c..cb3f79f1879 100644 --- a/doc/gitlab-basics/basicsimages/profile_settings.png +++ b/doc/gitlab-basics/basicsimages/profile_settings.png diff --git a/doc/gitlab-basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png Binary files differindex 6c06ff351fa..e1adb8d48c2 100644 --- a/doc/gitlab-basics/basicsimages/project_info.png +++ b/doc/gitlab-basics/basicsimages/project_info.png diff --git a/doc/gitlab-basics/basicsimages/public_file_link.png b/doc/gitlab-basics/basicsimages/public_file_link.png Binary files differindex 1a60a3d880a..f60df6807f4 100644 --- a/doc/gitlab-basics/basicsimages/public_file_link.png +++ b/doc/gitlab-basics/basicsimages/public_file_link.png diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png Binary files differindex d02c2255ff2..33b978dd899 100644 --- a/doc/gitlab-basics/basicsimages/select-group.png +++ b/doc/gitlab-basics/basicsimages/select-group.png diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png Binary files differindex fd40bce499b..aee22c638db 100644 --- a/doc/gitlab-basics/basicsimages/select-group2.png +++ b/doc/gitlab-basics/basicsimages/select-group2.png diff --git a/doc/gitlab-basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png Binary files differindex 3475b2df576..f72a3ffb57f 100644 --- a/doc/gitlab-basics/basicsimages/select_branch.png +++ b/doc/gitlab-basics/basicsimages/select_branch.png diff --git a/doc/gitlab-basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png Binary files differindex 6d5aa439124..3bb832ea8d0 100644 --- a/doc/gitlab-basics/basicsimages/select_project.png +++ b/doc/gitlab-basics/basicsimages/select_project.png diff --git a/doc/gitlab-basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png Binary files differindex 9bf9c5a0d39..78637013d9b 100644 --- a/doc/gitlab-basics/basicsimages/settings.png +++ b/doc/gitlab-basics/basicsimages/settings.png diff --git a/doc/gitlab-basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png Binary files differindex d7ef4dafe77..c87f11a9d3d 100644 --- a/doc/gitlab-basics/basicsimages/shh_keys.png +++ b/doc/gitlab-basics/basicsimages/shh_keys.png diff --git a/doc/gitlab-basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png Binary files differindex 18944417085..78b854c8903 100644 --- a/doc/gitlab-basics/basicsimages/submit_new_issue.png +++ b/doc/gitlab-basics/basicsimages/submit_new_issue.png diff --git a/doc/gitlab-basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png Binary files differindex e08eb628414..c31d61ec336 100644 --- a/doc/gitlab-basics/basicsimages/title_description_mr.png +++ b/doc/gitlab-basics/basicsimages/title_description_mr.png diff --git a/doc/gitlab-basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png Binary files differindex 6363a09360e..eaa969bdcf4 100644 --- a/doc/gitlab-basics/basicsimages/white_space.png +++ b/doc/gitlab-basics/basicsimages/white_space.png diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index 820934f97f1..1d5e5dd6e15 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -1,41 +1,3 @@ # Custom Git Hooks -**Note: Custom git hooks must be configured on the filesystem of the GitLab -server. Only GitLab server administrators will be able to complete these tasks. -Please explore [webhooks](../web_hooks/web_hooks.md) as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://docs.gitlab.com/ee/git_hooks/git_hooks.html).** - -Git natively supports hooks that are executed on different actions. -Examples of server-side git hooks include pre-receive, post-receive, and update. -See -[Git SCM Server-Side Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) -for more information about each hook type. - -As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab -administrators can add custom git hooks to any GitLab project. - -## Setup - -Normally, git hooks are placed in the repository or project's `hooks` directory. -GitLab creates a symlink from each project's `hooks` directory to the -gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell -upgrades. As such, custom hooks are implemented a little differently. Behavior -is exactly the same once the hook is created, though. Follow these steps to -set up a custom hook. - -1. Pick a project that needs a custom git hook. -1. On the GitLab server, navigate to the project's repository directory. -For an installation from source the path is usually -`/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is -usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`. -1. Create a new directory in this location called `custom_hooks`. -1. Inside the new `custom_hooks` directory, create a file with a name matching -the hook type. For a pre-receive hook the file name should be `pre-receive` with -no extension. -1. Make the hook file executable and make sure it's owned by git. -1. Write the code to make the git hook function as expected. Hooks can be -in any language. Ensure the 'shebang' at the top properly reflects the language -type. For example, if the script is in Ruby the shebang will probably be -`#!/usr/bin/env ruby`. - -That's it! Assuming the hook code is properly implemented the hook will fire -as appropriate. +This document was moved to [administration/custom_hooks.md](../administration/custom_hooks.md). diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index e51ff5a5de2..e8093f0b257 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -36,7 +36,7 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; # Grant the GitLab user necessary permissions on the database - mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; # Quit the database session mysql> \q diff --git a/doc/install/installation.md b/doc/install/installation.md index d9290b1fa76..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 @@ -391,10 +391,14 @@ GitLab Shell is an SSH access and repository management software developed speci ### Install gitlab-workhorse +GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.5 + sudo -u git -H git checkout v0.7.7 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md index a2d7e922aad..8d2c6351fb8 100644 --- a/doc/integration/external-issue-tracker.md +++ b/doc/integration/external-issue-tracker.md @@ -1,7 +1,7 @@ # External issue tracker GitLab has a great issue tracker but you can also use an external one such as -Jira or Redmine. Issue trackers are configurable per GitLab project and allow +Jira, Redmine, or Bugzilla. Issue trackers are configurable per GitLab project and allow you to do the following: - the **Issues** link on the GitLab project pages takes you to the appropriate @@ -20,6 +20,7 @@ Visit the links below for details: - [Redmine](../project_services/redmine.md) - [Jira](../project_services/jira.md) +- [Bugzilla](../project_services/bugzilla.md) ### Service Template diff --git a/doc/integration/github.md b/doc/integration/github.md index e7497e475c9..340c8a55fb3 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -19,7 +19,7 @@ GitHub will generate an application ID and secret key for you to use. - Application name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive. - Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com' - Application description: Fill this in if you wish. - - Default authorization callback URL is '${YOUR_DOMAIN}/import/github/callback' + - Authorization callback URL is 'http(s)://${YOUR_DOMAIN}' 1. Select "Register application". 1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). diff --git a/doc/integration/img/akismet_settings.png b/doc/integration/img/akismet_settings.png Binary files differindex ccdd3adb1c5..c2aa97b132e 100644 --- a/doc/integration/img/akismet_settings.png +++ b/doc/integration/img/akismet_settings.png diff --git a/doc/integration/img/enabled-oauth-sign-in-sources.png b/doc/integration/img/enabled-oauth-sign-in-sources.png Binary files differindex 95f8bbdcd24..b23d6dcc595 100644 --- a/doc/integration/img/enabled-oauth-sign-in-sources.png +++ b/doc/integration/img/enabled-oauth-sign-in-sources.png diff --git a/doc/integration/img/facebook_api_keys.png b/doc/integration/img/facebook_api_keys.png Binary files differindex d6c44ac0f11..995845d5a69 100644 --- a/doc/integration/img/facebook_api_keys.png +++ b/doc/integration/img/facebook_api_keys.png diff --git a/doc/integration/img/facebook_app_settings.png b/doc/integration/img/facebook_app_settings.png Binary files differindex 30dd21e198a..1cd586ecd7c 100644 --- a/doc/integration/img/facebook_app_settings.png +++ b/doc/integration/img/facebook_app_settings.png diff --git a/doc/integration/img/facebook_website_url.png b/doc/integration/img/facebook_website_url.png Binary files differindex dc3088bb2fa..10e1bd5d5a6 100644 --- a/doc/integration/img/facebook_website_url.png +++ b/doc/integration/img/facebook_website_url.png diff --git a/doc/integration/img/github_app.png b/doc/integration/img/github_app.png Binary files differindex d890345ced9..de31242679a 100644 --- a/doc/integration/img/github_app.png +++ b/doc/integration/img/github_app.png diff --git a/doc/integration/img/gitlab_app.png b/doc/integration/img/gitlab_app.png Binary files differindex 3f9391a821b..065316fd3c7 100644 --- a/doc/integration/img/gitlab_app.png +++ b/doc/integration/img/gitlab_app.png diff --git a/doc/integration/img/gmail_action_buttons_for_gitlab.png b/doc/integration/img/gmail_action_buttons_for_gitlab.png Binary files differindex b08f54d137b..a6704139091 100644 --- a/doc/integration/img/gmail_action_buttons_for_gitlab.png +++ b/doc/integration/img/gmail_action_buttons_for_gitlab.png diff --git a/doc/integration/img/google_app.png b/doc/integration/img/google_app.png Binary files differindex 5a62ad35009..08f7f714553 100644 --- a/doc/integration/img/google_app.png +++ b/doc/integration/img/google_app.png diff --git a/doc/integration/img/oauth_provider_admin_application.png b/doc/integration/img/oauth_provider_admin_application.png Binary files differindex a2d8e14c120..fc5f7596fcc 100644 --- a/doc/integration/img/oauth_provider_admin_application.png +++ b/doc/integration/img/oauth_provider_admin_application.png diff --git a/doc/integration/img/oauth_provider_application_form.png b/doc/integration/img/oauth_provider_application_form.png Binary files differindex 3a676b22393..606ab3e3467 100644 --- a/doc/integration/img/oauth_provider_application_form.png +++ b/doc/integration/img/oauth_provider_application_form.png diff --git a/doc/integration/img/oauth_provider_application_id_secret.png b/doc/integration/img/oauth_provider_application_id_secret.png Binary files differindex 6d68df001af..cbedcef8376 100644 --- a/doc/integration/img/oauth_provider_application_id_secret.png +++ b/doc/integration/img/oauth_provider_application_id_secret.png diff --git a/doc/integration/img/oauth_provider_authorized_application.png b/doc/integration/img/oauth_provider_authorized_application.png Binary files differindex efc3b807d71..6a2ea09073c 100644 --- a/doc/integration/img/oauth_provider_authorized_application.png +++ b/doc/integration/img/oauth_provider_authorized_application.png diff --git a/doc/integration/img/oauth_provider_user_wide_applications.png b/doc/integration/img/oauth_provider_user_wide_applications.png Binary files differindex 45ad8a6d468..0c7b095a2dd 100644 --- a/doc/integration/img/oauth_provider_user_wide_applications.png +++ b/doc/integration/img/oauth_provider_user_wide_applications.png diff --git a/doc/integration/img/twitter_app_api_keys.png b/doc/integration/img/twitter_app_api_keys.png Binary files differindex 1076337172a..15b29ac7d16 100644 --- a/doc/integration/img/twitter_app_api_keys.png +++ b/doc/integration/img/twitter_app_api_keys.png diff --git a/doc/integration/img/twitter_app_details.png b/doc/integration/img/twitter_app_details.png Binary files differindex b95e8af8a74..323112a88bb 100644 --- a/doc/integration/img/twitter_app_details.png +++ b/doc/integration/img/twitter_app_details.png 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/doc/integration/shibboleth.md b/doc/integration/shibboleth.md index b6b2d4e5e88..5210ce0de9a 100644 --- a/doc/integration/shibboleth.md +++ b/doc/integration/shibboleth.md @@ -2,7 +2,7 @@ This documentation is for enabling shibboleth with omnibus-gitlab package. -In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however I did not found way to easily configure Nginx that is bundled in omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider. +In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled NIGNX provided in the omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider. To enable the Shibboleth OmniAuth provider you must: diff --git a/doc/markdown/img/logo.png b/doc/markdown/img/logo.png Binary files differindex 7da5f23ed9b..05c8b0d0ccf 100644 --- a/doc/markdown/img/logo.png +++ b/doc/markdown/img/logo.png diff --git a/doc/monitoring/img/health_check_token.png b/doc/monitoring/img/health_check_token.png Binary files differindex 2daf8606b00..2d7c82a65a8 100644 --- a/doc/monitoring/img/health_check_token.png +++ b/doc/monitoring/img/health_check_token.png diff --git a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/monitoring/performance/img/grafana_dashboard_dropdown.png Binary files differindex b4448c7a09f..7e34fad71ce 100644 --- a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png +++ b/doc/monitoring/performance/img/grafana_dashboard_dropdown.png diff --git a/doc/monitoring/performance/img/grafana_dashboard_import.png b/doc/monitoring/performance/img/grafana_dashboard_import.png Binary files differindex 5a2d3c0937a..f97624365c7 100644 --- a/doc/monitoring/performance/img/grafana_dashboard_import.png +++ b/doc/monitoring/performance/img/grafana_dashboard_import.png diff --git a/doc/monitoring/performance/img/grafana_data_source_configuration.png b/doc/monitoring/performance/img/grafana_data_source_configuration.png Binary files differindex 7e2e111f570..7d50e4c88c2 100644 --- a/doc/monitoring/performance/img/grafana_data_source_configuration.png +++ b/doc/monitoring/performance/img/grafana_data_source_configuration.png diff --git a/doc/monitoring/performance/img/grafana_data_source_empty.png b/doc/monitoring/performance/img/grafana_data_source_empty.png Binary files differindex 11e27571e64..aa39a53acae 100644 --- a/doc/monitoring/performance/img/grafana_data_source_empty.png +++ b/doc/monitoring/performance/img/grafana_data_source_empty.png diff --git a/doc/monitoring/performance/img/grafana_save_icon.png b/doc/monitoring/performance/img/grafana_save_icon.png Binary files differindex 3d4265bee8e..c740e33cd1c 100644 --- a/doc/monitoring/performance/img/grafana_save_icon.png +++ b/doc/monitoring/performance/img/grafana_save_icon.png diff --git a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png Binary files differindex 14d82b6ac98..e6ed45a0386 100644 --- a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png +++ b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png 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/doc/profile/2fa.png b/doc/profile/2fa.png Binary files differindex bbf415210d5..bb464efa685 100644 --- a/doc/profile/2fa.png +++ b/doc/profile/2fa.png diff --git a/doc/profile/2fa_auth.png b/doc/profile/2fa_auth.png Binary files differindex 4a4fbe68984..0caaed10805 100644 --- a/doc/profile/2fa_auth.png +++ b/doc/profile/2fa_auth.png diff --git a/doc/project_services/bugzilla.md b/doc/project_services/bugzilla.md new file mode 100644 index 00000000000..215ed6fe9cc --- /dev/null +++ b/doc/project_services/bugzilla.md @@ -0,0 +1,17 @@ +# Bugzilla Service + +Go to your project's **Settings > Services > Bugzilla** and fill in the required +details as described in the table below. + +| Field | Description | +| ----- | ----------- | +| `description` | A name for the issue tracker (to differentiate between instances, for example) | +| `project_url` | The URL to the project in Bugzilla which is being linked to this GitLab project. Note that the `project_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. | +| `issues_url` | The URL to the issue in Bugzilla project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. | +| `new_issue_url` | This is the URL to create a new issue in Bugzilla for the project linked to this GitLab project. Note that the `new_issue_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. | + +Once you have configured and enabled Bugzilla: + +- the **Issues** link on the GitLab project pages takes you to the appropriate + Bugzilla product page +- clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue diff --git a/doc/project_services/img/builds_emails_service.png b/doc/project_services/img/builds_emails_service.png Binary files differindex e604dd73ffa..88943dc410e 100644 --- a/doc/project_services/img/builds_emails_service.png +++ b/doc/project_services/img/builds_emails_service.png diff --git a/doc/project_services/img/jira_add_gitlab_commit_message.png b/doc/project_services/img/jira_add_gitlab_commit_message.png Binary files differindex 85e54861b3e..aec472b9118 100644 --- a/doc/project_services/img/jira_add_gitlab_commit_message.png +++ b/doc/project_services/img/jira_add_gitlab_commit_message.png diff --git a/doc/project_services/img/jira_add_user_to_group.png b/doc/project_services/img/jira_add_user_to_group.png Binary files differindex e4576433889..0ba737bda9a 100644 --- a/doc/project_services/img/jira_add_user_to_group.png +++ b/doc/project_services/img/jira_add_user_to_group.png diff --git a/doc/project_services/img/jira_create_new_group.png b/doc/project_services/img/jira_create_new_group.png Binary files differindex edaa1326058..0609060cb05 100644 --- a/doc/project_services/img/jira_create_new_group.png +++ b/doc/project_services/img/jira_create_new_group.png diff --git a/doc/project_services/img/jira_create_new_group_name.png b/doc/project_services/img/jira_create_new_group_name.png Binary files differindex 9e518ad7843..53d77b17df0 100644 --- a/doc/project_services/img/jira_create_new_group_name.png +++ b/doc/project_services/img/jira_create_new_group_name.png diff --git a/doc/project_services/img/jira_create_new_user.png b/doc/project_services/img/jira_create_new_user.png Binary files differindex 57e433dd818..9eaa444ed25 100644 --- a/doc/project_services/img/jira_create_new_user.png +++ b/doc/project_services/img/jira_create_new_user.png diff --git a/doc/project_services/img/jira_group_access.png b/doc/project_services/img/jira_group_access.png Binary files differindex 47716ca6d0e..8d4657427ae 100644 --- a/doc/project_services/img/jira_group_access.png +++ b/doc/project_services/img/jira_group_access.png diff --git a/doc/project_services/img/jira_issue_closed.png b/doc/project_services/img/jira_issue_closed.png Binary files differindex cabec1ae137..acdd83702d3 100644 --- a/doc/project_services/img/jira_issue_closed.png +++ b/doc/project_services/img/jira_issue_closed.png diff --git a/doc/project_services/img/jira_issue_reference.png b/doc/project_services/img/jira_issue_reference.png Binary files differindex 15739a22dc7..1a2d9f04a6c 100644 --- a/doc/project_services/img/jira_issue_reference.png +++ b/doc/project_services/img/jira_issue_reference.png diff --git a/doc/project_services/img/jira_issues_workflow.png b/doc/project_services/img/jira_issues_workflow.png Binary files differindex 28e17be3a84..0703081d77b 100644 --- a/doc/project_services/img/jira_issues_workflow.png +++ b/doc/project_services/img/jira_issues_workflow.png diff --git a/doc/project_services/img/jira_merge_request_close.png b/doc/project_services/img/jira_merge_request_close.png Binary files differindex 1e78daf105f..47785e3ba27 100644 --- a/doc/project_services/img/jira_merge_request_close.png +++ b/doc/project_services/img/jira_merge_request_close.png diff --git a/doc/project_services/img/jira_project_name.png b/doc/project_services/img/jira_project_name.png Binary files differindex 5986fdb63fb..e785ec6140d 100644 --- a/doc/project_services/img/jira_project_name.png +++ b/doc/project_services/img/jira_project_name.png diff --git a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png Binary files differindex 0149181dc86..fb270d85e3c 100644 --- a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png +++ b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png diff --git a/doc/project_services/img/jira_service.png b/doc/project_services/img/jira_service.png Binary files differindex 1f6628c4371..13aefce6f84 100644 --- a/doc/project_services/img/jira_service.png +++ b/doc/project_services/img/jira_service.png diff --git a/doc/project_services/img/jira_service_close_issue.png b/doc/project_services/img/jira_service_close_issue.png Binary files differindex 67dfc6144c4..eed69e80d2c 100644 --- a/doc/project_services/img/jira_service_close_issue.png +++ b/doc/project_services/img/jira_service_close_issue.png diff --git a/doc/project_services/img/jira_service_page.png b/doc/project_services/img/jira_service_page.png Binary files differindex c225daa81e1..a5b49c501ba 100644 --- a/doc/project_services/img/jira_service_page.png +++ b/doc/project_services/img/jira_service_page.png diff --git a/doc/project_services/img/jira_submit_gitlab_merge_request.png b/doc/project_services/img/jira_submit_gitlab_merge_request.png Binary files differindex e935d9362aa..77630d39d39 100644 --- a/doc/project_services/img/jira_submit_gitlab_merge_request.png +++ b/doc/project_services/img/jira_submit_gitlab_merge_request.png diff --git a/doc/project_services/img/jira_user_management_link.png b/doc/project_services/img/jira_user_management_link.png Binary files differindex 2745916972c..5f002b59bac 100644 --- a/doc/project_services/img/jira_user_management_link.png +++ b/doc/project_services/img/jira_user_management_link.png diff --git a/doc/project_services/img/jira_workflow_screenshot.png b/doc/project_services/img/jira_workflow_screenshot.png Binary files differindex 8635a32eb68..937a50a77d9 100644 --- a/doc/project_services/img/jira_workflow_screenshot.png +++ b/doc/project_services/img/jira_workflow_screenshot.png diff --git a/doc/project_services/img/redmine_configuration.png b/doc/project_services/img/redmine_configuration.png Binary files differindex d14e526ad33..e9d8c0d2da8 100644 --- a/doc/project_services/img/redmine_configuration.png +++ b/doc/project_services/img/redmine_configuration.png diff --git a/doc/project_services/img/services_templates_redmine_example.png b/doc/project_services/img/services_templates_redmine_example.png Binary files differindex 384d057fc8e..77c2b98e5d0 100644 --- a/doc/project_services/img/services_templates_redmine_example.png +++ b/doc/project_services/img/services_templates_redmine_example.png diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index f81a035f70b..e15d5db3253 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -30,6 +30,7 @@ further configuration instructions and details. Contributions are welcome. | [Atlassian Bamboo CI](bamboo.md) | A continuous integration and build server | | Buildkite | Continuous integration and deployments | | [Builds emails](builds_emails.md) | Email the builds status to a list of recipients | +| [Bugzilla](bugzilla.md) | Bugzilla issue tracker | | Campfire | Simple web-based real-time group chat | | Custom Issue Tracker | Custom issue tracker | | Drone CI | Continuous Integration platform built on Docker, written in Go | 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 diff --git a/doc/raketasks/backup_hrz.png b/doc/raketasks/backup_hrz.png Binary files differindex 03e50df1d76..42084717ebe 100644 --- a/doc/raketasks/backup_hrz.png +++ b/doc/raketasks/backup_hrz.png diff --git a/doc/raketasks/check_repos_output.png b/doc/raketasks/check_repos_output.png Binary files differindex 916b1685101..1f632566b00 100644 --- a/doc/raketasks/check_repos_output.png +++ b/doc/raketasks/check_repos_output.png diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 8a38937062e..2b305cb5c99 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -14,7 +14,8 @@ - For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed it in the `/etc/gitlab/gitlab.rb` file. - For installations from source, it is usually located at: `/home/git/repositories` or you can see where -your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry. +your repositories are located by looking at `config/gitlab.yml` under the `repositories => storages` entries +(you'll usually use the `default` storage path to start). New folder needs to have git user ownership and read/write/execute access for git user and its group: diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png Binary files differindex aa51ce030bb..6af5feabb13 100644 --- a/doc/security/img/two_factor_authentication_settings.png +++ b/doc/security/img/two_factor_authentication_settings.png diff --git a/doc/ssh/README.md b/doc/ssh/README.md index a1198e5878f..d6a0979f6ec 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -17,10 +17,6 @@ cat ~/.ssh/id_rsa.pub If you see a long string starting with `ssh-rsa`, you can skip the `ssh-keygen` step. -Note: It is a best practice to use a password for an SSH key, but it is not -required and you can skip creating a password by pressing enter. Note that -the password you choose here can't be altered or retrieved. - To generate a new SSH key, use the following command: ```bash ssh-keygen -t rsa -C "$your_email" @@ -30,6 +26,12 @@ pair and for a password. When prompted for the location and filename, just press enter to use the default. If you use a different name, the key will not be used automatically. +Note: It is a best practice to use a password for an SSH key, but it is not +required and you can skip creating a password by pressing enter. + +If you want to change the password of your key later, you can use the following +command: `ssh-keygen -p <keyname>` + Use the command below to show your public key: **Windows Command Line:** diff --git a/doc/update/8.8-to-8.9.md b/doc/update/8.8-to-8.9.md index f14046bb4be..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` @@ -122,18 +138,31 @@ 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-9-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 -### 8. Start application +### 9. Start application sudo service gitlab start sudo service nginx restart -### 9. Check application status +### 10. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md new file mode 100644 index 00000000000..84065a84e50 --- /dev/null +++ b/doc/update/8.9-to-8.10.md @@ -0,0 +1,191 @@ +# From 8.9 to 8.10 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-10-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-10-stable-ee +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v3.2.0 +``` + +### 5. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +cd /home/git/gitlab-workhorse +sudo -u git -H git fetch --all +sudo -u git -H git checkout v0.7.7 +sudo -u git -H make +``` + +### 6. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +# Login to MySQL +mysql -u root -p + +# Grant the GitLab user the REFERENCES permission on the database +GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Quit the database session +mysql> \q +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +``` + +### 8. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-9-stable:config/gitlab.yml.example origin/8-10-stable:config/gitlab.yml.example +``` + +#### Git configuration + +Disable `git gc --auto` because GitLab runs `git gc` for us already. + +```sh +sudo -u git -H git config --global gc.auto 0 +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-9-stable:lib/support/nginx/gitlab-ssl origin/8-10-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-9-stable:lib/support/nginx/gitlab origin/8-10-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-10-stable/lib/support/init.d/gitlab.default.example#L37 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/v8.9.0/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.9) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.8 to 8.9](8.8-to-8.9.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. diff --git a/doc/user/project/highlighting.md b/doc/user/project/highlighting.md new file mode 100644 index 00000000000..73a2d176b54 --- /dev/null +++ b/doc/user/project/highlighting.md @@ -0,0 +1,31 @@ +[Rouge]: https://rubygems.org/gems/rouge + +# Syntax Highlighting + +GitLab provides syntax highlighting on all files and snippets through the [Rouge][] rubygem. It will try to guess what language to use based on the file extension, which most of the time is sufficient. + +If GitLab is guessing wrong, you can override its choice of language using the `gitlab-language` attribute in `.gitattributes`. For example, if you are working in a Prolog project and using the `.pl` file extension (which would normally be highlighted as Perl), you can add the following to your `.gitattributes` file: + +``` conf +*.pl gitlab-language=prolog +``` + +When you check in and push that change, all `*.pl` files in your project will be highlighted as Prolog. + +The paths here are simply git's builtin [`.gitattributes` interface](https://git-scm.com/docs/gitattributes). So, if you were to invent a file format called a `Nicefile` at the root of your project that used ruby syntax, all you need is: + +``` conf +/Nicefile gitlab-language=ruby +``` + +To disable highlighting entirely, use `gitlab-language=text`. Lots more fun shenanigans are available through CGI options, such as: + +``` conf +# json with erb in it +/my-cool-file gitlab-language=erb?parent=json + +# an entire file of highlighting errors! +/other-file gitlab-language=text?token=Error +``` + +Please note that these configurations will only take effect when the `.gitattributes` file is in your default branch (usually `master`). diff --git a/doc/web_hooks/ssl.png b/doc/web_hooks/ssl.png Binary files differindex 698f1a0f64a..8c4f08d1825 100644 --- a/doc/web_hooks/ssl.png +++ b/doc/web_hooks/ssl.png diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/workflow/add-user/img/access_requests_management.png Binary files differindex e9641cb4f85..5c9b510ba9d 100644 --- a/doc/workflow/add-user/img/access_requests_management.png +++ b/doc/workflow/add-user/img/access_requests_management.png diff --git a/doc/workflow/add-user/img/add_new_user_to_project_settings.png b/doc/workflow/add-user/img/add_new_user_to_project_settings.png Binary files differindex 3da18cdae53..5da0552f9d6 100644 --- a/doc/workflow/add-user/img/add_new_user_to_project_settings.png +++ b/doc/workflow/add-user/img/add_new_user_to_project_settings.png diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/workflow/add-user/img/add_user_email_accept.png Binary files differindex 18aabf93d50..a2954ad7c37 100644 --- a/doc/workflow/add-user/img/add_user_email_accept.png +++ b/doc/workflow/add-user/img/add_user_email_accept.png diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/workflow/add-user/img/add_user_email_ready.png Binary files differindex 385d64330c0..19d91bc0999 100644 --- a/doc/workflow/add-user/img/add_user_email_ready.png +++ b/doc/workflow/add-user/img/add_user_email_ready.png diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/workflow/add-user/img/add_user_email_search.png Binary files differindex 84741edbca4..cb31b77d941 100644 --- a/doc/workflow/add-user/img/add_user_email_search.png +++ b/doc/workflow/add-user/img/add_user_email_search.png diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/workflow/add-user/img/add_user_give_permissions.png Binary files differindex 7e580384e54..e6b77022f06 100644 --- a/doc/workflow/add-user/img/add_user_give_permissions.png +++ b/doc/workflow/add-user/img/add_user_give_permissions.png diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png Binary files differindex 8dbd73a5bc8..1068589c5ff 100644 --- a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png +++ b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/workflow/add-user/img/add_user_imported_members.png Binary files differindex abac1f59c02..5cd120a4245 100644 --- a/doc/workflow/add-user/img/add_user_imported_members.png +++ b/doc/workflow/add-user/img/add_user_imported_members.png diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/workflow/add-user/img/add_user_list_members.png Binary files differindex e17d88c6f5f..5fe3482192e 100644 --- a/doc/workflow/add-user/img/add_user_list_members.png +++ b/doc/workflow/add-user/img/add_user_list_members.png diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/workflow/add-user/img/add_user_members_menu.png Binary files differindex ec5d39f402d..340d15c9830 100644 --- a/doc/workflow/add-user/img/add_user_members_menu.png +++ b/doc/workflow/add-user/img/add_user_members_menu.png diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/workflow/add-user/img/add_user_search_people.png Binary files differindex eaa062376f4..1c05d70ca31 100644 --- a/doc/workflow/add-user/img/add_user_search_people.png +++ b/doc/workflow/add-user/img/add_user_search_people.png diff --git a/doc/workflow/award_emoji.png b/doc/workflow/award_emoji.png Binary files differindex 3408ed95841..481680af80c 100644 --- a/doc/workflow/award_emoji.png +++ b/doc/workflow/award_emoji.png diff --git a/doc/workflow/ci_mr.png b/doc/workflow/ci_mr.png Binary files differindex a577356f8e8..f8a7708643e 100644 --- a/doc/workflow/ci_mr.png +++ b/doc/workflow/ci_mr.png diff --git a/doc/workflow/close_issue_mr.png b/doc/workflow/close_issue_mr.png Binary files differindex a136d642e12..5e520240233 100644 --- a/doc/workflow/close_issue_mr.png +++ b/doc/workflow/close_issue_mr.png diff --git a/doc/workflow/environment_branches.png b/doc/workflow/environment_branches.png Binary files differindex ee893ced13b..13fb0478eaa 100644 --- a/doc/workflow/environment_branches.png +++ b/doc/workflow/environment_branches.png diff --git a/doc/workflow/forking/branch_select.png b/doc/workflow/forking/branch_select.png Binary files differindex 275f64d113b..7f19414f3a9 100644 --- a/doc/workflow/forking/branch_select.png +++ b/doc/workflow/forking/branch_select.png diff --git a/doc/workflow/forking/merge_request.png b/doc/workflow/forking/merge_request.png Binary files differindex 2dc00ed08a1..e2da42a2be7 100644 --- a/doc/workflow/forking/merge_request.png +++ b/doc/workflow/forking/merge_request.png diff --git a/doc/workflow/four_stages.png b/doc/workflow/four_stages.png Binary files differindex 2f444fc6f79..49413087dca 100644 --- a/doc/workflow/four_stages.png +++ b/doc/workflow/four_stages.png diff --git a/doc/workflow/git_pull.png b/doc/workflow/git_pull.png Binary files differindex 7d47064eb14..9a1fdf899bf 100644 --- a/doc/workflow/git_pull.png +++ b/doc/workflow/git_pull.png diff --git a/doc/workflow/gitdashflow.png b/doc/workflow/gitdashflow.png Binary files differindex f2f091dd10b..e456cf9309d 100644 --- a/doc/workflow/gitdashflow.png +++ b/doc/workflow/gitdashflow.png diff --git a/doc/workflow/github_flow.png b/doc/workflow/github_flow.png Binary files differindex 88addb623ee..b3fca97cc2d 100644 --- a/doc/workflow/github_flow.png +++ b/doc/workflow/github_flow.png diff --git a/doc/workflow/gitlab_flow.png b/doc/workflow/gitlab_flow.png Binary files differindex 1ea191a672b..d85d4ff374e 100644 --- a/doc/workflow/gitlab_flow.png +++ b/doc/workflow/gitlab_flow.png diff --git a/doc/workflow/good_commit.png b/doc/workflow/good_commit.png Binary files differindex 3737a026644..7958feea4d9 100644 --- a/doc/workflow/good_commit.png +++ b/doc/workflow/good_commit.png diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md index 1a316e80976..9b50286b179 100644 --- a/doc/workflow/groups.md +++ b/doc/workflow/groups.md @@ -51,7 +51,7 @@ If necessary, you can increase the access level of an individual user for a spec ![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png) -## Request access to a group +## Requesting access to a group As a user, you can request to be a member of a group. Go to the group you'd like to be a member of, and click the **Request Access** button on the right diff --git a/doc/workflow/groups/access_requests_management.png b/doc/workflow/groups/access_requests_management.png Binary files differindex ffede8e9bd6..5202434f00f 100644 --- a/doc/workflow/groups/access_requests_management.png +++ b/doc/workflow/groups/access_requests_management.png diff --git a/doc/workflow/groups/add_member_to_group.png b/doc/workflow/groups/add_member_to_group.png Binary files differindex fa340ce572f..6e3f660d2e4 100644 --- a/doc/workflow/groups/add_member_to_group.png +++ b/doc/workflow/groups/add_member_to_group.png diff --git a/doc/workflow/groups/group_dashboard.png b/doc/workflow/groups/group_dashboard.png Binary files differindex 7fc9048d74d..662c932e536 100644 --- a/doc/workflow/groups/group_dashboard.png +++ b/doc/workflow/groups/group_dashboard.png diff --git a/doc/workflow/groups/group_with_two_projects.png b/doc/workflow/groups/group_with_two_projects.png Binary files differindex 87242781e4f..dc3475949f5 100644 --- a/doc/workflow/groups/group_with_two_projects.png +++ b/doc/workflow/groups/group_with_two_projects.png diff --git a/doc/workflow/groups/max_access_level.png b/doc/workflow/groups/max_access_level.png Binary files differindex 71106a8a5a0..2855a514013 100644 --- a/doc/workflow/groups/max_access_level.png +++ b/doc/workflow/groups/max_access_level.png diff --git a/doc/workflow/groups/new_group_button.png b/doc/workflow/groups/new_group_button.png Binary files differindex 51e82798658..26136312c8f 100644 --- a/doc/workflow/groups/new_group_button.png +++ b/doc/workflow/groups/new_group_button.png diff --git a/doc/workflow/groups/new_group_form.png b/doc/workflow/groups/new_group_form.png Binary files differindex bf992c40bc2..dc50a069ef2 100644 --- a/doc/workflow/groups/new_group_form.png +++ b/doc/workflow/groups/new_group_form.png diff --git a/doc/workflow/groups/other_group_sees_shared_project.png b/doc/workflow/groups/other_group_sees_shared_project.png Binary files differindex cbf2c3c1fdc..2230720cecd 100644 --- a/doc/workflow/groups/other_group_sees_shared_project.png +++ b/doc/workflow/groups/other_group_sees_shared_project.png diff --git a/doc/workflow/groups/override_access_level.png b/doc/workflow/groups/override_access_level.png Binary files differindex f4225a63679..9d6aaf4c363 100644 --- a/doc/workflow/groups/override_access_level.png +++ b/doc/workflow/groups/override_access_level.png diff --git a/doc/workflow/groups/project_members_via_group.png b/doc/workflow/groups/project_members_via_group.png Binary files differindex b13cb1cfd95..58270936a0b 100644 --- a/doc/workflow/groups/project_members_via_group.png +++ b/doc/workflow/groups/project_members_via_group.png diff --git a/doc/workflow/groups/request_access_button.png b/doc/workflow/groups/request_access_button.png Binary files differindex ff0ac8747a7..0eec5cb937d 100644 --- a/doc/workflow/groups/request_access_button.png +++ b/doc/workflow/groups/request_access_button.png diff --git a/doc/workflow/groups/share_project_with_groups.png b/doc/workflow/groups/share_project_with_groups.png Binary files differindex a5dbc89fe90..5772d4deced 100644 --- a/doc/workflow/groups/share_project_with_groups.png +++ b/doc/workflow/groups/share_project_with_groups.png diff --git a/doc/workflow/groups/transfer_project.png b/doc/workflow/groups/transfer_project.png Binary files differindex 044fe10d073..0aef3ab3f0f 100644 --- a/doc/workflow/groups/transfer_project.png +++ b/doc/workflow/groups/transfer_project.png diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/workflow/groups/withdraw_access_request_button.png Binary files differindex 99d7a326ed8..b7de830a780 100644 --- a/doc/workflow/groups/withdraw_access_request_button.png +++ b/doc/workflow/groups/withdraw_access_request_button.png diff --git a/doc/workflow/img/award_emoji_select.png b/doc/workflow/img/award_emoji_select.png Binary files differindex fffdfedda5d..ad664c0aeff 100644 --- a/doc/workflow/img/award_emoji_select.png +++ b/doc/workflow/img/award_emoji_select.png diff --git a/doc/workflow/img/award_emoji_votes_least_popular.png b/doc/workflow/img/award_emoji_votes_least_popular.png Binary files differindex 2ef5be7154f..57d595d9602 100644 --- a/doc/workflow/img/award_emoji_votes_least_popular.png +++ b/doc/workflow/img/award_emoji_votes_least_popular.png diff --git a/doc/workflow/img/award_emoji_votes_most_popular.png b/doc/workflow/img/award_emoji_votes_most_popular.png Binary files differindex 5b089730d93..432bd09b8a7 100644 --- a/doc/workflow/img/award_emoji_votes_most_popular.png +++ b/doc/workflow/img/award_emoji_votes_most_popular.png diff --git a/doc/workflow/img/award_emoji_votes_sort_options.png b/doc/workflow/img/award_emoji_votes_sort_options.png Binary files differindex 9bbf3f82a0b..ae6e224b317 100644 --- a/doc/workflow/img/award_emoji_votes_sort_options.png +++ b/doc/workflow/img/award_emoji_votes_sort_options.png diff --git a/doc/workflow/img/cherry_pick_changes_commit.png b/doc/workflow/img/cherry_pick_changes_commit.png Binary files differindex ae91d2cae53..7fb68cc9e9b 100644 --- a/doc/workflow/img/cherry_pick_changes_commit.png +++ b/doc/workflow/img/cherry_pick_changes_commit.png diff --git a/doc/workflow/img/cherry_pick_changes_commit_modal.png b/doc/workflow/img/cherry_pick_changes_commit_modal.png Binary files differindex f502f87677a..5267e04562f 100644 --- a/doc/workflow/img/cherry_pick_changes_commit_modal.png +++ b/doc/workflow/img/cherry_pick_changes_commit_modal.png diff --git a/doc/workflow/img/cherry_pick_changes_mr.png b/doc/workflow/img/cherry_pick_changes_mr.png Binary files differindex 59c610e620b..975fb13e463 100644 --- a/doc/workflow/img/cherry_pick_changes_mr.png +++ b/doc/workflow/img/cherry_pick_changes_mr.png diff --git a/doc/workflow/img/cherry_pick_changes_mr_modal.png b/doc/workflow/img/cherry_pick_changes_mr_modal.png Binary files differindex 96a80f4726d..6c003bacbe3 100644 --- a/doc/workflow/img/cherry_pick_changes_mr_modal.png +++ b/doc/workflow/img/cherry_pick_changes_mr_modal.png diff --git a/doc/workflow/img/file_finder_find_button.png b/doc/workflow/img/file_finder_find_button.png Binary files differindex c5005d0d7ca..96e383f0213 100644 --- a/doc/workflow/img/file_finder_find_button.png +++ b/doc/workflow/img/file_finder_find_button.png diff --git a/doc/workflow/img/file_finder_find_file.png b/doc/workflow/img/file_finder_find_file.png Binary files differindex 58500f4c163..c6508514c76 100644 --- a/doc/workflow/img/file_finder_find_file.png +++ b/doc/workflow/img/file_finder_find_file.png diff --git a/doc/workflow/img/forking_workflow_choose_namespace.png b/doc/workflow/img/forking_workflow_choose_namespace.png Binary files differindex eefe5769554..1839d5e8be2 100644 --- a/doc/workflow/img/forking_workflow_choose_namespace.png +++ b/doc/workflow/img/forking_workflow_choose_namespace.png diff --git a/doc/workflow/img/forking_workflow_fork_button.png b/doc/workflow/img/forking_workflow_fork_button.png Binary files differindex 49e68d33e89..cc79d6fd40c 100644 --- a/doc/workflow/img/forking_workflow_fork_button.png +++ b/doc/workflow/img/forking_workflow_fork_button.png diff --git a/doc/workflow/img/forking_workflow_path_taken_error.png b/doc/workflow/img/forking_workflow_path_taken_error.png Binary files differindex 7a3139506fe..a859155aef0 100644 --- a/doc/workflow/img/forking_workflow_path_taken_error.png +++ b/doc/workflow/img/forking_workflow_path_taken_error.png diff --git a/doc/workflow/img/new_branch_from_issue.png b/doc/workflow/img/new_branch_from_issue.png Binary files differindex 394c139e17e..61acdd30ae9 100644 --- a/doc/workflow/img/new_branch_from_issue.png +++ b/doc/workflow/img/new_branch_from_issue.png diff --git a/doc/workflow/img/revert_changes_commit.png b/doc/workflow/img/revert_changes_commit.png Binary files differindex d84211e20db..e7194fc3504 100644 --- a/doc/workflow/img/revert_changes_commit.png +++ b/doc/workflow/img/revert_changes_commit.png diff --git a/doc/workflow/img/revert_changes_commit_modal.png b/doc/workflow/img/revert_changes_commit_modal.png Binary files differindex e94d151a2af..c660ec7eaec 100644 --- a/doc/workflow/img/revert_changes_commit_modal.png +++ b/doc/workflow/img/revert_changes_commit_modal.png diff --git a/doc/workflow/img/revert_changes_mr.png b/doc/workflow/img/revert_changes_mr.png Binary files differindex 7adad88463b..3002f0ac1c5 100644 --- a/doc/workflow/img/revert_changes_mr.png +++ b/doc/workflow/img/revert_changes_mr.png diff --git a/doc/workflow/img/revert_changes_mr_modal.png b/doc/workflow/img/revert_changes_mr_modal.png Binary files differindex 9da78f84828..c6aaeecc8a6 100644 --- a/doc/workflow/img/revert_changes_mr_modal.png +++ b/doc/workflow/img/revert_changes_mr_modal.png diff --git a/doc/workflow/img/todos_icon.png b/doc/workflow/img/todos_icon.png Binary files differindex a63bad0c258..bba77f88913 100644 --- a/doc/workflow/img/todos_icon.png +++ b/doc/workflow/img/todos_icon.png diff --git a/doc/workflow/img/todos_index.png b/doc/workflow/img/todos_index.png Binary files differindex 4ee18dd1285..f1438ef7355 100644 --- a/doc/workflow/img/todos_index.png +++ b/doc/workflow/img/todos_index.png diff --git a/doc/workflow/img/web_editor_new_branch_dropdown.png b/doc/workflow/img/web_editor_new_branch_dropdown.png Binary files differindex 009e4b05adf..a8e635d2faf 100644 --- a/doc/workflow/img/web_editor_new_branch_dropdown.png +++ b/doc/workflow/img/web_editor_new_branch_dropdown.png diff --git a/doc/workflow/img/web_editor_new_branch_page.png b/doc/workflow/img/web_editor_new_branch_page.png Binary files differindex dd6cfc6e7bb..7f36b7faf63 100644 --- a/doc/workflow/img/web_editor_new_branch_page.png +++ b/doc/workflow/img/web_editor_new_branch_page.png diff --git a/doc/workflow/img/web_editor_new_directory_dialog.png b/doc/workflow/img/web_editor_new_directory_dialog.png Binary files differindex 2c76f84f395..d16e3c67116 100644 --- a/doc/workflow/img/web_editor_new_directory_dialog.png +++ b/doc/workflow/img/web_editor_new_directory_dialog.png diff --git a/doc/workflow/img/web_editor_new_directory_dropdown.png b/doc/workflow/img/web_editor_new_directory_dropdown.png Binary files differindex cedf46aedfd..c8d77b16ee8 100644 --- a/doc/workflow/img/web_editor_new_directory_dropdown.png +++ b/doc/workflow/img/web_editor_new_directory_dropdown.png diff --git a/doc/workflow/img/web_editor_new_file_dropdown.png b/doc/workflow/img/web_editor_new_file_dropdown.png Binary files differindex 6e884f6504d..3fcb91c9b93 100644 --- a/doc/workflow/img/web_editor_new_file_dropdown.png +++ b/doc/workflow/img/web_editor_new_file_dropdown.png diff --git a/doc/workflow/img/web_editor_new_file_editor.png b/doc/workflow/img/web_editor_new_file_editor.png Binary files differindex c76473bcfa7..21c340b9288 100644 --- a/doc/workflow/img/web_editor_new_file_editor.png +++ b/doc/workflow/img/web_editor_new_file_editor.png diff --git a/doc/workflow/img/web_editor_new_push_widget.png b/doc/workflow/img/web_editor_new_push_widget.png Binary files differindex a2108735741..c7738a4c930 100644 --- a/doc/workflow/img/web_editor_new_push_widget.png +++ b/doc/workflow/img/web_editor_new_push_widget.png diff --git a/doc/workflow/img/web_editor_new_tag_dropdown.png b/doc/workflow/img/web_editor_new_tag_dropdown.png Binary files differindex 263dd635b95..ac7415009b3 100644 --- a/doc/workflow/img/web_editor_new_tag_dropdown.png +++ b/doc/workflow/img/web_editor_new_tag_dropdown.png diff --git a/doc/workflow/img/web_editor_new_tag_page.png b/doc/workflow/img/web_editor_new_tag_page.png Binary files differindex 64d7cd11ed1..231e1a13fc0 100644 --- a/doc/workflow/img/web_editor_new_tag_page.png +++ b/doc/workflow/img/web_editor_new_tag_page.png diff --git a/doc/workflow/img/web_editor_start_new_merge_request.png b/doc/workflow/img/web_editor_start_new_merge_request.png Binary files differindex be12a151cac..2755501dfd1 100644 --- a/doc/workflow/img/web_editor_start_new_merge_request.png +++ b/doc/workflow/img/web_editor_start_new_merge_request.png diff --git a/doc/workflow/img/web_editor_upload_file_dialog.png b/doc/workflow/img/web_editor_upload_file_dialog.png Binary files differindex 6dd2207bca0..9d6d8250bbe 100644 --- a/doc/workflow/img/web_editor_upload_file_dialog.png +++ b/doc/workflow/img/web_editor_upload_file_dialog.png diff --git a/doc/workflow/img/web_editor_upload_file_dropdown.png b/doc/workflow/img/web_editor_upload_file_dropdown.png Binary files differindex bf6528701b0..6b5205b05ec 100644 --- a/doc/workflow/img/web_editor_upload_file_dropdown.png +++ b/doc/workflow/img/web_editor_upload_file_dropdown.png diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png Binary files differindex 0e08703f421..1a5661de75d 100644 --- a/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png +++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png Binary files differindex 205c515bd3f..fd7a4d3fabf 100644 --- a/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png +++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png Binary files differindex a1e348d46ad..fd1ba6f5884 100644 --- a/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png +++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png Binary files differindex ed362846909..186c1563951 100644 --- a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png +++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png Binary files differindex d2fbd0267bd..2f84d3232f2 100644 --- a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png +++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png Binary files differindex b1cc4b58525..652ca20b9ab 100644 --- a/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png +++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png diff --git a/doc/workflow/importing/gitlab_importer/importer.png b/doc/workflow/importing/gitlab_importer/importer.png Binary files differindex d2a286d8cac..35a7ddc8318 100644 --- a/doc/workflow/importing/gitlab_importer/importer.png +++ b/doc/workflow/importing/gitlab_importer/importer.png diff --git a/doc/workflow/importing/gitlab_importer/new_project_page.png b/doc/workflow/importing/gitlab_importer/new_project_page.png Binary files differindex 5e239208e1e..81074d2d016 100644 --- a/doc/workflow/importing/gitlab_importer/new_project_page.png +++ b/doc/workflow/importing/gitlab_importer/new_project_page.png diff --git a/doc/workflow/importing/img/import_projects_from_github_importer.png b/doc/workflow/importing/img/import_projects_from_github_importer.png Binary files differindex f744dc06f81..b6ed8dd692a 100644 --- a/doc/workflow/importing/img/import_projects_from_github_importer.png +++ b/doc/workflow/importing/img/import_projects_from_github_importer.png diff --git a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png Binary files differindex 86be35acb37..c8f35a50f48 100644 --- a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png +++ b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index a7dfac2c120..a2b2a4b88f9 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -1,8 +1,10 @@ # Import your project from GitHub to GitLab
>**Note:**
-In order to enable the GitHub import setting, you should first
-enable the [GitHub integration][gh-import] in your GitLab instance.
+In order to enable the GitHub import setting, you may also want to
+enable the [GitHub integration][gh-import] in your GitLab instance. This
+configuration is optional, you will be able import your GitHub repositories
+with a Personal Access Token.
At its current state, GitHub importer can import:
@@ -20,9 +22,15 @@ It is not yet possible to import your cross-repository pull requests (those from forks). We are working on improving this in the near future.
The importer page is visible when you [create a new project][new-project].
-Click on the **GitHub** link and you will be redirected to GitHub for
-permission to access your projects. After accepting, you'll be automatically
-redirected to the importer.
+Click on the **GitHub** link and, if you are logged in via the GitHub
+integration, you will be redirected to GitHub for permission to access your
+projects. After accepting, you'll be automatically redirected to the importer.
+
+If you are not using the GitHub integration, you can still perform a one-off
+authorization with GitHub to access your projects.
+
+Alternatively, you can also enter a GitHub Personal Access Token. Once you enter
+your token, you'll be taken to the importer.
![New project page on GitLab](img/import_projects_from_github_new_project_page.png)
diff --git a/doc/workflow/merge_commits.png b/doc/workflow/merge_commits.png Binary files differindex 757b589d0db..8aa1587cde6 100644 --- a/doc/workflow/merge_commits.png +++ b/doc/workflow/merge_commits.png diff --git a/doc/workflow/merge_request.png b/doc/workflow/merge_request.png Binary files differindex fde3ff5c854..6aad1d82f6e 100644 --- a/doc/workflow/merge_request.png +++ b/doc/workflow/merge_request.png diff --git a/doc/workflow/merge_requests/commit_compare.png b/doc/workflow/merge_requests/commit_compare.png Binary files differindex dfd7ee220f0..0e4a2b23c04 100644 --- a/doc/workflow/merge_requests/commit_compare.png +++ b/doc/workflow/merge_requests/commit_compare.png diff --git a/doc/workflow/merge_requests/merge_request_diff.png b/doc/workflow/merge_requests/merge_request_diff.png Binary files differindex f368423c746..3ebbfb75ea3 100644 --- a/doc/workflow/merge_requests/merge_request_diff.png +++ b/doc/workflow/merge_requests/merge_request_diff.png diff --git a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png Binary files differindex b2d03bb66f9..a0db535019c 100644 --- a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png +++ b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png diff --git a/doc/workflow/merge_when_build_succeeds/enable.png b/doc/workflow/merge_when_build_succeeds/enable.png Binary files differindex 633efa1246f..b86e6d7b3fd 100644 --- a/doc/workflow/merge_when_build_succeeds/enable.png +++ b/doc/workflow/merge_when_build_succeeds/enable.png diff --git a/doc/workflow/merge_when_build_succeeds/status.png b/doc/workflow/merge_when_build_succeeds/status.png Binary files differindex c856c7d14dc..f3ea61d8147 100644 --- a/doc/workflow/merge_when_build_succeeds/status.png +++ b/doc/workflow/merge_when_build_succeeds/status.png diff --git a/doc/workflow/messy_flow.png b/doc/workflow/messy_flow.png Binary files differindex 1addb95ca54..8d2c0dae8c2 100644 --- a/doc/workflow/messy_flow.png +++ b/doc/workflow/messy_flow.png diff --git a/doc/workflow/milestones/form.png b/doc/workflow/milestones/form.png Binary files differindex de44c1ffc1a..3965ca4d083 100644 --- a/doc/workflow/milestones/form.png +++ b/doc/workflow/milestones/form.png diff --git a/doc/workflow/milestones/group_form.png b/doc/workflow/milestones/group_form.png Binary files differindex 38862dcca68..ff20df8081f 100644 --- a/doc/workflow/milestones/group_form.png +++ b/doc/workflow/milestones/group_form.png diff --git a/doc/workflow/mr_inline_comments.png b/doc/workflow/mr_inline_comments.png Binary files differindex e851b95bcef..af7df3100d0 100644 --- a/doc/workflow/mr_inline_comments.png +++ b/doc/workflow/mr_inline_comments.png diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index fe4485e148a..b4a9c2f3d3e 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -37,12 +37,14 @@ This means that you can set a different level of notifications per group while s to have a finer level setting per project. Organization like this is suitable for users that belong to different groups but don't have the same need for being notified for every group they are member of. +These settings can be configured on group page or user profile notifications dropdown. #### Project Settings Project Settings are at the top level and any setting placed at this level will take precedence of any other setting. This is suitable for users that have different needs for notifications per project basis. +These settings can be configured on project page or user profile notifications dropdown. ## Notification events diff --git a/doc/workflow/notifications/settings.png b/doc/workflow/notifications/settings.png Binary files differindex 7c6857aad1a..d50757beffc 100644 --- a/doc/workflow/notifications/settings.png +++ b/doc/workflow/notifications/settings.png diff --git a/doc/workflow/production_branch.png b/doc/workflow/production_branch.png Binary files differindex 33fb26dd621..d88a3687151 100644 --- a/doc/workflow/production_branch.png +++ b/doc/workflow/production_branch.png 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 Binary files differindex 5c2a3de5f70..c00443803de 100644 --- a/doc/workflow/protected_branches/protected_branches1.png +++ b/doc/workflow/protected_branches/protected_branches1.png diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png Binary files differindex 2dca3541365..a4f664d3b21 100644 --- a/doc/workflow/protected_branches/protected_branches2.png +++ b/doc/workflow/protected_branches/protected_branches2.png diff --git a/doc/workflow/protected_branches/protected_branches3.png b/doc/workflow/protected_branches/protected_branches3.png Binary files differnew file mode 100644 index 00000000000..2a50cb174bb --- /dev/null +++ b/doc/workflow/protected_branches/protected_branches3.png diff --git a/doc/workflow/rebase.png b/doc/workflow/rebase.png Binary files differindex ef82c834755..df353311fa0 100644 --- a/doc/workflow/rebase.png +++ b/doc/workflow/rebase.png diff --git a/doc/workflow/release_branches.png b/doc/workflow/release_branches.png Binary files differindex da7ae53413a..c2162248d25 100644 --- a/doc/workflow/release_branches.png +++ b/doc/workflow/release_branches.png diff --git a/doc/workflow/releases/new_tag.png b/doc/workflow/releases/new_tag.png Binary files differindex e2b64bfe17f..2456a8500f4 100644 --- a/doc/workflow/releases/new_tag.png +++ b/doc/workflow/releases/new_tag.png diff --git a/doc/workflow/releases/tags.png b/doc/workflow/releases/tags.png Binary files differindex aca91906c68..eeda967afd6 100644 --- a/doc/workflow/releases/tags.png +++ b/doc/workflow/releases/tags.png diff --git a/doc/workflow/remove_checkbox.png b/doc/workflow/remove_checkbox.png Binary files differindex 3e247d38155..3b0393deb0f 100644 --- a/doc/workflow/remove_checkbox.png +++ b/doc/workflow/remove_checkbox.png diff --git a/doc/workflow/share_with_group.png b/doc/workflow/share_with_group.png Binary files differindex a0ca6f14552..2c47625e29a 100644 --- a/doc/workflow/share_with_group.png +++ b/doc/workflow/share_with_group.png diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png Binary files differindex 16be0413b64..a9b1c4b4dcc 100644 --- a/doc/workflow/shortcuts.png +++ b/doc/workflow/shortcuts.png diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png Binary files differindex 4791e5de972..89c458aa8d9 100644 --- a/doc/workflow/wip_merge_requests/blocked_accept_button.png +++ b/doc/workflow/wip_merge_requests/blocked_accept_button.png diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png Binary files differindex 8fa83a201ac..9c37354a653 100644 --- a/doc/workflow/wip_merge_requests/mark_as_wip.png +++ b/doc/workflow/wip_merge_requests/mark_as_wip.png diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png Binary files differindex d45e68f31c5..31f7326beb0 100644 --- a/doc/workflow/wip_merge_requests/unmark_as_wip.png +++ b/doc/workflow/wip_merge_requests/unmark_as_wip.png diff --git a/features/admin/projects.feature b/features/admin/projects.feature index c5ee80136c8..8929bcf8d80 100644 --- a/features/admin/projects.feature +++ b/features/admin/projects.feature @@ -10,10 +10,11 @@ Feature: Admin Projects Then I should see all non-archived projects And I should not see project "Archive" + @javascript Scenario: I should see all projects in the list Given archived project "Archive" When I visit admin projects page - And I check "Show archived projects" + And I select "Show archived projects" Then I should see all projects And I should see "archived" label @@ -22,6 +23,7 @@ Feature: Admin Projects And I click on first project Then I should see project details + @javascript Scenario: Transfer project Given group 'Web' And I visit admin project page diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index db73309804c..1f4c9020731 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -7,6 +7,7 @@ Feature: Dashboard And project "Shop" has CI enabled And project "Shop" has CI build And project "Shop" has labels: "bug", "feature", "enhancement" + And project "Shop" has issue: "bug report" And I visit dashboard page Scenario: I should see projects list diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature index 56b4a639c01..8ddafb6a7ac 100644 --- a/features/dashboard/new_project.feature +++ b/features/dashboard/new_project.feature @@ -21,7 +21,7 @@ Background: Scenario: I should see instructions on how to import from GitHub Given I see "New Project" page When I click on "Import project from GitHub" - Then I see instructions on how to import from GitHub + Then I am redirected to the GitHub import page @javascript Scenario: I should see Google Code import page 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/project/merge_requests.feature b/features/project/merge_requests.feature index 0e97e4d5954..21768c15c17 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -89,13 +89,6 @@ Feature: Project Merge Requests Then The list should be sorted by "Oldest updated" @javascript - Scenario: Visiting Issues after being sorted the list - Given I visit project "Shop" merge requests page - And I sort the list by "Oldest updated" - And I visit project "Shop" issues page - Then The list should be sorted by "Oldest updated" - - @javascript Scenario: Visiting Merge Requests from a differente Project after sorting Given I visit project "Shop" merge requests page And I sort the list by "Oldest updated" diff --git a/features/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/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 8613dc537cc..0c89a3db9ad 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -62,7 +62,8 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps step 'I should see "johndoe@gitlab.com" in team list in every project as "Reporter"' do page.within ".group-users-list" do - expect(page).to have_content "johndoe@gitlab.com – Invited by" + expect(page).to have_content "johndoe@gitlab.com" + expect(page).to have_content "Invited by" expect(page).to have_content "Reporter" end end diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb index a7a28755a6c..d77945a6b9c 100644 --- a/features/steps/admin/projects.rb +++ b/features/steps/admin/projects.rb @@ -18,9 +18,9 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps end end - step 'I check "Show archived projects"' do - page.check 'Show archived projects' - click_button "Search" + step 'I select "Show archived projects"' do + find(:css, '#sort-projects-dropdown').click + click_link 'Show archived projects' end step 'I should see "archived" label' do @@ -45,7 +45,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps step 'I transfer project to group \'Web\'' do allow_any_instance_of(Projects::TransferService). to receive(:move_uploads_to_new_namespace).and_return(true) - find(:xpath, "//input[@id='new_namespace_id']").set group.id + click_button 'Search for Namespace' + click_link 'group: web' click_button 'Transfer' end diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 29e6b9f1a01..727a6a71373 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -10,7 +10,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps end step 'I see "New Project" page' do - expect(page).to have_content('Project owner') + expect(page).to have_content('Project path') expect(page).to have_content('Project name') end @@ -28,14 +28,8 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps first('.import_github').click end - step 'I see instructions on how to import from GitHub' do - github_modal = first('.modal-body') - expect(github_modal).to be_visible - expect(github_modal).to have_content "To enable importing projects from GitHub" - - page.all('.modal-body').each do |element| - expect(element).not_to be_visible unless element == github_modal - end + step 'I am redirected to the GitHub import page' do + expect(current_path).to eq new_import_github_path end step 'I click on "Repo by URL"' do @@ -55,5 +49,4 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps step 'I redirected to Google Code import page' do expect(current_path).to eq new_import_google_code_path end - end diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb index cb6fa8a47da..2b4a5ab0864 100644 --- a/features/steps/explore/projects.rb +++ b/features/steps/explore/projects.rb @@ -69,7 +69,6 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps visit namespace_project_issues_path(public_project.namespace, public_project) end - step 'I should see list of issues for "Community" project' do expect(page).to have_content "Bug" expect(page).to have_content public_project.name @@ -88,7 +87,6 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps visit namespace_project_issues_path(internal_project.namespace, internal_project) end - step 'I should see list of issues for "Internal" project' do expect(page).to have_content "Internal Bug" expect(page).to have_content internal_project.name @@ -137,7 +135,6 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps @public_project ||= Project.find_by!(name: 'Community') end - def internal_merge_request @internal_merge_request ||= MergeRequest.find_by!(title: 'Feature implemented') end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 483370f41c6..4fa7d7c6567 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -93,7 +93,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'I should see new group "Owned" avatar' do expect(owned_group.avatar).to be_instance_of AvatarUploader - expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{Group.find_by(name:"Owned").id}/banana_sample.gif" + expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif" end step 'I should see the "Remove avatar" button' do diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 9e5602dacf1..4ee6784a086 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -155,8 +155,11 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I click on my profile picture' do - find(:css, '.side-nav-toggle').click - find(:css, '.sidebar-user').click + find(:css, '.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_link "Profile" + end end step 'I should see my user page' do diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb index db1387763d5..b6f1d417e21 100644 --- a/features/steps/project/archived.rb +++ b/features/steps/project/archived.rb @@ -33,5 +33,4 @@ class Spinach::Features::ProjectArchived < Spinach::FeatureSteps When 'I set project unarchived' do click_link "Unarchive" end - end diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 0ead83d6937..6b56a77b832 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -153,6 +153,6 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps # Verify a link is generated against the correct project def verify_commit_link(container_div, container_project) # This should force a wait for the javascript to execute - expect(find(:div,container_div).find(".commit_short_id")['href']).to have_content "#{container_project.path_with_namespace}/commit" + expect(find(:div, container_div).find(".commit_short_id")['href']).to have_content "#{container_project.path_with_namespace}/commit" end end diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 1b14659b4df..1498f899cf5 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -81,9 +81,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I search "hand"' do - page.within('.emoji-menu-content') do - fill_in 'emoji_search', with: 'hand' - end + fill_in 'emoji_search', with: 'hand' end step 'I see search result for "hand"' do diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index 439363e6f14..35f166c7c08 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -124,14 +124,12 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'project "Shop" has milestone "v2.2"' do - milestone = create(:milestone, title: "v2.2", project: project) 3.times { create(:issue, project: project, milestone: milestone) } end step 'project "Shop" has milestone "v3.0"' do - milestone = create(:milestone, title: "v3.0", project: project) 3.times { create(:issue, project: project, milestone: milestone) } @@ -147,7 +145,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end When 'I select first assignee from "Shop" project' do - first_assignee = project.users.first select first_assignee.name, from: "assignee_id" end @@ -160,7 +157,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'project "Shop" have "Release 0.4" open issue' do - create(:issue, title: "Release 0.4", project: project, @@ -360,5 +356,4 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps def filter_issue(text) fill_in 'issue_search', with: text end - end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 640f1720a6c..da848afd48e 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -272,10 +272,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do mr = MergeRequest.find_by(title: "Bug NS-05") - create(:note_on_merge_request_diff, project: project, + create(:diff_note_on_merge_request, project: project, noteable: mr, author: user_exists("John Doe"), - line_code: sample_commit.line_code, note: 'Line is wrong') end @@ -519,7 +518,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step '"Bug NS-05" has CI status' do project = merge_request.source_project project.enable_ci - pipeline = create :ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch + pipeline = create :ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch create :ci_build, pipeline: pipeline end diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb index 90771847909..b8da5e6435d 100644 --- a/features/steps/project/project_find_file.rb +++ b/features/steps/project/project_find_file.rb @@ -66,7 +66,6 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps expect(page).not_to have_content(".gitignore") end - def find_file(text) fill_in 'file_find', with: text end 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/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index c6572cf386e..b5fd24d246f 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -189,5 +189,4 @@ module SharedIssuable expect(page).to have_content content end end - end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index b3411c03118..0b4920883b8 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -223,6 +223,11 @@ module SharedProject create(:label, project: project, title: 'enhancement') end + step 'project "Shop" has issue: "bug report"' do + project = Project.find_by(name: "Shop") + create(:issue, project: project, title: "bug report") + end + step 'project "Shop" has CI enabled' do project = Project.find_by(name: "Shop") project.enable_ci diff --git a/features/steps/snippet_search.rb b/features/steps/snippet_search.rb index cf999879579..32e29ffad1e 100644 --- a/features/steps/snippet_search.rb +++ b/features/steps/snippet_search.rb @@ -52,5 +52,4 @@ class Spinach::Features::SnippetSearch < Spinach::FeatureSteps step 'I should not see "Personal snippet private" in results' do expect(page).not_to have_content 'Personal snippet private' end - end diff --git a/features/support/env.rb b/features/support/env.rb index edc08cf0986..ab3f0ca7aeb 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -2,11 +2,6 @@ if ENV['SIMPLECOV'] require 'simplecov' end -if ENV['COVERALLS'] - require 'coveralls' - Coveralls.wear_merged! -end - ENV['RAILS_ENV'] = 'test' require './config/environment' require 'rspec/expectations' diff --git a/fixtures/emojis/digests.json b/fixtures/emojis/digests.json index 41ca617847e..50ee5089d8f 100644 --- a/fixtures/emojis/digests.json +++ b/fixtures/emojis/digests.json @@ -2,62 +2,62 @@ { "name": "100", "unicode": "1F4AF", - "digest": "6d57c7cc93335f853e1a5670233f121bc94730dbd82b2b3c5c5a509e092ef0fd" + "digest": "add3bd7d06b6dd445788b277f8c9e5dcf42a54d3ec8b7fb9e7a39695dd95d094" }, { "name": "1234", "unicode": "1F522", - "digest": "727763fd9f18fd5df59e9f78e678ea4ec753e674d70f15d4e77c7802067d660b" + "digest": "c5ac5c8147f5bfd644fad6b470432bba86ffc7bcee04a0e0d277cd1ca485207f" }, { "name": "8ball", "unicode": "1F3B1", - "digest": "1aecf21951452ba24e921ec71b3d313b7ddc2e185b0339c9e0eebc85be4f031d" + "digest": "a6e6855775b66c505adee65926a264103ebddf2e2d963db7c009b4fec3a24178" }, { "name": "a", "unicode": "1F170", - "digest": "2272113a5bcb7faf8db7c1bd35df576d32f2f7cbd881463934ad3382eb87c723" + "digest": "bddbb39e8a1d35d42b7c08e7d47f63988cb4d8614b79f74e70b9c67c221896cc" }, { "name": "ab", "unicode": "1F18E", - "digest": "6f8a237751fdc84db4121f408272d9a23258515449610e4c6c54f50f6e995627" + "digest": "67430fe5fce981160e2ea9052962e49f264322d3abfc2828fbc311b6cdf67ae8" }, { "name": "abc", "unicode": "1F524", - "digest": "652a2381a7b587d8a52d5178e2d7d6c8600b33d36160fa69677943da374105bc" + "digest": "282c817ee3414d77a74b815962c33dd9fe71fabaea8c7a9cec466100fbe32187" }, { "name": "abcd", "unicode": "1F521", - "digest": "35ade4fd3d75294ebb72c24490aa32745604edc6cabe095b90634cd3ce78c07b" + "digest": "686728c759f4683c64762ee4eda0a91bf2041f0ae4f358aacf6c09bf51892eff" }, { "name": "accept", "unicode": "1F251", - "digest": "8212ed158cc447c92813273fc915e84d3d5c4c48d1b38e498c088bad27ab8145" + "digest": "7208d34c761f10a7fd28f98e25535eba13ff91a64442fc282a98bb77722614f1" }, { "name": "aerial_tramway", "unicode": "1F6A1", - "digest": "8039d7f67e6e5b211066cab6cf2142afc3aca5c830a357369362c9b484029563" + "digest": "98df666f34370fc34ce280d84bba5a7e617f733fbbfe66caa424b2afa6ab6777" }, { "name": "airplane", "unicode": "2708", - "digest": "18f4dfac323555d8cdabb79148874c0185ce98e1a08e69414d236b23e502a854" + "digest": "cc12cf259ef88e57717620cd2bd5aa6a02a8631ee532a3bde24bee78edc5de33" }, { "name": "airplane_arriving", "unicode": "1F6EC", - "digest": "9a1c81d97512e5d0e3acec40290d00f616ec182140909859e366a734b9f840bb" + "digest": "80d5b4675f91c4cff06d146d795a065b0ce2a74557df4d9e3314e3d3b5c4ae82" }, { "name": "airplane_departure", "unicode": "1F6EB", - "digest": "e3c5ff4038db998c1897cb237d0b865da0bc60331c758f204e45a979d5fab445" + "digest": "5544eace06b8e1b6ea91940e893e013d33d6b166e14e6d128a87f2cd2de88332" }, { "name": "airplane_northeast", @@ -72,12 +72,12 @@ { "name": "airplane_small", "unicode": "1F6E9", - "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3" + "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d" }, { "name": "small_airplane", "unicode": "1F6E9", - "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3" + "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d" }, { "name": "airplane_small_up", @@ -102,67 +102,67 @@ { "name": "alarm_clock", "unicode": "23F0", - "digest": "84ddd7b3b857c165410b7b44863e5354ca0f3591c3bfe56231f12c9f7531a96f" + "digest": "fef05a3cd1cddbeca4de8091b94bddb93790b03fa213da86c0eec420f8c49599" }, { "name": "alembic", "unicode": "2697", - "digest": "45698914a21683f06931d807af171bcb6984e5ebce66012bba71b467565bd69d" + "digest": "c94b2a4bf24ccf4db27a22c9725cfe900f4a99ec49ef2411d67952bcb2ca1bfb" }, { "name": "alien", "unicode": "1F47D", - "digest": "94dbe4e90614c654145aba93610c43e3ab86df8ca07391bd4e56383f9329c008" + "digest": "856ba98202b244c13a5ee3014a6f7ad592d8c119a30d79e4fc790b74b0e321f7" }, { "name": "ambulance", "unicode": "1F691", - "digest": "82ef36bcd13c88a4b2397c918b8048adc6bf045ed2532ff568e0dfd1b1b29c3c" + "digest": "d9b3c1873de496a4554e715342c72290fb69a9c6766d7885f38bfe9491d052da" }, { "name": "amphora", "unicode": "1F3FA", - "digest": "d3758d88aa1fc3be01894102f57479d3a49790510d38ad3d06a2774962010608" + "digest": "4015f907b649b5e348502cc0e3685ed184e180dca5cc81c43ec516e14df127bf" }, { "name": "anchor", "unicode": "2693", - "digest": "27c6034f769d9f020362fc5b227b9279651cc940861e727d1f6ccd59af98f851" + "digest": "2b29b34ef896ebab70016301e3d1880209bbc3c5a5b8d832e43afff9b17ad792" }, { "name": "angel", "unicode": "1F47C", - "digest": "c1b8ad2adc7686e7fbbe4ec357071e7228a5e0762e001bb589e2f97ff258d5c7" + "digest": "db75c2460aaf9cd07cb41fe22c8a6079f3667ffe612a71611358720e2b5512a4" }, { "name": "angel_tone1", "unicode": "1F47C-1F3FB", - "digest": "90b701c43311b1096c4a012d9905a186f1a16829ea2707921a8418c28617d751" + "digest": "5871a622469b96296365adaf77d83167759692124c20e5a6e062a525af33472a" }, { "name": "angel_tone2", "unicode": "1F47C-1F3FC", - "digest": "d6bcaf1b76e25d486d4ab9b159cf727782d508543d1ae27c8d2c12d2f13d6eb0" + "digest": "f5993198a5d9daf39e761c783461f07bca237f4e9b739ac300bb8ca001a69a1a" }, { "name": "angel_tone3", "unicode": "1F47C-1F3FD", - "digest": "3069285e6218c8083cb0085aa10017bcdea033e321d97ba339a84892074b903a" + "digest": "f0c97a7c4354626267d6ab0f388e4297ad255ab9b061f9c68fbcaa0abfc52783" }, { "name": "angel_tone4", "unicode": "1F47C-1F3FE", - "digest": "dbb87019752d9caa94ce086858c1e3225b62e221ad599f5106548fda2456fc2b" + "digest": "6e5dc724c1939d1b0d1a91343662b5bd61ced7709c97802977145ffab6a1f7ac" }, { "name": "angel_tone5", "unicode": "1F47C-1F3FF", - "digest": "f77703df97720c27a128b5f3c0948b9e04a6b6b81ea5306468154f9bf56225db" + "digest": "52186e1de350c27d25d6010edf44f64a30338b65912ca178429fbcfbd88113c2" }, { "name": "anger", "unicode": "1F4A2", - "digest": "2253b7ff0894f247bc6f04d841a748c56d6c94684880c13df42387691ff20e75" + "digest": "332493913891aa0eda2743b4bb16c4682400f249998bf34eb292246c9009e17f" }, { "name": "anger_left", @@ -177,152 +177,152 @@ { "name": "anger_right", "unicode": "1F5EF", - "digest": "24b572d64c519251a3ae8844e8d66fd6955752aff99aebe7dc20179505a466c4" + "digest": "8b049511ef3b1b28325841e2f87c60773eaf2f65cabba58d8b0ec3de9b10c0ae" }, { "name": "right_anger_bubble", "unicode": "1F5EF", - "digest": "24b572d64c519251a3ae8844e8d66fd6955752aff99aebe7dc20179505a466c4" + "digest": "8b049511ef3b1b28325841e2f87c60773eaf2f65cabba58d8b0ec3de9b10c0ae" }, { "name": "angry", "unicode": "1F620", - "digest": "c4188ba70df99d8ccef5706d711176725d3dd50d62f065a177d68d85c7828107" + "digest": "7e09e7e821f511606341fb5ce4011a8ed9809766ab86b7983ffa6ea352b39ec1" }, { "name": "anguished", "unicode": "1F627", - "digest": "9c2347308133ae50dc04da62042fff847f4c477b2956b8aa976f0413899e38bc" + "digest": "a2b6f052996969a17150249d9ef5db742da3d6585bd38ca61eb14c4c13cda54f" }, { "name": "ant", "unicode": "1F41C", - "digest": "d2af2ed1cfe15d649aa329d965764a1e8726941d833841781a5b66d7dd0b0921" + "digest": "929abeaff7ba21ab71cd1ab798af7a6b611e3b3ce1af80cede09a116b223e442" }, { "name": "apple", "unicode": "1F34E", - "digest": "a9babee24f454934a5e1fb8d781cbce354dfd88e8a8e01f02e8b30071fd40460" + "digest": "2a1b85ce57e3d236ae7777dcf332ec37d03bfd7b19806521a353bc532083224d" }, { "name": "aquarius", "unicode": "2652", - "digest": "1a168c252678847d1f9ef450887489e3bdc207ecae4b6fb05e92295ff861ae2c" + "digest": "fdc42cd41b0dace5eae6baba3143f1e40295d48a29e7103a5bba1d84a056c39d" }, { "name": "aries", "unicode": "2648", - "digest": "bde262a8795e12f8b0ebb3f0f8c3a56104062fcee8d5d678cf4bb445a7daf698" + "digest": "deb135debcde0a98f40361a84ab64d57c18b5b445cd2f4199e8936f052899737" }, { "name": "arrow_backward", "unicode": "25C0", - "digest": "ddae36d1febf5c246e51d599e2898a8aa30cd47f88b5bcb469e3ca9d22538b97" + "digest": "e162ac82e90d1e925d479fa5c45b9340e0a53287be04e43cbbb2a89c7e7e45e4" }, { "name": "arrow_double_down", "unicode": "23EC", - "digest": "906f42b5f788128ed90d2d162cf03e6e595a50ad05e0aa5f64e925637379d0cd" + "digest": "03ca890b05338d40972c7a056d672df620a203c6ca52ff3ff530f1a710905507" }, { "name": "arrow_double_up", "unicode": "23EB", - "digest": "2129a57402980de6fc6f59ad8354525c2dbcd66d1b78f4de091181ddc81e0693" + "digest": "e753f05bce993d62d5dc79e33c441ced059381b6ce21fa3ea4200f1b3236e59d" }, { "name": "arrow_down", "unicode": "2B07", - "digest": "370e4f41565d5dab245c20e45c502505a56d26c2392283781b841eb3e905edb2" + "digest": "9bf1bd2ea652ca9321087de58c7a112ea04c35676a6ee0766154183f8b95af6c" }, { "name": "arrow_down_small", "unicode": "1F53D", - "digest": "98a2b183f2daec425160bbfce1d2b940b8baa0d5032fdacfa9453e39bed5651b" + "digest": "7766198bc60cf59d6cdaeeaa700c2282bfff2f0fdeb22cf4581ca284b87a3bb7" }, { "name": "arrow_forward", "unicode": "25B6", - "digest": "348627b8e0f55cf1e9ab19c9de1d170371b2c4cb4dda9a2aa8e0c558db08b18a" + "digest": "db77d9accd1e02224f5d612f79cd691e6befdf22063475204836be6572510fb7" }, { "name": "arrow_heading_down", "unicode": "2935", - "digest": "96c64953fc3134711247bef320f252c48993ebc90494925b7fee42ffce2a2ec2" + "digest": "f5396069c8f63c13e6c3e0ecd34267c932451309ade9c1171d410563153bf909" }, { "name": "arrow_heading_up", "unicode": "2934", - "digest": "94f94e74176cc050703b3584f3f700debf86e4e61b893a441825a21fa3f8ce74" + "digest": "1cad71923fa3df24cf543cae4ce775b0f74936f2edd685fd86a7525c41a14568" }, { "name": "arrow_left", "unicode": "2B05", - "digest": "4553be62a63d7550deac4f7dbeffce6006f769ae6cddfb8c795671672011ba0b" + "digest": "b629bb3dbe161ef89cfcfced0c7968a68e44a019ad509132987e4973bdc874e7" }, { "name": "arrow_lower_left", "unicode": "2199", - "digest": "10f83c252110d705cdcfebc35a70c341ad288730d0c0729479e3a96e263d5120" + "digest": "879136ba0e24e6bf3be70118abcb716d71bd74f7b62347bc052b6533c0ea534d" }, { "name": "arrow_lower_right", "unicode": "2198", - "digest": "ee33abd4c96c19e9b80a2fc1500ba8ecaa6668c49310cc816a496e8c61af3850" + "digest": "86d52ac9b961991e3aaa6a9f9b5ace4db6ffd1b5c171c09c23b516473b55066d" }, { "name": "arrow_right", "unicode": "27A1", - "digest": "2611e9138a2651916f414015d0287f5f0af266514d96a42915d32b04fb652a90" + "digest": "45f26a1cbb0f00ed3609b39da52e9d9e896a77e361c4c8036b1bf8038171bd49" }, { "name": "arrow_right_hook", "unicode": "21AA", - "digest": "628b06384a2963a4fe81e9fbf4e22511f697878d9b9db7d2fc98f8aadbe8f4f9" + "digest": "4f452679c71bcea4fc4a701c55156fef3ddc1ebbc70570bedfc9d3a029637ab1" }, { "name": "arrow_up", "unicode": "2B06", - "digest": "c09e5f41c01028b45707c525d30d3d6731ec57b7447f0d7ba4ad6c1404449e5c" + "digest": "982b988ef6651d8a71867ba7c87f640f62dd0eeb0b7c358f5a5c37e8fe507b8b" }, { "name": "arrow_up_down", "unicode": "2195", - "digest": "e7fd92d24a01702f76c7fcc0de998bc81fbfb93711d076984f6da91d1dccd84c" + "digest": "645ed8fb6646f49bfd95af1752336deacdadbe5cba13904023a704288f3b0e2c" }, { "name": "arrow_up_small", "unicode": "1F53C", - "digest": "bc48dad74bc1d0c5579cbf5e3d005314b0d21bc5b5ebbba2b05136e33f49296d" + "digest": "4a8c5789c13a852517e639e7a62c2d331464e6fb0358985aa97c1515e97b5e8b" }, { "name": "arrow_upper_left", "unicode": "2196", - "digest": "792a9709f03843024e53d201cb4769c59b656c3bf0dff2306e8e605493a66b93" + "digest": "79026f828d6ceb7c55a9542770962ba6dcd08203995f6ceeb70333a12307d376" }, { "name": "arrow_upper_right", "unicode": "2197", - "digest": "ee934b0c9cff270efd30a6cafc15253d405efd2c93b4785ac2ed4ea6420266a6" + "digest": "7e0f33dfbe65628991c170130d366a3e2cedaf8862ddfcaf3960f395d3da1926" }, { "name": "arrows_clockwise", "unicode": "1F503", - "digest": "914f4120513730d7a19c9f8c4e59223a90568de0b25a225b712b31fa9697ef4f" + "digest": "88669679977f7157f0acaa9d6a1b77ccf84d25eb78c5bc8afcde38d3635e7144" }, { "name": "arrows_counterclockwise", "unicode": "1F504", - "digest": "86d87597e4e3db6dbba9907ee82412db0cbab1ea875bd0be6505dd886dc19b90" + "digest": "a2c6a6d3643c128aee3304cd03bb3d7cfe4d35d3ba825bc9c1142d7832b4426e" }, { "name": "art", "unicode": "1F3A8", - "digest": "dfc6b0da780199df86507d65b0499ba1706c266ae7badcb0e7fb5b85af7c9578" + "digest": "b6bc6c4bfb594aadcbb641d006031867678504764bbe0ab84e7b08567a9498da" }, { "name": "articulated_lorry", "unicode": "1F69B", - "digest": "4c4de240ebd175f7b53453eda4e51f2e57d0db2a98d317f804116e14e47cff1d" + "digest": "c115e6613ebd718268aa31d265e017138b9fb58bbb8201eb3f40de2380e460aa" }, { "name": "ascending_notes", @@ -332,117 +332,117 @@ { "name": "asterisk", "unicode": "002A-20E3", - "digest": "0b7f27f545b616677c83d40ff957337477b2881459b4d3c839ae55e23797419f" + "digest": "33d92093f2914448d5a939cf62e8ee3e32931923abdef5f0210e8a8150fa312d" }, { "name": "keycap_asterisk", "unicode": "002A-20E3", - "digest": "0b7f27f545b616677c83d40ff957337477b2881459b4d3c839ae55e23797419f" + "digest": "33d92093f2914448d5a939cf62e8ee3e32931923abdef5f0210e8a8150fa312d" }, { "name": "astonished", "unicode": "1F632", - "digest": "58632b97e274ade5183752db2b3c5c4fe29effcd5a9720a8d01fa809b97023dc" + "digest": "f8531bdda5070d10492709085f4ff652b8be9be6458758940358b9fc594a1f14" }, { "name": "athletic_shoe", "unicode": "1F45F", - "digest": "1fc55d85a4d6751f9e60467801b051d2fb3341bdcc33b8d3695d5143359edb43" + "digest": "1f90dc390e0dea679085465b7f9e786dfd7dd56a3b219987144ed37ab1e9bf95" }, { "name": "atm", "unicode": "1F3E7", - "digest": "bf827ef6c349f5b6912d821457975a4720d1750529d907e94ece429b7a388d7e" + "digest": "7d3ce6a6afb4951546883404b8e36904179f88f1aa533706cf7bf0bbe0d6fd3c" }, { "name": "atom", "unicode": "269B", - "digest": "cbce1725602efbb77a935cfae5407e4d75489ee988910296c7f6140665afc669" + "digest": "6b6bb83b00707a314e46ff8eefbda40978a291ec7881caba1b1ee273f49c1368" }, { "name": "atom_symbol", "unicode": "269B", - "digest": "cbce1725602efbb77a935cfae5407e4d75489ee988910296c7f6140665afc669" + "digest": "6b6bb83b00707a314e46ff8eefbda40978a291ec7881caba1b1ee273f49c1368" }, { "name": "b", "unicode": "1F171", - "digest": "9116256b3189977e37f6da7ddedf82bb29b0358829a4e8718fd59e51d9b86b3c" + "digest": "722f9db9442e7c0fc0d0ac0f5291fbf47c6a0ac4d8abd42e97957da705fb82bf" }, { "name": "baby", "unicode": "1F476", - "digest": "66596bea11015154e0b1752b85f349f4286c6643ee6f51ee5e60e0d625c4ae9a" + "digest": "219ae5a571aaf90c060956cd1c56dcc27708c827cecdca3ba1122058a3c4847b" }, { "name": "baby_bottle", "unicode": "1F37C", - "digest": "ed42994b4a539b8bfeccde0f3c7e9c7f54d6696ff48ce7e48171bbab51002348" + "digest": "4fb71689e9d634e8d1699cf454a71e43f2b5b1a5dbab0bf186626934fdf5b782" }, { "name": "baby_chick", "unicode": "1F424", - "digest": "ea2cfa0e5c2cbff5fffdb52cc04dfe7872834bd7cfeaa45e0541b8faffcbd0e9" + "digest": "14119874e9b5548028dfb9cc593a541efc1d075ac839a565b92e0c3253cffe7e" }, { "name": "baby_symbol", "unicode": "1F6BC", - "digest": "65df04dff8739b86f7663ae9c0648927341f360a986655e109721b0e16013b75" + "digest": "fb4db66868cda45ea3879ffc2ff4f763c56d2d889ae0ab17fe171129ede02f98" }, { "name": "baby_tone1", "unicode": "1F476-1F3FB", - "digest": "bc747527a2d723cf99ef3fc2539c19d29634c92ff417736982d3bf87d65d06eb" + "digest": "cd3faf223a298c34e05d469d9d0db08438d97df7fd82c0973f8a9e07d553f5b1" }, { "name": "baby_tone2", "unicode": "1F476-1F3FC", - "digest": "b82bba7a666b7d070751726e54acc7fb8f96e2dfc09e9610d61cfd20947aef9c" + "digest": "5b4539e22e0dd726c27eb8af2357f9240a52aed3f710f3234571cff029cc6198" }, { "name": "baby_tone3", "unicode": "1F476-1F3FD", - "digest": "7f45dfd4ea2ae8515d419ffa13e7ee5c625b024b4e521ace5344c414bb929da0" + "digest": "720e740e1ac63c6372269132b1fb6e07a6b91f5c808cc3adef59f0b4500e5e72" }, { "name": "baby_tone4", "unicode": "1F476-1F3FE", - "digest": "80b1854626616f15426649cc6415e4911a55c8f761422fe48a08af9e8ac6a7cb" + "digest": "5e43b69c509bd526ad6f081764578c30b6f3285fb7442222e05ccf62e53bfb64" }, { "name": "baby_tone5", "unicode": "1F476-1F3FF", - "digest": "9f890804d19a61bee76a29644c818045dd96cf69d67cfbca2d11f4ad376b27da" + "digest": "85bba6e0940ccfb99999fe124e815f9dd340d00a5568e13967b02245a62dbf54" }, { "name": "back", "unicode": "1F519", - "digest": "1dc73947b8f56e033777ca3f747407923bd16b07e53a6c78b09950ca474b7e7a" + "digest": "083e4e48b51092c28efb4532e840e1091b5d4b685c6e0f221aa0228f061cd91e" }, { "name": "badminton", "unicode": "1F3F8", - "digest": "3f95180c1175d0248ebf4b8650cf86566c39e0486d828078244080194c14d4fe" + "digest": "353eb7ee93decd9fe0072e4d78a5618d5e2d9e77a6e4de9fe171870d75e02a66" }, { "name": "baggage_claim", "unicode": "1F6C4", - "digest": "7c1a69511aa2a93984d601da4d1cef1cb4cefbbf127b1486278da8c01345bbf3" + "digest": "7d6bceca92c266da6d2b91dfcf244546fc11022e039e7da8e6888c1696bb2186" }, { "name": "balloon", "unicode": "1F388", - "digest": "a10c2b0865179cdbdef339494ec9b2a109451a356e53738d6a9dd43232500956" + "digest": "65760aedc1503b426927cff78c24449d563843a274961d962718fa9638375d54" }, { "name": "ballot_box", "unicode": "1F5F3", - "digest": "0455ea75612efe78354315b4c345953d2d559bb471d5b01c1adc1d6b74ed693a" + "digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892" }, { "name": "ballot_box_with_ballot", "unicode": "1F5F3", - "digest": "0455ea75612efe78354315b4c345953d2d559bb471d5b01c1adc1d6b74ed693a" + "digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892" }, { "name": "ballot_box_check", @@ -457,7 +457,7 @@ { "name": "ballot_box_with_check", "unicode": "2611", - "digest": "5f5cec7fe462557d31e8d2b836534c1e76d546cc0061236fa2af3667972b84aa" + "digest": "c98d6f3588dd87e2f318bbfe6c646399a905450edfd814edae4e5b1bddef2134" }, { "name": "ballot_box_x", @@ -482,277 +482,277 @@ { "name": "bamboo", "unicode": "1F38D", - "digest": "feb0cf2f1012a1c0649b8c66f7e96e2d8bcdefe879c5a52dab3e25c51009e3b2" + "digest": "e4ee65088df43d7081b1ce6fd996f66f3e0accd88840855c47a98a22997823dd" }, { "name": "banana", "unicode": "1F34C", - "digest": "aa9a1e6db00efa94a7f414c570eff7fc29011be64031a24d03b7f37b617cfd2d" + "digest": "f9e8ff910c282c20a8907ff64926b5de4ee250529a1ed718fb33302e6fff8dd9" }, { "name": "bangbang", "unicode": "203C", - "digest": "bdd350766ccd1c0138f6294f7ebfa3e9867b02bda40a743f7062e52c68358765" + "digest": "76536fee63fe964a3f3839d309b1f45028fb0c43f4d1eeee495f17e1532b4def" }, { "name": "bank", "unicode": "1F3E6", - "digest": "c9648c93049cf8e7884242e58ae3145383d2e5034c9090e0d34c53f5bbce397f" + "digest": "f5d2976bf6d521638ccacc74be06bd4abfeab06c5d898a9d245edad45a5b6306" }, { "name": "bar_chart", "unicode": "1F4CA", - "digest": "942277f72a5b754b13454dab62c85b1ff3447544f38ec76a285f3be32f6f5d12" + "digest": "65a328a1b2d7a5332dd4d93f4dbca13d976f0a505b00835c3fc458e394804240" }, { "name": "barber", "unicode": "1F488", - "digest": "e1526eea685aafc56fb83d07f8ff63c9967600e447b0e5f831a17d6153f2062d" + "digest": "5e8053d3bb3765a8632fd1cbfe21163f74ed79f6be377eb9603eaaf883d8dc46" }, { "name": "baseball", "unicode": "26BE", - "digest": "3d028b16a898f3a15874bc9d3891f9fbf59ea1c226c5c774eddb58a712c489ae" + "digest": "46ac16f8b5455b942f6dbff9483a6fd277721e6719d2731573baabd21c44b34f" }, { "name": "basketball", "unicode": "1F3C0", - "digest": "b2f5a3904d505db066337a24fc840ef75b49ef4c5f152227d8e632ff82285b12" + "digest": "cc83e2aea8fcd2e9a5789e1932ee3766c40843c142fd3565c4e77dafb21ec7d7" }, { "name": "basketball_player", "unicode": "26F9", - "digest": "e94beb69f631667479a80095bf313ceb3aa109d6ebb80f182722360a6d2a214e" + "digest": "793ba53c95e8def769383b612037bc9b9bceecaf1e0430c50a4cc128ad18d9b9" }, { "name": "person_with_ball", "unicode": "26F9", - "digest": "e94beb69f631667479a80095bf313ceb3aa109d6ebb80f182722360a6d2a214e" + "digest": "793ba53c95e8def769383b612037bc9b9bceecaf1e0430c50a4cc128ad18d9b9" }, { "name": "basketball_player_tone1", "unicode": "26F9-1F3FB", - "digest": "6fc77cf2f26ee18e9a3faea500d4277839f77633f31ee618a68c301f1ad32d90" + "digest": "2a06522b971e68ee5b8777a58253009b548f4da2fb723c638acb3d7b04edba8f" }, { "name": "person_with_ball_tone1", "unicode": "26F9-1F3FB", - "digest": "6fc77cf2f26ee18e9a3faea500d4277839f77633f31ee618a68c301f1ad32d90" + "digest": "2a06522b971e68ee5b8777a58253009b548f4da2fb723c638acb3d7b04edba8f" }, { "name": "basketball_player_tone2", "unicode": "26F9-1F3FC", - "digest": "6ee9060c24d92708e12a854fb0bdf5c717c90b8c0350d8aa40c278b41bfa12fc" + "digest": "ecc0e44ab9bc478ba45a055fd69a3a38377b917aac5047963fe80ff8ae5fd8e3" }, { "name": "person_with_ball_tone2", "unicode": "26F9-1F3FC", - "digest": "6ee9060c24d92708e12a854fb0bdf5c717c90b8c0350d8aa40c278b41bfa12fc" + "digest": "ecc0e44ab9bc478ba45a055fd69a3a38377b917aac5047963fe80ff8ae5fd8e3" }, { "name": "basketball_player_tone3", "unicode": "26F9-1F3FD", - "digest": "752e90dbfa7c7a9ae3f37de924e22f3c3d5a7e54dd41c8e8eb99cabb0dad73cf" + "digest": "2d38f1851c685d29532c042461d7b5b996e5f04f0ed54857c66073c62a99ceac" }, { "name": "person_with_ball_tone3", "unicode": "26F9-1F3FD", - "digest": "752e90dbfa7c7a9ae3f37de924e22f3c3d5a7e54dd41c8e8eb99cabb0dad73cf" + "digest": "2d38f1851c685d29532c042461d7b5b996e5f04f0ed54857c66073c62a99ceac" }, { "name": "basketball_player_tone4", "unicode": "26F9-1F3FE", - "digest": "38bedc3074e6243454d568d9b665f5764f1a3d983875651ce7a1cdb53da9f6c8" + "digest": "09e957c6e9ffc196415f28073aa261feba8efba0bdc694dc08f8f7cd1f88f720" }, { "name": "person_with_ball_tone4", "unicode": "26F9-1F3FE", - "digest": "38bedc3074e6243454d568d9b665f5764f1a3d983875651ce7a1cdb53da9f6c8" + "digest": "09e957c6e9ffc196415f28073aa261feba8efba0bdc694dc08f8f7cd1f88f720" }, { "name": "basketball_player_tone5", "unicode": "26F9-1F3FF", - "digest": "25ee1e84670d3db96d3ad098c859abd6b3448f55f668ce0c195ee2337a215de7" + "digest": "c631cefc5d2a0a31bdb9f0a0d97ea68b1c6928e565468998403034644572a0b0" }, { "name": "person_with_ball_tone5", "unicode": "26F9-1F3FF", - "digest": "25ee1e84670d3db96d3ad098c859abd6b3448f55f668ce0c195ee2337a215de7" + "digest": "c631cefc5d2a0a31bdb9f0a0d97ea68b1c6928e565468998403034644572a0b0" }, { "name": "bath", "unicode": "1F6C0", - "digest": "ae6301a6354630cd9dc06a5137f23f826d019c8298b2b012b6ff31b773a910b6" + "digest": "33b371832f90aad50baf5296f3ad4cc081c319b279f989c74409903d8568e917" }, { "name": "bath_tone1", "unicode": "1F6C0-1F3FB", - "digest": "fce7ae2e7ef3f7f44f36c2ad49348b4cf7fce0b0c17e1a90a1e85734cee95b2a" + "digest": "7ae2989e47788ba71359d52da68feec95aaff68a77d5a6556957df1617af8536" }, { "name": "bath_tone2", "unicode": "1F6C0-1F3FC", - "digest": "4d1c9444f16467488fe939fdad279d6855d28be564e5dcc1990451c4b9ae8c95" + "digest": "2e86f8edad54d15a7094cd52160cbe51d10aa1750cfb0b3b58e93533f070e327" }, { "name": "bath_tone3", "unicode": "1F6C0-1F3FD", - "digest": "9a59a4360effb48af4cbb1a953655ef61e69375407038b4d0bd8068fbaf3cc16" + "digest": "654c0cd083a67ff330a38d07352876d265390e5399e5352598d64a6c7e5eeba7" }, { "name": "bath_tone4", "unicode": "1F6C0-1F3FE", - "digest": "01aafa8a53a08018b9fbf28ec6b3b918d6bd0dee7a891196f32f81f60d114f0e" + "digest": "adad88c6830f31c4b5be194d1987d6aadf4adf45e4cb7f2e4657f0d20c0d663a" }, { "name": "bath_tone5", "unicode": "1F6C0-1F3FF", - "digest": "2733e81ccaee21231c2e47e3310b431e9bd784bf34f0db609f8eadcee359500d" + "digest": "952c4c9bf24e001e23a33ebf97bd92969cd9143e28ce93f9aafc708a8f966903" }, { "name": "bathtub", "unicode": "1F6C1", - "digest": "9515e3bb9ab41350305e64fc6877aae82d51e1ba8ce8b2b4b8ffaeda960820cd" + "digest": "844dffb87ef872594195069b0d0df27c3fe51f3967ccbc8b2df811a086dd483a" }, { "name": "battery", "unicode": "1F50B", - "digest": "7d4d475c1d5b1be55c319953e3363ff864fe4fcd921a8aa649b9a547c0894deb" + "digest": "949ae06648667fb13d9121a6dfdd03bf8692794b28c36e9a8e8ac4515664449a" }, { "name": "beach", "unicode": "1F3D6", - "digest": "52855d75cfa4476ccc23c58b4afcb76ee48abb22a9a6081210c8accefdf33099" + "digest": "37fa2158977d470186caaa1aa06669b6dc5026ba49a0c44c5255541f8e974e26" }, { "name": "beach_with_umbrella", "unicode": "1F3D6", - "digest": "52855d75cfa4476ccc23c58b4afcb76ee48abb22a9a6081210c8accefdf33099" + "digest": "37fa2158977d470186caaa1aa06669b6dc5026ba49a0c44c5255541f8e974e26" }, { "name": "beach_umbrella", "unicode": "26F1", - "digest": "cefe8e195d21d3e0769d3bfe15170db9e57c86db9d31cacb19fcdc8d2191b661" + "digest": "d045f1de10038b9fb1eaa2529b2f80b7e3be1cff503efcc2d680663d1fbbc18f" }, { "name": "umbrella_on_ground", "unicode": "26F1", - "digest": "cefe8e195d21d3e0769d3bfe15170db9e57c86db9d31cacb19fcdc8d2191b661" + "digest": "d045f1de10038b9fb1eaa2529b2f80b7e3be1cff503efcc2d680663d1fbbc18f" }, { "name": "bear", "unicode": "1F43B", - "digest": "b5ac126875c20c82b9e3140b143233944a2e4132d781d0b575e83673988523cb" + "digest": "a4b9066eaa5681e6af06e596a96a5217037460ffc3b013e8db4d34d762413246" }, { "name": "bed", "unicode": "1F6CF", - "digest": "1919245d7a76799aad0533eb72db2cbaa1f32ee8231a0c1989d3f233f2d42370" + "digest": "08f6e20db51b1fb650b390a0a3074938646772f3fcee8c295d47742e44fe1e30" }, { "name": "bee", "unicode": "1F41D", - "digest": "69ada63403c8dabae39c63ba143143aeb59b66faae6aa82d8342337925a9e6b5" + "digest": "5beb9a1650681b4adf69999d4808231c38f41a3ec693480b807cda86f964c570" }, { "name": "beer", "unicode": "1F37A", - "digest": "b71dd6efdb4ce7d9d71fdbf82a2ccf83841fb0cceb119ee7da1e575d3bfa853c" + "digest": "69e227104976548ee0f37375fe1526fd65ef0a328d2d92db2feb1edfd7032bd4" }, { "name": "beers", "unicode": "1F37B", - "digest": "994108cebfe0c614c05967af4e3864d8adbbfcf7cccef1cbd42a47b7dfabf80c" + "digest": "db8b32d93bf6d161a3b027e55651d8f51231b13928b3610987ef62bb634d7501" }, { "name": "beetle", "unicode": "1F41E", - "digest": "ec351ce238a81711eef00e5be1de2e198423cf524b60e531d435902b44420edc" + "digest": "5aaa428e3f63f7cd1696839ab05be03fa0cd0cbed30a05c36cb270da330c3849" }, { "name": "beginner", "unicode": "1F530", - "digest": "13288d9fc221dc02f4181b998104e13c3c5c98d3c4e650186bef59a46d39f6f0" + "digest": "2de4fdf92f182c42b12b7527034eaf767d996848b61f31ee69167728411ca0b1" }, { "name": "bell", "unicode": "1F514", - "digest": "784b9a82814ce14a264e54b3a8f8e706f3c7b763646d9f8174c4aa84ad41ef09" + "digest": "18d419417746ead408072b78fe2edb6314cdb49492873966fa9f9f06be09899b" }, { "name": "bellhop", "unicode": "1F6CE", - "digest": "c15455f1b52ac26404b5c13a0e1070212ed1830026422873f4f6335e26e31259" + "digest": "b8187bc4059f6a0924a47fe3f6c07f656bed0334bbcbfa1e89f800fe6594ff08" }, { "name": "bellhop_bell", "unicode": "1F6CE", - "digest": "c15455f1b52ac26404b5c13a0e1070212ed1830026422873f4f6335e26e31259" + "digest": "b8187bc4059f6a0924a47fe3f6c07f656bed0334bbcbfa1e89f800fe6594ff08" }, { "name": "bento", "unicode": "1F371", - "digest": "d59314b17a8646d4a78fefb7b79f289f33d4aaea893fed4cad0b890df63395e7" + "digest": "d46d4f681c5da7f7678b51be3445454a8ed18d917e132ae79077f05310e485f1" }, { "name": "bicyclist", "unicode": "1F6B4", - "digest": "e7359d615d40325bb08a145cfebde2ecef448deeb21695a34b55d3ccb971447f" + "digest": "3302147b6b47c16adb97d78b7b761a1ca80e6d0b41d0b60f4da338d2f55f968b" }, { "name": "bicyclist_tone1", "unicode": "1F6B4-1F3FB", - "digest": "e45808faa32f4ffb881d3569c0b8e2c69d4a64665f4d1fae24d7a1e5f1d3ea4b" + "digest": "27eaae0eb61f5e7b3cd9faf02c042d6643a368051a7c9d7da4e0fb9802d39242" }, { "name": "bicyclist_tone2", "unicode": "1F6B4-1F3FC", - "digest": "92a3494270d1da6a117e92402c7898d4a7fffbe3d6143fb9ae445c4827c0c8a4" + "digest": "39ee9e1071700da7079ad0146bf5711c3a222991eeca8b29b72a65677604444d" }, { "name": "bicyclist_tone3", "unicode": "1F6B4-1F3FD", - "digest": "6fdf1db2bbd08d06b643b08f0f29daeaa20e0b8c8abec21132191f435cc05e42" + "digest": "03e1d2c4232c896147a9d4bf43becd61edbb5c84fc7193ecea474c0f9fb36817" }, { "name": "bicyclist_tone4", "unicode": "1F6B4-1F3FE", - "digest": "d9c27848e1bcc8197c858e1ef12a537f4ed6c77fb211b6731388dc88c2bb7a61" + "digest": "61393d9c4805be0379d86dd5bec9a1b02314433ab36cfd85bb48dfd073746617" }, { "name": "bicyclist_tone5", "unicode": "1F6B4-1F3FF", - "digest": "4892af1a8a0229a813d7b8e3d88481c2365e3e1a5ce2e0e27ce432c5336da810" + "digest": "2b46d5f8303e5710dbf5db3a4edc9d88a032fe123fe79158024c9f51df5458c6" }, { "name": "bike", "unicode": "1F6B2", - "digest": "e726f97b5432f46ed51328c0930d1d63b3a2d7b67c5c2303a5ca997083cfcac1" + "digest": "b41daa7c549d483e2336186a28baaa8ecb11986f490c0c54c793c44900c8f652" }, { "name": "bikini", "unicode": "1F459", - "digest": "7612fcb72c005ae7172260825f588d6995f2bc919cb3d283dd4591f6872a1855" + "digest": "07fe156f64673818d69ce3bf03950ca59e3b5d346e45ca541da4078ab791f5ae" }, { "name": "biohazard", "unicode": "2623", - "digest": "81f8309318051255ed4dc18855a3cd3f8657a6f3b2d368caa531a57ce0e34235" + "digest": "96163e31f0b8dc5a59772133ede9cc2f40f94330d0b15e3d044b28747e2be788" }, { "name": "biohazard_sign", "unicode": "2623", - "digest": "81f8309318051255ed4dc18855a3cd3f8657a6f3b2d368caa531a57ce0e34235" + "digest": "96163e31f0b8dc5a59772133ede9cc2f40f94330d0b15e3d044b28747e2be788" }, { "name": "bird", "unicode": "1F426", - "digest": "3f219e5aa18e2f1febfd368ec133786cd2eab357db79984cb8ba07fed0eec7cd" + "digest": "f916eaf8f271b3767ade9eabb69594c0479f45472d471cabaf59f6e965c161e0" }, { "name": "birthday", "unicode": "1F382", - "digest": "9eb1adb0170ab851042cb3da8b64f02f4e4b63e7a07db405b55b50f5bbd3cacf" + "digest": "89e7c4c598ebee8ec8ab11ebe4ccc6defb7c4d2987ee2379a19b3b59827dd98a" }, { "name": "black_circle", @@ -762,82 +762,82 @@ { "name": "black_joker", "unicode": "1F0CF", - "digest": "1eb85b8e2b93dec221a97a1c309dee3683408f6166e1a1a1bd83cf2f64f007dd" + "digest": "d004b25f186494d5b2c65204caa9daecd749c840a0bea5718735e18109e5394d" }, { "name": "black_large_square", "unicode": "2B1B", - "digest": "0ff2112227c38ed8c30b0bddf2300e87d2a244cd7fe81886a1cb1a287a7e8bb6" + "digest": "cbd90dcbc2f674eafa53820548b5263c18c9845ab39937f085e85aca0aebb479" }, { "name": "black_medium_small_square", "unicode": "25FE", - "digest": "f1010aa694084ad4655a9d4ce5a1711eaab21029e31bf8798253f0ad644e8abb" + "digest": "ab38363c2e862b8f67c719397a09a18e1ef996eec190691fdf769f5cfb209660" }, { "name": "black_medium_square", "unicode": "25FC", - "digest": "06bf48ffbc84e71bbb90aa0f6c3f9f53533c6fd063ff168cefdb0a050dcf8302" + "digest": "c9ffa87c37e8ee65fadcf755176949901aec7367e02abb85e63cad60cd922116" }, { "name": "black_nib", "unicode": "2712", - "digest": "c1361df4a5ae9f2ed121d26928021e96c6865331861e1960700d39cb1bd49355" + "digest": "58fb23b1155102970eaa23765e7d529a21e8e545e076ec1158bf11b4de5f51a8" }, { "name": "black_small_square", "unicode": "25AA", - "digest": "d430ec419869fa1b5ba980ddeecb4c5ad5050a2b3421e45048cc184a6fc46899" + "digest": "f69be6de578fffce5a3e60eda690104b2ef6a855c630040104fb760a02ff1aef" }, { "name": "black_square_button", "unicode": "1F532", - "digest": "85b6587b6b2c3544ddb7bc07207b0740e437744ba134835836153899ae396135" + "digest": "9d818fcd08ed38cd0bbbcfd83e665aa29b3761c0d8b9806d8954d36785e267a8" }, { "name": "blossom", "unicode": "1F33C", - "digest": "029bbe385e07e2017dd918d685e107678c9c0e919a3bd1521b7a0d7c9172da05" + "digest": "e8cf369d4e4cdb4eccc2ebcbb35439b0344221115701daae642e58dff8544922" }, { "name": "blowfish", "unicode": "1F421", - "digest": "b5ee9f6ffabb74e3024067f016d17a631ee98536cb9c7269d55fa867f95a54fb" + "digest": "e706849ed00f08a82312381c76f6f9ba6cc261fbf87a839c85e7dd54138f9dc3" }, { "name": "blue_book", "unicode": "1F4D8", - "digest": "6fbf227fb9facc1957bb9dfb31749cbfe66c3afe8081347f2471fd64ef2e6b3a" + "digest": "4c845748fe890516b32981b0b62bf3e8e9d906840c2060179f4f844100780615" }, { "name": "blue_car", "unicode": "1F699", - "digest": "e61ef2299d11fc01e9d6c496d188a7211633946706f6e771c412368346ca16f4" + "digest": "eca91934eb5481726cfd897b1ed5eac306e14d02499fbe49316aaec6c72b6707" }, { "name": "blue_heart", "unicode": "1F499", - "digest": "1af8d04173e0a984360786f6031220000dd548b8c912a68fd51f2ba490a9e16a" + "digest": "2caa0c8d18538cc871c6fe328a52f71e1df8aabf4d1cc2f5324b261d1b8cb99a" }, { "name": "blush", "unicode": "1F60A", - "digest": "d615cda0f7c185ed8a92008204043ef769f3b7fb5424d595aeaaf3827bcdbd73" + "digest": "3bfe8d603cfa39999c164779f666d39bbc507f124ba80233ee72da7b3b0c0457" }, { "name": "boar", "unicode": "1F417", - "digest": "c23a06db0337597e361ae581eacd4faf9926c6b7db0510d3599eb2e2a73315cb" + "digest": "c9d67479cace427ac3c30460fcffa1bf9a8e5262c0390962405dbbe6bf830fa6" }, { "name": "bomb", "unicode": "1F4A3", - "digest": "0099e7435eba35f4f3ad273993293693a8b5cd110567c95ed83e5b4e2d0978ff" + "digest": "0155559abc4084f80e9b0b2a2091b8710ddd6369993b7fdd0685f4f8c2fd7e6c" }, { "name": "book", "unicode": "1F4D6", - "digest": "152408f2ff9949b7cbe57f623e4f875aa8dd0b02317e03cc914e1ea3712b3fc7" + "digest": "9d912a9d1bb10dc7f2645b345ed09e90461e83df0de275acb806f1f75cef1fcf" }, { "name": "book2", @@ -847,32 +847,32 @@ { "name": "bookmark", "unicode": "1F516", - "digest": "a2e0c6f5466c1b2fc148b20f6afcf4a878f4df55b0181f61fffa3ff727dcb251" + "digest": "5705e3108259d6900649157843c50e22d0086c3630b291d3f942da1a736e3e3d" }, { "name": "bookmark_tabs", "unicode": "1F4D1", - "digest": "16135d62ff440722bd1ce8f84219be6a5eb3120a1597bfda4aeed4a2d9e7d7b2" + "digest": "c8fc7c9f3f82e1ccc97fc591345fdd88b09eec0fca428d8d4632a121cf1bc39a" }, { "name": "books", "unicode": "1F4DA", - "digest": "ba019e4174639440caec424b30dfa016fe71a6f7436fe63025a2e3609ebfc012" + "digest": "cbcf55d39dd05d26ef7350bc51e0e2f064f78bb8f59d407b516d63f68558f8e4" }, { "name": "boom", "unicode": "1F4A5", - "digest": "ec26246935c99749950612d69c06435ccdc126f14426a48a7599c5b6b91d9d58" + "digest": "f5400e9583f7f997cd2385f21379f6229424a9b221445bc8f36c0bb64bdb3168" }, { "name": "boot", "unicode": "1F462", - "digest": "7ed639d52e285b0f46064dd4e1f4a8fb5814e1b2dc47c6f93cb349a6ac7ea97a" + "digest": "b4706ff35909a6fb759a3b8a797e90cb67ffc60e4853386a7d89ace9693a9364" }, { "name": "bouquet", "unicode": "1F490", - "digest": "b699f13af218560344f3571436f87b6f8c5c9f0fa0308836937667241b3fc7aa" + "digest": "b93751a27b40f6185a22b3e8b413f0fe09b6010d1057c672e1a23088e0b8286f" }, { "name": "bouquet2", @@ -887,77 +887,77 @@ { "name": "bow", "unicode": "1F647", - "digest": "5e260c38cfc80cd2f20ef78d982126dbf90934f7afa12c96d0b7b413beb6d4e0" + "digest": "33cd6da4d408f18d98bebc6a277dea8b914150e32ee472586ce3f1eb814462bd" }, { "name": "bow_and_arrow", "unicode": "1F3F9", - "digest": "1c23469256331ea4ff03c036f89f0e63ad3228c51faecba50129da99b7eaddf3" + "digest": "051b4d50ab21a68b8583a6313ec183e3e1e96f493b0f4541fbb888f0b95fdd4d" }, { "name": "archery", "unicode": "1F3F9", - "digest": "1c23469256331ea4ff03c036f89f0e63ad3228c51faecba50129da99b7eaddf3" + "digest": "051b4d50ab21a68b8583a6313ec183e3e1e96f493b0f4541fbb888f0b95fdd4d" }, { "name": "bow_tone1", "unicode": "1F647-1F3FB", - "digest": "d3ec7ef70b355ba310d6fae7130a4e4cd11526b6e219474b5678a2b3ba1077f0" + "digest": "995c8400ad60d5adc66c9ae5e3c0ecf56c48b478ad79418d45b6289933d25bdd" }, { "name": "bow_tone2", "unicode": "1F647-1F3FC", - "digest": "c2905c0feba15fbc533cc6b36038eeda30f729182aa544f1d9164f5ccfed64d5" + "digest": "af89eec2fccda99d9bdd373b2345595882fee1c0a15d29af9028089e20255325" }, { "name": "bow_tone3", "unicode": "1F647-1F3FD", - "digest": "298fc646d96c307eaa137c80b403d8355539ed8af13d3954a4ccacef67d341fa" + "digest": "015d8122abdf2d0caa03815545f50fb7a71e05dacd46aaa133cc9ace5192f266" }, { "name": "bow_tone4", "unicode": "1F647-1F3FE", - "digest": "27db8401aa62a2544b24ff839b332958b5e8c3ab3fd7a289d3c62c654705da60" + "digest": "e8409096a795b775def654d36aeccb8eb91e83d7d1b32145cd73fd0b7b9e885c" }, { "name": "bow_tone5", "unicode": "1F647-1F3FF", - "digest": "168cdf834edb54723cf1c32311d4117c288132c5f76d6c415726c7484158c52a" + "digest": "d87042cde8dbad9fb1a91a2ec60116e27b4a76388b5779d771a0bbae12a2814d" }, { "name": "bowling", "unicode": "1F3B3", - "digest": "0e888bcd1a5cc1ea7b07cea255ccb04dcdc87b0337b74cdc96a708aad7975768" + "digest": "737f2cdfa4ac964baade585a39771b18080bd5e9b55c8661d3518f468f344662" }, { "name": "boy", "unicode": "1F466", - "digest": "f349ab3e1015b4ccda5faab6a355f9c38e36e7c1cd667084563a14a2b11036ea" + "digest": "7bc0173d8c88f3f12d41f213f7a3a9f5ebf65efad610fd5a2a31935128a6a6c1" }, { "name": "boy_tone1", "unicode": "1F466-1F3FB", - "digest": "4d04a5e45c9f9749de580321a212e14304b4ffcd229fa971fb59d97e6124262f" + "digest": "c0e2f0483715b239fe145b0056566f7a3a722319d9a87c1e66733dff1916a19f" }, { "name": "boy_tone2", "unicode": "1F466-1F3FC", - "digest": "0c9d6b6b1b3da68b9ef1f0f01efa4d170a48cfc66de4f577f8669c160b81cc97" + "digest": "0001d0bd1ff4dbd898604ba965b4039d09667d955bc0349301b992f9ab6dd7fd" }, { "name": "boy_tone3", "unicode": "1F466-1F3FD", - "digest": "7dbecace78edb2aceffce6cb4d49ca132b93d80c26a8f1526a18832a2f23454a" + "digest": "e0f08755955fd2e0bd1c5d5e84429b2a234b24a744bb50bb9f1148495b2b29f9" }, { "name": "boy_tone4", "unicode": "1F466-1F3FE", - "digest": "49f9c633afa8ff81068c78717e0012f8936fb3dcdb8b57342410f57f0635ae7c" + "digest": "04b6bfee58a26b1ce2e5b403504a7033aaf395f03f5cd23e824f32c90c395fe6" }, { "name": "boy_tone5", "unicode": "1F466-1F3FF", - "digest": "17e2ec379c7b542e6c2c5deef992af5f1fbaa3e288d1f71c8c984fb91a698cd4" + "digest": "0f76e97237203950da36c737dcc6f56dcd6c123401a8c817a0636376c7f38ef5" }, { "name": "boys_symbol", @@ -967,72 +967,72 @@ { "name": "bread", "unicode": "1F35E", - "digest": "43697495538bfed11ed75213af8b1bdc14ef359d9b472cd7f9130fcb0a198680" + "digest": "81739830f16f33e6a1dd7cc17c25df207846062bb5167bb8abed7fdd49268b86" }, { "name": "bride_with_veil", "unicode": "1F470", - "digest": "37e75fbb2b0d06c900d51269b99107c60b61453dbf218b54df3011a455cd6dc3" + "digest": "8e24bd91c3f564cf6148f2b3b4a7d692c11dd059e76a13331fdfb04ae060ea70" }, { "name": "bride_with_veil_tone1", "unicode": "1F470-1F3FB", - "digest": "44072e54e0618d2675a5bfd6572108590e51e8e733381e091e8754ee96c2cf20" + "digest": "0bd2f16f72586f50e768b14b9b353f2e98ccbb2581a568c33b06be56e70ca063" }, { "name": "bride_with_veil_tone2", "unicode": "1F470-1F3FC", - "digest": "f0acd961e108db9d9dd5d1b06e708b2eb6a7ef7235d6c8678b9319077faf4fa8" + "digest": "e5463f811b2075754f0718b891757cd2e81071edf7af2215581227e1aad1d068" }, { "name": "bride_with_veil_tone3", "unicode": "1F470-1F3FD", - "digest": "3f7adddb41ead3cd07098799ab2a5b8e8842344307d9045264403fb685f20555" + "digest": "e5a053a26f7ccebae7eb12f638be5ed80f77b744708d783eab2eb8aa091cf516" }, { "name": "bride_with_veil_tone4", "unicode": "1F470-1F3FE", - "digest": "5f7199fd99319651f3a7b3553cc5387c59b65cac1eb020441e19b5c12c807dc7" + "digest": "410e23825e4401460946dc67a618bd3ace6e1a7c07dd88580a2349423685261f" }, { "name": "bride_with_veil_tone5", "unicode": "1F470-1F3FF", - "digest": "4b1f6c33dd72a3a11c764bb00e7be7441b39c7af78aae52141276a279d63ab78" + "digest": "454e87e5a74e13e5b4993541231516fbbe6dbe9f990e1a6f3f4a744d7d4c1615" }, { "name": "bridge_at_night", "unicode": "1F309", - "digest": "f81cc36de8edbdf3fe4d55932d5c6c8ad429487ec1f7af044611b6dc950ee09c" + "digest": "9d3cda5a59e27e3c90939f1ddbe7e998b3ea4fcacfa1467dea0edf39613c2d7f" }, { "name": "briefcase", "unicode": "1F4BC", - "digest": "a3c3e802191f3e131683dac1fcd81e294dea72af8e65c94972990924c79c5619" + "digest": "9d00d6a92632aaadc71b017f448c883b27eb31a7554ebb51f7e3a9841f0f7f2b" }, { "name": "broken_heart", "unicode": "1F494", - "digest": "4dee349274c2ea44d1c0395cbd39356b88897b0c45040aa40d8cb2607ee67420" + "digest": "c7ca53f444d72e596af46b61ffbc9e7c18a645020c22691e44f967db98dbf853" }, { "name": "bug", "unicode": "1F41B", - "digest": "bac4660ee8dcbef0023691804ee3fad3ea3d4bac20d847a5913cee6e7dca826c" + "digest": "0dccb1d5eb91769377b4c5b310f007b60f54a5c48ba9e467b3a06898a4831b90" }, { "name": "bulb", "unicode": "1F4A1", - "digest": "af5394230f95781c7eb8054b1a13732a6e6170318599c79e9ca2a816a5b821a2" + "digest": "ccdaa2dfde5a88a347035a94b9d4d86cfc335ce0a73292423f5788a4bd21a5a8" }, { "name": "bullettrain_front", "unicode": "1F685", - "digest": "59afcd289500bd4148b1b91f560a5ce8ac9e1b52eddb8fec857ff5d171f017fb" + "digest": "5195a6a6d23f28e1aa5ebac6ede0f6c6a8b7ff33a9edf034814f227fe976177a" }, { "name": "bullettrain_side", "unicode": "1F684", - "digest": "79ff8f579081a2f1c3b05311a18ca432adb026a7860875cea4a5460e49b2a474" + "digest": "96e74842e919716b7bbbab57339bfd70f099a9bcb4710dffd7c80cf38a7bbff7" }, { "name": "bullhorn", @@ -1052,37 +1052,37 @@ { "name": "burrito", "unicode": "1F32F", - "digest": "4babb1af1136ab2334d26495b0be779d0bcc9516fd956fc07ffde427d11122f0" + "digest": "b2cf81f1efdf87e674461f73f67cd4b58a5f695e65598d0dd3899f2597da43cf" }, { "name": "bus", "unicode": "1F68C", - "digest": "476e7a5e92f64038e5012205395efead51f1c10b3edb25380f38da97e2412edd" + "digest": "192850b762edad21ac8770df38b9cae6d2bc1697a838462f3e36066bfb4eee50" }, { "name": "busstop", "unicode": "1F68F", - "digest": "3bcf82872ab6abb0278238c71bd004a40c46696bdda05f54c153d45d6fe88f15" + "digest": "adabb1ec36402b33feb636eae3656e5a8b51ff1071bcb14125d8ab80d6d12d2a" }, { "name": "bust_in_silhouette", "unicode": "1F464", - "digest": "2230844993ab011fe2756a1aa3873ff7d5f7d888bddec408ba0b32e4f6003570" + "digest": "277ae43301f1e49e0be03c8e52f0dc7b70c67f9d146bca0a14172e0098f115e6" }, { "name": "busts_in_silhouette", "unicode": "1F465", - "digest": "d1c3cb6d437616834425a53621c0bc0a6b368d745dd9da2300a3db4543d57660" + "digest": "7fee96f1b68bb2c6002e47f2ed13c06baa6a3168441b9aca572db7ec45612f7b" }, { "name": "cactus", "unicode": "1F335", - "digest": "e87588e6548d201db903dc0523b3ccc83c6b559981d743eae1504ce668cd8be4" + "digest": "2c5c4c35f26c7046fdc002b337e0d939729b33a26980e675950f9934c91e40fd" }, { "name": "cake", "unicode": "1F370", - "digest": "3947783d128018f5e396602d0492cb5c31e8e8df98af01eda7cade71aea8d989" + "digest": "b928902df8084210d51c1da36f9119164a325393c391b28cd8ea914e0b95c17b" }, { "name": "calculator", @@ -1097,42 +1097,42 @@ { "name": "calendar", "unicode": "1F4C6", - "digest": "00bb700dd88efbc43bc64263491cdf77965130b1dc23f31e682905c3dfe4040c" + "digest": "9d990be27778daab041a3583edbd8f83fc8957e42a3aec729c0e2e224a8d05e3" }, { "name": "calendar_spiral", "unicode": "1F5D3", - "digest": "1dd5da98bb435c0c3f632bc0a5c9fdde694de7aee752bf4bb85def086e788a2a" + "digest": "441a0750eade7ce33e28e58bec76958990c412b68409fcdde59ebad1f25361bb" }, { "name": "spiral_calendar_pad", "unicode": "1F5D3", - "digest": "1dd5da98bb435c0c3f632bc0a5c9fdde694de7aee752bf4bb85def086e788a2a" + "digest": "441a0750eade7ce33e28e58bec76958990c412b68409fcdde59ebad1f25361bb" }, { "name": "calling", "unicode": "1F4F2", - "digest": "2375828085f2efd17b8a5ebb3cfec1e420190913328a7a0dd9ff0f67c7249ffb" + "digest": "acf668c75c11c36686005788266524a972fa1c5bcf666ff3403d909edc5cee91" }, { "name": "camel", "unicode": "1F42B", - "digest": "9ff789ab50b51cd9e7fdc7fbe8d6f913fda95dfd425949f97974548652a53ce1" + "digest": "5f927927a7ab1277d0dc8b8211436957968b1e11365a8bf535e9bb94f92c5631" }, { "name": "camera", "unicode": "1F4F7", - "digest": "d95192b9ba0f566d8874099125def031e15297d1306989ea9b6a49f7b9b56661" + "digest": "fde03e396822a36cd6ae756ede885b945a074395264162731ca5db47a3b39d80" }, { "name": "camera_with_flash", "unicode": "1F4F8", - "digest": "4db6fb3fdb9a004537dff97f4197c7ed87c9c978ba9ac562ed8bb7c1fa260d38" + "digest": "9afd380208187780f00244c45d4db6c5ea1ea088d4a1bd8fc92a8f3877149750" }, { "name": "camping", "unicode": "1F3D5", - "digest": "f0855dc78bf6f3d06b3c2fc19180c8ff23d9e22871658fcc26a8fde08d328a0a" + "digest": "a42a4ff9521affa72db7b0f01da169b4cb6afb9db1c5dfad47dd4c507bfc30d9" }, { "name": "cancellation_x", @@ -1142,47 +1142,47 @@ { "name": "cancer", "unicode": "264B", - "digest": "b990f85e9f62017d99526244eaef5c5e56f8808698011e85d44de1d2ed87f1a2" + "digest": "528c6f21df99a756b553d93a7f395b0f662b30a323affd05f0cedee8ff7b41d6" }, { "name": "candle", "unicode": "1F56F", - "digest": "5eefd555951e65298583009a307acc6fb6d02c88325ef3adf231717e75e5a333" + "digest": "211c04dc3a91b071c284d4180ed09f9d3320e3fd6ba8a9fddd0677bc97fd12cb" }, { "name": "candy", "unicode": "1F36C", - "digest": "f14203c408173fbb94b4ee69d6de67226a17dc51b0cbd776f62623ee03fd2eb3" + "digest": "9cff4538918f60f770fceb96e964f5dc3ce31fd08ddd2ab3bfdf2981bfa74100" }, { "name": "capital_abcd", "unicode": "1F520", - "digest": "2a7cc876218b8c244b9802448ee25ce5004671a4f00ea950a636d8c3b766dbef" + "digest": "a416d0b3f564037b680f801fb773b6eaf67225e2cbbfd2cb8a5db0de044321fa" }, { "name": "capricorn", "unicode": "2651", - "digest": "03a5fd064c10f47c7fd0ae318c573bb559c269b1b2d61b45aa5b8ce9b5fbd9df" + "digest": "f11abad102603737b55486fe2ea4d01f28b203394bcd84f19a7948156e6c4b96" }, { "name": "card_box", "unicode": "1F5C3", - "digest": "7d760ae1d44e6f4b2aac00895ca86b5743f8b5ca157ec2bd21ce2665e50ad23a" + "digest": "7a6199d562f30e02ed31094de6aebeb99eae8ac156f6910463dfed73256f4c9a" }, { "name": "card_file_box", "unicode": "1F5C3", - "digest": "7d760ae1d44e6f4b2aac00895ca86b5743f8b5ca157ec2bd21ce2665e50ad23a" + "digest": "7a6199d562f30e02ed31094de6aebeb99eae8ac156f6910463dfed73256f4c9a" }, { "name": "card_index", "unicode": "1F4C7", - "digest": "150950903eccb468981c58b87ed7c1ba44e17f52627d695f660ce96b3d9d6e8e" + "digest": "86e187e0a72ca5d00207d6ef34d66ce15046848a831c2b5184fb840c5332a2a8" }, { "name": "carousel_horse", "unicode": "1F3A0", - "digest": "d6862085550fa139a147dceb1b2b9f950a08dcd01cecd8b8697f9c7992ca054e" + "digest": "c0e7059efc39a64233f774c02ddb1ab51888fff180f906ce13a6e4f9509672fe" }, { "name": "cartridge", @@ -1197,17 +1197,17 @@ { "name": "cat", "unicode": "1F431", - "digest": "002208c0c9165971853ee05cd05513175a913376a462a345a939d73401c6acb7" + "digest": "e52d0d3a205a0ba99094717e171a7f572b713a0e21b276ffa4a826596fe5cafc" }, { "name": "cat2", "unicode": "1F408", - "digest": "fbdb726cc035f83784dcfe2d9adb85f8aeec429064aed5c5ca0b8be406068aa5" + "digest": "46aa67a99f782935932c77b8de93287142297abe52928c173191cf55bb8f4339" }, { "name": "cd", "unicode": "1F4BF", - "digest": "bd4d4eef2cc0b1e4ee1f5280f922743e76f27d35836987801b2b48969eac17d8" + "digest": "16363d8a34b873c12df6354b99f575cae3d80e0d27100ed7eea70f0310953c7b" }, { "name": "celtic_cross", @@ -1217,302 +1217,302 @@ { "name": "chains", "unicode": "26D3", - "digest": "a6a915d9c361e1564e13cf2d33ad5df3d684aa349b8dc5909e6343d67401beb9" + "digest": "3884cdbc6f2b433062af06f942552e563231c24727a2f10fa280b3bb7aa614e2" }, { "name": "champagne", "unicode": "1F37E", - "digest": "77395d3afe5cc10bfdc381120bae2ae4aefdaa96c529536413873a696c5fa713" + "digest": "9e6e8987f30a37ae0f3d7dab2f5eeb50aa32b4f31402b29315eb2994afc72457" }, { "name": "bottle_with_popping_cork", "unicode": "1F37E", - "digest": "77395d3afe5cc10bfdc381120bae2ae4aefdaa96c529536413873a696c5fa713" + "digest": "9e6e8987f30a37ae0f3d7dab2f5eeb50aa32b4f31402b29315eb2994afc72457" }, { "name": "chart", "unicode": "1F4B9", - "digest": "9fd5f8cd99988bbe0fabc89a0b23e28d1468641d2f9468e82b7148a1948d8236" + "digest": "a092dbc08f925b028286b2b495a5f59033b8537a586a694f46f4c1e7c3a1e27f" }, { "name": "chart_with_downwards_trend", "unicode": "1F4C9", - "digest": "6fe456d76c0a996c12049057b5d60129098a9deddfa2d133cff5c4400e4595a0" + "digest": "5db7ccbc37665736a9c0b2f50247dcc09e404ec37f39db45b7b8b9464172a18c" }, { "name": "chart_with_upwards_trend", "unicode": "1F4C8", - "digest": "e83cc4cf4228bd77e030a19755b11cf75cf671f40973c23e240afa54d9de478e" + "digest": "bc4ea250b102fe5c09847e471478aff065ad3df755d9717896d38d887d9c6733" }, { "name": "checkered_flag", "unicode": "1F3C1", - "digest": "77501c2c66af31f72f5c05f21e87598cd59740b5cfc02926c66dc755bab3c3cf" + "digest": "0e77180e0cf9fc87e755a5a42cf23aec6bf30931db41331311e97ba0be178b78" }, { "name": "cheese", "unicode": "1F9C0", - "digest": "5897036ba97b557868bb314fcee83b9d8a609c8447b270a0b3d34a29ce7496d1" + "digest": "50a6cb906c2120e2bbc0e22105924262007cfe1554d7b02b8cc84b6adedc6a0b" }, { "name": "cheese_wedge", "unicode": "1F9C0", - "digest": "5897036ba97b557868bb314fcee83b9d8a609c8447b270a0b3d34a29ce7496d1" + "digest": "50a6cb906c2120e2bbc0e22105924262007cfe1554d7b02b8cc84b6adedc6a0b" }, { "name": "cherries", "unicode": "1F352", - "digest": "5a0ba73039e4b56e3d16a1c70ad992f41af7a16f6d5ba4b5337bdf338276f0ff" + "digest": "13b8db9e7e6eec8509aa80c762966e1bf3538fcb1ac3d6eab18ee4da1528cf84" }, { "name": "cherry_blossom", "unicode": "1F338", - "digest": "b40533225291f539ffe97e4ab1d70d07e179b2f9345b2814355164d0407cf3bf" + "digest": "af3083f5f8dd94936113f2e16caba5aec7a774d5589aa08bf5de82a2d278cc66" }, { "name": "chestnut", "unicode": "1F330", - "digest": "6a2a37899d28326daf36965b343b2646492c2c0cee8871321cc17315d6252a9a" + "digest": "9f85b79b207a69ab81ab88dcef04954000965b039b4cf57de5f1b381745ab98b" }, { "name": "chicken", "unicode": "1F414", - "digest": "13d770684a11ea10c0ae7570a98c5dfafd4bfb78ac3f72f46729aef9060b85c0" + "digest": "57ceb4459d183740009caac6ebed089d2f1e12f67c138e1be1d0f992313c0ac4" }, { "name": "children_crossing", "unicode": "1F6B8", - "digest": "654d2502c1edc57c5ab4237df76db3121f6b8735eb13d30bffd305605a083445" + "digest": "0ded7d9aca0161e8ef8e2858c3c198e70e4badc7105ac3a6886e06975de19106" }, { "name": "chipmunk", "unicode": "1F43F", - "digest": "1ae3c838450afcbbe8a96992481dde252e343ab83546d0789ebed81a78ca9188" + "digest": "5b0dc1a859163097727ba2ba5ffca38b0a54d925eebb089977d28d0b4d917a3f" }, { "name": "chocolate_bar", "unicode": "1F36B", - "digest": "2486b7265048eb2294d6be0a0a8a4d6067df95721ace9d131d8f715a27ba8cf0" + "digest": "dd273e5050488acaf885f8a18b6e2b3901f69c5b39fa6465fb60621783d4109a" }, { "name": "christmas_tree", "unicode": "1F384", - "digest": "454c08870eaa84283c19731ed3b10c4868d2e2f0cc44f2feba0de9ba4cc9c4e1" + "digest": "ce60cbe2ebbe8057be8edea2392455fedd2bcda64a0a831f6a1942028af7e747" }, { "name": "church", "unicode": "26EA", - "digest": "b62e838ffb0dfefeced1707359437b6815e0721783b549212282e08617402f6f" + "digest": "2c328456528f7336e59443e20ec3ab22fe71f1fccb1dd50d0ad68eb206937557" }, { "name": "cinema", "unicode": "1F3A6", - "digest": "6df56f6a0008d0352740d1e045ffdb702e80c2a6d88b6db1a8bcd27eb3c12dcc" + "digest": "4c26dcdc76f93dbc2a1dc49ed4e132b8e8f2b7cdc1acf5e09b3dfd99430d97cd" }, { "name": "circus_tent", "unicode": "1F3AA", - "digest": "f8b7a7f4cf4f9efd20423acc30abb3a28e2a5183b3e39f5cc88e7e0ed7757d64" + "digest": "fec5f2a06222be8be549178b29720343cc00145177ec387ca4e6f3432481fe77" }, { "name": "city_dusk", "unicode": "1F306", - "digest": "8779066dc9386d05c951b1df1753983c2937a5f3b84d5fc09ed0b172d4ef914e" + "digest": "bba345e949dcc51f5f018220f000223797970c82ead2ab9c822f9dc0847aa155" }, { "name": "city_sunset", "unicode": "1F307", - "digest": "c2530d12204eb518c5a3c8d7deba11170b1412fdf406aea05a69d4c026210d1b" + "digest": "a846df1a4c7c778f8e1729804aece86eb29d2fcb95dc39eaaf2aae1897f3dcc7" }, { "name": "city_sunrise", "unicode": "1F307", - "digest": "c2530d12204eb518c5a3c8d7deba11170b1412fdf406aea05a69d4c026210d1b" + "digest": "a846df1a4c7c778f8e1729804aece86eb29d2fcb95dc39eaaf2aae1897f3dcc7" }, { "name": "cityscape", "unicode": "1F3D9", - "digest": "15251a708d50fc721bd67d8abb2a517c0bade196df3b736e21d79191d749241f" + "digest": "ee360be7514c4bfb0d539dd28f3b2031ebcef04e850723ec0685fb54bd8e6d5f" }, { "name": "cl", "unicode": "1F191", - "digest": "104591d8e7b980cf38dcf8326d36c845384b7a4e6d94c49f36e9946484712a95" + "digest": "fcec2855dbad9fda11d6e2802bc0dcaabab0b5be233508f5e439f156f07602c1" }, { "name": "clap", "unicode": "1F44F", - "digest": "ed6ef8bb78ca1fa295b87222c440c6d5ba4f154f2752bf0d428941260d66aaac" + "digest": "a1860ce7812a9f6fb55e45761e1b79a2f8f0620eb04f80748a38420889d58a2a" }, { "name": "clap_tone1", "unicode": "1F44F-1F3FB", - "digest": "57a1fd1fa2578c30b8a47abb84e81af5f5bbc6c301a5daf0c53d4d07b017e777" + "digest": "18a7022e08223fb2109af5a9b9a5b4f47dc870ce4453f4987d2d0b729ef54586" }, { "name": "clap_tone2", "unicode": "1F44F-1F3FC", - "digest": "2ad4dcd513e55486f21151bf3792e1febf116574d238545b07b4290901430fdd" + "digest": "5954c8658b15e755d2018d8674df84d38e22ffededc4d726c6a33b709f71426a" }, { "name": "clap_tone3", "unicode": "1F44F-1F3FD", - "digest": "2d8c705d4fcc162fb65cd51e2c6683f1129ebc72fba13343533f64ede1c62687" + "digest": "22639b6bd3c53784a2f855d6db7bdf31621519f19dfc29a6bc310eee6421f742" }, { "name": "clap_tone4", "unicode": "1F44F-1F3FE", - "digest": "40ffd41b2b4f59d0040e9d20497e57c4e47f18aeae43fcae02be5c2f50069102" + "digest": "e55248dc163d1bbd118b50cd8767750ead86d082151febbc0a75b32d63abceec" }, { "name": "clap_tone5", "unicode": "1F44F-1F3FF", - "digest": "be55df1ac7600ba086c2ef6ea223ebc62271fa47876c53ade1a1c0151fdc994c" + "digest": "76046b8157dabbe048a07fc318122456020c9c980fc1b8ab76802330e07b3b53" }, { "name": "clapper", "unicode": "1F3AC", - "digest": "a8748398f56fd2c1e6e87fe0c77edec444df7c7dd462d43dbcea6d8de97c81c5" + "digest": "8149752a0e3e8abede2d433d1afab6d217877d0c76adb1e2845a0142c0cdcbaa" }, { "name": "classical_building", "unicode": "1F3DB", - "digest": "6a607b0666141b51d6e944b04f3f6188a5c026396e6105f1d2a5e6b6350cd66b" + "digest": "9ee0d00c43d6e22b6a3ddea67619737270cc7e9294797a19c7c60d5f92aa44fa" }, { "name": "clipboard", "unicode": "1F4CB", - "digest": "4ca1a0b864a962b111d6bdb65373b779f3fff571ffd32d029666f9b708e1ab73" + "digest": "bdd7f7d973c714e59d2903d401a876e6018794c7987c9ca57108c137c5edc25f" }, { "name": "clock", "unicode": "1F570", - "digest": "c48314ccde8bf01acc2b1bc9a6b5aa7d796fc0c8769f80398bc74545fcef31ed" + "digest": "302835eab2637db799acf69b3d795571ef3432251267050db0704f2954e8b190" }, { "name": "mantlepiece_clock", "unicode": "1F570", - "digest": "c48314ccde8bf01acc2b1bc9a6b5aa7d796fc0c8769f80398bc74545fcef31ed" + "digest": "302835eab2637db799acf69b3d795571ef3432251267050db0704f2954e8b190" }, { "name": "clock1", "unicode": "1F550", - "digest": "c0550fa0c385920cbdb775bdaaa5e812097a484c4a32e35ebbafe3a364a4a438" + "digest": "1778eec07ce061c9393e5abee5ca83b24e1ce61d8a75fa2e39efcb31aa160395" }, { "name": "clock10", "unicode": "1F559", - "digest": "25651ac5520505f326457364428de3679cc22ca57278d4c54cc4b60420fa7b74" + "digest": "601fc12ea5280a54c2e69dbb685f454e4165fe771756ed6f89016e29e683a24f" }, { "name": "clock1030", "unicode": "1F565", - "digest": "dbf682bac968fc5a3959af2b96eaaa5ee78306f6341c43c1345b94bc561a3d04" + "digest": "4fd155f08f797542d52cff4b0aa3ca9f080f37a41c301b82f90ff6d4693c890e" }, { "name": "clock11", "unicode": "1F55A", - "digest": "333732dd6c3184f257964bcf5a20a6111f9adb04560b5d12dc613636e846df5b" + "digest": "5c79dc812e812e8a01993ea633b323d654ce3a7ea258692781a4896e4ad2017e" }, { "name": "clock1130", "unicode": "1F566", - "digest": "005999cb37998adea1645d7df63b2705a42db3b4f1a734891d79af3e833764ff" + "digest": "41497ee2020ee5ac9aa5f9b07560f7afca7c422b04214449cfc5cea9f020f52e" }, { "name": "clock12", "unicode": "1F55B", - "digest": "6690e591bec1751e1c5472e0bf52f66779b2113e5b8c6c578e65dbb83d091b16" + "digest": "046bb7ffa5f5d27c2e3411ba543484d9dabb8ebf6d6e7a7e9bfb088c1813500c" }, { "name": "clock1230", "unicode": "1F567", - "digest": "549f3921bcff7f330c5a41e6756d8c15601f1f8278b35b369148771c60be2a6f" + "digest": "bbfe9db5a2043aaba19a7a2a0185c7efcebf1e8c9263b8233f75b53c4825f0f4" }, { "name": "clock130", "unicode": "1F55C", - "digest": "9332ef07a9dde8ccaa1e58a3e97edee0601a1152fc6d351b782816c838d2a408" + "digest": "8662cb395ee680c2781123305c4c8ce8c0df9565c2c942668940be540cc0c094" }, { "name": "clock2", "unicode": "1F551", - "digest": "9d1ec8fbdae627880e1c067c10d6a40f1e4494a246c77224b3cd7b287554c4b4" + "digest": "42f7429748b612dce7de77221cbbc710655811f7bb23e2a986c36e6d662f0ec4" }, { "name": "clock230", "unicode": "1F55D", - "digest": "3578a39c28695d4e617a648a1eb44e0bb5a8a11dcbe04fa2eb2aea0a60589067" + "digest": "e710b6ef14227cd240ea3e2a867c8ef45b5c060adf3cb30ba9077c2351fe6677" }, { "name": "clock3", "unicode": "1F552", - "digest": "c2e2a27301b6ac27dc359be590448eb1e65fe87211f1af30a473d8bde4f3db47" + "digest": "7340d465b398a378211dff9ec806db579d061206fd6fc238623d070cfe0a55ce" }, { "name": "clock330", "unicode": "1F55E", - "digest": "7a77cf8cf9a98f4767a2dca1d3795be45938eee185db81120d85cedebe128899" + "digest": "7aa4a15cc8de04ed3bdeb0f8a54a7915065f2809a07054e002d89926c9766831" }, { "name": "clock4", "unicode": "1F553", - "digest": "0945c4199400d546350cfff25bc9e9160789d1cf9890b3318bdc462ac6cc9782" + "digest": "36fd88e81ad488b0ec49a911a838693281573fa14736ae4a6dd1c40a4ff69bb1" }, { "name": "clock430", "unicode": "1F55F", - "digest": "9fdb6f1fa076c4c6a395dbf6db27499ee447b3558f3aa64d913686c360e428a8" + "digest": "7bd5dd71e89d95dcf18b9e8c1fe2a353a7da3b69aadb8dda80ee9bafb05da58d" }, { "name": "clock5", "unicode": "1F554", - "digest": "855b3500eb6d20bb6e51d3a6c9d1a5131c06404c6c149841c7cca52201036428" + "digest": "aa406409e56a0bfd8c850e44efe45fd190ffd7bf7061e934ed7928dfbdfc9eba" }, { "name": "clock530", "unicode": "1F560", - "digest": "a6ebd9f884d45a1f43650351a1f1da9724bc044d7da2f6d99ffb3d1fa0c31c5d" + "digest": "25dd3bcc53ddd98eeea498d7dbd4c306ef39dd033f15909063388a0800febf41" }, { "name": "clock6", "unicode": "1F555", - "digest": "e38f9fc4f87f12ee602dcf2285d59dbc343fc0fc37662992cfe9866c20f58e87" + "digest": "0a321eaf1bc5db8436bbadac66c45ba257fc98ad4c7569ce3fc6602c824b6d7c" }, { "name": "clock630", "unicode": "1F561", - "digest": "735954a650791fc38c845c43998023e652d36e55534850e43952878b8804b2f1" + "digest": "55a4c5a665fdd38a724e9357a93c55401fcd5f1b13078c25754bd70c3fc4ccec" }, { "name": "clock7", "unicode": "1F556", - "digest": "2c4244ec4019e9624e6ea5a751bb735ab87bead33b1ea160265c81bba3c2f736" + "digest": "6154306545716e865da0ec537ee4f22bfe6c7294502a64a2dcf425c587d0e2a2" }, { "name": "clock730", "unicode": "1F562", - "digest": "0bcf20e30be1bb23394696770301867e307f8e5014e0ed7d75ed96efe34d625d" + "digest": "6925654de642e50f84661f94364a96c87757d73fffe766aacbf4bbd70130547b" }, { "name": "clock8", "unicode": "1F557", - "digest": "af454047a1765ef1c8355969302a826d4c47f5c61a6ec47fdec3510a8003b0d8" + "digest": "9be2d189c7ea56d39fd259f84853d753c1cf33e64f8ed57f86f822d9ae23a1ee" }, { "name": "clock830", "unicode": "1F563", - "digest": "e48b81dac055dc6d5f7832cf34368329c573d03b35bfe076fed1c6e6d48a82e7" + "digest": "16878613c0000d2f558c88d080551f424a8bd9df1358e0f931dd25c3da68f2d9" }, { "name": "clock9", "unicode": "1F558", - "digest": "f2a3d1bc029dc0e6406cdaa96542e77503e4cfb79d99c69cb454b8cf635a73fc" + "digest": "1d1e7e3c9d085ffa5b7c0f3d9fd394b734f16ae3b60df09af50fe6c8d4f3c8bb" }, { "name": "clock930", "unicode": "1F564", - "digest": "bb1b2b83052e8e6fb97c48c13bce0d950907e044eb2dabf21d7fed321f75110b" + "digest": "9fdef6a4939315c017b165e1dbac7710fb335df8c309be3fe2a011ef7fc28d74" }, { "name": "clockwise_arrows", @@ -1527,102 +1527,102 @@ { "name": "closed_book", "unicode": "1F4D5", - "digest": "afd6dae5fa0f59330fc2adb922e92b3410a33a80a2667651718c7dac588010bc" + "digest": "b18288629d201bfdfc5d66ec47df89809d00642b15732757e6a04789f36a7d9f" }, { "name": "closed_lock_with_key", "unicode": "1F510", - "digest": "d0ed5c00f939111ce86f9c741b733b22e04ebbd871aa33da3eb0f46a6f38b707" + "digest": "e39adfe9b30973bca16472c2b7e6462b064a93b9d452aa48edd74c727641a83d" }, { "name": "closed_umbrella", "unicode": "1F302", - "digest": "3ef08b299f9170007a5433fe82d0953bf0f75b6685d0ce58972f9af032dc471a" + "digest": "2cc0592c74601f7439e88c3c1ec4f05e3459608ef1ea6558c5824ed7c3889727" }, { "name": "cloud", "unicode": "2601", - "digest": "d1e7932551e85c6e86bfb3b41f0c936a6d0953bf9f9119b8cca3eaed22ac0c01" + "digest": "5b3a19718dfa8a381929665afdc2284464d24020c8dd0caff4dad465a1f536ba" }, { "name": "cloud_lightning", "unicode": "1F329", - "digest": "fc9c85cc95f9c456635692c974f72b6d40e14943824b8129a21c47265c3416f4" + "digest": "2b32f6d87726df2935ad81870879ccec30ce9b4fd5861d1a6317f9eca2f013d9" }, { "name": "cloud_with_lightning", "unicode": "1F329", - "digest": "fc9c85cc95f9c456635692c974f72b6d40e14943824b8129a21c47265c3416f4" + "digest": "2b32f6d87726df2935ad81870879ccec30ce9b4fd5861d1a6317f9eca2f013d9" }, { "name": "cloud_rain", "unicode": "1F327", - "digest": "f4406e62ed98f6141ab70736f6d5c540023e805396db0346ee6b7082c3f5e8e2" + "digest": "1e1e8bc59e168e1d2e72bf11f2d43cb578cbf0a5f1daf383bba5c56fb750ee71" }, { "name": "cloud_with_rain", "unicode": "1F327", - "digest": "f4406e62ed98f6141ab70736f6d5c540023e805396db0346ee6b7082c3f5e8e2" + "digest": "1e1e8bc59e168e1d2e72bf11f2d43cb578cbf0a5f1daf383bba5c56fb750ee71" }, { "name": "cloud_snow", "unicode": "1F328", - "digest": "948990cd13dd927917208c026089519fcf8e258a8a284684ace67c9a2f9a8149" + "digest": "2d364f859b83e684213e8eece1640208d80a8de0a49d0fc8e0e24c5a8493a3b1" }, { "name": "cloud_with_snow", "unicode": "1F328", - "digest": "948990cd13dd927917208c026089519fcf8e258a8a284684ace67c9a2f9a8149" + "digest": "2d364f859b83e684213e8eece1640208d80a8de0a49d0fc8e0e24c5a8493a3b1" }, { "name": "cloud_tornado", "unicode": "1F32A", - "digest": "44753516d0bd05d47cfa6eb922aba570ba6a87f805f325772b2cff071460ead1" + "digest": "7cbed2343c280ba3996082b3d0fb9d8cd57d6e62fe6c9ecb159f46b4a2e49151" }, { "name": "cloud_with_tornado", "unicode": "1F32A", - "digest": "44753516d0bd05d47cfa6eb922aba570ba6a87f805f325772b2cff071460ead1" + "digest": "7cbed2343c280ba3996082b3d0fb9d8cd57d6e62fe6c9ecb159f46b4a2e49151" }, { "name": "clubs", "unicode": "2663", - "digest": "5fd19fadd3b0887a6a59819ffbbe33a061055c043200700c31be30e14a5d36d5" + "digest": "b8cf72ecd8568ced077b475d94788fb282bdb06d25031b5d54dd63e25effb138" }, { "name": "cocktail", "unicode": "1F378", - "digest": "cf096ebe15b4053702d490cd96f04d565b4993529bcd6d8d50cb821200d1cd92" + "digest": "3792def2cde885cf32167f04904d3b0b788388e8af410c63e4cd31550feba775" }, { "name": "coffee", "unicode": "2615", - "digest": "6ea6128e353d9f74aee99caaaaa30c53f996fb242bf3bffb0fa92e6b4d373e57" + "digest": "0d29615a7a67d3aafa257b909bb915dc74fa8f854acb0d9a2c29e94eedf80326" }, { "name": "coffin", "unicode": "26B0", - "digest": "b59772d7aa262c4d7433f9cdf76d50011f4c63421b730c8ab4a08675f730c39f" + "digest": "78eccc1aad2a822649fba8503d4d30354bef367c4271193c40ddb692308f9db8" }, { "name": "cold_sweat", "unicode": "1F630", - "digest": "f0d0057bf01db8d930f6e4632c5bf8d0b1bc709bcfb6463a1f1973b5f1d70a83" + "digest": "f53aab523ed3fa2224a16881d263fb5e039f163380f92feb2c63c20f9b14dcd2" }, { "name": "comet", "unicode": "2604", - "digest": "00252ec55d1846d95c8d4c704b35251232d9810029fc215a7da08262dd1f3541" + "digest": "40ce93e55c6e57a88d80670b37171190bd5ffc87b7078891d8de5b15795385c5" }, { "name": "compression", "unicode": "1F5DC", - "digest": "432fbe66e5e3c38ebfeb4eb03465667a1e1be868b4afe510ec95eadda6481bde" + "digest": "c8841f7afb5345f1c31da116a7fb41d07232ea58d3f7f1a75c5890aa1a80bfd6" }, { "name": "computer", "unicode": "1F4BB", - "digest": "99777be010488867c7872b2e235be7c35b1a6f28d92baa921b61ced5491c0257" + "digest": "c970ce76b5607434895b0407bdaa93140f887930781a17dd7dcf16f711451d93" }, { "name": "computer_old", @@ -1637,237 +1637,237 @@ { "name": "confetti_ball", "unicode": "1F38A", - "digest": "e77d0c0970d3d12e123e548639fc0fa3ce41668667e4be55baefc09dfaa22cb0" + "digest": "a638b16f1acdbcf69edf760161b1bd7ff1fd5426c5b1203ad9d294dcc0701f10" }, { "name": "confounded", "unicode": "1F616", - "digest": "0f51db64149151d3d7ae5dce08c9af3d064123524fa36fe1f51a78cbd966b6ea" + "digest": "e2ff3b4df65d00c1ca9ae0cb379f959ea2cecefb3d676d4f8c2c5f2c103da4f6" }, { "name": "confused", "unicode": "1F615", - "digest": "ed23587432c1be98356156784ca4fe0b374b7b3b371660d45cfb0a1efd44e322" + "digest": "118d7f830ec08a3ac4b798eebb77a989b8c142f2588727181be4a2548e3c4f06" }, { "name": "congratulations", "unicode": "3297", - "digest": "2a46d640bf24fd4dc7649baf4b28c4adb30eda8d24d70eda07036c85b48195e0" + "digest": "02fd1338c54fe5f9a0fd861f23c56edc1d39bcd3140b68f0f626f9e2494d2d1c" }, { "name": "construction", "unicode": "1F6A7", - "digest": "73fac9fb5eb91954b0f998f9d05fb953241eed988c134fa42477393159fa34fa" + "digest": "c3a0401331111b9eda1206bee5f322db80b0870547d307b10dcac1314e4078c8" }, { "name": "construction_site", "unicode": "1F3D7", - "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" + "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb" }, { "name": "construction_worker", "unicode": "1F477", - "digest": "2be436fa7ad0a31e328fc6f776044bd1eec35c99541ced891792e3bef738d0a0" + "digest": "8c094733987e7c4da8d3aa4588b530ae07042bd70cf337b1fd412a70ee8f0ed6" }, { "name": "construction_worker_tone1", "unicode": "1F477-1F3FB", - "digest": "172cebc84f91237a85292c5ab0a105cc3abbb96e7423c4ae81feffd00bdb3b26" + "digest": "fcd927405fef4486105cd3aff62155467d21cebbc013924d4b52b717b566602b" }, { "name": "construction_worker_tone2", "unicode": "1F477-1F3FC", - "digest": "3e9b96ddfd639eefda99ad3a0ad26a28a0f2c8be72988c2bdbd648e6104638b6" + "digest": "d1ec773828936c703dd6e334e696dc3cf7c34c0a8ec691564a384b735cdeaaba" }, { "name": "construction_worker_tone3", "unicode": "1F477-1F3FD", - "digest": "11f83c565168dce5ac2387b873769d85ec4087171d6e92fc766c209ea06cd4f3" + "digest": "37c114d6879b9b32b800b0d4cf770dcbe04d1455698130ecd709a0cb9dea880b" }, { "name": "construction_worker_tone4", "unicode": "1F477-1F3FE", - "digest": "09e320e78e3a2940f0c5a0ef9a235ab72c51e053fd8ff433843fdb62571c8e70" + "digest": "5264996c1bedb6061a0dfdddce233d863bf308d27127ad152b63bfd983162cf7" }, { "name": "construction_worker_tone5", "unicode": "1F477-1F3FF", - "digest": "7ac2a1a0038e7aefea889380be604a98255823587e90799165f7db39dd03a0cc" + "digest": "87051aec81fd5dfd4dc44ff0411a528ee08253e9494d37efa550694e28dde6d3" }, { "name": "control_knobs", "unicode": "1F39B", - "digest": "9f10e578b410ff6aa7cc7fe806a0f1181893765303c0ca3867b652f1392a8a22" + "digest": "0d7f33ff7acc1cc3a81e6a786ff007df20da145e3070f338505dfed5100e9fcb" }, { "name": "contruction_site", "unicode": "1F3D7", - "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" + "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb" }, { "name": "building_construction", "unicode": "1F3D7", - "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9" + "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb" }, { "name": "convenience_store", "unicode": "1F3EA", - "digest": "1ff4351e4a4503f58ed5d35074a2112c681337e35ffe55332187481685573606" + "digest": "975dcf9b8e9e3fb1e29574b41300b9d96fd64703b3c18ff52f9f1875d1cf1b52" }, { "name": "cookie", "unicode": "1F36A", - "digest": "5c78ce2e721b0a3767d6ce0b59c1e88fdf94a7edc94e98c4d6b7aadb5b2aeea7" + "digest": "4bed3522bd50091ac5b68ca760661eb484d7f1b9c9d564d2097bd812b7f28ae4" }, { "name": "cool", "unicode": "1F192", - "digest": "54a96697a5070388ce8364a5ee2e0d78a53acc8b4f6755b1359fd67252cc41e8" + "digest": "5739a37341c782a4736adfce804e12776ae33081098a3d052d8ae9a64b4d22d1" }, { "name": "cop", "unicode": "1F46E", - "digest": "16bee252c2a133bcf57f6d7b8372a61364744a2f662acb90e2005732555135fa" + "digest": "78996521bbe231d03ebea355226d8a1515f47cde7b2fbeca1037e7b7e5133466" }, { "name": "cop_tone1", "unicode": "1F46E-1F3FB", - "digest": "2fc52f3ed735e327d12dadb15f9feb7b7f720fc6857b551548a2a84809053817" + "digest": "8a38cd107f5f4c0b821ac43f32df5dc57facaf39fbafb98483ec00fd7df41baf" }, { "name": "cop_tone2", "unicode": "1F46E-1F3FC", - "digest": "6208f3174ced4f07ba3820ba838b247d7438d69d86eb04927333e7436e56af7e" + "digest": "8ab8ab086f3ff82aa4bf4760c3c822846ec2696c41d21dffdac12d5afbe398b7" }, { "name": "cop_tone3", "unicode": "1F46E-1F3FD", - "digest": "2427d30bdfe127be4d8c3870472cae191eece142c784a5c2809df938f43e7c53" + "digest": "fce710a99fd44a7c8af3ea01b2007e46d3ff38d7a0dff1ef26d6f893ede7e6d2" }, { "name": "cop_tone4", "unicode": "1F46E-1F3FE", - "digest": "6e73f8abdf816f3cb2728b971a5a8d006a236c1d71b2ee1788ab60329f406323" + "digest": "3017dd73ef475379911c5e6c79bd0f9f533dbbc5057bce6a11244faa12996ba0" }, { "name": "cop_tone5", "unicode": "1F46E-1F3FF", - "digest": "4b146465cc95ade7e9ca722e31a1b06311214dae8f7f4d95c6329d56c45b451f" + "digest": "a3b8807b3f2a8d6ee9bcec0339355bda486e8c930f727139f5447a4b046a6307" }, { "name": "copyright", "unicode": "00A9", - "digest": "8143583821085dfc8ac21079fe220288ba3a3b6ca3014dc5dc98b18da77589c1" + "digest": "cc28663cdd3f8333d9bb57b511348cde4e51bda19cf0629dccb05c8fc425e079" }, { "name": "corn", "unicode": "1F33D", - "digest": "0160502226b5f9af81763545f288dbbb20632039d7509f347c751cfdb49dc5b5" + "digest": "a099a0b291fa758690e6ee6c762b9ade9a0e3350a707c52d968dfffbcc467de5" }, { "name": "couch", "unicode": "1F6CB", - "digest": "a93fffed194b404200495abda8772bb35539cfc8499eb0a9bf09c508afad6676" + "digest": "84cd734dbaa7f9f519438036d687e7a53217130779bc3de30258f163521b9474" }, { "name": "couch_and_lamp", "unicode": "1F6CB", - "digest": "a93fffed194b404200495abda8772bb35539cfc8499eb0a9bf09c508afad6676" + "digest": "84cd734dbaa7f9f519438036d687e7a53217130779bc3de30258f163521b9474" }, { "name": "couple", "unicode": "1F46B", - "digest": "97fe611a613216a1788f9bd88a9deb4714ee123a66b5fd3d0ac916fbb4da7304" + "digest": "c897ba76e24e2f43a4aa261c2754800a8473f43c7ce53f9909a6af2c4897732a" }, { "name": "couple_mm", "unicode": "1F468-2764-1F468", - "digest": "3ae6fbf3ba168256ea85c756ac1e7b83fdb8b780d33f06128ed80706ff627eea" + "digest": "c812471d35d46e12270653039a907d1dfa2dea0defd65596283e5b8e03cea803" }, { "name": "couple_with_heart_mm", "unicode": "1F468-2764-1F468", - "digest": "3ae6fbf3ba168256ea85c756ac1e7b83fdb8b780d33f06128ed80706ff627eea" + "digest": "c812471d35d46e12270653039a907d1dfa2dea0defd65596283e5b8e03cea803" }, { "name": "couple_with_heart", "unicode": "1F491", - "digest": "d9701173a5e8dff052ab6a15a42494dbb61dc7146d3734c82916abc9c05f76db" + "digest": "420bfa81bad10365550c77a98e1c07eb00d03663fe7b610fab1aca8a0a9d201b" }, { "name": "couple_ww", "unicode": "1F469-2764-1F469", - "digest": "d2a2ec29c1a1234ea0aa1d9fc6707cf8be8bb36ea8b92523ffa1c3071bcf0b06" + "digest": "7ac49153a612d63302299eee996308b7dcafa0a152473dab679215036fe6567e" }, { "name": "couple_with_heart_ww", "unicode": "1F469-2764-1F469", - "digest": "d2a2ec29c1a1234ea0aa1d9fc6707cf8be8bb36ea8b92523ffa1c3071bcf0b06" + "digest": "7ac49153a612d63302299eee996308b7dcafa0a152473dab679215036fe6567e" }, { "name": "couplekiss", "unicode": "1F48F", - "digest": "e722730de82397da7c8f88d79319b391e8f01fbe4a9133850cc92ad34e77bd82" + "digest": "1acfef9d375c4c1deb235babd856b0f90ad4f3194751694cb6abb44f00f29e42" }, { "name": "cow", "unicode": "1F42E", - "digest": "dcc1efef2f02588806a156ed43da959c587d4c576ff6badec77f820ed3ba507f" + "digest": "d71c854ff8b343ee24b8c2b9d56c7cb3fc6fa1a6dc0d7a137841b9f646e6d71b" }, { "name": "cow2", "unicode": "1F404", - "digest": "dcf59f92fd0a37b2ca720bcda606defa4357b58d8f4ad15c1288ad8d814b2bc7" + "digest": "e7a5131d7dee0f3356814b0ac1ea8ff280b12a7b580181e20ddb0b7eeb7e7339" }, { "name": "crab", "unicode": "1F980", - "digest": "59d34a4e92326ebeab188d9e33b25c20f4d54d187c274713fa3256b03b9e662a" + "digest": "e6be16699fdb5d87f42f28f6cc141a44b7ffd834ecdd536813c4b5b86d3fc4a5" }, { "name": "crayon", "unicode": "1F58D", - "digest": "0f3351c2e68a8d47d27b45a9901be6160de0f9a291bd8680df84d0fc679bcb31" + "digest": "b180d6afa4777861222a4228164ce284230fe90c589f52ffa9351bac777e901a" }, { "name": "lower_left_crayon", "unicode": "1F58D", - "digest": "0f3351c2e68a8d47d27b45a9901be6160de0f9a291bd8680df84d0fc679bcb31" + "digest": "b180d6afa4777861222a4228164ce284230fe90c589f52ffa9351bac777e901a" }, { "name": "credit_card", "unicode": "1F4B3", - "digest": "708c0e7008e06e5d1b3b4e68a7e0ada9f4ae22ab6c28285d81a340f913fd9a84" + "digest": "808cd120fd3738eb2be1f6c6c029d98387b0e03fca7d1451e8fbf9c5ab3f643f" }, { "name": "crescent_moon", "unicode": "1F319", - "digest": "0959f838a410e8bfeebf00aa9658df56e515dbd2361142021071e17244662bfc" + "digest": "042e7e01e6e88b97a763b7cc41e2a2b3fe68a649bacf4a090cd28fc653baf640" }, { "name": "cricket", "unicode": "1F3CF", - "digest": "00eb11254e887c71db5e8945ad211e9e0280f1e02f4b77a4799b64bba2bbe9b3" + "digest": "4c4559d0b4efe24cc248fa57f413541307992e519d0cb9fb8828637ac2f4cc16" }, { "name": "cricket_bat_ball", "unicode": "1F3CF", - "digest": "00eb11254e887c71db5e8945ad211e9e0280f1e02f4b77a4799b64bba2bbe9b3" + "digest": "4c4559d0b4efe24cc248fa57f413541307992e519d0cb9fb8828637ac2f4cc16" }, { "name": "crocodile", "unicode": "1F40A", - "digest": "99abcb42264d40d2450aaca8c3759a019bfd600a311cf3027243f1ca200d4639" + "digest": "59cb4164c50b6bc9ae311ce6f7610467c1aaafa848b5fff7614f064715f91992" }, { "name": "cross", "unicode": "271D", - "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42" + "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653" }, { "name": "latin_cross", "unicode": "271D", - "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42" + "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653" }, { "name": "cross_heavy", @@ -1902,157 +1902,157 @@ { "name": "crossed_flags", "unicode": "1F38C", - "digest": "d4da057db289bec83f0106a94c89bd0cd9b52c7c7f8bc69bc8cbce480d53e12b" + "digest": "2841c671075e6f1a79c61c2d716423159fb0bc0786e3fb0049697766533bf262" }, { "name": "crossed_swords", "unicode": "2694", - "digest": "f159978583fa77c73ba6de85d35c4195cbd55963e537bd2bfd8f98ab8ff3559a" + "digest": "3771a5b26b514236521ce44e15f7730fa9148c6a782b9b600ab870a1f7de6f9f" }, { "name": "crown", "unicode": "1F451", - "digest": "e6fe2a28b7d80749ca121cabbe89321dcecdd760a122e73fb1562ea9bb40e90d" + "digest": "6741e58d8f823194e0a3484ac1563e20d9e0b44c1bc46d82444dfffa092cdfc7" }, { "name": "cruise_ship", "unicode": "1F6F3", - "digest": "90519c46ddfb63e71bc76661953da9041e5f0b97e9f8a7a8696518b4d529f3dd" + "digest": "2b7b62db5d118a632673564099e3405ea6d61ea9b8e123b5a2aaf011bb2a54a4" }, { "name": "passenger_ship", "unicode": "1F6F3", - "digest": "90519c46ddfb63e71bc76661953da9041e5f0b97e9f8a7a8696518b4d529f3dd" + "digest": "2b7b62db5d118a632673564099e3405ea6d61ea9b8e123b5a2aaf011bb2a54a4" }, { "name": "cry", "unicode": "1F622", - "digest": "2d6a096796222c29b050f74db6b5aff9b9f61390c5eb56e45d1801918751002f" + "digest": "fc3307ec4fe75539770c1123a0e8e721d9e021009a502655132f68d7cc453816" }, { "name": "crying_cat_face", "unicode": "1F63F", - "digest": "df057d4e3e5c5c87caedf87ea3a6f936811b93f228f46bb7018d2bb5afaa6d35" + "digest": "4942c24935c22babdcb8af41d2c0a7588356b6b674bc238902e2f10ad03e2c5b" }, { "name": "crystal_ball", "unicode": "1F52E", - "digest": "7de438f88134c32c4db67d705e5fecf2a6187a87f56ebbb5bcc5ba09626e2935" + "digest": "05f73b30b1e5b0fc66fb5dc6caddd2d547ee7b9d2f97513dc908ba1a2e352e30" }, { "name": "cupid", "unicode": "1F498", - "digest": "7cb3f7d1ddf9678982197ef0e65735fb465ae8e3652d611f37d3bcccf4d7e2c1" + "digest": "246e71f44c6ebc2e4f887e25438e4f894e8cc92e06069e711b893ff391abb658" }, { "name": "curly_loop", "unicode": "27B0", - "digest": "881a43ae406cb74b2ef136bf970db9928bcdc3bbbb7393e90d2c597fe1dd9a96" + "digest": "9e4eb98d6597888f91208080c6a79824adb432ea34f46c85da26cb630bd1cc73" }, { "name": "currency_exchange", "unicode": "1F4B1", - "digest": "c4d76e9e61fac8d3c0cb9e07f1fbf1a7fcac6f4d4c78776ff7f04fc9391ce689" + "digest": "b85377265b9876888969aa42b65bba0be523a370175baf226f20131e535af554" }, { "name": "curry", "unicode": "1F35B", - "digest": "ebe41ee864c873e3a371888c0087b11dbcb124335812895002ed81fe2b6ba571" + "digest": "a01c0a713662817720b485f7739f57e61afc025f5c43792f4de961c94f92f31e" }, { "name": "custard", "unicode": "1F36E", - "digest": "afc192f405c30e2d529ec0f4b31a7faf474bcd01fded5294dc38880b8bb22155" + "digest": "85c2b9ac904134a6c3587eb0a0806f2ab4282c5ed5c79d41734f3203998f757e" }, { "name": "customs", "unicode": "1F6C3", - "digest": "5abb98151a79cebc1032c0ea149617093e42f41e50574a790a91074cabaa4c3a" + "digest": "eb2546e1e617d4c1a1f614318af5e5dacf3e8d9479ffa08108977defa83ded32" }, { "name": "cyclone", "unicode": "1F300", - "digest": "ae77e15bf2f312f03dbc5c7813d304005bbb549953482db9beb91810c585dc0e" + "digest": "7a0f8564d76adf2d0ed272f56dc0d01fb7b557852e0ca797e73f5472b8630bf3" }, { "name": "dagger", "unicode": "1F5E1", - "digest": "377060a7ce930566a4732b361be98e8a193a546846dfbba2a00abeeef41d1976" + "digest": "35a179168198d03295e626cc27d3b92d30a732c55a2ca75d7a11a0fbed414772" }, { "name": "dagger_knife", "unicode": "1F5E1", - "digest": "377060a7ce930566a4732b361be98e8a193a546846dfbba2a00abeeef41d1976" + "digest": "35a179168198d03295e626cc27d3b92d30a732c55a2ca75d7a11a0fbed414772" }, { "name": "dancer", "unicode": "1F483", - "digest": "e050db55afbb968e02219a58c7e82b824848d299a4df64f0d08d4e1872816203" + "digest": "66ffa86827e85acae4aa870c0859fe3a9dad03d21ff4bc800b61c95c902a8a90" }, { "name": "dancer_tone1", "unicode": "1F483-1F3FB", - "digest": "350f6b2e4589fdd436173163035621b8da0bd49c7b9ec9f39593aae5e0ed0641" + "digest": "bdbee740addc890e369d3469a3585eb0d1e4fbc7e04dd6f6aca762d8aeee6a8c" }, { "name": "dancer_tone2", "unicode": "1F483-1F3FC", - "digest": "a9efc84ec80582f286147ca34162a27fd5989f4030084acdbc309d4368660f5b" + "digest": "9f7b4c627241eaa2def9717a5286a423f0b9c1b044dd9ea4442a76f1858d14a4" }, { "name": "dancer_tone3", "unicode": "1F483-1F3FD", - "digest": "ef187f44278fdb8605c80f5cf199e0b3de8a49085dada2e215bb91e1d7d3be5d" + "digest": "a6bd49a377ce6c2004bf126b6f66d0b21d8c14103c2add7b10f12ed9e1c2d302" }, { "name": "dancer_tone4", "unicode": "1F483-1F3FE", - "digest": "5195bc352dc9d24cc5505a167c756038e55c05048c61799ea1bfdf2debe44ac2" + "digest": "4ec2a7629c01b0e9006b5cda4deae3bf297ce3b71d18063f93eeb5c14be19a1a" }, { "name": "dancer_tone5", "unicode": "1F483-1F3FF", - "digest": "55cb7eee9fa11a16a3932800a19e334546f7396df6aadde22e58fe3185926b16" + "digest": "2b48e3a6b366c6f55f73b816e6fb03c39e9890f586f7e9c9043cf0c013d9cdd5" }, { "name": "dancers", "unicode": "1F46F", - "digest": "39e7dfd9dafeee20f2968960b1179ee4bf3f2b63a3035fc1944024d0ae8b5de1" + "digest": "12be66ed19d232bb387270f40bece68bd0cb2342b318f6c9bb8b49c64ff7d0ad" }, { "name": "dango", "unicode": "1F361", - "digest": "2a1b50abe5dc72335344878d9b701028ccad651964d9e3affeedbf3c2bfd652a" + "digest": "34e8cd153c50f2d725abe8934c35c96a3ab533f0cc5fbb1e1474eafad1dc1fc2" }, { "name": "dark_sunglasses", "unicode": "1F576", - "digest": "6bb1e911a93d5eb0581d3ce8f8929125d3d8fc04e086f3263cfd25af1348ce6c" + "digest": "d0a735ad5bf0ece00af2a21abf950b89292ebd8ca6e28b1dbb1368252fb44afe" }, { "name": "dart", "unicode": "1F3AF", - "digest": "6f28741543a4c1eead21856128ffea1fcf772954fe6af40844dfde47f092ed32" + "digest": "998642f06a875905e0a6bf30963c025baff1cf55b8e76884b9119f2d71188b0c" }, { "name": "dash", "unicode": "1F4A8", - "digest": "25aef37611f1c2f2e96518bf8aeba80580dca9634c8505d390c147388adf6746" + "digest": "f7aae7d3887c67d76f3329c2dc9e6807dc580a4b07ab35599c7805e41823a345" }, { "name": "date", "unicode": "1F4C5", - "digest": "de591b8fad608be761b839beefe9e4c2316320bcf0c44c543a1bc4b89923d938" + "digest": "d0b695e4a7cfbbe71b4fbebf345b66ca98f0cf1c751362928e54c23ca78d4c7b" }, { "name": "deciduous_tree", "unicode": "1F333", - "digest": "ff31a52096ac1eae770f7f71b6d802198add2c8b4d9d7c9327071b6d6ab86c7b" + "digest": "3c70f1a77f2754f41c830e88d43b7d53c14311d64626ded164aa9ac7d2695790" }, { "name": "department_store", "unicode": "1F3EC", - "digest": "c1e200d5fdd792121acabdb17bbcfe8e28a63757cfd895c72d4909f14de95ac2" + "digest": "4be910d2efe74d8ce2c1f41d7753c8873579faca83fcf779a4887d8ab9e5923b" }, { "name": "descending_notes", @@ -2062,17 +2062,17 @@ { "name": "desert", "unicode": "1F3DC", - "digest": "e45815250bfc5411de516f87efa218874bcda4b0420b4c17182efc22ba0ce80d" + "digest": "d4b1a11c5130debe042df6cc2b3389f15c68a5cb32dc1b3a82b78f733d0c9e4e" }, { "name": "desktop", "unicode": "1F5A5", - "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de" + "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e" }, { "name": "desktop_computer", "unicode": "1F5A5", - "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de" + "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e" }, { "name": "desktop_window", @@ -2082,47 +2082,47 @@ { "name": "diamond_shape_with_a_dot_inside", "unicode": "1F4A0", - "digest": "4e0e6364b8682dec9a9e20676161c9c9c0faf0a5fdd5402ca2668b18f2bb850a" + "digest": "e91323577ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3" }, { "name": "diamonds", "unicode": "2666", - "digest": "42b13b2ed8e5fc63fbe81263c06cc203ba18a45ed5cc2a4fdbf617d219a0d3b4" + "digest": "bf3d9a020afe8aa226db73590bc193a9c2c3e6e642edd2445c5960c3e67cf153" }, { "name": "disappointed", "unicode": "1F61E", - "digest": "7f1a619fef84960a9f312d17a58aa58105a4f20a4072efb10227892ab22475d8" + "digest": "c0f406c6beea0fd1328adefc097d04aa16b72f7a5afa0867967d8ea25d72db17" }, { "name": "disappointed_relieved", "unicode": "1F625", - "digest": "a389f5e0a4b619dbc406217967fb1f8f3d0e49b3f790e554ae0ececadbf98967" + "digest": "c826f5dd4f2f7e5289d720851d4826ab8284d915606c1b152ab229b7fadbba14" }, { "name": "dividers", "unicode": "1F5C2", - "digest": "bf4c303452a4c0b4986925041dbec5b7e478060d560630b7c5bc2f997fcad668" + "digest": "4b2c653b18cf0fa31f1f0ac94a6fbd214ea0d1b0a90a450ab6e169906fc5764f" }, { "name": "card_index_dividers", "unicode": "1F5C2", - "digest": "bf4c303452a4c0b4986925041dbec5b7e478060d560630b7c5bc2f997fcad668" + "digest": "4b2c653b18cf0fa31f1f0ac94a6fbd214ea0d1b0a90a450ab6e169906fc5764f" }, { "name": "dizzy", "unicode": "1F4AB", - "digest": "d6fba9b906f0eabd46686e416273a2ca6634249374385f2abf7ed284f0eef995" + "digest": "d577545c2de42389695447c6ebbfef895f30f0fda84eef45684f9bf4a9c27ff1" }, { "name": "dizzy_face", "unicode": "1F635", - "digest": "b55e20c1551a2912bb5ec64a66c788c9d6f21594cc1da66032188f3814b03f40" + "digest": "7b3aeaffb4e15ccf633b91dda4a44847a1eb28d78ce58b4d171b20a771bde414" }, { "name": "do_not_litter", "unicode": "1F6AF", - "digest": "126f8c4085e0a8de8241f211f96c3f42c3e3400ea7d8fdf79a14443c3eceb972" + "digest": "98b07fbbcdb438d1b8a755869fa2de8e180a77fce359ec830eb46d38ec3e67cb" }, { "name": "document", @@ -2142,182 +2142,182 @@ { "name": "dog", "unicode": "1F436", - "digest": "c7b729de8a0967b1f38c3fa5ded94e77e329588caeaaf43abfd1090f420e62bf" + "digest": "3b31ce067b13e463284ce85536512cb1f8cd8b52fe73659f69971d0d6c1dfc11" }, { "name": "dog2", "unicode": "1F415", - "digest": "e1897ca60bb3d2662cbe7933352e2b9c50739adf5901d3328797bf399575b97a" + "digest": "0a8901bce5ed994533ff84299b2a1364de28d872c9f9510d3426a83e8a9d2e34" }, { "name": "dollar", "unicode": "1F4B5", - "digest": "7db1e57f799439df1295d42b5249393f1e8cacc8df54caf30499c967a7282742" + "digest": "52438e38867aedc021740bb41f9ba336e75a50faa148419412a01d75d8c93155" }, { "name": "dolls", "unicode": "1F38E", - "digest": "398e7ff5780328700aadded7ce8c50757b1096af5cec66cc4d813a6714686b6d" + "digest": "a687184e9a0915deef44bb3cacfb19d3f3f19cf2c110f1da90191dd567333c57" }, { "name": "dolphin", "unicode": "1F42C", - "digest": "27385af08848d93acdd13f72751074c2cbccb5ab3c6047e334598af74ed4862d" + "digest": "0b7ee08f4236232ca533ed3a3023d28020d36f178efaec5ce8b0e13a84778512" }, { "name": "door", "unicode": "1F6AA", - "digest": "3365d7834086328ecbf1da0037f1cf1d0eb49534e173f7962a9e8f4b2ab87e26" + "digest": "984a9ca88852ebdb539e0c385d9c6ffe5010e9189bc372a3d00f5c8d44c8e6f5" }, { "name": "doughnut", "unicode": "1F369", - "digest": "b4b99fdfe8d07b49cbdd78f8c57e4424819a4ffc8a3ba4867da44cbb3b3a5cca" + "digest": "27634587e6a53807baa32157bb06b0e115c8ad8aefebba7ebb0b65a084170e3a" }, { "name": "dove", "unicode": "1F54A", - "digest": "4e2e9c47e5632efe6ccf945d61dbc2f1155a2e52905e17f307b502a2c951bdb8" + "digest": "7c665f8594ffa53e72b01647e9d27360fb87d52d02fe9f20fc5fda08f9797dc3" }, { "name": "dove_of_peace", "unicode": "1F54A", - "digest": "4e2e9c47e5632efe6ccf945d61dbc2f1155a2e52905e17f307b502a2c951bdb8" + "digest": "7c665f8594ffa53e72b01647e9d27360fb87d52d02fe9f20fc5fda08f9797dc3" }, { "name": "dragon", "unicode": "1F409", - "digest": "d7d016568b54d67017681a075fb799d4a2a790ecfa2946d02dbcee629eb4975d" + "digest": "2abcb3d945d848e34ffc76203b29ef26df7458856166fffd155611f7bbe72652" }, { "name": "dragon_face", "unicode": "1F432", - "digest": "4d0025f1df63b62448477a8f08a50704e15caafb10fea476b529113f41797ab9" + "digest": "0030548931b931e3b51f26cf660394aee36499e688ba83ce9cfccb635dcd4d54" }, { "name": "dress", "unicode": "1F457", - "digest": "02d56ed227280eaf5ad92830ee304afb81f74bb5a13c855397bcd04dd7fa51fb" + "digest": "96ceba928fb356f7c0ae99bf22552321f08a65d5f1c0340ab89641219ad366ad" }, { "name": "dromedary_camel", "unicode": "1F42A", - "digest": "5afe8a0b73f9f4560264020b1e02a566149dbc38c15a00d2fb5cd90b32d09a75" + "digest": "e06ef69c29f0fb12481727c0b4124e700572d3d7955e173279320f43f286518d" }, { "name": "droplet", "unicode": "1F4A7", - "digest": "a92c419792cbd3ba90ed21547362134cfac3e17a5304ee4e3872c9f7b561f834" + "digest": "6475b4a4460a672c436a68f282ac97fb31e2934db4b80620063ee816159aa7c3" }, { "name": "dvd", "unicode": "1F4C0", - "digest": "1ba23e2f01ced5e192e4c1d2f766d9bce400470e81c81410139fd3c0739422df" + "digest": "3b7903285d91277181c26fdc9df857761bbac509d352e320c2519ea3b132704f" }, { "name": "e-mail", "unicode": "1F4E7", - "digest": "12135310cfedc091d120426f5b132df82b538c5fcad458bf6b21588f353c3adb" + "digest": "39b5a57a2376e4a1137e381be02a1775bd580e0371438f5297a401ea634f1830" }, { "name": "email", "unicode": "1F4E7", - "digest": "12135310cfedc091d120426f5b132df82b538c5fcad458bf6b21588f353c3adb" + "digest": "39b5a57a2376e4a1137e381be02a1775bd580e0371438f5297a401ea634f1830" }, { "name": "ear", "unicode": "1F442", - "digest": "70ba1103a34e68590d91a3b6f8acdbad3b1c65e46e31e26ee1cb855c1e21095e" + "digest": "4fdeb5a46e69311ecfd09c5b45c9018c24b625e28475cca8fa516b086ef952f8" }, { "name": "ear_of_rice", "unicode": "1F33E", - "digest": "ddd5f3cc83dbdafd9115861eecd0128e52165bb1dd0049df06ffc564b650d384" + "digest": "2997c340c2b333d6ba9b73f94ff1a1881735fe0cc4f0c72d7719b305499fc425" }, { "name": "ear_tone1", "unicode": "1F442-1F3FB", - "digest": "72977be94f5d287a09d175f98fba8b7955ae13aa12ce8e029c0ca875c02ee820" + "digest": "5ca759b8569a377a4e63e30d94b585b9f76d15348a8a0c1ba19fdc522790615e" }, { "name": "ear_tone2", "unicode": "1F442-1F3FC", - "digest": "5ff2e46cb3be7f13b8b94092246b58dab4c2a9ee2a5a46e0b84cf35a6928141f" + "digest": "12aafb3ef2cfcdc892b2877c2e24920620f0f77f850e12afbfe55eadce9e37df" }, { "name": "ear_tone3", "unicode": "1F442-1F3FD", - "digest": "19b523f5ada2acaea94b922059c458a3303f4da1dd4c197cf25d31a0e6ecc4b2" + "digest": "f4d28d9f72cf116ac92d80061eb84c918d6523bf53b2ad526f5457aba487d527" }, { "name": "ear_tone4", "unicode": "1F442-1F3FE", - "digest": "6a5cca9f49c539ef7d0883a2f39652f33ee2d3b25dca0234e4ba027ebbb2b466" + "digest": "eaa9453670f7e3adc6ec6934ee70efc9bf60fe6c99c5804b7ba9e3804aec65de" }, { "name": "ear_tone5", "unicode": "1F442-1F3FF", - "digest": "a0a56e8abd36e9be6e2448bcee6f56ecb8bf62d728b19ab6e8f9c6338e226b67" + "digest": "54bd0782419489556b80e9e0d15b05df74757aa4e04ba565f45c20d3dd60e3f1" }, { "name": "earth_africa", "unicode": "1F30D", - "digest": "d4921b543d7cf0c7344fa50c5e4d5a76c208d900be852adc1ee82ed4e8861a39" + "digest": "c691a6f591f5a07b268fd64efe113e81cec8d5963ad83ced2537422343ff7ecf" }, { "name": "earth_americas", "unicode": "1F30E", - "digest": "61691e6aa9b8d90fc7f75fbc6cc7add5c36022d38f3e05c9d7c54dc44cf865bb" + "digest": "a9c60cf8341ff59a9cc1a715b7144af734fcd28915a8e003a31ebf2abf9aedb1" }, { "name": "earth_asia", "unicode": "1F30F", - "digest": "262904cb552c7f5cf828a11071b3d430a74824b7464e8759ef93ee23b1705767" + "digest": "ee2beb61fb8c87279161c5a8c4ad17bb71ce790123f8fa33522941d027e060a5" }, { "name": "egg", "unicode": "1F373", - "digest": "a7dd617cad489c481ffd14937d9ed491cdd5756903e00473f42600c2fbefb600" + "digest": "563ffd6cae381ce1e318cdacc54e70040d6a01a50d0db8aeb50edbbe413eac58" }, { "name": "eggplant", "unicode": "1F346", - "digest": "e5402e8ae5b7f9699ed86b97c242f7939d5731c5a364a2d5b9d04ea5d293cda1" + "digest": "ec0a460e0cf0e615f51279677594a899672e1b4ecd9396e17a8cfa2a3efe5238" }, { "name": "eight", "unicode": "0038-20E3", - "digest": "34e293d3228e4643a0132d592f96db91b651fe6ced056ac3c8a3fd49c5ed3416" + "digest": "57ff905033a32747690adba6486d12b09eb4d45de556f4e1ab6fb04e1fb861a8" }, { "name": "eight_pointed_black_star", "unicode": "2734", - "digest": "c3c2da75731a9a0f4f0a8d1f9cffef75c35e19b7f5d4081da33ac12b46be5fc2" + "digest": "7bf11f6e28591e3d0625296aaabf4ecb75c982e425abf3049339e93494acc17e" }, { "name": "eight_spoked_asterisk", "unicode": "2733", - "digest": "cc69618c1074d2b00e6f2c49df5e2c5ff6f4c0fae305505eb8c9daa65a0ea340" + "digest": "bb0758e7cc0e357285937671a91489bd32ce9d248eecdcc9c275a53a66325b26" }, { "name": "electric_plug", "unicode": "1F50C", - "digest": "732e1d1675233a0b4643cb73d0c352f8a5a56a11ee90d26627ad1e43c2e4a8e5" + "digest": "b10ce87af86fa4f4022572ceb5ecd73bea867347a86832a7ea248364b0aad8d0" }, { "name": "elephant", "unicode": "1F418", - "digest": "08df3910c4d5d8f49a72c47dd938195e495bde8fd8b3e7b17098a2c1afc41634" + "digest": "b7750f4b013fbd28ac5330e1694ef4d3b4a9c6fc7b807879db0c24b035a16c29" }, { "name": "end", "unicode": "1F51A", - "digest": "05844ab9dcb43deff86f04617af6ea09215595de1415dcfaae018bced57938fe" + "digest": "dd93aee6986eb637a8b58f234da47568b88525599f73246e322af030351997a2" }, { "name": "envelope", "unicode": "2709", - "digest": "aad272511d0db910437ba25cf1fb9c806d47aad92a232edb87055916daf4676a" + "digest": "f5a512022a2f5280f372ff39c22cbda815f698710ca66f8f8c4d08418f98ca78" }, { "name": "envelope_back", @@ -2362,192 +2362,192 @@ { "name": "envelope_with_arrow", "unicode": "1F4E9", - "digest": "c1ba19b5e7cf64c547ac46eee139e6af70700d49ab511a96e6828c30feb116bc" + "digest": "f8643212e6a94f58ccf2bcedc54c5fda8ebeab274f4a8803f253de5f50ddb1d6" }, { "name": "euro", "unicode": "1F4B6", - "digest": "f571952583ffecfa5777065e4d1b680c423d25bc80e567a48fb5d7a1c1b5e735" + "digest": "3af3e223e8f26468a94f6f5c17198432656e8d20b3bab31566c2b5a86e717df4" }, { "name": "european_castle", "unicode": "1F3F0", - "digest": "db82e383975d079a7bb006e7868035088d75c33bd4031cf8466b71089b65426f" + "digest": "21082d0be7e3b2794e59ff0170da0cfe42a9b734cf02704603e3b52ff48202ba" }, { "name": "european_post_office", "unicode": "1F3E4", - "digest": "d9b38e0f0ca3ad8895b40c767bdbb2b142ccaf03a86c2f275f57a31ed478801a" + "digest": "02b4c7602939f0cb9cb2b4e05996bcdb6bd93cf8025c2ea02db8cbe13ca397d0" }, { "name": "evergreen_tree", "unicode": "1F332", - "digest": "60d8b2d86b20255341f7ecad6d0f178ba9db5fa6b3de92f1b439cdb19f2fc0b1" + "digest": "74b226098e66c0a94a92e0f22b9d631736e12dca72c34182c9d0ba56aa593172" }, { "name": "exclamation", "unicode": "2757", - "digest": "cd900ecf82de2b26f0d7783dac4b3232ae94d2cddad5bfacea2eaf65b7ac0a09" + "digest": "45b87ae4593656d7da49ff5645fb6a2a18d582553295358da9f09f1ae8272445" }, { "name": "expressionless", "unicode": "1F611", - "digest": "2ec9466b2d629907ce4c3e24e57f7ee556d2258ff011d972e14d0ae969a40c51" + "digest": "34e2a1c8121f4f0bc4ce33d226d8cc1a4ebf5260746df2b23e29eef24ee9372e" }, { "name": "eye", "unicode": "1F441", - "digest": "790841e8fce647173eec3c5019440ad9c7e916c535f92acb3132bd92df148cad" + "digest": "79ecff79c2edee630e72725b54e67ee2e96d24ca03fef2954a56a09c0a2227f8" }, { "name": "eye_in_speech_bubble", "unicode": "1F441-1F5E8", - "digest": "bcde5a89a7653bff302685d9d632dd2723796a7ac73125fb7b9493d1ca848e0a" + "digest": "c0050c026c2a3060723cab2df2603c1c7da7ed81faedb9ebe16cd89721928a55" }, { "name": "eyeglasses", "unicode": "1F453", - "digest": "fd140bef19c420bafe59368d35dd58a58a53e7145b104bae94be10f90679213b" + "digest": "d4a9585d6c43ef514a97c45c64607162e775a45544821f1470c6f8f25b93ab81" }, { "name": "eyes", "unicode": "1F440", - "digest": "57ed1f87ebe2485ea32ea69abdb8c5f7ccdcc149b33e74230d801f0883c68c5d" + "digest": "1d5cae0b9b2e51e1de54295685d7f0c72ee794e2e6335a95b1d056c7e77260e8" }, { "name": "factory", "unicode": "1F3ED", - "digest": "6e6b35ae013e5dd26852c9a95d05c39e89c1c1950a33f47e7b951c34af18f37c" + "digest": "c7aeb61ed8b0ac5c91d5197c73f1e2bb801921c22a76bb82c7659d990680dcb0" }, { "name": "fallen_leaf", "unicode": "1F342", - "digest": "28ba8628065ffa973b525dd1455691c828d49c2b8c814af387880c13f6707f7e" + "digest": "81fce04231d48db0e55f3697f930e9a7e3306bed5e35f1234e98c40a24ac5626" }, { "name": "family", "unicode": "1F46A", - "digest": "b5307f86e54cfea581e8406f4b95c801e250a893a9d208cc9a69a6d910b90932" + "digest": "06f2ce63768ffe43b3d9b2a9660b34d043f37b3c91610dd62343ba21df8ecbe5" }, { "name": "family_mmb", "unicode": "1F468-1F468-1F466", - "digest": "49a753c3fcd4420800dd1cda585dae6bfa81615ad4862b477246456f86dc9e82" + "digest": "41a18405be796699a7eb7c36ab6f7d898e322749997f45387377acf5bb16a50f" }, { "name": "family_mmbb", "unicode": "1F468-1F468-1F466-1F466", - "digest": "882a3a0048efd666b0ab3a07b9f08041aa3a2acdab02664d0feff30bbfa70d68" + "digest": "87255d1d18c6971c8c083c818e598424c1bd717eed892478b7e9516639dbfb45" }, { "name": "family_mmg", "unicode": "1F468-1F468-1F467", - "digest": "45dd75c19d260a658c8ac93cf878976b96d2000f0efc9c59e72dacc80afb08fa" + "digest": "a132b1b8f10b318d8e23aee15dab4caa14528aeb3c89966d4bcc25fb54af72ad" }, { "name": "family_mmgb", "unicode": "1F468-1F468-1F467-1F466", - "digest": "910f44a348a951d36ee1f1484d237085bec5083c3875a4d908831dfc64530eaf" + "digest": "eb2bc1966df406aaf38ce5a58db9324162799cdacf31f74f40e6384807a8efc2" }, { "name": "family_mmgg", "unicode": "1F468-1F468-1F467-1F467", - "digest": "012e75ad0d1b16c2ce63bf80a1ebfb1fc194229cfaf1241039599b82832f6aee" + "digest": "24f3d60f98fbd6b687f7cacfb629390b90509a754036e5439ae5294759c0606b" }, { "name": "family_mwbb", "unicode": "1F468-1F469-1F466-1F466", - "digest": "049a32f61c54f093d2124e25f8b2ec7eac13161e2f2ebf6dc067797698cbe831" + "digest": "2f77692bcb9275c4df501b64a18401dcaf8c68b21f26fbdad59b1feab0c98fd1" }, { "name": "family_mwg", "unicode": "1F468-1F469-1F467", - "digest": "ba32c637caba634bda99ccba2a1a2a4b6f33aaaed933c30c7d5a51e8de1790d0" + "digest": "1a976d13127665d9386cebfdb24e5572dc499bda484c0ee05585886edc616130" }, { "name": "family_mwgb", "unicode": "1F468-1F469-1F467-1F466", - "digest": "198faba987f45429329b93bbce4f111329f284558bf0eecfa1424186b5f009fe" + "digest": "960ec2cbac13ef208e73644cd36711b83e6c070c36950f834f3669812839b7f8" }, { "name": "family_mwgg", "unicode": "1F468-1F469-1F467-1F467", - "digest": "3fa2e57cba314dcff04cf8186914823e1e081aabf34fa7437b05c58015df400c" + "digest": "8353b03dfa5c24aba75a0abdfdac01603f593819d54b4c7f2f88aafb31da0c6a" }, { "name": "family_wwb", "unicode": "1F469-1F469-1F466", - "digest": "b9592fc110a25a478569075deaa520308ef74579cd47aa44df9836599d68143f" + "digest": "07a5dd397718c553573689f6512f386729c13a12d5dc78be47c06405769cd98a" }, { "name": "family_wwbb", "unicode": "1F469-1F469-1F466-1F466", - "digest": "88f398997835fcf5153f17f6baf0deeb2a9c25ce2f8422192c18ac23e90b3193" + "digest": "b627f460f1da0d47b0b662402940b2b77c9538d380d05436dfca4b456c50c939" }, { "name": "family_wwg", "unicode": "1F469-1F469-1F467", - "digest": "c8d859d3c957fe0d535efccde295fe99bab76e3d28ab5a49c8e736608461cb2e" + "digest": "2d6f373bed53f1028f0fbe9caf036465a351f37b9e00fca7d722cc5a1984f251" }, { "name": "family_wwgb", "unicode": "1F469-1F469-1F467-1F466", - "digest": "006506e4a3d0c82642a0c8481ce95e5e3b969e20fe2def0a16dd686afddbc705" + "digest": "72be5c85e1621f73d6794edd6e428febdb366b9e4c816f7829897fd1ab34642b" }, { "name": "family_wwgg", "unicode": "1F469-1F469-1F467-1F467", - "digest": "2553f0deab133aad09b99411d9dd68b56fede30f55ee1f354358767765e36673" + "digest": "c39e0916069460d2d9741bddf58e76f5d6a09254cba0eeb262345adf8630bc32" }, { "name": "fast_forward", "unicode": "23E9", - "digest": "1baaed10969b60c083da754ee056bb71df36182cc65af40640acfb76f6b39200" + "digest": "e7d2d8085cfd406c2b096e8dd147dd3722290a5727b1f7df185989526a2335ec" }, { "name": "fax", "unicode": "1F4E0", - "digest": "b0a392192d03bd5d1ad5ee8eea933cf64725b1776819537bbed27561d78192e7" + "digest": "ff85ffa440c5379c9b138ebe2d7912d6098da3b37a051b80442d5557b7f993b0" }, { "name": "fearful", "unicode": "1F628", - "digest": "7c4cc4de3357c2a6d6e779342b09dabb3ef832a32f2778a0ba074b446f588e8f" + "digest": "b72bdf7d075d5c4e38bbd8512fb45fda2e85c9c8732a47e67575ae9f2ed4c5df" }, { "name": "feet", "unicode": "1F43E", - "digest": "cae13fb54ec64dbcf86ea25bebe2b79877e2d4f5d810b867f095f1d3dfc7f144" + "digest": "45aca538d3a9831a0c7de491e5656c17705c07b8f4ac8e85254656b608976016" }, { "name": "ferris_wheel", "unicode": "1F3A1", - "digest": "a710a8a0fb039d953313b75330db37e3228d856593547b1f04dc83c00168b987" + "digest": "24b4551b7b79a2a5fd73de61542f2b444f896a52030c5f29791c8fcfcc28b95c" }, { "name": "ferry", "unicode": "26F4", - "digest": "21ea239b5adb68dc1ce6c5a1993b0a0b835ef6cc7a0a27cb890838d8475504f6" + "digest": "5002a72af2e3c4cef9a36ad5987aeed7d99f96bfd13e56f78957315ec7e749a3" }, { "name": "field_hockey", "unicode": "1F3D1", - "digest": "1e46c7f0b5b79c90a5d211ea14cd7e358b1a26a3c8294439253f2b08d0e5c92e" + "digest": "4ee091d96161ba719ab8fd6f2b03f96d902a6f22cffe0563b930618bb8ac2b67" }, { "name": "file_cabinet", "unicode": "1F5C4", - "digest": "c0b7bdab6c98909eb0fbf1ac89da0008bb00ddb1cb57fe64b4a5ac993eeb18c9" + "digest": "92914147bf93e6d64271ff99d217a18a9850a367d08a5f9f458ecf9311a5bbe9" }, { "name": "file_folder", "unicode": "1F4C1", - "digest": "d98f93c6d7283df0c45f08d3d31ecf5b91b6db1b735959f19e42bfada500a0d1" + "digest": "62a42a929267cfbfdb795ead381c9657c343458bc5fca95ea8a0ab892c61d4f6" }, { "name": "film_frames", "unicode": "1F39E", - "digest": "754a0a60e978f8299a0c4f8959e1f9260f01683e15ae943db430036f01a79b18" + "digest": "4da212148cadb9c4ea91e60d2d8316e38cea99ef4f14afc023711dd7c54ade5a" }, { "name": "finger_pointing_down", @@ -2602,17 +2602,17 @@ { "name": "fire", "unicode": "1F525", - "digest": "b44311874681135acbb5e7226febe4365c732da3a9617f10d7074a3b1ade1641" + "digest": "b3e67c913903d900f5e50e7e7e4d7e9370bb6ceedfbee548be39e4c9e4b69416" }, { "name": "flame", "unicode": "1F525", - "digest": "b44311874681135acbb5e7226febe4365c732da3a9617f10d7074a3b1ade1641" + "digest": "b3e67c913903d900f5e50e7e7e4d7e9370bb6ceedfbee548be39e4c9e4b69416" }, { "name": "fire_engine", "unicode": "1F692", - "digest": "3ae03fa34a7088ada95458eb4ee3e97691b3489149f6bbc168086f0483ed3bb2" + "digest": "c3a518f27d625e3b62dffa227eb82764bf0a147f10ec0e7f4f43f3f96751af20" }, { "name": "fire_engine_oncoming", @@ -2627,507 +2627,507 @@ { "name": "fireworks", "unicode": "1F386", - "digest": "3dee83a27c406960253ca1460eb88a599c7b81506051b69605a421b17fe8282c" + "digest": "b62ae08a00c0cc6eba8f9666c8fd9946ce57c3cfc01fe99542a8690a4a566a65" }, { "name": "first_quarter_moon", "unicode": "1F313", - "digest": "8fa066362d77bd889090bbe0904ca47f34704e29781c67133c6eaa521c3e1972" + "digest": "a207ce93084448622a4a5c49c85c566a9fda6be7337c86a013eeb713fe47fd29" }, { "name": "first_quarter_moon_with_face", "unicode": "1F31B", - "digest": "8877edb366f8eaa00fd83200acf5a17c3b84d246a250519d565dda3aea866ec3" + "digest": "1d1f54a5075f2311bcc017c44898b9d8c58edc13b298d58c238fff9ab8ee2ef3" }, { "name": "fish", "unicode": "1F41F", - "digest": "9ce742108794cc15e59f7719623ae938efbd8155c93ad72585a32f4e32ea9414" + "digest": "8f62f08fbeaf39694c19816b5c7d4f292017fe5bf9f8dd7e40f1630f5f83b28b" }, { "name": "fish_cake", "unicode": "1F365", - "digest": "1b5b14509287e30da9b8d7abcec376b247f9095aea4bf3fc320349f061a4c321" + "digest": "5a6ca2100c8830927b22afa6f1d2fc821f5692cd23507fe5a776f6e085cbbfb2" }, { "name": "fishing_pole_and_fish", "unicode": "1F3A3", - "digest": "35db56776db1fcec7c8479922d57d54da2577cfe44a894bfd78c51c950c450fb" + "digest": "f8fb84eccceec88321b0a2a46f732ecfc378f787c19c27ac1327735f1ca9a48b" }, { "name": "fist", "unicode": "270A", - "digest": "6b80ac2e4d8b830ae06f7c1626d456460094e4ba20c20fb82dabb6b3d2ce7605" + "digest": "557f96d85615b8d78436bc67266115bfc8556c97c14f7909dfda1cf134e8344f" }, { "name": "fist_tone1", "unicode": "270A-1F3FB", - "digest": "d7c79f4f988dd68f064baa5a3a568ab299f8d409db45c8463f39b80e5dd6081f" + "digest": "6c1b946f9e01abc39b5085e24e8b6077fc0e34188e8daa30c6a3adddd387413e" }, { "name": "fist_tone2", "unicode": "270A-1F3FC", - "digest": "d1108194e2d962f9ccd00131876d769a8e003117a460d18b2ccbf93e0a0ea346" + "digest": "e9b9e1ec638dca4d5e1519bca7338f58cce2f2a282ee4c3581e8643166fc415f" }, { "name": "fist_tone3", "unicode": "270A-1F3FD", - "digest": "12f5644b632c95a5c2e41cc9af299e286e266db8b3860091ef5be5f0c4ccc026" + "digest": "8c14d24055c143960b3d2a27fe23c55d2d3ac5f84f87e4e876616235e8698c7f" }, { "name": "fist_tone4", "unicode": "270A-1F3FE", - "digest": "521a3ac573381f3bc37a08ddd2d122767aaa0b6b7a38050d3671a12343351816" + "digest": "923f034f481e952e6e5d1664588f99f79bd5416d4197b0ade6621f2669ce5765" }, { "name": "fist_tone5", "unicode": "270A-1F3FF", - "digest": "604e5a234da1b9160e506b3c9026faf9e04268fced7b44baa1ef5e3d4efa83a4" + "digest": "d691d2902216080916a29047e07d7a5bf2aed07e062067ca9d01cbf6fdf48c8d" }, { "name": "five", "unicode": "0035-20E3", - "digest": "0cbd6cd11eb6c2d67749112750d125f4f0a07b53bb7bfb1de0986d943ea9d632" + "digest": "8f03f62fdbf744ae49c8a60fbf715ebfccbd6b62d91148e0923907006f3c2726" }, { "name": "flag_ac", "unicode": "1F1E6-1F1E8", - "digest": "d9db1edeb709824a1083c2bba79ca5f683ed0edded35918bb167d1ee7396c8da" + "digest": "2e5c08535dc8ea96422d56a36b4fffc0b3bd2a13f2ab0d8dbd0e3a29bf3fc40c" }, { "name": "ac", "unicode": "1F1E6-1F1E8", - "digest": "d9db1edeb709824a1083c2bba79ca5f683ed0edded35918bb167d1ee7396c8da" + "digest": "2e5c08535dc8ea96422d56a36b4fffc0b3bd2a13f2ab0d8dbd0e3a29bf3fc40c" }, { "name": "flag_ad", "unicode": "1F1E6-1F1E9", - "digest": "04a8c1745d9b8b20e903302379f2557e8082f72e33878db4cb2cd6b33eb97952" + "digest": "184fdcf790b8e2fd851b2b2b32f8636c595dd289734d12dc01ae4aa177e2043a" }, { "name": "ad", "unicode": "1F1E6-1F1E9", - "digest": "04a8c1745d9b8b20e903302379f2557e8082f72e33878db4cb2cd6b33eb97952" + "digest": "184fdcf790b8e2fd851b2b2b32f8636c595dd289734d12dc01ae4aa177e2043a" }, { "name": "flag_ae", "unicode": "1F1E6-1F1EA", - "digest": "868324ac2e7bea1547f5de95f39633b77b8d62f3b3433b3d1a4ee96d169a09cd" + "digest": "4a3257a9ce118e97567e76280f24d60fb555f1bada2eb26a2442a47f9398d21e" }, { "name": "ae", "unicode": "1F1E6-1F1EA", - "digest": "868324ac2e7bea1547f5de95f39633b77b8d62f3b3433b3d1a4ee96d169a09cd" + "digest": "4a3257a9ce118e97567e76280f24d60fb555f1bada2eb26a2442a47f9398d21e" }, { "name": "flag_af", "unicode": "1F1E6-1F1EB", - "digest": "9a94458519e9db5d6cf1557e54fdf62d7e48aaf7de25744a093ec8f284656226" + "digest": "0f6c719cac7ab3140694f6b580787ecdbf503e38f16de7ec5803f7d06a088ec3" }, { "name": "af", "unicode": "1F1E6-1F1EB", - "digest": "9a94458519e9db5d6cf1557e54fdf62d7e48aaf7de25744a093ec8f284656226" + "digest": "0f6c719cac7ab3140694f6b580787ecdbf503e38f16de7ec5803f7d06a088ec3" }, { "name": "flag_ag", "unicode": "1F1E6-1F1EC", - "digest": "ea59fabc2bd9024df06a59a34412f52bebfeb03eb6abd73d8fe153e3a68e28f4" + "digest": "92bf5a0e74564739862e9ba79331ffa656b7bae2ace0fc8dfd288984e4d510d4" }, { "name": "ag", "unicode": "1F1E6-1F1EC", - "digest": "ea59fabc2bd9024df06a59a34412f52bebfeb03eb6abd73d8fe153e3a68e28f4" + "digest": "92bf5a0e74564739862e9ba79331ffa656b7bae2ace0fc8dfd288984e4d510d4" }, { "name": "flag_ai", "unicode": "1F1E6-1F1EE", - "digest": "75676ded736ad2ebb921e9fd8ebfef49819a35c3dcf005bbc3b7e8c6e75178f2" + "digest": "aeaadc7ffafd8a1e01fdabc69d35f725d5f737b4c284a36191d96729f4e66e8f" }, { "name": "ai", "unicode": "1F1E6-1F1EE", - "digest": "75676ded736ad2ebb921e9fd8ebfef49819a35c3dcf005bbc3b7e8c6e75178f2" + "digest": "aeaadc7ffafd8a1e01fdabc69d35f725d5f737b4c284a36191d96729f4e66e8f" }, { "name": "flag_al", "unicode": "1F1E6-1F1F1", - "digest": "77b835dcff399b609e2479cbf10f08344c8fc277370ba8e4540165ca15563847" + "digest": "5ce7866d214d18c5f3438d480d14e77d104c4de679f0fdfca8cf0a44ce48eeea" }, { "name": "al", "unicode": "1F1E6-1F1F1", - "digest": "77b835dcff399b609e2479cbf10f08344c8fc277370ba8e4540165ca15563847" + "digest": "5ce7866d214d18c5f3438d480d14e77d104c4de679f0fdfca8cf0a44ce48eeea" }, { "name": "flag_am", "unicode": "1F1E6-1F1F2", - "digest": "3b820c628dd5a93137f7288a43553778f60b0beea4c0a239d063893c0723e73d" + "digest": "b40f5705f0cf9ef0fa7ffff0b371c4099319001ce79f894c317912f4dc5de4c8" }, { "name": "am", "unicode": "1F1E6-1F1F2", - "digest": "3b820c628dd5a93137f7288a43553778f60b0beea4c0a239d063893c0723e73d" + "digest": "b40f5705f0cf9ef0fa7ffff0b371c4099319001ce79f894c317912f4dc5de4c8" }, { "name": "flag_ao", "unicode": "1F1E6-1F1F4", - "digest": "d26439d4ecbe8b67bb1ae9753454505358ebb6b802624f19800471e53ee27187" + "digest": "eab6fbc1824d6e3cd152e8ec1d82e1beaebe02b53b35c6f7a883b8548af02f3a" }, { "name": "ao", "unicode": "1F1E6-1F1F4", - "digest": "d26439d4ecbe8b67bb1ae9753454505358ebb6b802624f19800471e53ee27187" + "digest": "eab6fbc1824d6e3cd152e8ec1d82e1beaebe02b53b35c6f7a883b8548af02f3a" }, { "name": "flag_aq", "unicode": "1F1E6-1F1F6", - "digest": "6b0b4e800d88ab289ae4b6d449bfa115e92543958b477d13ad348468a74e4616" + "digest": "367f6677a683a5f0e7248ab3a8f46d06ba146a0fd75004c70bac0e913147cdaa" }, { "name": "aq", "unicode": "1F1E6-1F1F6", - "digest": "6b0b4e800d88ab289ae4b6d449bfa115e92543958b477d13ad348468a74e4616" + "digest": "367f6677a683a5f0e7248ab3a8f46d06ba146a0fd75004c70bac0e913147cdaa" }, { "name": "flag_ar", "unicode": "1F1E6-1F1F7", - "digest": "ca76db601dd3f5794f1caace8ab5641fe3786b86e4ae030706162f0ce07d27b3" + "digest": "f0dc466b3216957f2679d7208c2d7cf288448b0739b9270a7c5fa717577bdf25" }, { "name": "ar", "unicode": "1F1E6-1F1F7", - "digest": "ca76db601dd3f5794f1caace8ab5641fe3786b86e4ae030706162f0ce07d27b3" + "digest": "f0dc466b3216957f2679d7208c2d7cf288448b0739b9270a7c5fa717577bdf25" }, { "name": "flag_as", "unicode": "1F1E6-1F1F8", - "digest": "170e1dde0e3fd2e0f2149de5cc8845e15580cc0412e81a643d61bd387de16141" + "digest": "fcb7a865c7763c63b23485cc27207b99a3a8492e83d5b5ee2df259a9f68f77d6" }, { "name": "as", "unicode": "1F1E6-1F1F8", - "digest": "170e1dde0e3fd2e0f2149de5cc8845e15580cc0412e81a643d61bd387de16141" + "digest": "fcb7a865c7763c63b23485cc27207b99a3a8492e83d5b5ee2df259a9f68f77d6" }, { "name": "flag_at", "unicode": "1F1E6-1F1F9", - "digest": "0ab3675a16b4988e87c81e87453c160d6616c7be76247f54c471dc63aa8b42ba" + "digest": "1d3d58e9abc034f9a093a94716eddf9811d54dfaf27969fd322b3809fac70217" }, { "name": "at", "unicode": "1F1E6-1F1F9", - "digest": "0ab3675a16b4988e87c81e87453c160d6616c7be76247f54c471dc63aa8b42ba" + "digest": "1d3d58e9abc034f9a093a94716eddf9811d54dfaf27969fd322b3809fac70217" }, { "name": "flag_au", "unicode": "1F1E6-1F1FA", - "digest": "b6f17d3dfd3547c069a0b6cddd4cf44fb8ce1d1d300e24284fb292ac142537e3" + "digest": "789563b64c71a5ad49078d335dc166ef614edb56d1e401885d32fb191c198fbd" }, { "name": "au", "unicode": "1F1E6-1F1FA", - "digest": "b6f17d3dfd3547c069a0b6cddd4cf44fb8ce1d1d300e24284fb292ac142537e3" + "digest": "789563b64c71a5ad49078d335dc166ef614edb56d1e401885d32fb191c198fbd" }, { "name": "flag_aw", "unicode": "1F1E6-1F1FC", - "digest": "7857bc907f04dfb7ccc4401c05034ad8afb6383a022db77973cfcafa4d6c16c8" + "digest": "1504dc3fd8457b44fdf75c15e136dc46a13e8342d1f98949728cdc1238843e0c" }, { "name": "aw", "unicode": "1F1E6-1F1FC", - "digest": "7857bc907f04dfb7ccc4401c05034ad8afb6383a022db77973cfcafa4d6c16c8" + "digest": "1504dc3fd8457b44fdf75c15e136dc46a13e8342d1f98949728cdc1238843e0c" }, { "name": "flag_ax", "unicode": "1F1E6-1F1FD", - "digest": "ab8f1fd4af7c220a54d478cec5a9f7f3beb5fc83439c448f3ac9848af8391ac1" + "digest": "e96fa3525f3be25016a4cf8428261735f3ed5fc9fe5b827b461746a3f08877bf" }, { "name": "ax", "unicode": "1F1E6-1F1FD", - "digest": "ab8f1fd4af7c220a54d478cec5a9f7f3beb5fc83439c448f3ac9848af8391ac1" + "digest": "e96fa3525f3be25016a4cf8428261735f3ed5fc9fe5b827b461746a3f08877bf" }, { "name": "flag_az", "unicode": "1F1E6-1F1FF", - "digest": "187cc7b6d39800c5910a34409db1e6b1d8aac808c72a93e922a419d9b054fd0b" + "digest": "12c366ac2c38b91314fb29056e09fa6e7417766cebde3045859cdb127549f4a2" }, { "name": "az", "unicode": "1F1E6-1F1FF", - "digest": "187cc7b6d39800c5910a34409db1e6b1d8aac808c72a93e922a419d9b054fd0b" + "digest": "12c366ac2c38b91314fb29056e09fa6e7417766cebde3045859cdb127549f4a2" }, { "name": "flag_ba", "unicode": "1F1E7-1F1E6", - "digest": "cd22c744213087384cf79ed314742026787212c9ceb6999ed166534670f7864a" + "digest": "0819ea3901510ac20c7f10e67e5f6c818210f17a362c1d12e299c41feb07f828" }, { "name": "ba", "unicode": "1F1E7-1F1E6", - "digest": "cd22c744213087384cf79ed314742026787212c9ceb6999ed166534670f7864a" + "digest": "0819ea3901510ac20c7f10e67e5f6c818210f17a362c1d12e299c41feb07f828" }, { "name": "flag_bb", "unicode": "1F1E7-1F1E7", - "digest": "44ff0a48ac2d2180374baa58b1b7c64f26d0d151a48811eb08ffa20758104512" + "digest": "cf32778a272ed6cbc8e783b59befd9b204009c69c61a425e148d867808b7fab9" }, { "name": "bb", "unicode": "1F1E7-1F1E7", - "digest": "44ff0a48ac2d2180374baa58b1b7c64f26d0d151a48811eb08ffa20758104512" + "digest": "cf32778a272ed6cbc8e783b59befd9b204009c69c61a425e148d867808b7fab9" }, { "name": "flag_bd", "unicode": "1F1E7-1F1E9", - "digest": "c18793d2b963458607a0bab94c57e62c8278fce870e96fd8dda78067a8fbde18" + "digest": "e6ed186644a874588e879513aec92f8107220dcdd14c766dee61f266ce045665" }, { "name": "bd", "unicode": "1F1E7-1F1E9", - "digest": "c18793d2b963458607a0bab94c57e62c8278fce870e96fd8dda78067a8fbde18" + "digest": "e6ed186644a874588e879513aec92f8107220dcdd14c766dee61f266ce045665" }, { "name": "flag_be", "unicode": "1F1E7-1F1EA", - "digest": "6e6ccfca064a43b93c8acc04a9425f95af204198022ca20b9ee6c491e99ad950" + "digest": "4d941011d15d9f6e755d6f7694884758baf17ac0691bf5d63700f8d6dbcdb948" }, { "name": "be", "unicode": "1F1E7-1F1EA", - "digest": "6e6ccfca064a43b93c8acc04a9425f95af204198022ca20b9ee6c491e99ad950" + "digest": "4d941011d15d9f6e755d6f7694884758baf17ac0691bf5d63700f8d6dbcdb948" }, { "name": "flag_bf", "unicode": "1F1E7-1F1EB", - "digest": "d69c0394a1c7cb6323f54f024b7d740c728f229ca5e1b54ac374d5024f5470a5" + "digest": "fcc57dbda9a86f725f558b6c6309484c97e65f1644aae4f9fb5e642681f6c2e0" }, { "name": "bf", "unicode": "1F1E7-1F1EB", - "digest": "d69c0394a1c7cb6323f54f024b7d740c728f229ca5e1b54ac374d5024f5470a5" + "digest": "fcc57dbda9a86f725f558b6c6309484c97e65f1644aae4f9fb5e642681f6c2e0" }, { "name": "flag_bg", "unicode": "1F1E7-1F1EC", - "digest": "413a270caf4a9155e84bdba6c9512277f5642246f6ba8d701383a5eeb02f7e95" + "digest": "816c47ed96c36c90723da150645902ea8ba18b44757fdd776c7b3542cfecfb18" }, { "name": "bg", "unicode": "1F1E7-1F1EC", - "digest": "413a270caf4a9155e84bdba6c9512277f5642246f6ba8d701383a5eeb02f7e95" + "digest": "816c47ed96c36c90723da150645902ea8ba18b44757fdd776c7b3542cfecfb18" }, { "name": "flag_bh", "unicode": "1F1E7-1F1ED", - "digest": "9243ed65d7f24c824c2a3207335a2d4ad25251258547c16d0b7b7cbb9df6f8de" + "digest": "2cd5c21775a6e73f59d08c9ee0cedf4e8241e562eab939573501d47681987737" }, { "name": "bh", "unicode": "1F1E7-1F1ED", - "digest": "9243ed65d7f24c824c2a3207335a2d4ad25251258547c16d0b7b7cbb9df6f8de" + "digest": "2cd5c21775a6e73f59d08c9ee0cedf4e8241e562eab939573501d47681987737" }, { "name": "flag_bi", "unicode": "1F1E7-1F1EE", - "digest": "63056519030524b2d2dcd47448267d817205dbd6b98075c97f011a8f1d4d1a4b" + "digest": "2da82acbec5518360633c1b0b56d55a79b67237f67d92af5e5cd75a2f3bd550e" }, { "name": "bi", "unicode": "1F1E7-1F1EE", - "digest": "63056519030524b2d2dcd47448267d817205dbd6b98075c97f011a8f1d4d1a4b" + "digest": "2da82acbec5518360633c1b0b56d55a79b67237f67d92af5e5cd75a2f3bd550e" }, { "name": "flag_bj", "unicode": "1F1E7-1F1EF", - "digest": "93b245eed85d22260d27d1a8c77f51fb3439309e09b2aeca6cd504dbea77b509" + "digest": "8fe8c34651eb4e28ab395261a5b72b6f37579535ed676d15de131914e19c0436" }, { "name": "bj", "unicode": "1F1E7-1F1EF", - "digest": "93b245eed85d22260d27d1a8c77f51fb3439309e09b2aeca6cd504dbea77b509" + "digest": "8fe8c34651eb4e28ab395261a5b72b6f37579535ed676d15de131914e19c0436" }, { "name": "flag_bl", "unicode": "1F1E7-1F1F1", - "digest": "5e1e478deaf02bbaa26595e4cefc5f5c9bec6105ce521b7b9ab4fa5e7a452c14" + "digest": "d37f2a215ee7ef5b5ab62d2a0c87e90553b17c6ee310f803a71e9fd72db880e7" }, { "name": "bl", "unicode": "1F1E7-1F1F1", - "digest": "5e1e478deaf02bbaa26595e4cefc5f5c9bec6105ce521b7b9ab4fa5e7a452c14" + "digest": "d37f2a215ee7ef5b5ab62d2a0c87e90553b17c6ee310f803a71e9fd72db880e7" }, { "name": "flag_black", "unicode": "1F3F4", - "digest": "df131e5c28e9f51dea53fe7f33551f91d420f7d686b7a62980f0154c6b5357a6" + "digest": "3740bfc9bcb3b46b697b8b7c47ab2c3e95eca9dbcba12f2bf98a01302704f203" }, { "name": "waving_black_flag", "unicode": "1F3F4", - "digest": "df131e5c28e9f51dea53fe7f33551f91d420f7d686b7a62980f0154c6b5357a6" + "digest": "3740bfc9bcb3b46b697b8b7c47ab2c3e95eca9dbcba12f2bf98a01302704f203" }, { "name": "flag_bm", "unicode": "1F1E7-1F1F2", - "digest": "9dcd9e60faebe7f93eb19157e99f2ad654a8145c61738de96e6ecd11a246764a" + "digest": "ccd21655573f3c955d616c5c7b1eac2be1d4772ff611648d6713ba55d9e4aa9b" }, { "name": "bm", "unicode": "1F1E7-1F1F2", - "digest": "9dcd9e60faebe7f93eb19157e99f2ad654a8145c61738de96e6ecd11a246764a" + "digest": "ccd21655573f3c955d616c5c7b1eac2be1d4772ff611648d6713ba55d9e4aa9b" }, { "name": "flag_bn", "unicode": "1F1E7-1F1F3", - "digest": "078af6ca481a77871ba005e251a46ce63951c27b1b0cd33b9c1d0d31d349bc1a" + "digest": "54330c3d7a37392e69098c213fd8c78f3faab4e7e5909c039188110422514228" }, { "name": "bn", "unicode": "1F1E7-1F1F3", - "digest": "078af6ca481a77871ba005e251a46ce63951c27b1b0cd33b9c1d0d31d349bc1a" + "digest": "54330c3d7a37392e69098c213fd8c78f3faab4e7e5909c039188110422514228" }, { "name": "flag_bo", "unicode": "1F1E7-1F1F4", - "digest": "92516d04e922a3bcbabe2e7619194bc972c09ba97576e8155f9829c397a71d8c" + "digest": "32aff973b26f4f91ca19dddd7861b564da43cfbee87603d8c004f1111342366c" }, { "name": "bo", "unicode": "1F1E7-1F1F4", - "digest": "92516d04e922a3bcbabe2e7619194bc972c09ba97576e8155f9829c397a71d8c" + "digest": "32aff973b26f4f91ca19dddd7861b564da43cfbee87603d8c004f1111342366c" }, { "name": "flag_bq", "unicode": "1F1E7-1F1F6", - "digest": "7832df5267a2bb8dddb83aeb11162ce79aeebdb718f2ac0e54adcf3d87936171" + "digest": "b1ebc959c43f706ca430d8633d9efaa9c60133871506b5f030b730cfb4c19e6f" }, { "name": "bq", "unicode": "1F1E7-1F1F6", - "digest": "7832df5267a2bb8dddb83aeb11162ce79aeebdb718f2ac0e54adcf3d87936171" + "digest": "b1ebc959c43f706ca430d8633d9efaa9c60133871506b5f030b730cfb4c19e6f" }, { "name": "flag_br", "unicode": "1F1E7-1F1F7", - "digest": "aabcc1c082124045ed214f7d9778d8e2ed791ebb8433defea91db458658abeec" + "digest": "64fb154d71fa34ff4838bc405f3e58a4102cf0cb49ca4b06fc3c7a6bf39671f0" }, { "name": "br", "unicode": "1F1E7-1F1F7", - "digest": "aabcc1c082124045ed214f7d9778d8e2ed791ebb8433defea91db458658abeec" + "digest": "64fb154d71fa34ff4838bc405f3e58a4102cf0cb49ca4b06fc3c7a6bf39671f0" }, { "name": "flag_bs", "unicode": "1F1E7-1F1F8", - "digest": "f628f39003608e181696634929522884165e27ccef55270293f92eeef991635f" + "digest": "c4b07e5f652ab06ece95d3774ce8b1399a935f8a28d440cb13cc8bd0b9728ed5" }, { "name": "bs", "unicode": "1F1E7-1F1F8", - "digest": "f628f39003608e181696634929522884165e27ccef55270293f92eeef991635f" + "digest": "c4b07e5f652ab06ece95d3774ce8b1399a935f8a28d440cb13cc8bd0b9728ed5" }, { "name": "flag_bt", "unicode": "1F1E7-1F1F9", - "digest": "af24a8ab34815da04c3e5af49a47449e0de93b068957cbda695816d0f830ca12" + "digest": "901ddbd999dd89a87c1e1208b1470cb4e604a9bc023d0cbcdee64e1bc54079ba" }, { "name": "bt", "unicode": "1F1E7-1F1F9", - "digest": "af24a8ab34815da04c3e5af49a47449e0de93b068957cbda695816d0f830ca12" + "digest": "901ddbd999dd89a87c1e1208b1470cb4e604a9bc023d0cbcdee64e1bc54079ba" }, { "name": "flag_bv", "unicode": "1F1E7-1F1FB", - "digest": "ff0037f6eed95d4bb5f2b502902360e1ff41426e2896daf3e0730cef1f8f7e41" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "bv", "unicode": "1F1E7-1F1FB", - "digest": "ff0037f6eed95d4bb5f2b502902360e1ff41426e2896daf3e0730cef1f8f7e41" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "flag_bw", "unicode": "1F1E7-1F1FC", - "digest": "3e3241ecb97946cc3e467b083d113a57dd305595e1512d4da18cc403e8689c1d" + "digest": "05aa351bc04dc0fe2669441ab500e000d48b1f0d7ad9e885c7abfb898aa0eb3f" }, { "name": "bw", "unicode": "1F1E7-1F1FC", - "digest": "3e3241ecb97946cc3e467b083d113a57dd305595e1512d4da18cc403e8689c1d" + "digest": "05aa351bc04dc0fe2669441ab500e000d48b1f0d7ad9e885c7abfb898aa0eb3f" }, { "name": "flag_by", "unicode": "1F1E7-1F1FE", - "digest": "bdd21885c6fac475241884a44149b887297772e17617ee59dd9fe8518d52cf3d" + "digest": "6eda3b87336ecf0aae4963986d86b916a055d8268c70520303288f235a93b0d9" }, { "name": "by", "unicode": "1F1E7-1F1FE", - "digest": "bdd21885c6fac475241884a44149b887297772e17617ee59dd9fe8518d52cf3d" + "digest": "6eda3b87336ecf0aae4963986d86b916a055d8268c70520303288f235a93b0d9" }, { "name": "flag_bz", "unicode": "1F1E7-1F1FF", - "digest": "21c16e1da641af004576000bf1db44b9a1e0fccfddc775e96022721c2f18eeea" + "digest": "d76ed945b1408558a30a99b8eed6712de968fc49fba1721b5660b8f48087e45a" }, { "name": "bz", "unicode": "1F1E7-1F1FF", - "digest": "21c16e1da641af004576000bf1db44b9a1e0fccfddc775e96022721c2f18eeea" + "digest": "d76ed945b1408558a30a99b8eed6712de968fc49fba1721b5660b8f48087e45a" }, { "name": "flag_ca", "unicode": "1F1E8-1F1E6", - "digest": "0d00e459084d58d3ea9c60488a9e51bf45f71b77f1600f190225d5ca6ca6c796" + "digest": "2fd036047d89751c05de5577909b58347883bc89c3b7d90bec28ad4770a98ecd" }, { "name": "ca", "unicode": "1F1E8-1F1E6", - "digest": "0d00e459084d58d3ea9c60488a9e51bf45f71b77f1600f190225d5ca6ca6c796" + "digest": "2fd036047d89751c05de5577909b58347883bc89c3b7d90bec28ad4770a98ecd" }, { "name": "flag_cc", "unicode": "1F1E8-1F1E8", - "digest": "86ab27164603ef0f1f83fe898eda6fbb7bc5709f2518f5577f00817860806a7b" + "digest": "837ba181a01c71f05d438d205efaaee99f93b2370c97b13e6132f99860323e36" }, { "name": "cc", "unicode": "1F1E8-1F1E8", - "digest": "86ab27164603ef0f1f83fe898eda6fbb7bc5709f2518f5577f00817860806a7b" + "digest": "837ba181a01c71f05d438d205efaaee99f93b2370c97b13e6132f99860323e36" }, { "name": "flag_cd", "unicode": "1F1E8-1F1E9", - "digest": "fdc2796530ada4bd0bae37ace4bbe707b321b287dcd64568f8e01d3a9df56066" + "digest": "318689274b4b3b58aed7fc1654127499a9da69bff1b83e592e86e69d167ce16f" }, { "name": "congo", "unicode": "1F1E8-1F1E9", - "digest": "fdc2796530ada4bd0bae37ace4bbe707b321b287dcd64568f8e01d3a9df56066" + "digest": "318689274b4b3b58aed7fc1654127499a9da69bff1b83e592e86e69d167ce16f" }, { "name": "flag_cf", "unicode": "1F1E8-1F1EB", - "digest": "5943bec02bede0931e21e7c34a68f375499f60a34883cc1edf2f21e9834b15ce" + "digest": "06d6042849d3b7b217c2b18ba787aae449e8c7d2537e2e5974744ec196062228" }, { "name": "cf", "unicode": "1F1E8-1F1EB", - "digest": "5943bec02bede0931e21e7c34a68f375499f60a34883cc1edf2f21e9834b15ce" + "digest": "06d6042849d3b7b217c2b18ba787aae449e8c7d2537e2e5974744ec196062228" }, { "name": "flag_cg", "unicode": "1F1E8-1F1EC", - "digest": "54498482e2772371e148e05cfb7c5eb55f6a22cd528662abdea10bad47d157da" + "digest": "09f45d2dcb5a24d8349ef86e7405cc29ef3d65a908c0bff3221c3b4546547813" }, { "name": "cg", "unicode": "1F1E8-1F1EC", - "digest": "54498482e2772371e148e05cfb7c5eb55f6a22cd528662abdea10bad47d157da" + "digest": "09f45d2dcb5a24d8349ef86e7405cc29ef3d65a908c0bff3221c3b4546547813" }, { "name": "flag_ch", @@ -3142,2162 +3142,2162 @@ { "name": "flag_ci", "unicode": "1F1E8-1F1EE", - "digest": "3a173a3058a5c0174dc88750852cafec264e901ce82a6c69db122c8c0ea71a3a" + "digest": "7d85a0c314b7397c9397a54ce2f3a4dc5f40d0234e586dbd8a541a8666f0f51e" }, { "name": "ci", "unicode": "1F1E8-1F1EE", - "digest": "3a173a3058a5c0174dc88750852cafec264e901ce82a6c69db122c8c0ea71a3a" + "digest": "7d85a0c314b7397c9397a54ce2f3a4dc5f40d0234e586dbd8a541a8666f0f51e" }, { "name": "flag_ck", "unicode": "1F1E8-1F1F0", - "digest": "42f395ff53c618b72b8a224cd4343d1a32f5ad82ced56bf590170a5ff0d5134c" + "digest": "c1aa105fe106ed09ed59a596859a0ce4e65a415c59f63df51961491cb947b136" }, { "name": "ck", "unicode": "1F1E8-1F1F0", - "digest": "42f395ff53c618b72b8a224cd4343d1a32f5ad82ced56bf590170a5ff0d5134c" + "digest": "c1aa105fe106ed09ed59a596859a0ce4e65a415c59f63df51961491cb947b136" }, { "name": "flag_cl", "unicode": "1F1E8-1F1F1", - "digest": "9d6255feb690596904d800e72d5acdb5cda941c5a741b031ea39a3c7650ac46f" + "digest": "0fffdad0d892f5c08aaa332af1ed2c228583d89a43190e979a3c3cb020d5a723" }, { "name": "chile", "unicode": "1F1E8-1F1F1", - "digest": "9d6255feb690596904d800e72d5acdb5cda941c5a741b031ea39a3c7650ac46f" + "digest": "0fffdad0d892f5c08aaa332af1ed2c228583d89a43190e979a3c3cb020d5a723" }, { "name": "flag_cm", "unicode": "1F1E8-1F1F2", - "digest": "ffc99d14e0a8b46a980331090ed9f36f31a87f1b0f8dd8c09007a31c6127c69e" + "digest": "e9f55e41a1fd2735a82ad7a7ac39326a944cb20423ffba3608ac53a46036caad" }, { "name": "cm", "unicode": "1F1E8-1F1F2", - "digest": "ffc99d14e0a8b46a980331090ed9f36f31a87f1b0f8dd8c09007a31c6127c69e" + "digest": "e9f55e41a1fd2735a82ad7a7ac39326a944cb20423ffba3608ac53a46036caad" }, { "name": "flag_cn", "unicode": "1F1E8-1F1F3", - "digest": "869a98c52bdc33591f87e2aab6cb4f13e98bb19136250ff25805d0312a8b7c8a" + "digest": "e2c8fee7e3bd51b13d6083d5bf344abe6b9b642e3cbb099d38b4ce341c99d890" }, { "name": "cn", "unicode": "1F1E8-1F1F3", - "digest": "869a98c52bdc33591f87e2aab6cb4f13e98bb19136250ff25805d0312a8b7c8a" + "digest": "e2c8fee7e3bd51b13d6083d5bf344abe6b9b642e3cbb099d38b4ce341c99d890" }, { "name": "flag_co", "unicode": "1F1E8-1F1F4", - "digest": "6aa458440eb2500ad307fea40fd8f1171a1506a6e32af144a4fd51545bb56151" + "digest": "51c60d0979bf8342eaff7cda9faf4b0dfab38efaf5ddf3717eb8f0e2a595b15f" }, { "name": "co", "unicode": "1F1E8-1F1F4", - "digest": "6aa458440eb2500ad307fea40fd8f1171a1506a6e32af144a4fd51545bb56151" + "digest": "51c60d0979bf8342eaff7cda9faf4b0dfab38efaf5ddf3717eb8f0e2a595b15f" }, { "name": "flag_cp", "unicode": "1F1E8-1F1F5", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "cp", "unicode": "1F1E8-1F1F5", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "flag_cr", "unicode": "1F1E8-1F1F7", - "digest": "0f3b54d8330c5bb136647547dafc598bda755697cfd6b7d872a2443ba7b5cad4" + "digest": "907905971b219e617a34eef4839b0bd08d98f3480e2631bce523120dcef95196" }, { "name": "cr", "unicode": "1F1E8-1F1F7", - "digest": "0f3b54d8330c5bb136647547dafc598bda755697cfd6b7d872a2443ba7b5cad4" + "digest": "907905971b219e617a34eef4839b0bd08d98f3480e2631bce523120dcef95196" }, { "name": "flag_cu", "unicode": "1F1E8-1F1FA", - "digest": "69bc973002475bb3d9b54cb0ba9ec9cb85f144c1cf54689da0ee8f414ebb0d83" + "digest": "d88cea729dc9dbbbcadac0409ec561995f061b2280577c01c6c6b37de347f150" }, { "name": "cu", "unicode": "1F1E8-1F1FA", - "digest": "69bc973002475bb3d9b54cb0ba9ec9cb85f144c1cf54689da0ee8f414ebb0d83" + "digest": "d88cea729dc9dbbbcadac0409ec561995f061b2280577c01c6c6b37de347f150" }, { "name": "flag_cv", "unicode": "1F1E8-1F1FB", - "digest": "af2e135cf3c1b03a5937c068a75061b5cd332e95902fd0f8dffb2ac2dc89692a" + "digest": "5ce97944adfce09e96387e6f872256482ac99ccbc60017c4d58ddd15b6fb67a7" }, { "name": "cv", "unicode": "1F1E8-1F1FB", - "digest": "af2e135cf3c1b03a5937c068a75061b5cd332e95902fd0f8dffb2ac2dc89692a" + "digest": "5ce97944adfce09e96387e6f872256482ac99ccbc60017c4d58ddd15b6fb67a7" }, { "name": "flag_cw", "unicode": "1F1E8-1F1FC", - "digest": "df4b2228a82f766c5c64c13c1388482a68549e59dd843671ee0eb43506e33411" + "digest": "a6fc31bd66ddc2ee8e7bde3aeabfe1c4ad00c9688abae234a541cc1236d68c1b" }, { "name": "cw", "unicode": "1F1E8-1F1FC", - "digest": "df4b2228a82f766c5c64c13c1388482a68549e59dd843671ee0eb43506e33411" + "digest": "a6fc31bd66ddc2ee8e7bde3aeabfe1c4ad00c9688abae234a541cc1236d68c1b" }, { "name": "flag_cx", "unicode": "1F1E8-1F1FD", - "digest": "db12e513345a7be53954167d359ede0b3effbfb292508ee4d726123e3a8f83d7" + "digest": "1261b32bfa22fa1441f5390ff499ac6b921d7ac59cc8acda3deb3a2beb4fb345" }, { "name": "cx", "unicode": "1F1E8-1F1FD", - "digest": "db12e513345a7be53954167d359ede0b3effbfb292508ee4d726123e3a8f83d7" + "digest": "1261b32bfa22fa1441f5390ff499ac6b921d7ac59cc8acda3deb3a2beb4fb345" }, { "name": "flag_cy", "unicode": "1F1E8-1F1FE", - "digest": "0cea41d4820746e2c6eb408f7ec7419afba9f7396401d92e6c1d77382f721d0b" + "digest": "82b1baa05ecffa0ea1f9a83b518163cbd7910985a21955740520bb16b7bb624f" }, { "name": "cy", "unicode": "1F1E8-1F1FE", - "digest": "0cea41d4820746e2c6eb408f7ec7419afba9f7396401d92e6c1d77382f721d0b" + "digest": "82b1baa05ecffa0ea1f9a83b518163cbd7910985a21955740520bb16b7bb624f" }, { "name": "flag_cz", "unicode": "1F1E8-1F1FF", - "digest": "a1c2405916963be306f761539123486a2845af53716c9dfe94ad5420e14d36c4" + "digest": "a169b18968992a52299b67c24fba495e84de28dec2ebb947a08e0d615ac54a5a" }, { "name": "cz", "unicode": "1F1E8-1F1FF", - "digest": "a1c2405916963be306f761539123486a2845af53716c9dfe94ad5420e14d36c4" + "digest": "a169b18968992a52299b67c24fba495e84de28dec2ebb947a08e0d615ac54a5a" }, { "name": "flag_de", "unicode": "1F1E9-1F1EA", - "digest": "74a80b64437bc4e31bdd7cbb753ecd2d719bf34c506cbac535db83a644174cce" + "digest": "99d1906944966a188c72ae592362ed907e2a0bfe95263955c34a0941507b30c1" }, { "name": "de", "unicode": "1F1E9-1F1EA", - "digest": "74a80b64437bc4e31bdd7cbb753ecd2d719bf34c506cbac535db83a644174cce" + "digest": "99d1906944966a188c72ae592362ed907e2a0bfe95263955c34a0941507b30c1" }, { "name": "flag_dg", "unicode": "1F1E9-1F1EC", - "digest": "13cb5ea872f94a9c3fb579cef417e2d1ed38e8cbe95059576380cacd59bc4b9d" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "dg", "unicode": "1F1E9-1F1EC", - "digest": "13cb5ea872f94a9c3fb579cef417e2d1ed38e8cbe95059576380cacd59bc4b9d" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "flag_dj", "unicode": "1F1E9-1F1EF", - "digest": "5b479654c28d3eeb70055c5e25dc46ccaba9eeea7537cc45ca9dbb8186b743b6" + "digest": "e90ba4e98fca71ff0ca5e65c28b911cc52f043428f375d8f954ecbd3b0c8f4dd" }, { "name": "dj", "unicode": "1F1E9-1F1EF", - "digest": "5b479654c28d3eeb70055c5e25dc46ccaba9eeea7537cc45ca9dbb8186b743b6" + "digest": "e90ba4e98fca71ff0ca5e65c28b911cc52f043428f375d8f954ecbd3b0c8f4dd" }, { "name": "flag_dk", "unicode": "1F1E9-1F1F0", - "digest": "dee7fa9644a9b447417518a353e7edcbb37b2af8bc7d13a6ed71d7210c43ca3c" + "digest": "65b3b5f31935a4969d81fedbb8279c7ad32da454d15c5eafcceba5d140927c77" }, { "name": "dk", "unicode": "1F1E9-1F1F0", - "digest": "dee7fa9644a9b447417518a353e7edcbb37b2af8bc7d13a6ed71d7210c43ca3c" + "digest": "65b3b5f31935a4969d81fedbb8279c7ad32da454d15c5eafcceba5d140927c77" }, { "name": "flag_dm", "unicode": "1F1E9-1F1F2", - "digest": "2e339190a8a0a238140f42e329f6646af5be75763a787ea268488a2e0440dc4c" + "digest": "f6225ded6d2cfd6c182ab1a53b8c49dc9df195df11eb7ff27b15f5d3721ba0eb" }, { "name": "dm", "unicode": "1F1E9-1F1F2", - "digest": "2e339190a8a0a238140f42e329f6646af5be75763a787ea268488a2e0440dc4c" + "digest": "f6225ded6d2cfd6c182ab1a53b8c49dc9df195df11eb7ff27b15f5d3721ba0eb" }, { "name": "flag_do", "unicode": "1F1E9-1F1F4", - "digest": "be5dafcd32d7197a96d37299a91835a8009299452f05a66d91c5fdec17448230" + "digest": "dc2ad6856cebbe47c5bd7f5dcf087e4f680d396b2d49440a9b71f0ad49fb8102" }, { "name": "do", "unicode": "1F1E9-1F1F4", - "digest": "be5dafcd32d7197a96d37299a91835a8009299452f05a66d91c5fdec17448230" + "digest": "dc2ad6856cebbe47c5bd7f5dcf087e4f680d396b2d49440a9b71f0ad49fb8102" }, { "name": "flag_dz", "unicode": "1F1E9-1F1FF", - "digest": "cf525d56bac45fe689f92d441274fc0ecbed4f95591d2c066598f72b1ee8d618" + "digest": "ea69fffc4d545f9c0fcef6768257501952955ba4d274c9b81843229a1265c5ed" }, { "name": "dz", "unicode": "1F1E9-1F1FF", - "digest": "cf525d56bac45fe689f92d441274fc0ecbed4f95591d2c066598f72b1ee8d618" + "digest": "ea69fffc4d545f9c0fcef6768257501952955ba4d274c9b81843229a1265c5ed" }, { "name": "flag_ea", "unicode": "1F1EA-1F1E6", - "digest": "1acb13950f7c3692f9a36e618d8ec10a73ead5d7fa80fb52b6b2a18e3d456002" + "digest": "e63bfe15428c481dd23b569e7aaf0a76106e58a946995b4415a81097ecd53b7d" }, { "name": "ea", "unicode": "1F1EA-1F1E6", - "digest": "1acb13950f7c3692f9a36e618d8ec10a73ead5d7fa80fb52b6b2a18e3d456002" + "digest": "e63bfe15428c481dd23b569e7aaf0a76106e58a946995b4415a81097ecd53b7d" }, { "name": "flag_ec", "unicode": "1F1EA-1F1E8", - "digest": "4d9d35450efc6026651ccc2278e70fb90b001ca5e5eecd31361b1e4e23253dbd" + "digest": "0cdabf85cd567047fda1d9a4508220cab829943a7c542c315078db0aac33edac" }, { "name": "ec", "unicode": "1F1EA-1F1E8", - "digest": "4d9d35450efc6026651ccc2278e70fb90b001ca5e5eecd31361b1e4e23253dbd" + "digest": "0cdabf85cd567047fda1d9a4508220cab829943a7c542c315078db0aac33edac" }, { "name": "flag_ee", "unicode": "1F1EA-1F1EA", - "digest": "86ec7b2f618fe71dddec3d5a621b56b878d683780f1e0ad446f965326d42df48" + "digest": "6dc4e3377e8e2af3ff40cf940a914bc7840980b4a14e7da86954343f2b1025fe" }, { "name": "ee", "unicode": "1F1EA-1F1EA", - "digest": "86ec7b2f618fe71dddec3d5a621b56b878d683780f1e0ad446f965326d42df48" + "digest": "6dc4e3377e8e2af3ff40cf940a914bc7840980b4a14e7da86954343f2b1025fe" }, { "name": "flag_eg", "unicode": "1F1EA-1F1EC", - "digest": "f06d36a6fec15af4c1a76de30e8469847dde2728bb5a48956b4e466098b778a4" + "digest": "2ed6bc056015694d75993eb5ee3c1850921d5630681207b04dfbdb982ab346a2" }, { "name": "eg", "unicode": "1F1EA-1F1EC", - "digest": "f06d36a6fec15af4c1a76de30e8469847dde2728bb5a48956b4e466098b778a4" + "digest": "2ed6bc056015694d75993eb5ee3c1850921d5630681207b04dfbdb982ab346a2" }, { "name": "flag_eh", "unicode": "1F1EA-1F1ED", - "digest": "eb63f5b92c62c98dc008dfa7ad8830aa17fa23964f812a28055bd8b6f5960c5b" + "digest": "72adb55943e4df99c00843c65463718609d937480f73dcf4a4451d46b9967a5e" }, { "name": "eh", "unicode": "1F1EA-1F1ED", - "digest": "eb63f5b92c62c98dc008dfa7ad8830aa17fa23964f812a28055bd8b6f5960c5b" + "digest": "72adb55943e4df99c00843c65463718609d937480f73dcf4a4451d46b9967a5e" }, { "name": "flag_er", "unicode": "1F1EA-1F1F7", - "digest": "e901195f7b37b22a6872d36713de0ec176f6424c209e261e5c849ce318c772f6" + "digest": "3fa59331eb5300c8c1f7b1f1bc15cfcfe688da6fa4a79341854598086a44eebc" }, { "name": "er", "unicode": "1F1EA-1F1F7", - "digest": "e901195f7b37b22a6872d36713de0ec176f6424c209e261e5c849ce318c772f6" + "digest": "3fa59331eb5300c8c1f7b1f1bc15cfcfe688da6fa4a79341854598086a44eebc" }, { "name": "flag_es", "unicode": "1F1EA-1F1F8", - "digest": "27ab5cc6c2e9f26ccdfa632887533eebcd9b514f80cec9e721cf8e5e2544339c" + "digest": "1fa1d5cb0a7e8b14aaec758b2e7bf49cdf8f3d09bbcc7dfd589053a432eeae25" }, { "name": "es", "unicode": "1F1EA-1F1F8", - "digest": "27ab5cc6c2e9f26ccdfa632887533eebcd9b514f80cec9e721cf8e5e2544339c" + "digest": "1fa1d5cb0a7e8b14aaec758b2e7bf49cdf8f3d09bbcc7dfd589053a432eeae25" }, { "name": "flag_et", "unicode": "1F1EA-1F1F9", - "digest": "6cdb3718c9b3ec713258dd36781db58b7da53f3017445056c1a76233e3b4a7de" + "digest": "72771decfb214394e4beb594e848ea590c3615800adbba24b5df4c5db6ee9617" }, { "name": "et", "unicode": "1F1EA-1F1F9", - "digest": "6cdb3718c9b3ec713258dd36781db58b7da53f3017445056c1a76233e3b4a7de" + "digest": "72771decfb214394e4beb594e848ea590c3615800adbba24b5df4c5db6ee9617" }, { "name": "flag_eu", "unicode": "1F1EA-1F1FA", - "digest": "363f60e8a747166d5cec8d70bfdf266411eec2ff07933b6187975075caadfd74" + "digest": "4bfa1b2ef23764ead5ef7899806f93e13fd29a09c75e61431579a4116c836aa4" }, { "name": "eu", "unicode": "1F1EA-1F1FA", - "digest": "363f60e8a747166d5cec8d70bfdf266411eec2ff07933b6187975075caadfd74" + "digest": "4bfa1b2ef23764ead5ef7899806f93e13fd29a09c75e61431579a4116c836aa4" }, { "name": "flag_fi", "unicode": "1F1EB-1F1EE", - "digest": "1a1959cb551a0e8bdaee8c04657fb7387a4d83173f7759f89468da12e1818a9e" + "digest": "d0208cdd5b153a2865f9f674179c62871d4675abb0fb639fba88fcd62553f54e" }, { "name": "fi", "unicode": "1F1EB-1F1EE", - "digest": "1a1959cb551a0e8bdaee8c04657fb7387a4d83173f7759f89468da12e1818a9e" + "digest": "d0208cdd5b153a2865f9f674179c62871d4675abb0fb639fba88fcd62553f54e" }, { "name": "flag_fj", "unicode": "1F1EB-1F1EF", - "digest": "f26dc36ea9c1f32d9bb54874ea384e7118b6e2585be69245fdd73acd8304ae78" + "digest": "6c5ec41114af3846b093a418f6e2b5ff7a83cb72cecde75a7dc62e8cb6dcfe45" }, { "name": "fj", "unicode": "1F1EB-1F1EF", - "digest": "f26dc36ea9c1f32d9bb54874ea384e7118b6e2585be69245fdd73acd8304ae78" + "digest": "6c5ec41114af3846b093a418f6e2b5ff7a83cb72cecde75a7dc62e8cb6dcfe45" }, { "name": "flag_fk", "unicode": "1F1EB-1F1F0", - "digest": "0479e233499b704f91a9b13d083e66296efe2f28ed917ab1496b223bfb09adb8" + "digest": "c69ad641d53785deff5c3934b7dcfcd3dc32ffc31b6d3e799d0555b03c23fc15" }, { "name": "fk", "unicode": "1F1EB-1F1F0", - "digest": "0479e233499b704f91a9b13d083e66296efe2f28ed917ab1496b223bfb09adb8" + "digest": "c69ad641d53785deff5c3934b7dcfcd3dc32ffc31b6d3e799d0555b03c23fc15" }, { "name": "flag_fm", "unicode": "1F1EB-1F1F2", - "digest": "142ea7b4b4a7004329925b495da43ab82351cbaac383c8da6e614b39ba58d05e" + "digest": "1e29fb06b273f253c23a9e4aa8ff84bfe22cffb5fa158a0c6f4cdeabe0216990" }, { "name": "fm", "unicode": "1F1EB-1F1F2", - "digest": "142ea7b4b4a7004329925b495da43ab82351cbaac383c8da6e614b39ba58d05e" + "digest": "1e29fb06b273f253c23a9e4aa8ff84bfe22cffb5fa158a0c6f4cdeabe0216990" }, { "name": "flag_fo", "unicode": "1F1EB-1F1F4", - "digest": "f1c800d4f4d39e2aead9a11ed500f16108d6bc48bd24bd2a1af7b966d8e76752" + "digest": "f4907d2f606f4f9d3bef06c6d38e8e88f2a148197b1573668866431a007afc2e" }, { "name": "fo", "unicode": "1F1EB-1F1F4", - "digest": "f1c800d4f4d39e2aead9a11ed500f16108d6bc48bd24bd2a1af7b966d8e76752" + "digest": "f4907d2f606f4f9d3bef06c6d38e8e88f2a148197b1573668866431a007afc2e" }, { "name": "flag_fr", "unicode": "1F1EB-1F1F7", - "digest": "6f52f36b5199c65ab1cad13ff4e77d2d8b48a8ff79b92166976674ffdc7829ee" + "digest": "5a1308ab3cbf6bffcab12588cf3325151a6c72990db7408c2b8605d89f94ed6e" }, { "name": "fr", "unicode": "1F1EB-1F1F7", - "digest": "6f52f36b5199c65ab1cad13ff4e77d2d8b48a8ff79b92166976674ffdc7829ee" + "digest": "5a1308ab3cbf6bffcab12588cf3325151a6c72990db7408c2b8605d89f94ed6e" }, { "name": "flag_ga", "unicode": "1F1EC-1F1E6", - "digest": "50a0d5a07466e419b74a4d532738f7958de9baa37df6191be4f3755dccc3b326" + "digest": "ddc32dee2976507be878ec3d3d2408632ca21bc434cd9f58db4f6ac9774a2db5" }, { "name": "ga", "unicode": "1F1EC-1F1E6", - "digest": "50a0d5a07466e419b74a4d532738f7958de9baa37df6191be4f3755dccc3b326" + "digest": "ddc32dee2976507be878ec3d3d2408632ca21bc434cd9f58db4f6ac9774a2db5" }, { "name": "flag_gb", "unicode": "1F1EC-1F1E7", - "digest": "220f7da6d5a231b766c79f2e1b7d3fdb74ec0c0c17558cc00a8a8ccdf2afc2e0" + "digest": "6b3bb254d134870b02cb066b06e206f652638a915c84b8649ceb30ec67fbebde" }, { "name": "gb", "unicode": "1F1EC-1F1E7", - "digest": "220f7da6d5a231b766c79f2e1b7d3fdb74ec0c0c17558cc00a8a8ccdf2afc2e0" + "digest": "6b3bb254d134870b02cb066b06e206f652638a915c84b8649ceb30ec67fbebde" }, { "name": "flag_gd", "unicode": "1F1EC-1F1E9", - "digest": "3e162b0d13f4ceea7f663b1d425f13863d104e80df75a640f526e276bcd04081" + "digest": "b6a210541ca22d816405f2a7d0d5241dc4d5488c8a36e15bd1e3063f9c41327f" }, { "name": "gd", "unicode": "1F1EC-1F1E9", - "digest": "3e162b0d13f4ceea7f663b1d425f13863d104e80df75a640f526e276bcd04081" + "digest": "b6a210541ca22d816405f2a7d0d5241dc4d5488c8a36e15bd1e3063f9c41327f" }, { "name": "flag_ge", "unicode": "1F1EC-1F1EA", - "digest": "35897f8254675d2efe9e3070c88af9ef214f08440e6ee75ebe81d28cdb57ea2b" + "digest": "e9a5035b7a46b925737e7f7b0ae2419cc4af0e980fbee5bd916edeef13823367" }, { "name": "ge", "unicode": "1F1EC-1F1EA", - "digest": "35897f8254675d2efe9e3070c88af9ef214f08440e6ee75ebe81d28cdb57ea2b" + "digest": "e9a5035b7a46b925737e7f7b0ae2419cc4af0e980fbee5bd916edeef13823367" }, { "name": "flag_gf", "unicode": "1F1EC-1F1EB", - "digest": "3a34df321635f71a0f2cc4e1eda58d85c29230c77456362345196351bf56533d" + "digest": "ce1bcd8c303897c1c22c5994182f21240b4aa635f0d7ce9944f76cbdbf0e4956" }, { "name": "gf", "unicode": "1F1EC-1F1EB", - "digest": "3a34df321635f71a0f2cc4e1eda58d85c29230c77456362345196351bf56533d" + "digest": "ce1bcd8c303897c1c22c5994182f21240b4aa635f0d7ce9944f76cbdbf0e4956" }, { "name": "flag_gg", "unicode": "1F1EC-1F1EC", - "digest": "c972f8d190b4e9ca8890df41503d202ffd73981833d3f3750f563302167bcd66" + "digest": "a435aab3609533ab2d68acd97deba844bfb0fc27b2adac68668223011f23ae5d" }, { "name": "gg", "unicode": "1F1EC-1F1EC", - "digest": "c972f8d190b4e9ca8890df41503d202ffd73981833d3f3750f563302167bcd66" + "digest": "a435aab3609533ab2d68acd97deba844bfb0fc27b2adac68668223011f23ae5d" }, { "name": "flag_gh", "unicode": "1F1EC-1F1ED", - "digest": "9c3d3569bd411389fa0af7c6938d4325cedeb9c0e8f059dc1d5a74c6b8d6d01b" + "digest": "7cad43b40f69b9b00cc1b38036789ce774fd3d597c89f0bf38433847ea69be26" }, { "name": "gh", "unicode": "1F1EC-1F1ED", - "digest": "9c3d3569bd411389fa0af7c6938d4325cedeb9c0e8f059dc1d5a74c6b8d6d01b" + "digest": "7cad43b40f69b9b00cc1b38036789ce774fd3d597c89f0bf38433847ea69be26" }, { "name": "flag_gi", "unicode": "1F1EC-1F1EE", - "digest": "ede638bc6fedc30a01821025d87ec19297500da9c04a7a155984fca186118649" + "digest": "70e9b17d18bf3e0e4d03f4f824323a57909416e4082ca9d8a0796a6959de4f07" }, { "name": "gi", "unicode": "1F1EC-1F1EE", - "digest": "ede638bc6fedc30a01821025d87ec19297500da9c04a7a155984fca186118649" + "digest": "70e9b17d18bf3e0e4d03f4f824323a57909416e4082ca9d8a0796a6959de4f07" }, { "name": "flag_gl", "unicode": "1F1EC-1F1F1", - "digest": "a2ce3371eff1da8331671925f707232aa593ac7400d59555c9ca689729ce24ec" + "digest": "1963d8cca1c1f06b7536b7fb8f5a4782ac0bb05afdf6e481101bce45c58cdd4b" }, { "name": "gl", "unicode": "1F1EC-1F1F1", - "digest": "a2ce3371eff1da8331671925f707232aa593ac7400d59555c9ca689729ce24ec" + "digest": "1963d8cca1c1f06b7536b7fb8f5a4782ac0bb05afdf6e481101bce45c58cdd4b" }, { "name": "flag_gm", "unicode": "1F1EC-1F1F2", - "digest": "932bf6eb75ddd4278268dd2f09d8fffcfef89f8fd6b6e86a08a414cd3ceec94d" + "digest": "6c776a8daa3f4daa2597b0025aec06fc0a53aed262e845d4da3897cd7a89c6a1" }, { "name": "gm", "unicode": "1F1EC-1F1F2", - "digest": "932bf6eb75ddd4278268dd2f09d8fffcfef89f8fd6b6e86a08a414cd3ceec94d" + "digest": "6c776a8daa3f4daa2597b0025aec06fc0a53aed262e845d4da3897cd7a89c6a1" }, { "name": "flag_gn", "unicode": "1F1EC-1F1F3", - "digest": "ebf543713895adaa09d64897f24bd461191191b8fcbbcede52bdaf4bd2dc67a8" + "digest": "134cf7c839370d171ae80a72e5d18d32ea1967df19c191d1a4ea446d649e9558" }, { "name": "gn", "unicode": "1F1EC-1F1F3", - "digest": "ebf543713895adaa09d64897f24bd461191191b8fcbbcede52bdaf4bd2dc67a8" + "digest": "134cf7c839370d171ae80a72e5d18d32ea1967df19c191d1a4ea446d649e9558" }, { "name": "flag_gp", "unicode": "1F1EC-1F1F5", - "digest": "2e6c48d80c571b34f31fa9b3622dcc51e1707c0118e991e9c177742ff02a8a96" + "digest": "be3e906b039ba4884053c78f4f14de9aa87c5573860ccb69ec766068ae3887c2" }, { "name": "gp", "unicode": "1F1EC-1F1F5", - "digest": "2e6c48d80c571b34f31fa9b3622dcc51e1707c0118e991e9c177742ff02a8a96" + "digest": "be3e906b039ba4884053c78f4f14de9aa87c5573860ccb69ec766068ae3887c2" }, { "name": "flag_gq", "unicode": "1F1EC-1F1F6", - "digest": "b0f5810180d12fc48faf75e73f882dc59072d7bf957f8455bf7e1e336539dc41" + "digest": "d476059c4ab41f5a1ef88583087362a5bc57cede930126f37041d1546564ab70" }, { "name": "gq", "unicode": "1F1EC-1F1F6", - "digest": "b0f5810180d12fc48faf75e73f882dc59072d7bf957f8455bf7e1e336539dc41" + "digest": "d476059c4ab41f5a1ef88583087362a5bc57cede930126f37041d1546564ab70" }, { "name": "flag_gr", "unicode": "1F1EC-1F1F7", - "digest": "8d60d6f8910f5179d851dbea0798b56a492c6be85f3d55e1a1126cd1d6663a3b" + "digest": "b9fa9304647aaa08167a07858bb18d778dcc399375f86f580b8d4244794678bc" }, { "name": "gr", "unicode": "1F1EC-1F1F7", - "digest": "8d60d6f8910f5179d851dbea0798b56a492c6be85f3d55e1a1126cd1d6663a3b" + "digest": "b9fa9304647aaa08167a07858bb18d778dcc399375f86f580b8d4244794678bc" }, { "name": "flag_gs", "unicode": "1F1EC-1F1F8", - "digest": "7b07915af0e2364ebc386a162d44846f3a7986fdd24e20ad2bc56d64a103fe9c" + "digest": "de33fbef6e294eb7af36e5b94d8ff573b354a4ff1ebdccf50ca528b86ed601d9" }, { "name": "gs", "unicode": "1F1EC-1F1F8", - "digest": "7b07915af0e2364ebc386a162d44846f3a7986fdd24e20ad2bc56d64a103fe9c" + "digest": "de33fbef6e294eb7af36e5b94d8ff573b354a4ff1ebdccf50ca528b86ed601d9" }, { "name": "flag_gt", "unicode": "1F1EC-1F1F9", - "digest": "0c78108ede45bf34917b409a0867f5ec8253c74b694beda083f3e8d04d7a10d8" + "digest": "4160843e5d642df597c8423eb8e3b74deafe304f3d141c8a4d2fc07509e44832" }, { "name": "gt", "unicode": "1F1EC-1F1F9", - "digest": "0c78108ede45bf34917b409a0867f5ec8253c74b694beda083f3e8d04d7a10d8" + "digest": "4160843e5d642df597c8423eb8e3b74deafe304f3d141c8a4d2fc07509e44832" }, { "name": "flag_gu", "unicode": "1F1EC-1F1FA", - "digest": "909f1bc98fa1507adb787eb3875503b21ea937d6ae8bb152153916c2da5e13bb" + "digest": "3b0cb257ba5b1c3e15d9102410c5f7418da03372e91ce90513de25b9f45283e3" }, { "name": "gu", "unicode": "1F1EC-1F1FA", - "digest": "909f1bc98fa1507adb787eb3875503b21ea937d6ae8bb152153916c2da5e13bb" + "digest": "3b0cb257ba5b1c3e15d9102410c5f7418da03372e91ce90513de25b9f45283e3" }, { "name": "flag_gw", "unicode": "1F1EC-1F1FC", - "digest": "f5f34410c7b22d5ed9994b47d0e7a9d9a6a1f05c4d3142f7fef3e4409725f5e6" + "digest": "bdf07a8f93c0f0a573af5f5361be404a3ba65b729c1a4c05b7632c03d85efc72" }, { "name": "gw", "unicode": "1F1EC-1F1FC", - "digest": "f5f34410c7b22d5ed9994b47d0e7a9d9a6a1f05c4d3142f7fef3e4409725f5e6" + "digest": "bdf07a8f93c0f0a573af5f5361be404a3ba65b729c1a4c05b7632c03d85efc72" }, { "name": "flag_gy", "unicode": "1F1EC-1F1FE", - "digest": "4939cf52ab34a924a31032b42668960a2c7d8d4f998b16b065c247110df334be" + "digest": "b47d8c98b747556f827ad0d1169264eb68ecaf9d2fb76595e8c31866361cbfc6" }, { "name": "gy", "unicode": "1F1EC-1F1FE", - "digest": "4939cf52ab34a924a31032b42668960a2c7d8d4f998b16b065c247110df334be" + "digest": "b47d8c98b747556f827ad0d1169264eb68ecaf9d2fb76595e8c31866361cbfc6" }, { "name": "flag_hk", "unicode": "1F1ED-1F1F0", - "digest": "bde0916df6d62f6b1cf8f85a8a39526c97fc6ef6fedb0b0cae2adb127a08eafe" + "digest": "8e5a54b2e4bd4f5182085299b9648062463da05d535cf0e46a7d9c58eaeb171f" }, { "name": "hk", "unicode": "1F1ED-1F1F0", - "digest": "bde0916df6d62f6b1cf8f85a8a39526c97fc6ef6fedb0b0cae2adb127a08eafe" + "digest": "8e5a54b2e4bd4f5182085299b9648062463da05d535cf0e46a7d9c58eaeb171f" }, { "name": "flag_hm", "unicode": "1F1ED-1F1F2", - "digest": "603e6c9bff9a0dc941970a313fe98fbf53ff5a57028f1a2766420be4211711cc" + "digest": "63c3e080c5e82a72c6d4cf5997ac823dc02184719ec59aadea6dd41b127abf22" }, { "name": "hm", "unicode": "1F1ED-1F1F2", - "digest": "603e6c9bff9a0dc941970a313fe98fbf53ff5a57028f1a2766420be4211711cc" + "digest": "63c3e080c5e82a72c6d4cf5997ac823dc02184719ec59aadea6dd41b127abf22" }, { "name": "flag_hn", "unicode": "1F1ED-1F1F3", - "digest": "2953ad0909bc32c02615f6ad5a4e5f331ba794a41632b1f0fc366e1c640cc2b9" + "digest": "87c1d160db810b5ed208fb33add54f96c17b0f08d87b81f6f09429abf6ec93ac" }, { "name": "hn", "unicode": "1F1ED-1F1F3", - "digest": "2953ad0909bc32c02615f6ad5a4e5f331ba794a41632b1f0fc366e1c640cc2b9" + "digest": "87c1d160db810b5ed208fb33add54f96c17b0f08d87b81f6f09429abf6ec93ac" }, { "name": "flag_hr", "unicode": "1F1ED-1F1F7", - "digest": "41c9ffc4f0faaa2d77e5cffb781329e7d2489ce879bd8eb9c503621e834abc50" + "digest": "8b68112f79baea38565673acf4f1cb90675a5829ff17e4cf9415c928b62aed88" }, { "name": "hr", "unicode": "1F1ED-1F1F7", - "digest": "41c9ffc4f0faaa2d77e5cffb781329e7d2489ce879bd8eb9c503621e834abc50" + "digest": "8b68112f79baea38565673acf4f1cb90675a5829ff17e4cf9415c928b62aed88" }, { "name": "flag_ht", "unicode": "1F1ED-1F1F9", - "digest": "6a56c3d71b4f858e1774aa2134a9f5584087fec968e9ee8bb1046d2ec93bf059" + "digest": "05dbd548c310ef1ebd1724aa85d821f8320106b16ddbf1f6442ea37e4407d5e1" }, { "name": "ht", "unicode": "1F1ED-1F1F9", - "digest": "6a56c3d71b4f858e1774aa2134a9f5584087fec968e9ee8bb1046d2ec93bf059" + "digest": "05dbd548c310ef1ebd1724aa85d821f8320106b16ddbf1f6442ea37e4407d5e1" }, { "name": "flag_hu", "unicode": "1F1ED-1F1FA", - "digest": "72f5809818d4cab8c0cee73df7f67b820fb8471eea4199911a5917ac099795e8" + "digest": "5079f3d6f1459e6df8dda5c19d2367ead8f5a755b8874ac999bae58e3c9f47a7" }, { "name": "hu", "unicode": "1F1ED-1F1FA", - "digest": "72f5809818d4cab8c0cee73df7f67b820fb8471eea4199911a5917ac099795e8" + "digest": "5079f3d6f1459e6df8dda5c19d2367ead8f5a755b8874ac999bae58e3c9f47a7" }, { "name": "flag_ic", "unicode": "1F1EE-1F1E8", - "digest": "7e2a7667fcd05f927af47e64c5790c104a9956dd9f1a45f03cb0fdcc85d866d3" + "digest": "8dcb18c4b75a60867a68d2f6edbf81e782aafb4b9a0404c8081f872dfe71e432" }, { "name": "ic", "unicode": "1F1EE-1F1E8", - "digest": "7e2a7667fcd05f927af47e64c5790c104a9956dd9f1a45f03cb0fdcc85d866d3" + "digest": "8dcb18c4b75a60867a68d2f6edbf81e782aafb4b9a0404c8081f872dfe71e432" }, { "name": "flag_id", "unicode": "1F1EE-1F1E9", - "digest": "4721f616fae2e443e52f1e9cc96e4835bddca16a2d75d7d5afea57cdee866b7f" + "digest": "1b0eb69a158ed3afe24be448d44751f95dcc5cbc7d1393a5753293f16ef0a66c" }, { "name": "indonesia", "unicode": "1F1EE-1F1E9", - "digest": "4721f616fae2e443e52f1e9cc96e4835bddca16a2d75d7d5afea57cdee866b7f" + "digest": "1b0eb69a158ed3afe24be448d44751f95dcc5cbc7d1393a5753293f16ef0a66c" }, { "name": "flag_ie", "unicode": "1F1EE-1F1EA", - "digest": "84b19833e6c9fb43187f8a28d85045a3df58816f20a07edab90474323174b1f3" + "digest": "5fc8c101ad7296224455f72f73c335aa4f676023b68645bafaf69087f69af390" }, { "name": "ie", "unicode": "1F1EE-1F1EA", - "digest": "84b19833e6c9fb43187f8a28d85045a3df58816f20a07edab90474323174b1f3" + "digest": "5fc8c101ad7296224455f72f73c335aa4f676023b68645bafaf69087f69af390" }, { "name": "flag_il", "unicode": "1F1EE-1F1F1", - "digest": "c99d4bd8c2541cf3a7392c4faf4477d96bc47065dd1423b9e06450483e69b34f" + "digest": "5aea4207415b7615dcdd69413705aefda700aefd0d27010cd0a0a338d879d9b8" }, { "name": "il", "unicode": "1F1EE-1F1F1", - "digest": "c99d4bd8c2541cf3a7392c4faf4477d96bc47065dd1423b9e06450483e69b34f" + "digest": "5aea4207415b7615dcdd69413705aefda700aefd0d27010cd0a0a338d879d9b8" }, { "name": "flag_im", "unicode": "1F1EE-1F1F2", - "digest": "5eeb12c0315b527ce61649a38b64d76af726a73b2d381d1a1ddd1366bafb1bfc" + "digest": "1ee9b3a5f1a52fc6d8369bfd81995fc0567e7a61deacd013701b3ec5fd64502e" }, { "name": "im", "unicode": "1F1EE-1F1F2", - "digest": "5eeb12c0315b527ce61649a38b64d76af726a73b2d381d1a1ddd1366bafb1bfc" + "digest": "1ee9b3a5f1a52fc6d8369bfd81995fc0567e7a61deacd013701b3ec5fd64502e" }, { "name": "flag_in", "unicode": "1F1EE-1F1F3", - "digest": "ecc3cfcff3368fe0875a51a8be9f4dfd449a187e5beb41a2b34241736247f73b" + "digest": "202ede502f34d55d180726ac2f29141c6875516f1b3e7ee99f266b16c2fe4bfd" }, { "name": "in", "unicode": "1F1EE-1F1F3", - "digest": "ecc3cfcff3368fe0875a51a8be9f4dfd449a187e5beb41a2b34241736247f73b" + "digest": "202ede502f34d55d180726ac2f29141c6875516f1b3e7ee99f266b16c2fe4bfd" }, { "name": "flag_io", "unicode": "1F1EE-1F1F4", - "digest": "26243d60e04ba3bc9eb8f008bfc77b2a64bcf1a3d0073eb0449a8c8121618c9c" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "io", "unicode": "1F1EE-1F1F4", - "digest": "26243d60e04ba3bc9eb8f008bfc77b2a64bcf1a3d0073eb0449a8c8121618c9c" + "digest": "dd45e1afe792fca57d4161434bf611bcb7170072d63e4a27fb9dcd6e8912621e" }, { "name": "flag_iq", "unicode": "1F1EE-1F1F6", - "digest": "a1fb5e59575081920b3be5290f654d57a9be099deb56d4ed69eba81a2b531cb3" + "digest": "bef294772b5ffccd6c061c19d60af66f61b248d78705faf347ade9ebfca2b46d" }, { "name": "iq", "unicode": "1F1EE-1F1F6", - "digest": "a1fb5e59575081920b3be5290f654d57a9be099deb56d4ed69eba81a2b531cb3" + "digest": "bef294772b5ffccd6c061c19d60af66f61b248d78705faf347ade9ebfca2b46d" }, { "name": "flag_ir", "unicode": "1F1EE-1F1F7", - "digest": "ab89488b934af1d4bdae7ed16dfc74fffe658bb8e95d5161b48cdd06de44ae85" + "digest": "d4faca93577a5546330ab6a09252307e19fb420d89912c0b48ceb90bf409d48e" }, { "name": "ir", "unicode": "1F1EE-1F1F7", - "digest": "ab89488b934af1d4bdae7ed16dfc74fffe658bb8e95d5161b48cdd06de44ae85" + "digest": "d4faca93577a5546330ab6a09252307e19fb420d89912c0b48ceb90bf409d48e" }, { "name": "flag_is", "unicode": "1F1EE-1F1F8", - "digest": "55db1fc9e6c56d4c9bcb9a46e5e4300cf2a0c32fa91dc24b487a1d56c8097268" + "digest": "b2fc04226b274009b4d99d92bcb72b255b534b6fd4b76d82dce1575ad975a456" }, { "name": "is", "unicode": "1F1EE-1F1F8", - "digest": "55db1fc9e6c56d4c9bcb9a46e5e4300cf2a0c32fa91dc24b487a1d56c8097268" + "digest": "b2fc04226b274009b4d99d92bcb72b255b534b6fd4b76d82dce1575ad975a456" }, { "name": "flag_it", "unicode": "1F1EE-1F1F9", - "digest": "36fc993fb00ab607578a4d0e573e988e17b9459a68a000a48de905a8238589d0" + "digest": "735760f193855d55460a0fb93dad55ff67253cab63176eceb90b9bde1faead1e" }, { "name": "it", "unicode": "1F1EE-1F1F9", - "digest": "36fc993fb00ab607578a4d0e573e988e17b9459a68a000a48de905a8238589d0" + "digest": "735760f193855d55460a0fb93dad55ff67253cab63176eceb90b9bde1faead1e" }, { "name": "flag_je", "unicode": "1F1EF-1F1EA", - "digest": "c608dbfd1259330e2f8c40dc5d12ffd0489396f4fc5f3ca57bcb2f0d9d05c20c" + "digest": "671a487a60571d928d2abaf306d0a9ba50239ec54ada14ea29a9a99df658d3cc" }, { "name": "je", "unicode": "1F1EF-1F1EA", - "digest": "c608dbfd1259330e2f8c40dc5d12ffd0489396f4fc5f3ca57bcb2f0d9d05c20c" + "digest": "671a487a60571d928d2abaf306d0a9ba50239ec54ada14ea29a9a99df658d3cc" }, { "name": "flag_jm", "unicode": "1F1EF-1F1F2", - "digest": "a8224b68b2d324f848d75e4376875ef76a8174e6ba32790d9ca622fe1eabfd5f" + "digest": "fb9047199d030b78fc0dcfc58d9b524fdb929238d922809da88147b7cebf4211" }, { "name": "jm", "unicode": "1F1EF-1F1F2", - "digest": "a8224b68b2d324f848d75e4376875ef76a8174e6ba32790d9ca622fe1eabfd5f" + "digest": "fb9047199d030b78fc0dcfc58d9b524fdb929238d922809da88147b7cebf4211" }, { "name": "flag_jo", "unicode": "1F1EF-1F1F4", - "digest": "2403563dc2ab4ed0e7e3a0761cc09f96801550bba6b177b54d651d8804ad987d" + "digest": "19f7d536d0293ebf3db49e05a158097cbde467115ef96523a0553808fd0b4178" }, { "name": "jo", "unicode": "1F1EF-1F1F4", - "digest": "2403563dc2ab4ed0e7e3a0761cc09f96801550bba6b177b54d651d8804ad987d" + "digest": "19f7d536d0293ebf3db49e05a158097cbde467115ef96523a0553808fd0b4178" }, { "name": "flag_jp", "unicode": "1F1EF-1F1F5", - "digest": "aea8eebd0a0139818cb7629d9c9a8e55160b458eb8ffeee2f36c5cff4b507fd3" + "digest": "51e971f777fe481ca9f7e077ecb2ce252c3cc0086b76384e7b965cdc337f3f9e" }, { "name": "jp", "unicode": "1F1EF-1F1F5", - "digest": "aea8eebd0a0139818cb7629d9c9a8e55160b458eb8ffeee2f36c5cff4b507fd3" + "digest": "51e971f777fe481ca9f7e077ecb2ce252c3cc0086b76384e7b965cdc337f3f9e" }, { "name": "flag_ke", "unicode": "1F1F0-1F1EA", - "digest": "9c8365f74858743bcdce4a9cf6a6f4110faf2dc6433e5dc7d98c24bb3b32a36d" + "digest": "0cec8f068548cfd3e7a20c10af84f97ca415fd6f8ab8b50783bf982e77d7260e" }, { "name": "ke", "unicode": "1F1F0-1F1EA", - "digest": "9c8365f74858743bcdce4a9cf6a6f4110faf2dc6433e5dc7d98c24bb3b32a36d" + "digest": "0cec8f068548cfd3e7a20c10af84f97ca415fd6f8ab8b50783bf982e77d7260e" }, { "name": "flag_kg", "unicode": "1F1F0-1F1EC", - "digest": "0c72bdb1d64b1e3be3d9516a50655a6162d8501851d2cf2fadb8c6ef7740df4e" + "digest": "5803ea6ab028261923fd7570c670a50518c6f462a2fb4d463531b12c3e382e6f" }, { "name": "kg", "unicode": "1F1F0-1F1EC", - "digest": "0c72bdb1d64b1e3be3d9516a50655a6162d8501851d2cf2fadb8c6ef7740df4e" + "digest": "5803ea6ab028261923fd7570c670a50518c6f462a2fb4d463531b12c3e382e6f" }, { "name": "flag_kh", "unicode": "1F1F0-1F1ED", - "digest": "49e41e488732d789e395091e144cd6215c6818ba2073e5e22ea21203a737d03c" + "digest": "287d357afe47179853fd485fb102834ead145598ed892664fc62d245cac16080" }, { "name": "kh", "unicode": "1F1F0-1F1ED", - "digest": "49e41e488732d789e395091e144cd6215c6818ba2073e5e22ea21203a737d03c" + "digest": "287d357afe47179853fd485fb102834ead145598ed892664fc62d245cac16080" }, { "name": "flag_ki", "unicode": "1F1F0-1F1EE", - "digest": "9d7f168adbcf5f4cfe28470addfdb0a8b231438d593edb70f633981bfa4c7638" + "digest": "ae4aee0d9cd7a21d4e250d45a484f5f641acdab3d79b437337b25fe34a0b49b0" }, { "name": "ki", "unicode": "1F1F0-1F1EE", - "digest": "9d7f168adbcf5f4cfe28470addfdb0a8b231438d593edb70f633981bfa4c7638" + "digest": "ae4aee0d9cd7a21d4e250d45a484f5f641acdab3d79b437337b25fe34a0b49b0" }, { "name": "flag_km", "unicode": "1F1F0-1F1F2", - "digest": "9318c28957fa7a19eba5ec452c1cbce01a5a83d41d29d081614d3abb0585d478" + "digest": "2d1730acbf5421fd02bd5483e26a86d82ec2fa99f0ff75bfd728a9df7914ad3b" }, { "name": "km", "unicode": "1F1F0-1F1F2", - "digest": "9318c28957fa7a19eba5ec452c1cbce01a5a83d41d29d081614d3abb0585d478" + "digest": "2d1730acbf5421fd02bd5483e26a86d82ec2fa99f0ff75bfd728a9df7914ad3b" }, { "name": "flag_kn", "unicode": "1F1F0-1F1F3", - "digest": "eac7e7d0f023dee5c0c8559bc2c9a96273adda54ce47598025120b30d8d6ebc1" + "digest": "b9ed979db9c6d243b00f61f19a9ec0f2c2390b2e5cace5ad61d9371dc8c670ac" }, { "name": "kn", "unicode": "1F1F0-1F1F3", - "digest": "eac7e7d0f023dee5c0c8559bc2c9a96273adda54ce47598025120b30d8d6ebc1" + "digest": "b9ed979db9c6d243b00f61f19a9ec0f2c2390b2e5cace5ad61d9371dc8c670ac" }, { "name": "flag_kp", "unicode": "1F1F0-1F1F5", - "digest": "d4d53db6f8363174de6db864c056267ba8a7d7e87b5527f2f42bb9b8ac3f362b" + "digest": "1bab0b9cab8028a95ce7231ad8d88ebcd31601cfa321284bba017ead47f6c729" }, { "name": "kp", "unicode": "1F1F0-1F1F5", - "digest": "d4d53db6f8363174de6db864c056267ba8a7d7e87b5527f2f42bb9b8ac3f362b" + "digest": "1bab0b9cab8028a95ce7231ad8d88ebcd31601cfa321284bba017ead47f6c729" }, { "name": "flag_kr", "unicode": "1F1F0-1F1F7", - "digest": "5c7e61ab4a2aae70cbe51f0ca4718516002bc943b35d870bd853a0c98c4e0ed5" + "digest": "33be8c09ebe273e203aa703cc827d52a6d9bf1699f5445bba13a77af2df45fa6" }, { "name": "kr", "unicode": "1F1F0-1F1F7", - "digest": "5c7e61ab4a2aae70cbe51f0ca4718516002bc943b35d870bd853a0c98c4e0ed5" + "digest": "33be8c09ebe273e203aa703cc827d52a6d9bf1699f5445bba13a77af2df45fa6" }, { "name": "flag_kw", "unicode": "1F1F0-1F1FC", - "digest": "5d229cd99d25f4285bd30d98cfcc3cd8346648897476e2905a1811ceeef48d37" + "digest": "04d901a92ea55b13dc4983a9e3adb52dc89c9f3decee86fd06022aa902678b6d" }, { "name": "kw", "unicode": "1F1F0-1F1FC", - "digest": "5d229cd99d25f4285bd30d98cfcc3cd8346648897476e2905a1811ceeef48d37" + "digest": "04d901a92ea55b13dc4983a9e3adb52dc89c9f3decee86fd06022aa902678b6d" }, { "name": "flag_ky", "unicode": "1F1F0-1F1FE", - "digest": "9ce3d8dfc273d3a400960876c434b702f93df92c6c00682dbed2ec8e3966d8a8" + "digest": "10f4d02f33cadd34da89de71a3b763809bad480cd9ae9d2ec000db026bd94cd1" }, { "name": "ky", "unicode": "1F1F0-1F1FE", - "digest": "9ce3d8dfc273d3a400960876c434b702f93df92c6c00682dbed2ec8e3966d8a8" + "digest": "10f4d02f33cadd34da89de71a3b763809bad480cd9ae9d2ec000db026bd94cd1" }, { "name": "flag_kz", "unicode": "1F1F0-1F1FF", - "digest": "a6f0be0a767fa4824495d568d9fc2bd8d4c1a26f363873d3b65362e9383e2a50" + "digest": "dfaff69a78cf635f7fad41bd5bdcc8003298454708a6178ba7348b1b40c360c1" }, { "name": "kz", "unicode": "1F1F0-1F1FF", - "digest": "a6f0be0a767fa4824495d568d9fc2bd8d4c1a26f363873d3b65362e9383e2a50" + "digest": "dfaff69a78cf635f7fad41bd5bdcc8003298454708a6178ba7348b1b40c360c1" }, { "name": "flag_la", "unicode": "1F1F1-1F1E6", - "digest": "ab2ae96da87f7b53ab212f8dcd897a591cff9ea6666270097a8e739ee0b8f8cb" + "digest": "4fcfbdc694cf99ae3f832500cdcdedb88c444b6df88bc9b7141f4f26ba3d5bfd" }, { "name": "la", "unicode": "1F1F1-1F1E6", - "digest": "ab2ae96da87f7b53ab212f8dcd897a591cff9ea6666270097a8e739ee0b8f8cb" + "digest": "4fcfbdc694cf99ae3f832500cdcdedb88c444b6df88bc9b7141f4f26ba3d5bfd" }, { "name": "flag_lb", "unicode": "1F1F1-1F1E7", - "digest": "0c3fcab22e9fae1c78658290aff97de785d0b6adb5e3702d00073ce774b7ed54" + "digest": "af4b1f784bea0ec7a712495491dffbd1152cc857a99fd433f76bfeb313819a62" }, { "name": "lb", "unicode": "1F1F1-1F1E7", - "digest": "0c3fcab22e9fae1c78658290aff97de785d0b6adb5e3702d00073ce774b7ed54" + "digest": "af4b1f784bea0ec7a712495491dffbd1152cc857a99fd433f76bfeb313819a62" }, { "name": "flag_lc", "unicode": "1F1F1-1F1E8", - "digest": "e154b0b3a1635a36e0d9ad518c0ea12259320e5f1ebbda982248486492065d28" + "digest": "40784aa558b75d07ae499c004e2cc5d0b2efdfc3e5be705b5a9f6b70d681c396" }, { "name": "lc", "unicode": "1F1F1-1F1E8", - "digest": "e154b0b3a1635a36e0d9ad518c0ea12259320e5f1ebbda982248486492065d28" + "digest": "40784aa558b75d07ae499c004e2cc5d0b2efdfc3e5be705b5a9f6b70d681c396" }, { "name": "flag_li", "unicode": "1F1F1-1F1EE", - "digest": "bbc393a89e73cc8c29a0a9297428d07aa1d4717ea9b7d4dd9d69f21ac7d0605d" + "digest": "c4eb4c43f457ce60ff9d046adb512c1d3462203403eeb595bff3ebc010ed6633" }, { "name": "li", "unicode": "1F1F1-1F1EE", - "digest": "bbc393a89e73cc8c29a0a9297428d07aa1d4717ea9b7d4dd9d69f21ac7d0605d" + "digest": "c4eb4c43f457ce60ff9d046adb512c1d3462203403eeb595bff3ebc010ed6633" }, { "name": "flag_lk", "unicode": "1F1F1-1F1F0", - "digest": "376bd501d113a844971ca1006ab31aa086cd55d74842ea5f3dedaba997b58693" + "digest": "a5285cdfdc3715fa3941f5f0eb03dc425969eaaf22c719c27ab4418628d09bc5" }, { "name": "lk", "unicode": "1F1F1-1F1F0", - "digest": "376bd501d113a844971ca1006ab31aa086cd55d74842ea5f3dedaba997b58693" + "digest": "a5285cdfdc3715fa3941f5f0eb03dc425969eaaf22c719c27ab4418628d09bc5" }, { "name": "flag_lr", "unicode": "1F1F1-1F1F7", - "digest": "9a6ebe1c9d9a53079ee77292a5ad0965f96409b0417f92876a1c3bd463d6a9bc" + "digest": "ed04334264953b4da570db8c392b99d2fab4e0b7efc2331427016c6a08e818be" }, { "name": "lr", "unicode": "1F1F1-1F1F7", - "digest": "9a6ebe1c9d9a53079ee77292a5ad0965f96409b0417f92876a1c3bd463d6a9bc" + "digest": "ed04334264953b4da570db8c392b99d2fab4e0b7efc2331427016c6a08e818be" }, { "name": "flag_ls", "unicode": "1F1F1-1F1F8", - "digest": "e2f4b05414f6e0c3d629a92b0534d4145475f0214a83a62c902fe0884c833c89" + "digest": "cd56022106d027317cc9bf4c848758cf29ffe277ce71fdb9c1cf89ac4fd6e6db" }, { "name": "ls", "unicode": "1F1F1-1F1F8", - "digest": "e2f4b05414f6e0c3d629a92b0534d4145475f0214a83a62c902fe0884c833c89" + "digest": "cd56022106d027317cc9bf4c848758cf29ffe277ce71fdb9c1cf89ac4fd6e6db" }, { "name": "flag_lt", "unicode": "1F1F1-1F1F9", - "digest": "d5e2f8b2ffa820a33ea6d612fccd61e32467d25154342f5be134d3520e48387f" + "digest": "3c4395b068e421100fd97a102f170cb8d5c093885eef7cb40d3faff4f4e47fe9" }, { "name": "lt", "unicode": "1F1F1-1F1F9", - "digest": "d5e2f8b2ffa820a33ea6d612fccd61e32467d25154342f5be134d3520e48387f" + "digest": "3c4395b068e421100fd97a102f170cb8d5c093885eef7cb40d3faff4f4e47fe9" }, { "name": "flag_lu", "unicode": "1F1F1-1F1FA", - "digest": "f43277103292195b51981d08e2dde68eab660a65c7875f510e09a8b2370f1b5c" + "digest": "df15a2c47eecad17e0cc169bdf0d31c6a51eb22de7ca4e70d2431359a33f930d" }, { "name": "lu", "unicode": "1F1F1-1F1FA", - "digest": "f43277103292195b51981d08e2dde68eab660a65c7875f510e09a8b2370f1b5c" + "digest": "df15a2c47eecad17e0cc169bdf0d31c6a51eb22de7ca4e70d2431359a33f930d" }, { "name": "flag_lv", "unicode": "1F1F1-1F1FB", - "digest": "e1288ac5c80d6e9d577d652e34be247ca39bf9d3d7cfc8a6cae13c1f9ac9dc47" + "digest": "9b53c6ce23287935200da8ca8a8af78013a4b1572f9821e7e1724cbad248e7e2" }, { "name": "lv", "unicode": "1F1F1-1F1FB", - "digest": "e1288ac5c80d6e9d577d652e34be247ca39bf9d3d7cfc8a6cae13c1f9ac9dc47" + "digest": "9b53c6ce23287935200da8ca8a8af78013a4b1572f9821e7e1724cbad248e7e2" }, { "name": "flag_ly", "unicode": "1F1F1-1F1FE", - "digest": "5122294b769a174e3b6e3d238bb846b3e760929f5bb3c1a708d8a429f3f32f68" + "digest": "42efa9f3526ef006d6723fa17538a98ab9556ae25f14df1b06d21361bf7e1a44" }, { "name": "ly", "unicode": "1F1F1-1F1FE", - "digest": "5122294b769a174e3b6e3d238bb846b3e760929f5bb3c1a708d8a429f3f32f68" + "digest": "42efa9f3526ef006d6723fa17538a98ab9556ae25f14df1b06d21361bf7e1a44" }, { "name": "flag_ma", "unicode": "1F1F2-1F1E6", - "digest": "615a6447ff284de7689b4fd7b04fdda308f65dbbec958cfb96d2977514981d16" + "digest": "96c07296cfd7aa1cb642faed8ace26744105b81ca880157a4ef4caee0befe26e" }, { "name": "ma", "unicode": "1F1F2-1F1E6", - "digest": "615a6447ff284de7689b4fd7b04fdda308f65dbbec958cfb96d2977514981d16" + "digest": "96c07296cfd7aa1cb642faed8ace26744105b81ca880157a4ef4caee0befe26e" }, { "name": "flag_mc", "unicode": "1F1F2-1F1E8", - "digest": "08b48b28938acbfc0fbc15c25ee14dbad7164c5165d03df2eee370755ee7b4cf" + "digest": "6b44608842fe849ae2b4bae5eb87ccd436459a427051dfda25080196273d4b9f" }, { "name": "mc", "unicode": "1F1F2-1F1E8", - "digest": "08b48b28938acbfc0fbc15c25ee14dbad7164c5165d03df2eee370755ee7b4cf" + "digest": "6b44608842fe849ae2b4bae5eb87ccd436459a427051dfda25080196273d4b9f" }, { "name": "flag_md", "unicode": "1F1F2-1F1E9", - "digest": "93d61de68f821e1e08b30e63d91e8b4a657766475128538894cf9da9a3b4e3c0" + "digest": "78c7b01c698873a9129d52ba38b3eb4cfc683ef2ae10b7b922b17c07f1c938c8" }, { "name": "md", "unicode": "1F1F2-1F1E9", - "digest": "93d61de68f821e1e08b30e63d91e8b4a657766475128538894cf9da9a3b4e3c0" + "digest": "78c7b01c698873a9129d52ba38b3eb4cfc683ef2ae10b7b922b17c07f1c938c8" }, { "name": "flag_me", "unicode": "1F1F2-1F1EA", - "digest": "ee55c0eb78241aec2baf1822a47fa46d63209ceae3db7617ae886b823ae229ff" + "digest": "01aa0f9df89302edc4ae319b5dd78069ba8807c3f38cc7bfe01bff67c8efd416" }, { "name": "me", "unicode": "1F1F2-1F1EA", - "digest": "ee55c0eb78241aec2baf1822a47fa46d63209ceae3db7617ae886b823ae229ff" + "digest": "01aa0f9df89302edc4ae319b5dd78069ba8807c3f38cc7bfe01bff67c8efd416" }, { "name": "flag_mf", "unicode": "1F1F2-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "mf", "unicode": "1F1F2-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "flag_mg", "unicode": "1F1F2-1F1EC", - "digest": "86ec8140e2c4854f52cff74757baf0cbb75a4aacca8be6af8c8f9c939a7b866c" + "digest": "56ebcd2a2e144d656d3b38a62595138fe6e50f9c1144f70b0a120cce7a72eb5b" }, { "name": "mg", "unicode": "1F1F2-1F1EC", - "digest": "86ec8140e2c4854f52cff74757baf0cbb75a4aacca8be6af8c8f9c939a7b866c" + "digest": "56ebcd2a2e144d656d3b38a62595138fe6e50f9c1144f70b0a120cce7a72eb5b" }, { "name": "flag_mh", "unicode": "1F1F2-1F1ED", - "digest": "8311ea3422c9d5e94b55e19b03bedd6fe6e2a191b7657e15ac75a48932958a5b" + "digest": "008660adc4c2e4d04830498988184d1ef8a372a6c085da369a94ee6b820dbbb7" }, { "name": "mh", "unicode": "1F1F2-1F1ED", - "digest": "8311ea3422c9d5e94b55e19b03bedd6fe6e2a191b7657e15ac75a48932958a5b" + "digest": "008660adc4c2e4d04830498988184d1ef8a372a6c085da369a94ee6b820dbbb7" }, { "name": "flag_mk", "unicode": "1F1F2-1F1F0", - "digest": "5c6f504f88c5a875c06ac8b26fa6e81a9d79c42a1c7d1fad9a5d4c8ad06ca502" + "digest": "f3c4c5106ace81c21fc0c6a7cc5c5e04e9453468fbc6ccbc851bb8dd61ff237f" }, { "name": "mk", "unicode": "1F1F2-1F1F0", - "digest": "5c6f504f88c5a875c06ac8b26fa6e81a9d79c42a1c7d1fad9a5d4c8ad06ca502" + "digest": "f3c4c5106ace81c21fc0c6a7cc5c5e04e9453468fbc6ccbc851bb8dd61ff237f" }, { "name": "flag_ml", "unicode": "1F1F2-1F1F1", - "digest": "d08a4973db40cf28e58ca3c80e8bd4e50d68ba1080b31917aeefdb0e210b5c50" + "digest": "e70a6b30e46adc2e19684308a848fef2c3ad76e2cac4bb493ee3270ad39f9d1b" }, { "name": "ml", "unicode": "1F1F2-1F1F1", - "digest": "d08a4973db40cf28e58ca3c80e8bd4e50d68ba1080b31917aeefdb0e210b5c50" + "digest": "e70a6b30e46adc2e19684308a848fef2c3ad76e2cac4bb493ee3270ad39f9d1b" }, { "name": "flag_mm", "unicode": "1F1F2-1F1F2", - "digest": "5e95089514ca09bb93afb481b317477c9d053adcf450e0b711d78ed1078c7470" + "digest": "720f5d38887202ba049cd5a46c183679be6a01f169d99e6e656c73b515793a7d" }, { "name": "mm", "unicode": "1F1F2-1F1F2", - "digest": "5e95089514ca09bb93afb481b317477c9d053adcf450e0b711d78ed1078c7470" + "digest": "720f5d38887202ba049cd5a46c183679be6a01f169d99e6e656c73b515793a7d" }, { "name": "flag_mn", "unicode": "1F1F2-1F1F3", - "digest": "7a0ca72715dd2a36eeeed2f8c888497cb752f0000af8f07d6930743caf6e4273" + "digest": "5f0fd6fcb2ed73a5a6d9396c3703612503c1f16283bbb4e9362a1c8324b762ad" }, { "name": "mn", "unicode": "1F1F2-1F1F3", - "digest": "7a0ca72715dd2a36eeeed2f8c888497cb752f0000af8f07d6930743caf6e4273" + "digest": "5f0fd6fcb2ed73a5a6d9396c3703612503c1f16283bbb4e9362a1c8324b762ad" }, { "name": "flag_mo", "unicode": "1F1F2-1F1F4", - "digest": "d2c7c2191bc1bc83d85f2270968cb4de5cf26a11f70e166a8b32c108287ef729" + "digest": "fc2a9e7323867cf195f551e59afdab778c56b84c96af28c20207c9870caa2c39" }, { "name": "mo", "unicode": "1F1F2-1F1F4", - "digest": "d2c7c2191bc1bc83d85f2270968cb4de5cf26a11f70e166a8b32c108287ef729" + "digest": "fc2a9e7323867cf195f551e59afdab778c56b84c96af28c20207c9870caa2c39" }, { "name": "flag_mp", "unicode": "1F1F2-1F1F5", - "digest": "89ad06121fd7981338fe188464491bea371f85125bfb4fc01fb5cad606613b1e" + "digest": "ddce3be9d72914240c42e1b97ea97af01016d0a3879999cb0e447552682c06ba" }, { "name": "mp", "unicode": "1F1F2-1F1F5", - "digest": "89ad06121fd7981338fe188464491bea371f85125bfb4fc01fb5cad606613b1e" + "digest": "ddce3be9d72914240c42e1b97ea97af01016d0a3879999cb0e447552682c06ba" }, { "name": "flag_mq", "unicode": "1F1F2-1F1F6", - "digest": "98176f3af823b26a3657a17c5073ee22367898b40bd3973de76329aa87ca5a2e" + "digest": "888f455b1322d6fb83dc9f469f5505fea3dd6ece77d17d0d7345319c3ebcec0e" }, { "name": "mq", "unicode": "1F1F2-1F1F6", - "digest": "98176f3af823b26a3657a17c5073ee22367898b40bd3973de76329aa87ca5a2e" + "digest": "888f455b1322d6fb83dc9f469f5505fea3dd6ece77d17d0d7345319c3ebcec0e" }, { "name": "flag_mr", "unicode": "1F1F2-1F1F7", - "digest": "cc3e705ad84f83fe2d544385c39564743024dab26595d62469b35fdb791f6015" + "digest": "72621914c92dd9c9f3ac9973ee3589583bfe42b841cdd35f47af75e2f629726c" }, { "name": "mr", "unicode": "1F1F2-1F1F7", - "digest": "cc3e705ad84f83fe2d544385c39564743024dab26595d62469b35fdb791f6015" + "digest": "72621914c92dd9c9f3ac9973ee3589583bfe42b841cdd35f47af75e2f629726c" }, { "name": "flag_ms", "unicode": "1F1F2-1F1F8", - "digest": "465e3d5700b557f2589bd6e34a0c6b12c634a6ed4dcfbee3c1c841c5de3413f0" + "digest": "5944996295132f41ec55261ff7927518bd47aec95d274a6ff257c357b43657bc" }, { "name": "ms", "unicode": "1F1F2-1F1F8", - "digest": "465e3d5700b557f2589bd6e34a0c6b12c634a6ed4dcfbee3c1c841c5de3413f0" + "digest": "5944996295132f41ec55261ff7927518bd47aec95d274a6ff257c357b43657bc" }, { "name": "flag_mt", "unicode": "1F1F2-1F1F9", - "digest": "e610ba22d8d8ad750ed10dff8e1b4d89bc34f066c3424bfa77dbdc1a5d79743a" + "digest": "95f0550e8823441a4e69b26c540baea94f3ddcc282100fd0239021c00df0b469" }, { "name": "mt", "unicode": "1F1F2-1F1F9", - "digest": "e610ba22d8d8ad750ed10dff8e1b4d89bc34f066c3424bfa77dbdc1a5d79743a" + "digest": "95f0550e8823441a4e69b26c540baea94f3ddcc282100fd0239021c00df0b469" }, { "name": "flag_mu", "unicode": "1F1F2-1F1FA", - "digest": "3daf015d3b95218677dafbb282b7804686aa68875a6bd1d70c165b7b149e19cb" + "digest": "5fda78a6df0ea7f5cac5fb4c8fd68529c14c5e15bac4e0b167493cb6ac459253" }, { "name": "mu", "unicode": "1F1F2-1F1FA", - "digest": "3daf015d3b95218677dafbb282b7804686aa68875a6bd1d70c165b7b149e19cb" + "digest": "5fda78a6df0ea7f5cac5fb4c8fd68529c14c5e15bac4e0b167493cb6ac459253" }, { "name": "flag_mv", "unicode": "1F1F2-1F1FB", - "digest": "d30e4bfd04f08177de92f3c175600aaafa89b9668bbe2b83f35f07a74382065c" + "digest": "f75c8f6fd3a68f2944a04c833c649d4b576997f491100cf3f3160fe77117fabb" }, { "name": "mv", "unicode": "1F1F2-1F1FB", - "digest": "d30e4bfd04f08177de92f3c175600aaafa89b9668bbe2b83f35f07a74382065c" + "digest": "f75c8f6fd3a68f2944a04c833c649d4b576997f491100cf3f3160fe77117fabb" }, { "name": "flag_mw", "unicode": "1F1F2-1F1FC", - "digest": "f364b1c8bfda3f86b5e26422eedc571ba11e312dcc634197631a6840cb22aede" + "digest": "d46b484a97e5b90b6b259f8de1712b553f93f0dfb6391209200358bb9429ebf5" }, { "name": "mw", "unicode": "1F1F2-1F1FC", - "digest": "f364b1c8bfda3f86b5e26422eedc571ba11e312dcc634197631a6840cb22aede" + "digest": "d46b484a97e5b90b6b259f8de1712b553f93f0dfb6391209200358bb9429ebf5" }, { "name": "flag_mx", "unicode": "1F1F2-1F1FD", - "digest": "eafb02ec0be9cefab7cef7c426c7d860d98e4947f4da04054154dc86d8f487c4" + "digest": "dc57c10307fc0aa09bd7fcd25ee0fca561f3b382276faa8432a927c1baea53fd" }, { "name": "mx", "unicode": "1F1F2-1F1FD", - "digest": "eafb02ec0be9cefab7cef7c426c7d860d98e4947f4da04054154dc86d8f487c4" + "digest": "dc57c10307fc0aa09bd7fcd25ee0fca561f3b382276faa8432a927c1baea53fd" }, { "name": "flag_my", "unicode": "1F1F2-1F1FE", - "digest": "9a690b357bc6b970781bd122c1e546ade3ccb73d930c2af1008b82027e36c7cf" + "digest": "15ca00660a1eb0096fdaa00b85a7b95fcf192bf2ee4781ba72c36d2d2cb015ef" }, { "name": "my", "unicode": "1F1F2-1F1FE", - "digest": "9a690b357bc6b970781bd122c1e546ade3ccb73d930c2af1008b82027e36c7cf" + "digest": "15ca00660a1eb0096fdaa00b85a7b95fcf192bf2ee4781ba72c36d2d2cb015ef" }, { "name": "flag_mz", "unicode": "1F1F2-1F1FF", - "digest": "36d0548ebfef9e0443ec1d0597ebfa6e95c25b997381f30c8c74008820743bb9" + "digest": "0c8605a9319dcf86672a833b4c4d6acea5f6aa25a3f8e1dfac78fbf7c452ba97" }, { "name": "mz", "unicode": "1F1F2-1F1FF", - "digest": "36d0548ebfef9e0443ec1d0597ebfa6e95c25b997381f30c8c74008820743bb9" + "digest": "0c8605a9319dcf86672a833b4c4d6acea5f6aa25a3f8e1dfac78fbf7c452ba97" }, { "name": "flag_na", "unicode": "1F1F3-1F1E6", - "digest": "4989dc9452b0bdfa101cfd3b7c83ef1195a7e45128b9ed00193fe712a6d02fca" + "digest": "e63cde5ee49d3ada1e33d2ab15dc24fbb129b90d65b6fd1d7c07455f71a53601" }, { "name": "na", "unicode": "1F1F3-1F1E6", - "digest": "4989dc9452b0bdfa101cfd3b7c83ef1195a7e45128b9ed00193fe712a6d02fca" + "digest": "e63cde5ee49d3ada1e33d2ab15dc24fbb129b90d65b6fd1d7c07455f71a53601" }, { "name": "flag_nc", "unicode": "1F1F3-1F1E8", - "digest": "7fc9d865eebf729d5496c4cd7576476ec599f65b379d4a6df66b4e399553c2eb" + "digest": "a4a350ce7404ba7bdda9a341e7a48fcfe16312be4964b1bd6eed7115acd2e329" }, { "name": "nc", "unicode": "1F1F3-1F1E8", - "digest": "7fc9d865eebf729d5496c4cd7576476ec599f65b379d4a6df66b4e399553c2eb" + "digest": "a4a350ce7404ba7bdda9a341e7a48fcfe16312be4964b1bd6eed7115acd2e329" }, { "name": "flag_ne", "unicode": "1F1F3-1F1EA", - "digest": "d3f10fb44ec44a04112bc66d05f0a44c6ec46dae73cfd3fe26cdc8b32ec06713" + "digest": "6b32483b4445bc52855509f618c570b9c9606de5649e4878b71b44ff2acbc9fd" }, { "name": "ne", "unicode": "1F1F3-1F1EA", - "digest": "d3f10fb44ec44a04112bc66d05f0a44c6ec46dae73cfd3fe26cdc8b32ec06713" + "digest": "6b32483b4445bc52855509f618c570b9c9606de5649e4878b71b44ff2acbc9fd" }, { "name": "flag_nf", "unicode": "1F1F3-1F1EB", - "digest": "d390e0d52215a025380af221ba9e955e5886edbb4c9f4b124f2fb60a8e019e42" + "digest": "96b1ec33acbd2b1ffe42703c11a2a633b036e6779849b0e6fa8f399167820584" }, { "name": "nf", "unicode": "1F1F3-1F1EB", - "digest": "d390e0d52215a025380af221ba9e955e5886edbb4c9f4b124f2fb60a8e019e42" + "digest": "96b1ec33acbd2b1ffe42703c11a2a633b036e6779849b0e6fa8f399167820584" }, { "name": "flag_ng", "unicode": "1F1F3-1F1EC", - "digest": "e69d1bb8f1db4a0c295c90dda23d8f97c2dea59f9a2da2ecb0e9a1dc4dbea101" + "digest": "f97d0630cbfa5e75440251df7529a67b58c22598643390cbeea82fb04a1cd956" }, { "name": "nigeria", "unicode": "1F1F3-1F1EC", - "digest": "e69d1bb8f1db4a0c295c90dda23d8f97c2dea59f9a2da2ecb0e9a1dc4dbea101" + "digest": "f97d0630cbfa5e75440251df7529a67b58c22598643390cbeea82fb04a1cd956" }, { "name": "flag_ni", "unicode": "1F1F3-1F1EE", - "digest": "dbaccc942637469b0ee75bd5f956958c3c5a89d8f69b69c96f02ab6594124894" + "digest": "c52fb5f9134122a91defa75425be2c6b3c909e051d546244e0e7bdf5f9ee1710" }, { "name": "ni", "unicode": "1F1F3-1F1EE", - "digest": "dbaccc942637469b0ee75bd5f956958c3c5a89d8f69b69c96f02ab6594124894" + "digest": "c52fb5f9134122a91defa75425be2c6b3c909e051d546244e0e7bdf5f9ee1710" }, { "name": "flag_nl", "unicode": "1F1F3-1F1F1", - "digest": "bda2eb0315763c3c19d37c664dab1ee4280f20888a0ca57677fd33cfa4240910" + "digest": "b8918f9c0c92513aa0ec6ba6cee5448270168cbe6f0a970fb06e7ceb9f52ec71" }, { "name": "nl", "unicode": "1F1F3-1F1F1", - "digest": "bda2eb0315763c3c19d37c664dab1ee4280f20888a0ca57677fd33cfa4240910" + "digest": "b8918f9c0c92513aa0ec6ba6cee5448270168cbe6f0a970fb06e7ceb9f52ec71" }, { "name": "flag_no", "unicode": "1F1F3-1F1F4", - "digest": "42b49dec756a220781ea271ca8fbcaba524dc3b38d5d8f999bfaa40ef9ebd302" + "digest": "05ce84095f8d93407d611b39d8b6a67fd9f11df6cfab7a185bcb4eec186d85ef" }, { "name": "no", "unicode": "1F1F3-1F1F4", - "digest": "42b49dec756a220781ea271ca8fbcaba524dc3b38d5d8f999bfaa40ef9ebd302" + "digest": "05ce84095f8d93407d611b39d8b6a67fd9f11df6cfab7a185bcb4eec186d85ef" }, { "name": "flag_np", "unicode": "1F1F3-1F1F5", - "digest": "b5259257db079235310d5d9537d2b5b61ae0326bc8920ba13084b009844e2957" + "digest": "cc41c2f97ec2b38fe5781d553792f6aab5d37cc3be02586f361fe89d12683bee" }, { "name": "np", "unicode": "1F1F3-1F1F5", - "digest": "b5259257db079235310d5d9537d2b5b61ae0326bc8920ba13084b009844e2957" + "digest": "cc41c2f97ec2b38fe5781d553792f6aab5d37cc3be02586f361fe89d12683bee" }, { "name": "flag_nr", "unicode": "1F1F3-1F1F7", - "digest": "1bd7d1fe2c3a5e98cfd4dff6e8d6dd6d3c74f0051ad615587d77d2291a9784cc" + "digest": "7837edf59ec33a25380d76afea5f04cfcab4f17df4e33fca0dcaacb517c5cbec" }, { "name": "nr", "unicode": "1F1F3-1F1F7", - "digest": "1bd7d1fe2c3a5e98cfd4dff6e8d6dd6d3c74f0051ad615587d77d2291a9784cc" + "digest": "7837edf59ec33a25380d76afea5f04cfcab4f17df4e33fca0dcaacb517c5cbec" }, { "name": "flag_nu", "unicode": "1F1F3-1F1FA", - "digest": "e2a7a398e07d2232147cc0917d72d18b519246d3d314e9f6f03dcf98d312d4ce" + "digest": "fd9ab45c6f32bc4da47542392e5beba73ddac302a4a9a00e6deedc913a4c087d" }, { "name": "nu", "unicode": "1F1F3-1F1FA", - "digest": "e2a7a398e07d2232147cc0917d72d18b519246d3d314e9f6f03dcf98d312d4ce" + "digest": "fd9ab45c6f32bc4da47542392e5beba73ddac302a4a9a00e6deedc913a4c087d" }, { "name": "flag_nz", "unicode": "1F1F3-1F1FF", - "digest": "ce8b1cb87dae3a3ec865575b57a0b4987a7f4bd3f170e7b210dd764fc2588cd4" + "digest": "0719830dcca400cefb30ce399bb03f49dd84c9a98f7d6a28270f9278e2a7af75" }, { "name": "nz", "unicode": "1F1F3-1F1FF", - "digest": "ce8b1cb87dae3a3ec865575b57a0b4987a7f4bd3f170e7b210dd764fc2588cd4" + "digest": "0719830dcca400cefb30ce399bb03f49dd84c9a98f7d6a28270f9278e2a7af75" }, { "name": "flag_om", "unicode": "1F1F4-1F1F2", - "digest": "29da72505a276a8a372a00c197388ebc5098c221cab26b3ff755bd62b10f740f" + "digest": "3f9039becd52e3454fdf7611cdb0d7fb1196e053eea29ef87daab6c21a94f1ee" }, { "name": "om", "unicode": "1F1F4-1F1F2", - "digest": "29da72505a276a8a372a00c197388ebc5098c221cab26b3ff755bd62b10f740f" + "digest": "3f9039becd52e3454fdf7611cdb0d7fb1196e053eea29ef87daab6c21a94f1ee" }, { "name": "flag_pa", "unicode": "1F1F5-1F1E6", - "digest": "180b673c9aceea43a8b55823a82d80600257e4982d0757d129860e3d8a14f458" + "digest": "1adf0e5d4084e072aa44bd9978829e77546e0be75785e9be69f92e326bd714a7" }, { "name": "pa", "unicode": "1F1F5-1F1E6", - "digest": "180b673c9aceea43a8b55823a82d80600257e4982d0757d129860e3d8a14f458" + "digest": "1adf0e5d4084e072aa44bd9978829e77546e0be75785e9be69f92e326bd714a7" }, { "name": "flag_pe", "unicode": "1F1F5-1F1EA", - "digest": "b61823ea2cd91e371e40832df5764558b81d44fac41030827a3f6d2564643c00" + "digest": "f8a4e257676f4ab8962ffe5509b8417777a8be2f0e9dc7735d3e014ff221aab1" }, { "name": "pe", "unicode": "1F1F5-1F1EA", - "digest": "b61823ea2cd91e371e40832df5764558b81d44fac41030827a3f6d2564643c00" + "digest": "f8a4e257676f4ab8962ffe5509b8417777a8be2f0e9dc7735d3e014ff221aab1" }, { "name": "flag_pf", "unicode": "1F1F5-1F1EB", - "digest": "e560421911f4af90c73a0dbdf8f42e69316003799304c9394fb127e3b83326fa" + "digest": "1ace6cc71d130cdf09246297740a911f14828c322e35330cc548ca5975015c23" }, { "name": "pf", "unicode": "1F1F5-1F1EB", - "digest": "e560421911f4af90c73a0dbdf8f42e69316003799304c9394fb127e3b83326fa" + "digest": "1ace6cc71d130cdf09246297740a911f14828c322e35330cc548ca5975015c23" }, { "name": "flag_pg", "unicode": "1F1F5-1F1EC", - "digest": "880e87db2ce0eac38db037683a5db46fd6ce30623cf56ae4a93a747103570044" + "digest": "9c37719d9f51ef31fec0f898d38e522b4253cd00344408e3f660132514efddb7" }, { "name": "pg", "unicode": "1F1F5-1F1EC", - "digest": "880e87db2ce0eac38db037683a5db46fd6ce30623cf56ae4a93a747103570044" + "digest": "9c37719d9f51ef31fec0f898d38e522b4253cd00344408e3f660132514efddb7" }, { "name": "flag_ph", "unicode": "1F1F5-1F1ED", - "digest": "49aae2f56bfd1385741dc76857aa1f1459778b2d39a1c955e469c5367585bfd5" + "digest": "f1af628cf6d1d290cedef3d564b2386e2d6f14ba4426d3fefc0312cb8772e517" }, { "name": "ph", "unicode": "1F1F5-1F1ED", - "digest": "49aae2f56bfd1385741dc76857aa1f1459778b2d39a1c955e469c5367585bfd5" + "digest": "f1af628cf6d1d290cedef3d564b2386e2d6f14ba4426d3fefc0312cb8772e517" }, { "name": "flag_pk", "unicode": "1F1F5-1F1F0", - "digest": "64379dbfc932df3a07935b5cfa11ca151f761d3728939e982604e12c663cd646" + "digest": "61c77f73d2a10a5acb289fadfe0d25d1a1c343e1223bd802099ff4e0e9356521" }, { "name": "pk", "unicode": "1F1F5-1F1F0", - "digest": "64379dbfc932df3a07935b5cfa11ca151f761d3728939e982604e12c663cd646" + "digest": "61c77f73d2a10a5acb289fadfe0d25d1a1c343e1223bd802099ff4e0e9356521" }, { "name": "flag_pl", "unicode": "1F1F5-1F1F1", - "digest": "3b688b074c2735d3dea0b7ab74b80eba243ce50cb05d68e585c9d701c1f14617" + "digest": "38c2c8618446e1f72cf983ab33e736d943f0db7c4cce52a187299e8cec2ea895" }, { "name": "pl", "unicode": "1F1F5-1F1F1", - "digest": "3b688b074c2735d3dea0b7ab74b80eba243ce50cb05d68e585c9d701c1f14617" + "digest": "38c2c8618446e1f72cf983ab33e736d943f0db7c4cce52a187299e8cec2ea895" }, { "name": "flag_pm", "unicode": "1F1F5-1F1F2", - "digest": "a13a69ee3131501dd8138173cfb669a35ee8039d84aa665e69dd7f0d0aa3e717" + "digest": "656be9ea1a79c3885a759c7ce353d338345a198d7939556949affaf5490cb644" }, { "name": "pm", "unicode": "1F1F5-1F1F2", - "digest": "a13a69ee3131501dd8138173cfb669a35ee8039d84aa665e69dd7f0d0aa3e717" + "digest": "656be9ea1a79c3885a759c7ce353d338345a198d7939556949affaf5490cb644" }, { "name": "flag_pn", "unicode": "1F1F5-1F1F3", - "digest": "d7ae3985cf66024e4a3001e79a8efbb3e75571f2b0abbd0fb87fc1efc795a2b3" + "digest": "2792260d8087ab0253b1214c1420f0160ab2eef9afe7315f9e7ff0b87cd15d72" }, { "name": "pn", "unicode": "1F1F5-1F1F3", - "digest": "d7ae3985cf66024e4a3001e79a8efbb3e75571f2b0abbd0fb87fc1efc795a2b3" + "digest": "2792260d8087ab0253b1214c1420f0160ab2eef9afe7315f9e7ff0b87cd15d72" }, { "name": "flag_pr", "unicode": "1F1F5-1F1F7", - "digest": "4910dc984bc908158506b770f28af56150cbb4509a4291947dfa2479b9e4b308" + "digest": "c4cfa1f2201dcda9de310a8247e6ce32d2798ae426a14dd70a9ebb00a2804d46" }, { "name": "pr", "unicode": "1F1F5-1F1F7", - "digest": "4910dc984bc908158506b770f28af56150cbb4509a4291947dfa2479b9e4b308" + "digest": "c4cfa1f2201dcda9de310a8247e6ce32d2798ae426a14dd70a9ebb00a2804d46" }, { "name": "flag_ps", "unicode": "1F1F5-1F1F8", - "digest": "b2bca7619fced25de94d7bd398537857460348a552e7d73d189aef3f428e6a13" + "digest": "197f2ec6294bf0ee4a08cf2f2d1e237ee867c98b3085454a3f42abc955eeb289" }, { "name": "ps", "unicode": "1F1F5-1F1F8", - "digest": "b2bca7619fced25de94d7bd398537857460348a552e7d73d189aef3f428e6a13" + "digest": "197f2ec6294bf0ee4a08cf2f2d1e237ee867c98b3085454a3f42abc955eeb289" }, { "name": "flag_pt", "unicode": "1F1F5-1F1F9", - "digest": "177282613b4b8b4d9551f1da6a1c3f66f1b96cf67c71c7d164213b26b3237395" + "digest": "86a50827963756b5bf471ed9df5b3f2a2058b4c5d778a303414b6b0556e2082b" }, { "name": "pt", "unicode": "1F1F5-1F1F9", - "digest": "177282613b4b8b4d9551f1da6a1c3f66f1b96cf67c71c7d164213b26b3237395" + "digest": "86a50827963756b5bf471ed9df5b3f2a2058b4c5d778a303414b6b0556e2082b" }, { "name": "flag_pw", "unicode": "1F1F5-1F1FC", - "digest": "2ff42a14bdc7df76b5f989dca381f94765032b26ae47d47b97844abde458cefe" + "digest": "a6321c47a0cd188fbfdf3b55f17a7170c63080d28d50e4f5463eb1ee09af2412" }, { "name": "pw", "unicode": "1F1F5-1F1FC", - "digest": "2ff42a14bdc7df76b5f989dca381f94765032b26ae47d47b97844abde458cefe" + "digest": "a6321c47a0cd188fbfdf3b55f17a7170c63080d28d50e4f5463eb1ee09af2412" }, { "name": "flag_py", "unicode": "1F1F5-1F1FE", - "digest": "80169b69a46c4c67d0090dc2c6bf05d1a14f133ac7ae56f811547e8e8f70d81b" + "digest": "1a169e8d8703c510c5a2265b57dbed2f811b03ec375bcb341ab4cd0b100a9dd6" }, { "name": "py", "unicode": "1F1F5-1F1FE", - "digest": "80169b69a46c4c67d0090dc2c6bf05d1a14f133ac7ae56f811547e8e8f70d81b" + "digest": "1a169e8d8703c510c5a2265b57dbed2f811b03ec375bcb341ab4cd0b100a9dd6" }, { "name": "flag_qa", "unicode": "1F1F6-1F1E6", - "digest": "589b44b975aa97426afb8db7f8b355491fca246b693903485824bf0f5a6953a2" + "digest": "de6283965cd98a244b7fa6288174f9ff0d8feb497f191f2e4ab3b690138a3d5d" }, { "name": "qa", "unicode": "1F1F6-1F1E6", - "digest": "589b44b975aa97426afb8db7f8b355491fca246b693903485824bf0f5a6953a2" + "digest": "de6283965cd98a244b7fa6288174f9ff0d8feb497f191f2e4ab3b690138a3d5d" }, { "name": "flag_re", "unicode": "1F1F7-1F1EA", - "digest": "77d242261742831a142c9ec74cd17d76b1e6d1af751ff3c6a356646744bc798a" + "digest": "260e1b97abc1562e5a73d7e53652ffed8059fc9b1c969741c466f48ec6ab0e80" }, { "name": "re", "unicode": "1F1F7-1F1EA", - "digest": "77d242261742831a142c9ec74cd17d76b1e6d1af751ff3c6a356646744bc798a" + "digest": "260e1b97abc1562e5a73d7e53652ffed8059fc9b1c969741c466f48ec6ab0e80" }, { "name": "flag_ro", "unicode": "1F1F7-1F1F4", - "digest": "d7d17026ea81f27456983722540f9a23343a3a1b22e7697c4fba118ce8b4719e" + "digest": "6d648e03955fa2a9fd2bad6f60ec96d3e20ee57f5855f3721a4d4e0c8e99f95c" }, { "name": "ro", "unicode": "1F1F7-1F1F4", - "digest": "d7d17026ea81f27456983722540f9a23343a3a1b22e7697c4fba118ce8b4719e" + "digest": "6d648e03955fa2a9fd2bad6f60ec96d3e20ee57f5855f3721a4d4e0c8e99f95c" }, { "name": "flag_rs", "unicode": "1F1F7-1F1F8", - "digest": "e466a18cc0368e623d3fe33a036c1e88db91ae24f7510e17caacc85c41f1bac8" + "digest": "95cd5e197ed364e403eeb7f1d18a83487d89166910ba8119ea994e5e19d6a7ee" }, { "name": "rs", "unicode": "1F1F7-1F1F8", - "digest": "e466a18cc0368e623d3fe33a036c1e88db91ae24f7510e17caacc85c41f1bac8" + "digest": "95cd5e197ed364e403eeb7f1d18a83487d89166910ba8119ea994e5e19d6a7ee" }, { "name": "flag_ru", "unicode": "1F1F7-1F1FA", - "digest": "86bf53a62dfc4c434d910f43df70f430fc67c0070fe3fc466c4fbfd6a5d8e646" + "digest": "a4a81617a59d9eaf3c526431ca6f90ed334a7c1f516bf70cbd3f1fdc6e6103d7" }, { "name": "ru", "unicode": "1F1F7-1F1FA", - "digest": "86bf53a62dfc4c434d910f43df70f430fc67c0070fe3fc466c4fbfd6a5d8e646" + "digest": "a4a81617a59d9eaf3c526431ca6f90ed334a7c1f516bf70cbd3f1fdc6e6103d7" }, { "name": "flag_rw", "unicode": "1F1F7-1F1FC", - "digest": "38ec5a01896c9747a8dbf865d5e8584770e587253b7af3d3b9c36cd993f67518" + "digest": "7a369f60db0876ffef111c319a3e8c9eaed620c875c51b98ed9ad5207b836dca" }, { "name": "rw", "unicode": "1F1F7-1F1FC", - "digest": "38ec5a01896c9747a8dbf865d5e8584770e587253b7af3d3b9c36cd993f67518" + "digest": "7a369f60db0876ffef111c319a3e8c9eaed620c875c51b98ed9ad5207b836dca" }, { "name": "flag_sa", "unicode": "1F1F8-1F1E6", - "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7" }, { "name": "saudiarabia", "unicode": "1F1F8-1F1E6", - "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7" }, { "name": "saudi", "unicode": "1F1F8-1F1E6", - "digest": "a44d0b145f2a0b68eace24ecfd27519e9525ec764836728bc9c1fe96ccb811a0" + "digest": "b249fbfd7ed415943f60bbd841965cf721979f960ccbe09396aebac1eca913d7" }, { "name": "flag_sb", "unicode": "1F1F8-1F1E7", - "digest": "8ffa24c5cb92be4dbe43f6cd85b61b9608a3101bd78ebccff4fe99c209b3e241" + "digest": "526b411260024ea7b6ea6c47f2549345c6cc6960e9a29bfa9aaec0772664d2dc" }, { "name": "sb", "unicode": "1F1F8-1F1E7", - "digest": "8ffa24c5cb92be4dbe43f6cd85b61b9608a3101bd78ebccff4fe99c209b3e241" + "digest": "526b411260024ea7b6ea6c47f2549345c6cc6960e9a29bfa9aaec0772664d2dc" }, { "name": "flag_sc", "unicode": "1F1F8-1F1E8", - "digest": "227d090ac2cbf317e594567b6114b5063a13cfe33abf990d37b200debcfadabb" + "digest": "d036b0d068745926120eaf746fa2e4433306e2e14c6b540d0cd6265e34471056" }, { "name": "sc", "unicode": "1F1F8-1F1E8", - "digest": "227d090ac2cbf317e594567b6114b5063a13cfe33abf990d37b200debcfadabb" + "digest": "d036b0d068745926120eaf746fa2e4433306e2e14c6b540d0cd6265e34471056" }, { "name": "flag_sd", "unicode": "1F1F8-1F1E9", - "digest": "350f3332e8ea1138e54facc870dd0fea5f2ab7d3fd4baa02ed8627ae79642f6c" + "digest": "889615bdb9b1f9c59c5f83ed4d22d54a0ed5dd5de263e729c58544cb06c55885" }, { "name": "sd", "unicode": "1F1F8-1F1E9", - "digest": "350f3332e8ea1138e54facc870dd0fea5f2ab7d3fd4baa02ed8627ae79642f6c" + "digest": "889615bdb9b1f9c59c5f83ed4d22d54a0ed5dd5de263e729c58544cb06c55885" }, { "name": "flag_se", "unicode": "1F1F8-1F1EA", - "digest": "c1b09f36c263727de83b54376f05e083a17a61941af9a1640b826629256a280d" + "digest": "f471d80cfff340960a752c8c152ed4fb482df2a3712b0a56dfab31b9b806926a" }, { "name": "se", "unicode": "1F1F8-1F1EA", - "digest": "c1b09f36c263727de83b54376f05e083a17a61941af9a1640b826629256a280d" + "digest": "f471d80cfff340960a752c8c152ed4fb482df2a3712b0a56dfab31b9b806926a" }, { "name": "flag_sg", "unicode": "1F1F8-1F1EC", - "digest": "e6fc26920dfc07e4fd3c8d897de9c607e0bf48a3b64a13630c858d707a8e7660" + "digest": "82f58a09f98593cc87e545f7e5c03d2aedaf82e54e73f71f58c18e994c3085ac" }, { "name": "sg", "unicode": "1F1F8-1F1EC", - "digest": "e6fc26920dfc07e4fd3c8d897de9c607e0bf48a3b64a13630c858d707a8e7660" + "digest": "82f58a09f98593cc87e545f7e5c03d2aedaf82e54e73f71f58c18e994c3085ac" }, { "name": "flag_sh", "unicode": "1F1F8-1F1ED", - "digest": "f2c22ab0eb49e3104c35f1c0268b1e63c3a67f41b0cfa9861b189525988e53b6" + "digest": "53914b1fa8c1b4f30bae6c1f6717f138fb4dbf482c3e20e33f7aea4ecfc0438d" }, { "name": "sh", "unicode": "1F1F8-1F1ED", - "digest": "f2c22ab0eb49e3104c35f1c0268b1e63c3a67f41b0cfa9861b189525988e53b6" + "digest": "53914b1fa8c1b4f30bae6c1f6717f138fb4dbf482c3e20e33f7aea4ecfc0438d" }, { "name": "flag_si", "unicode": "1F1F8-1F1EE", - "digest": "1ef0b10e498f71591322f9d8ec122d39838f479370cf7ee922560986ef6c4f2e" + "digest": "65d491daa69f9a11cec9ccc4df3a669f12ef95a5c312137776d4472719940ba3" }, { "name": "si", "unicode": "1F1F8-1F1EE", - "digest": "1ef0b10e498f71591322f9d8ec122d39838f479370cf7ee922560986ef6c4f2e" + "digest": "65d491daa69f9a11cec9ccc4df3a669f12ef95a5c312137776d4472719940ba3" }, { "name": "flag_sj", "unicode": "1F1F8-1F1EF", - "digest": "ce913b007f84a9cba2add8d754aa791901624c60e4200de426dfa25271cb0f78" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "sj", "unicode": "1F1F8-1F1EF", - "digest": "ce913b007f84a9cba2add8d754aa791901624c60e4200de426dfa25271cb0f78" + "digest": "bbf6daa6174c6fbbbf541c8274f31b6757c3a16007c2687015ea041fd1e2c6b6" }, { "name": "flag_sk", "unicode": "1F1F8-1F1F0", - "digest": "d8f8fc4024c82f906effe98facbef9d543fb3708b1134dc502c74dc4a442b30a" + "digest": "d4fd03eca5bd3c9fb324ee04fae37c9a2d852bac8335369e3e720ef9b98fff36" }, { "name": "sk", "unicode": "1F1F8-1F1F0", - "digest": "d8f8fc4024c82f906effe98facbef9d543fb3708b1134dc502c74dc4a442b30a" + "digest": "d4fd03eca5bd3c9fb324ee04fae37c9a2d852bac8335369e3e720ef9b98fff36" }, { "name": "flag_sl", "unicode": "1F1F8-1F1F1", - "digest": "dd7fd0452498d8d1c894cf0d5a662ddff9c5bcc02148bdc3dc7e6f25d0bb586e" + "digest": "1455c98c11c248623d82be5484ab1c4dcd1dae449adc393eb1aa2d8c74aa3f02" }, { "name": "sl", "unicode": "1F1F8-1F1F1", - "digest": "dd7fd0452498d8d1c894cf0d5a662ddff9c5bcc02148bdc3dc7e6f25d0bb586e" + "digest": "1455c98c11c248623d82be5484ab1c4dcd1dae449adc393eb1aa2d8c74aa3f02" }, { "name": "flag_sm", "unicode": "1F1F8-1F1F2", - "digest": "2b499606aee2b5cbf4037338753c80a4c8f75f4abcef2c8657bd9337e602bbd3" + "digest": "daec5864ac50c625d7bf49d6c1a170a094cf0d1b9a0bdf62a62406e7ec500a94" }, { "name": "sm", "unicode": "1F1F8-1F1F2", - "digest": "2b499606aee2b5cbf4037338753c80a4c8f75f4abcef2c8657bd9337e602bbd3" + "digest": "daec5864ac50c625d7bf49d6c1a170a094cf0d1b9a0bdf62a62406e7ec500a94" }, { "name": "flag_sn", "unicode": "1F1F8-1F1F3", - "digest": "03b46a9d8b129da13f60c23b820b04fba52050ca58a41b859ad57d5c3cc2515d" + "digest": "4e4d43c467e5eb84c70f535f37f4f468319bd4b06c6ec3db3b54f69efdafd334" }, { "name": "sn", "unicode": "1F1F8-1F1F3", - "digest": "03b46a9d8b129da13f60c23b820b04fba52050ca58a41b859ad57d5c3cc2515d" + "digest": "4e4d43c467e5eb84c70f535f37f4f468319bd4b06c6ec3db3b54f69efdafd334" }, { "name": "flag_so", "unicode": "1F1F8-1F1F4", - "digest": "ea416b6a05ddc5b16291ebe5101735360b08c834d55ac82c663ac1dd3e459048" + "digest": "c1434dca361563a8e3ba88f1ad19c3f6c9cbb8f3ebc17ce128fde2351ff67d0c" }, { "name": "so", "unicode": "1F1F8-1F1F4", - "digest": "ea416b6a05ddc5b16291ebe5101735360b08c834d55ac82c663ac1dd3e459048" + "digest": "c1434dca361563a8e3ba88f1ad19c3f6c9cbb8f3ebc17ce128fde2351ff67d0c" }, { "name": "flag_sr", "unicode": "1F1F8-1F1F7", - "digest": "012179fbcbcb7343e7b09d33e283fb63c7964a6eca35ccb9407d468e495a9874" + "digest": "f3c6bfee2a052f03d56ba917b88595450cef111ffa9e92c7f39ef8c3c3bd12d1" }, { "name": "sr", "unicode": "1F1F8-1F1F7", - "digest": "012179fbcbcb7343e7b09d33e283fb63c7964a6eca35ccb9407d468e495a9874" + "digest": "f3c6bfee2a052f03d56ba917b88595450cef111ffa9e92c7f39ef8c3c3bd12d1" }, { "name": "flag_ss", "unicode": "1F1F8-1F1F8", - "digest": "6723150482c640643c9dd7e33ea749f4a8b46aceacbd4f5e11aa33b3ee13aab7" + "digest": "c0ed7e4f41206f5363e8ebdc6c3f28080e2f07d99e6fb73c1f6226d83310e69d" }, { "name": "ss", "unicode": "1F1F8-1F1F8", - "digest": "6723150482c640643c9dd7e33ea749f4a8b46aceacbd4f5e11aa33b3ee13aab7" + "digest": "c0ed7e4f41206f5363e8ebdc6c3f28080e2f07d99e6fb73c1f6226d83310e69d" }, { "name": "flag_st", "unicode": "1F1F8-1F1F9", - "digest": "0947fcec2e3cb1b0e9943c3d00891e8ee226e8d0532e9b1fe807ddf2e8fbc49d" + "digest": "b022ae5d6885e28c6e9c83c17dd0c24c731d4f3d5773c49051768cdd4df51330" }, { "name": "st", "unicode": "1F1F8-1F1F9", - "digest": "0947fcec2e3cb1b0e9943c3d00891e8ee226e8d0532e9b1fe807ddf2e8fbc49d" + "digest": "b022ae5d6885e28c6e9c83c17dd0c24c731d4f3d5773c49051768cdd4df51330" }, { "name": "flag_sv", "unicode": "1F1F8-1F1FB", - "digest": "ce7e583db833c4b10e2f7a2d09b97bb522c02e96ea0b3f3a48a955f7d8f970d8" + "digest": "5bafdd04d243ee3f3998f4ec0a3d03ff5a3975e771b1f94f89d7713193d7a242" }, { "name": "sv", "unicode": "1F1F8-1F1FB", - "digest": "ce7e583db833c4b10e2f7a2d09b97bb522c02e96ea0b3f3a48a955f7d8f970d8" + "digest": "5bafdd04d243ee3f3998f4ec0a3d03ff5a3975e771b1f94f89d7713193d7a242" }, { "name": "flag_sx", "unicode": "1F1F8-1F1FD", - "digest": "c01fb238c7ba439f24a5ef821b6457f2a0fd0b99a1b2d02395bed87f0a4a88e5" + "digest": "fb92e9f514bcc2f7abbd4e146edde50f030c940c833f184618cbb48e56af22bd" }, { "name": "sx", "unicode": "1F1F8-1F1FD", - "digest": "c01fb238c7ba439f24a5ef821b6457f2a0fd0b99a1b2d02395bed87f0a4a88e5" + "digest": "fb92e9f514bcc2f7abbd4e146edde50f030c940c833f184618cbb48e56af22bd" }, { "name": "flag_sy", "unicode": "1F1F8-1F1FE", - "digest": "a77d87ef98c96140c59998d10d94837e2a056dd3ac5c7522e89e5c62eac69e69" + "digest": "ee330da644d4ce1fdba98be5eaab5054aed8d91a34ab617199a4b2b77f62a10b" }, { "name": "sy", "unicode": "1F1F8-1F1FE", - "digest": "a77d87ef98c96140c59998d10d94837e2a056dd3ac5c7522e89e5c62eac69e69" + "digest": "ee330da644d4ce1fdba98be5eaab5054aed8d91a34ab617199a4b2b77f62a10b" }, { "name": "flag_sz", "unicode": "1F1F8-1F1FF", - "digest": "2904ad01040a9107ad556ec4c2561781d96746005cca250babb1127b8ba21050" + "digest": "7fe0c7429efd9682cc39e57f4bba8d1491d301643ba999d57c4e1bc37517ed64" }, { "name": "sz", "unicode": "1F1F8-1F1FF", - "digest": "2904ad01040a9107ad556ec4c2561781d96746005cca250babb1127b8ba21050" + "digest": "7fe0c7429efd9682cc39e57f4bba8d1491d301643ba999d57c4e1bc37517ed64" }, { "name": "flag_ta", "unicode": "1F1F9-1F1E6", - "digest": "eda84db90e1a8854e8ff3c15b3b38ee65f7d6532b76970a6fbac304c30d8c959" + "digest": "b47e245a2708072a4dbaf190c9606baa4daf02e51627eeae6f20c3b4c95024c0" }, { "name": "ta", "unicode": "1F1F9-1F1E6", - "digest": "eda84db90e1a8854e8ff3c15b3b38ee65f7d6532b76970a6fbac304c30d8c959" + "digest": "b47e245a2708072a4dbaf190c9606baa4daf02e51627eeae6f20c3b4c95024c0" }, { "name": "flag_tc", "unicode": "1F1F9-1F1E8", - "digest": "4628fdf6dc598a2846beefe97f7d4c6812f4961394cec132924b44bbe79b3322" + "digest": "18cfff14c2503b9d24c91c668583d4a14efb17657d800eca86ae49b547c9da5c" }, { "name": "tc", "unicode": "1F1F9-1F1E8", - "digest": "4628fdf6dc598a2846beefe97f7d4c6812f4961394cec132924b44bbe79b3322" + "digest": "18cfff14c2503b9d24c91c668583d4a14efb17657d800eca86ae49b547c9da5c" }, { "name": "flag_td", "unicode": "1F1F9-1F1E9", - "digest": "125ff31e4285cb2a5493a52a2703ebe8e7138b918ec4dae3d0f8693632372df6" + "digest": "73d1db3365736915c4cdf9ba9343d9fd78962203b60334e8f3724d4b330b17db" }, { "name": "td", "unicode": "1F1F9-1F1E9", - "digest": "125ff31e4285cb2a5493a52a2703ebe8e7138b918ec4dae3d0f8693632372df6" + "digest": "73d1db3365736915c4cdf9ba9343d9fd78962203b60334e8f3724d4b330b17db" }, { "name": "flag_tf", "unicode": "1F1F9-1F1EB", - "digest": "489d591e11764ac341f2234020f7879db782b8f673fc9aae425fd713e4082334" + "digest": "3bffeb4bc9ceb9cbb150de88e957b6e46509862ca7d616d5693124af084eb435" }, { "name": "tf", "unicode": "1F1F9-1F1EB", - "digest": "489d591e11764ac341f2234020f7879db782b8f673fc9aae425fd713e4082334" + "digest": "3bffeb4bc9ceb9cbb150de88e957b6e46509862ca7d616d5693124af084eb435" }, { "name": "flag_tg", "unicode": "1F1F9-1F1EC", - "digest": "4ceedfcfcc22cd14d9add9d86d6748447995f19f7095fa4be883e21eb1aa86bc" + "digest": "eb13a0e85baf73326f3ae3bc75e8406eca42000d7e42b0641120e64c0ab7ebaa" }, { "name": "tg", "unicode": "1F1F9-1F1EC", - "digest": "4ceedfcfcc22cd14d9add9d86d6748447995f19f7095fa4be883e21eb1aa86bc" + "digest": "eb13a0e85baf73326f3ae3bc75e8406eca42000d7e42b0641120e64c0ab7ebaa" }, { "name": "flag_th", "unicode": "1F1F9-1F1ED", - "digest": "2798cc660af1c5dc4891c30aded3a53d7cfa0af128cc495df8141907b165902d" + "digest": "a4e42efa4bb94e90f3a92ae9ce14affaacd3a142c1e0da40d8cc839500e771fd" }, { "name": "th", "unicode": "1F1F9-1F1ED", - "digest": "2798cc660af1c5dc4891c30aded3a53d7cfa0af128cc495df8141907b165902d" + "digest": "a4e42efa4bb94e90f3a92ae9ce14affaacd3a142c1e0da40d8cc839500e771fd" }, { "name": "flag_tj", "unicode": "1F1F9-1F1EF", - "digest": "0483506fc5b5f2d4fc18ea3cd2f8a5da985d68fe4bf90bd3fd05e67e38f32398" + "digest": "ff926fa3e86e095683a61c4754355a5b4dd0ecb74393306bd791d130fd1a909d" }, { "name": "tj", "unicode": "1F1F9-1F1EF", - "digest": "0483506fc5b5f2d4fc18ea3cd2f8a5da985d68fe4bf90bd3fd05e67e38f32398" + "digest": "ff926fa3e86e095683a61c4754355a5b4dd0ecb74393306bd791d130fd1a909d" }, { "name": "flag_tk", "unicode": "1F1F9-1F1F0", - "digest": "d5d4a8c6ce3207731b7c154a9d8d8fa2af055a48f03b3cbbcfd3317d3b8a75f2" + "digest": "3fa732d457ded6c83cd5f73d934f64c4e687eb0cde7c157d2fdcdccaf3b5fb52" }, { "name": "tk", "unicode": "1F1F9-1F1F0", - "digest": "d5d4a8c6ce3207731b7c154a9d8d8fa2af055a48f03b3cbbcfd3317d3b8a75f2" + "digest": "3fa732d457ded6c83cd5f73d934f64c4e687eb0cde7c157d2fdcdccaf3b5fb52" }, { "name": "flag_tl", "unicode": "1F1F9-1F1F1", - "digest": "7a2ba8f91a6b627c60c88244223a9b9d0c12707f50b174f9c2eca07dd3440df7" + "digest": "0ec2a4d22fb832060693089e518bbe370a4e13bfc28748f110fc13726409f473" }, { "name": "tl", "unicode": "1F1F9-1F1F1", - "digest": "7a2ba8f91a6b627c60c88244223a9b9d0c12707f50b174f9c2eca07dd3440df7" + "digest": "0ec2a4d22fb832060693089e518bbe370a4e13bfc28748f110fc13726409f473" }, { "name": "flag_tm", "unicode": "1F1F9-1F1F2", - "digest": "adcf5f23adcf983ce626b44559482f8728251eab34b3ff5d8b125112f3a1010f" + "digest": "b4724aa7ad13352f16a0936e61cbb85f0bd147583fc66597aff7e8ee7cf19c21" }, { "name": "turkmenistan", "unicode": "1F1F9-1F1F2", - "digest": "adcf5f23adcf983ce626b44559482f8728251eab34b3ff5d8b125112f3a1010f" + "digest": "b4724aa7ad13352f16a0936e61cbb85f0bd147583fc66597aff7e8ee7cf19c21" }, { "name": "flag_tn", "unicode": "1F1F9-1F1F3", - "digest": "5ee690ee1f3c3c0cba9b36efdef902894ec59cefbc60c4baa341efd3d7bb9ba2" + "digest": "5ab308ffdde40f504d6ee080817bbddbe4f3f4ddb71f508c75e0144a8c8044d9" }, { "name": "tn", "unicode": "1F1F9-1F1F3", - "digest": "5ee690ee1f3c3c0cba9b36efdef902894ec59cefbc60c4baa341efd3d7bb9ba2" + "digest": "5ab308ffdde40f504d6ee080817bbddbe4f3f4ddb71f508c75e0144a8c8044d9" }, { "name": "flag_to", "unicode": "1F1F9-1F1F4", - "digest": "cde8672ca25b0e3a423865283fab9bc3ab10f472e04979b3b2f8032b71e96300" + "digest": "75b7e7198fa42f87986882b8ca251a229afcaa0a1188ae7b9f5ece87dc31a723" }, { "name": "to", "unicode": "1F1F9-1F1F4", - "digest": "cde8672ca25b0e3a423865283fab9bc3ab10f472e04979b3b2f8032b71e96300" + "digest": "75b7e7198fa42f87986882b8ca251a229afcaa0a1188ae7b9f5ece87dc31a723" }, { "name": "flag_tr", "unicode": "1F1F9-1F1F7", - "digest": "3d83c03ed084cfc81fa633310382acd7213e1eaa19d0ed97d142e7824032b55d" + "digest": "9cc48a8f8fa9c17c1627272f68d4740da0e7ce17a2cf8c6b5c08cc9b95e1390c" }, { "name": "tr", "unicode": "1F1F9-1F1F7", - "digest": "3d83c03ed084cfc81fa633310382acd7213e1eaa19d0ed97d142e7824032b55d" + "digest": "9cc48a8f8fa9c17c1627272f68d4740da0e7ce17a2cf8c6b5c08cc9b95e1390c" }, { "name": "flag_tt", "unicode": "1F1F9-1F1F9", - "digest": "d66d272ac27e2b398289d6b60128ccd3508aeb1f4a00a3920c5e6a21bfe357ed" + "digest": "f9e63543121bb3cd2e41bc7b0c2c4ba662bc1cc0520b79fc4e201ec6456fdf59" }, { "name": "tt", "unicode": "1F1F9-1F1F9", - "digest": "d66d272ac27e2b398289d6b60128ccd3508aeb1f4a00a3920c5e6a21bfe357ed" + "digest": "f9e63543121bb3cd2e41bc7b0c2c4ba662bc1cc0520b79fc4e201ec6456fdf59" }, { "name": "flag_tv", "unicode": "1F1F9-1F1FB", - "digest": "8716527383854cf1569f737d0f0f9ad77b46747255f24e02f5b2fbc850c2e35c" + "digest": "6431e5f06cc7995ae7208c429ecf39339b545854cb6d6b7447f465fe53614dfc" }, { "name": "tuvalu", "unicode": "1F1F9-1F1FB", - "digest": "8716527383854cf1569f737d0f0f9ad77b46747255f24e02f5b2fbc850c2e35c" + "digest": "6431e5f06cc7995ae7208c429ecf39339b545854cb6d6b7447f465fe53614dfc" }, { "name": "flag_tw", "unicode": "1F1F9-1F1FC", - "digest": "fb17b97e18e4423c5f60d60ec3ec60b917be579fc4dd9b5b23236786dcb35108" + "digest": "8395ab3c6a595023b006518a5345ac3612f2893d3a8f011b7e5802414236b03c" }, { "name": "tw", "unicode": "1F1F9-1F1FC", - "digest": "fb17b97e18e4423c5f60d60ec3ec60b917be579fc4dd9b5b23236786dcb35108" + "digest": "8395ab3c6a595023b006518a5345ac3612f2893d3a8f011b7e5802414236b03c" }, { "name": "flag_tz", "unicode": "1F1F9-1F1FF", - "digest": "a8a8cf57ae5227cb54620bf31d2d6e154d2067d6d049b8db64bc4e538222948b" + "digest": "716181733cd9ac7a8f51a9a64bc5d21020e8112f6768e8c49c4d651a3ee0b8a4" }, { "name": "tz", "unicode": "1F1F9-1F1FF", - "digest": "a8a8cf57ae5227cb54620bf31d2d6e154d2067d6d049b8db64bc4e538222948b" + "digest": "716181733cd9ac7a8f51a9a64bc5d21020e8112f6768e8c49c4d651a3ee0b8a4" }, { "name": "flag_ua", "unicode": "1F1FA-1F1E6", - "digest": "03aca4b3ffd60d944a5793eb7530f8d8ae527782f642f6606194e46ee314b12c" + "digest": "304570736345e28734f5ff84a2b0481c2bb00bf29d9892bd749b57dec7741e30" }, { "name": "ua", "unicode": "1F1FA-1F1E6", - "digest": "03aca4b3ffd60d944a5793eb7530f8d8ae527782f642f6606194e46ee314b12c" + "digest": "304570736345e28734f5ff84a2b0481c2bb00bf29d9892bd749b57dec7741e30" }, { "name": "flag_ug", "unicode": "1F1FA-1F1EC", - "digest": "70226a1585e88390b3b815b8b79a0ddb36d2961c6b465c4ff72aa444abfe982e" + "digest": "a1bafb74c54ee8c92cb025b55aebdb6081eec3fda6a7f86f2ee14d1b801a8e9c" }, { "name": "ug", "unicode": "1F1FA-1F1EC", - "digest": "70226a1585e88390b3b815b8b79a0ddb36d2961c6b465c4ff72aa444abfe982e" + "digest": "a1bafb74c54ee8c92cb025b55aebdb6081eec3fda6a7f86f2ee14d1b801a8e9c" }, { "name": "flag_um", "unicode": "1F1FA-1F1F2", - "digest": "aa83bf051149acf907140a860de5de1700710e4164ae5549ad1040b24d0a142b" + "digest": "b3c9ac72211f481f50cde09e10b92aa03b1ea90abf85418e60a35b84963273ee" }, { "name": "um", "unicode": "1F1FA-1F1F2", - "digest": "aa83bf051149acf907140a860de5de1700710e4164ae5549ad1040b24d0a142b" + "digest": "b3c9ac72211f481f50cde09e10b92aa03b1ea90abf85418e60a35b84963273ee" }, { "name": "flag_us", "unicode": "1F1FA-1F1F8", - "digest": "32ba2aa09a30514247e91d60762791b582f547a37d9151f98b700dff50f355ea" + "digest": "da79f9af0a188178a82e7dc3a62298fa416f4cfbcae432838df1abebca5c0d63" }, { "name": "us", "unicode": "1F1FA-1F1F8", - "digest": "32ba2aa09a30514247e91d60762791b582f547a37d9151f98b700dff50f355ea" + "digest": "da79f9af0a188178a82e7dc3a62298fa416f4cfbcae432838df1abebca5c0d63" }, { "name": "flag_uy", "unicode": "1F1FA-1F1FE", - "digest": "0e01b3f1df4bdf6d616dacc9c5825151b941bf074be750e8b24a07ea5d5bcacb" + "digest": "8348e901d775722497ee911c9c9b4bd767710760c507630a67ecb6d47cc646c7" }, { "name": "uy", "unicode": "1F1FA-1F1FE", - "digest": "0e01b3f1df4bdf6d616dacc9c5825151b941bf074be750e8b24a07ea5d5bcacb" + "digest": "8348e901d775722497ee911c9c9b4bd767710760c507630a67ecb6d47cc646c7" }, { "name": "flag_uz", "unicode": "1F1FA-1F1FF", - "digest": "903029ce83812a2134f24b65db35b183443a440ea5fecaa6ef7dcaaf65b2519c" + "digest": "2a1dc1e9469e01c58ea91f545ef3fe0bdfe5544a73a80407f8960d01b1e5db5c" }, { "name": "uz", "unicode": "1F1FA-1F1FF", - "digest": "903029ce83812a2134f24b65db35b183443a440ea5fecaa6ef7dcaaf65b2519c" + "digest": "2a1dc1e9469e01c58ea91f545ef3fe0bdfe5544a73a80407f8960d01b1e5db5c" }, { "name": "flag_va", "unicode": "1F1FB-1F1E6", - "digest": "fd3c1c5d0ac030e838f807288912c98a3e258f87901e252e46942a4dab9f8cb7" + "digest": "0e8134ec94bff032bfc63b0b08587d5298c9b7f31edd5a5b35633ae911434e61" }, { "name": "va", "unicode": "1F1FB-1F1E6", - "digest": "fd3c1c5d0ac030e838f807288912c98a3e258f87901e252e46942a4dab9f8cb7" + "digest": "0e8134ec94bff032bfc63b0b08587d5298c9b7f31edd5a5b35633ae911434e61" }, { "name": "flag_vc", "unicode": "1F1FB-1F1E8", - "digest": "7cd554ea8ca817b5366701160274587ab44167ae5a89c430bbaf237ea18b7421" + "digest": "e0290e1be72c8939ee6c398f00a107703b21b97d91b9bf465e553ffbf00304a7" }, { "name": "vc", "unicode": "1F1FB-1F1E8", - "digest": "7cd554ea8ca817b5366701160274587ab44167ae5a89c430bbaf237ea18b7421" + "digest": "e0290e1be72c8939ee6c398f00a107703b21b97d91b9bf465e553ffbf00304a7" }, { "name": "flag_ve", "unicode": "1F1FB-1F1EA", - "digest": "72930094fb088c1facabea07616035ec4771374358a90c3045219d087b350dd8" + "digest": "76a6a6c2353def1f984d1a6980831e63f3aea5af2201b574197834e7c203d57a" }, { "name": "ve", "unicode": "1F1FB-1F1EA", - "digest": "72930094fb088c1facabea07616035ec4771374358a90c3045219d087b350dd8" + "digest": "76a6a6c2353def1f984d1a6980831e63f3aea5af2201b574197834e7c203d57a" }, { "name": "flag_vg", "unicode": "1F1FB-1F1EC", - "digest": "78a59afd368b7a8312bfdb2f49927ff09e6b8f46aab0136c0453e3319e81df49" + "digest": "56fc9317b8dd62cccc60010819f8b895dd4569a9b06368a9250f815c39177b8a" }, { "name": "vg", "unicode": "1F1FB-1F1EC", - "digest": "78a59afd368b7a8312bfdb2f49927ff09e6b8f46aab0136c0453e3319e81df49" + "digest": "56fc9317b8dd62cccc60010819f8b895dd4569a9b06368a9250f815c39177b8a" }, { "name": "flag_vi", "unicode": "1F1FB-1F1EE", - "digest": "e070879f9605a9bae66bb84f2abf5a40c8b264baee65cd4f7a6720b826739f29" + "digest": "2526a3e13b8ccd301f0763580430898c227bd209e3ce482c7951140b28948375" }, { "name": "vi", "unicode": "1F1FB-1F1EE", - "digest": "e070879f9605a9bae66bb84f2abf5a40c8b264baee65cd4f7a6720b826739f29" + "digest": "2526a3e13b8ccd301f0763580430898c227bd209e3ce482c7951140b28948375" }, { "name": "flag_vn", "unicode": "1F1FB-1F1F3", - "digest": "100ddf06e0f239b170f4d6cb459450bf4945281ee818f7d3c061828b80562219" + "digest": "0cf6b9896bbe4da8ed7718d0abfd56cef1a8321e26f89d3ad1b48488eaffb7a5" }, { "name": "vn", "unicode": "1F1FB-1F1F3", - "digest": "100ddf06e0f239b170f4d6cb459450bf4945281ee818f7d3c061828b80562219" + "digest": "0cf6b9896bbe4da8ed7718d0abfd56cef1a8321e26f89d3ad1b48488eaffb7a5" }, { "name": "flag_vu", "unicode": "1F1FB-1F1FA", - "digest": "59fc9d16818295bba4f7f551598f85378cd07f2bd7e31a4eef2589aaa3847563" + "digest": "9dfa282ce1aafc62beacab76e1fc19a141c8bdeaa30898f69b083067b775d362" }, { "name": "vu", "unicode": "1F1FB-1F1FA", - "digest": "59fc9d16818295bba4f7f551598f85378cd07f2bd7e31a4eef2589aaa3847563" + "digest": "9dfa282ce1aafc62beacab76e1fc19a141c8bdeaa30898f69b083067b775d362" }, { "name": "flag_wf", "unicode": "1F1FC-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "wf", "unicode": "1F1FC-1F1EB", - "digest": "62627702e3e3768808c12f153a527ffcc492ad74d8cdc1858cfde971efd0c8ee" + "digest": "a0124683aa88cd7da886da70c65796c5ad84eb3751e356e9b2aa8ac249cf0bf9" }, { "name": "flag_white", "unicode": "1F3F3", - "digest": "96307e3a28e92d1e7147a06f154ffc291ee3cd1765cf8b7bfb06412294112559" + "digest": "d9be4b7ceb8309c48f88cfd07a9f7ce6758ea6e620e73293cf14baec03ca381c" }, { "name": "waving_white_flag", "unicode": "1F3F3", - "digest": "96307e3a28e92d1e7147a06f154ffc291ee3cd1765cf8b7bfb06412294112559" + "digest": "d9be4b7ceb8309c48f88cfd07a9f7ce6758ea6e620e73293cf14baec03ca381c" }, { "name": "flag_ws", "unicode": "1F1FC-1F1F8", - "digest": "0c95271d0f4b23f0d215ee0fba05cf08ecb70665d4c028e17463ecda2754b164" + "digest": "53addd0dc304a3c8893389ed227986ef2431828b8c071926aa09f9efd815b649" }, { "name": "ws", "unicode": "1F1FC-1F1F8", - "digest": "0c95271d0f4b23f0d215ee0fba05cf08ecb70665d4c028e17463ecda2754b164" + "digest": "53addd0dc304a3c8893389ed227986ef2431828b8c071926aa09f9efd815b649" }, { "name": "flag_xk", "unicode": "1F1FD-1F1F0", - "digest": "713aa7d228e96f4a06d58d1fb8c2a55296c3e56842f8177ca936f3e09f50da1e" + "digest": "eba1a832e489e1c2734e773e685df5d128271fa5559d23c060e68be067bf6469" }, { "name": "xk", "unicode": "1F1FD-1F1F0", - "digest": "713aa7d228e96f4a06d58d1fb8c2a55296c3e56842f8177ca936f3e09f50da1e" + "digest": "eba1a832e489e1c2734e773e685df5d128271fa5559d23c060e68be067bf6469" }, { "name": "flag_ye", "unicode": "1F1FE-1F1EA", - "digest": "3bb65bae9c913357bcae8b8b5878efc9e194ca308442ab69639c29716b49f078" + "digest": "edfa14266785042b6d5fe0f64fafa630b16a3ee7d010501de7cc8554c959afb0" }, { "name": "ye", "unicode": "1F1FE-1F1EA", - "digest": "3bb65bae9c913357bcae8b8b5878efc9e194ca308442ab69639c29716b49f078" + "digest": "edfa14266785042b6d5fe0f64fafa630b16a3ee7d010501de7cc8554c959afb0" }, { "name": "flag_yt", "unicode": "1F1FE-1F1F9", - "digest": "f86c86f4c194610a3af78971fcf221ad97b9499d08f6d64476e417a2f52a611e" + "digest": "472ebc676b5d31dec2ac5e02ce69014a3dd94609d30a95f39f3a752f49c85e8b" }, { "name": "yt", "unicode": "1F1FE-1F1F9", - "digest": "f86c86f4c194610a3af78971fcf221ad97b9499d08f6d64476e417a2f52a611e" + "digest": "472ebc676b5d31dec2ac5e02ce69014a3dd94609d30a95f39f3a752f49c85e8b" }, { "name": "flag_za", "unicode": "1F1FF-1F1E6", - "digest": "4dd4fa49a01fdcfc7c1c099a7869e0e9acba83a6a3debf6c8505ada4c796b872" + "digest": "dad162942a43392b4cff6929bd5cbf58c382a03dbc0e552f03c07ad2d8ff08ce" }, { "name": "za", "unicode": "1F1FF-1F1E6", - "digest": "4dd4fa49a01fdcfc7c1c099a7869e0e9acba83a6a3debf6c8505ada4c796b872" + "digest": "dad162942a43392b4cff6929bd5cbf58c382a03dbc0e552f03c07ad2d8ff08ce" }, { "name": "flag_zm", "unicode": "1F1FF-1F1F2", - "digest": "ab6790d89875447de3d1c7f4713b102761bc3e9afdd714b818689e175ca03011" + "digest": "1521ecaf1d1fdc8c15f0c96a6b04e6d4050f26f943a826b3d3d661f6ded6d438" }, { "name": "zm", "unicode": "1F1FF-1F1F2", - "digest": "ab6790d89875447de3d1c7f4713b102761bc3e9afdd714b818689e175ca03011" + "digest": "1521ecaf1d1fdc8c15f0c96a6b04e6d4050f26f943a826b3d3d661f6ded6d438" }, { "name": "flag_zw", "unicode": "1F1FF-1F1FC", - "digest": "9d39b934fe922174b2250f2cd1b174a548d2904091d3298f35b7cc59fbceb181" + "digest": "46d05b597c5c77c8e2dc7bd6d8dd62ebca01bc9c9dc9915dafe694ca56402825" }, { "name": "zw", "unicode": "1F1FF-1F1FC", - "digest": "9d39b934fe922174b2250f2cd1b174a548d2904091d3298f35b7cc59fbceb181" + "digest": "46d05b597c5c77c8e2dc7bd6d8dd62ebca01bc9c9dc9915dafe694ca56402825" }, { "name": "flags", "unicode": "1F38F", - "digest": "c3f4a66786e524a5562919afcba9486113091ed205f1342e91d2f6439845ad61" + "digest": "f860aa4df587cf140c3e9735bbd101e9fd5a1bfcea42e420d85ac0a9877fa21d" }, { "name": "flashlight", "unicode": "1F526", - "digest": "5f641b8fd1c7f1dcd43ec3b1ef78d14ef9929d723789c5567aca8b95d3d39803" + "digest": "e929bbe76e0fd2dc5bd6476858a0bbc717fd21467710435d35d80efb38033d73" }, { "name": "fleur-de-lis", "unicode": "269C", - "digest": "d6ddeeea355ed55103b7fc65ac1ee0dbaa79d01e0d136b265363a6b92284c073" + "digest": "ebf49007f367dc05580e9dab942e93e9dda12fa1dc2caa410ac7f8d8cd55d2a3" }, { "name": "flip_phone", @@ -5322,7 +5322,7 @@ { "name": "floppy_disk", "unicode": "1F4BE", - "digest": "e987961ca516032a90942ef6c398836f2da68a5981714bd172acfe7b0e369d0a" + "digest": "4ee0b5bba41b9e301ed125d3ee1c263bef171ca499e6e1b89276b09af2bc03a0" }, { "name": "floppy_white", @@ -5337,22 +5337,22 @@ { "name": "flower_playing_cards", "unicode": "1F3B4", - "digest": "451f361050b96ba9ed8dc5b64c8a90c1316fd9b83fb818152881a54e100eea6c" + "digest": "edba47c2e3051b2c7effd98794ec977174052782edcb491daec82a2b0d853869" }, { "name": "flushed", "unicode": "1F633", - "digest": "39cf51f9dec2a910c66ecd39a7bd616fea09d67e81801e57e84f03ed1e917750" + "digest": "e759d46bab92af5494d78b6c712c06568759afe397e7828ca0a0de1e3eab0165" }, { "name": "fog", "unicode": "1F32B", - "digest": "da6fdb9b682ed9a3368adcd7531f1a29e22755a620e3cca163fc3f33a6a78107" + "digest": "0cbd4733961d30fe0f40f95dd1f37254aebbef26f82dd18ad2000e799eb2898e" }, { "name": "foggy", "unicode": "1F301", - "digest": "b599f3178db289c6e30017f3f0a9d30b00a75417057c7a10c0c9eedac78edbf1" + "digest": "bc3631a4e9e8473b92e842008937add2cd9ffad5b7d772ce759fb5ff6c0e3dca" }, { "name": "folder", @@ -5372,52 +5372,52 @@ { "name": "football", "unicode": "1F3C8", - "digest": "834fe5f431d6aa8ef1186aa79e71f813393535d273483b6af4cc4bdb8380e5b4" + "digest": "ebd790471c3a28d3077818e3b31d915ffe443e06e299bc5cf0dd2534d080634c" }, { "name": "footprints", "unicode": "1F463", - "digest": "60dc938f6769ea21b05b5afcc481d3ddacf1f565e04f33310b271d5422e7ceb9" + "digest": "85bbf2bc0ae8e6259d83a06f513600095d7fcfc44372670f5b2405d380b78811" }, { "name": "fork_and_knife", "unicode": "1F374", - "digest": "7e07c9dc555d172fa2eaa41cefd8d46d9624be0137aff196dd003a8a82610ec3" + "digest": "f228accd36ddccb4ec636207c19d7185191ec79723b780a1bd5c3d00a4b1ef3b" }, { "name": "fork_knife_plate", "unicode": "1F37D", - "digest": "b4081b9edea6cdab5112fdd17535051ba17710953013f5020c7c40f84a1e3247" + "digest": "ec6be99dac8efd3d145807fa60d2b6d8f6d3c02cb95552b55cc0fac39a4db48e" }, { "name": "fork_and_knife_with_plate", "unicode": "1F37D", - "digest": "b4081b9edea6cdab5112fdd17535051ba17710953013f5020c7c40f84a1e3247" + "digest": "ec6be99dac8efd3d145807fa60d2b6d8f6d3c02cb95552b55cc0fac39a4db48e" }, { "name": "fountain", "unicode": "26F2", - "digest": "0acdca5e8f6d745a8d582d96012ec8fc55b9f5447e657ebfd998a4e332d99322" + "digest": "87043f9256e1d4615159307fcfd21bf6ae2aba0bada7de2bd50d7d6f2ab82395" }, { "name": "four", "unicode": "0034-20E3", - "digest": "36bd4ea6e2ae689835a79f8e60466eccd62fce7e91e84ed768cffd87dac628dd" + "digest": "c2c82a966bbb599aae557d930a4fc42604f2081aa45528872f5caf4942ee79d9" }, { "name": "four_leaf_clover", "unicode": "1F340", - "digest": "12ee2343df25bbd9077fdc12314c1edb51c0cdb556af7e22590e8a578ef57f17" + "digest": "ebee16e86bc9be843dfc72ab5372fb462f06be4486b5b25d7d4cac9b2c8b01c8" }, { "name": "frame_photo", "unicode": "1F5BC", - "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349" + "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c" }, { "name": "frame_with_picture", "unicode": "1F5BC", - "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349" + "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c" }, { "name": "frame_tiles", @@ -5442,122 +5442,122 @@ { "name": "free", "unicode": "1F193", - "digest": "c1d9172a656717f78d941303c5da8790c6cd9827838d8f7dc3719afb53bcab80" + "digest": "9973522457158362fc5bdd7da858e6371e28a8403d1ef9e4b6427195c7f72cfa" }, { "name": "fried_shrimp", "unicode": "1F364", - "digest": "c0c19e95f2c38f6cf870920bf3c2d4d69c36ea6e7dc9a5c45c3e8b285269d40a" + "digest": "0792bdc4484852de970c8f43bc3a1a339dc0e48090ec77d6de97cbfcdd17f9e1" }, { "name": "fries", "unicode": "1F35F", - "digest": "0f546534684de29d319cbcbab4162acb321c4f8f3202fe17d69e1894ab7c8195" + "digest": "47915aea67251d358d91a0e4dc3dcc347155336007d6b931a192be72a743b4e9" }, { "name": "frog", "unicode": "1F438", - "digest": "6a417757fa6ee39e7a277cbd53c690ff88af0b1d76728d56f9bc645cb628aeb7" + "digest": "d024b2ce771df64040534fb0906737d18b562bc3578dee62c2f25ec03c7caffd" }, { "name": "frowning", "unicode": "1F626", - "digest": "fb39f5c2aea98054adb02a3a0ac34a2e38d83f32cd590e9d2449e06a9702f2f5" + "digest": "c01af48537b0011d313d8f65103e1401fce4f5c0269c68e0e9806926c59acc44" }, { "name": "anguished", "unicode": "1F626", - "digest": "fb39f5c2aea98054adb02a3a0ac34a2e38d83f32cd590e9d2449e06a9702f2f5" + "digest": "c01af48537b0011d313d8f65103e1401fce4f5c0269c68e0e9806926c59acc44" }, { "name": "frowning2", "unicode": "2639", - "digest": "7bb6c682a6c9f98bf3a5ae986e317fd26d1af497c857500deec2f06b6a3af5da" + "digest": "6568ee393b950c852d440112e86908c456b89fb7780e27778c5fcec168373fbf" }, { "name": "white_frowning_face", "unicode": "2639", - "digest": "7bb6c682a6c9f98bf3a5ae986e317fd26d1af497c857500deec2f06b6a3af5da" + "digest": "6568ee393b950c852d440112e86908c456b89fb7780e27778c5fcec168373fbf" }, { "name": "fuelpump", "unicode": "26FD", - "digest": "9cbb2646c93b255bd3de87dc01aa1193ab96e39a3013975d250472ab8aae61d6" + "digest": "105e736469f19911b8bab4ab6d29f949ded4b061b54e3dd763726577d6453095" }, { "name": "full_moon", "unicode": "1F315", - "digest": "0b4f08ef2089397ead034b444a60e6e9810073454581b52a46b2369e3b9cd5f9" + "digest": "aaa87f4676a5aaa29c1b721a3b582e89db6c1f35a25c52e4b480bd193ef39c43" }, { "name": "full_moon_with_face", "unicode": "1F31D", - "digest": "a371cb9e1f28a7db739dd058234642a2e333dff4b6df9882df85a6d984e4b5e8" + "digest": "05c4b9c339fcdf81ae67027641522baa99c370d87873ff4c8133b8349e627e33" }, { "name": "game_die", "unicode": "1F3B2", - "digest": "6584909a4348c350c04417421b63eace1245087f7d239051b30a0cd37fe929f9" + "digest": "00d19ce8e21dba2cdfeb18709fa8741f3af9d6207f81d5657b68e05e64f105a8" }, { "name": "gear", "unicode": "2699", - "digest": "b0ff5fd007daa366a9eecb7422dbeb8a973e123a04267b88fef96c7453238294" + "digest": "c5ba354c0f7a36dce95477091984e352ecc59af8c9f26a94ad8e296dc042b9de" }, { "name": "gem", "unicode": "1F48E", - "digest": "d75d854f35975e4e291c3b9fcaf8437467f6d7eb27b29e2d7c0f0038fc666fe2" + "digest": "180e66f19d9285e02d0a5e859722c608206826e80323942b9938fc49d44973b1" }, { "name": "gemini", "unicode": "264A", - "digest": "392abe62872736a0bf92979a8c25a814985d0ff0a08dc7ab2a5c058aeda7e685" + "digest": "278239c598d490a110f1f3f52fc3b85259be8e76034b38228ef3f68d7ddd8cdd" }, { "name": "ghost", "unicode": "1F47B", - "digest": "f084b14483476e2d07563840f8c33b46da9c17f791da07fde3acffeb77342947" + "digest": "80d528fcf8ef9198631527547e43a608a4332a799f9e5550b8318dec67c9c4d2" }, { "name": "gift", "unicode": "1F381", - "digest": "c9a2ae6ea05c02e78e9567dcbd971701a2f869eb46c62d85cef23d0834388d8c" + "digest": "4061a84a59f0300473299678c43e533341eb965db09597fffc6e221fd7b77376" }, { "name": "gift_heart", "unicode": "1F49D", - "digest": "e0c5aacf1ce89117d86b148f10a02dc18fe0cd22a75fbf6f0f88f2fad3ca80fe" + "digest": "5420199b515b9b32c964a3c19d87e07461639e3068a939dae26c6436335c0cee" }, { "name": "girl", "unicode": "1F467", - "digest": "0758cbc4cbc7d72d6df8f66fc3a6b2b283c6634b053e59d61c6cac44cf8bffda" + "digest": "8d2d0b72a91e6e44921b71030ffc4c89c0f50f1364787784afe1e7e568cf1bc6" }, { "name": "girl_tone1", "unicode": "1F467-1F3FB", - "digest": "7afdece55cb64e8056e2202de8c17b66ddb616f224ac374ec9a160d06b3138cc" + "digest": "bda12a6b38994a578ee65166bbdd93ea04df4101697b52ed236de8d687df09de" }, { "name": "girl_tone2", "unicode": "1F467-1F3FC", - "digest": "c160aa65fee70ad52930d01246ac9f282ff6abf1d93c5cc5b299fc257ee81db1" + "digest": "de7a0925c30b7181a289f71b1a849c1b7751ee8c104e8f2029bd9c2fe3f91c64" }, { "name": "girl_tone3", "unicode": "1F467-1F3FD", - "digest": "b8a5687cd637855a41b8c7dc686f0e69fda379875408cd269f1b330a805c72f4" + "digest": "e41272816db0e642d003dce7cb262e1593a592251f46729f7830f4515149e1f2" }, { "name": "girl_tone4", "unicode": "1F467-1F3FE", - "digest": "a9cf743936b733634f323790a1abe3a410601b6841484baebea484b392f4e98e" + "digest": "8d6a4513ecbf08408c0ecc5336767777a2216f7a19437faf9e51f65101822469" }, { "name": "girl_tone5", "unicode": "1F467-1F3FF", - "digest": "c902170e67b81eee35eeefb6a5c62c6109cb423dcae88d4e036ddd50b240c072" + "digest": "f55e4b16a41b6f5e3c817a301420360ba4486e4e82e1092a56a3e3cc4069087d" }, { "name": "girls_symbol", @@ -5567,172 +5567,172 @@ { "name": "globe_with_meridians", "unicode": "1F310", - "digest": "945646de3d8f057760fe374494a253d9a6aa8a132309154b0a5bdbffb5b20c3f" + "digest": "725bebeb3c09a9e3701ebe49e672dcfbf2b73575e05f0821263511577b013b75" }, { "name": "goat", "unicode": "1F410", - "digest": "f99cbc6755d119cb5c1dce08cabd20871f98d009bb773da4a146dae60476a235" + "digest": "d07e384d08529ddcaddd2710f2ad913e5665dc15d5f99c28e16dadd245a111e8" }, { "name": "golf", "unicode": "26F3", - "digest": "74a7876d185f8ff6a6533e4db2e1eb787119b2f8d8b07c36d99ec3163fb48485" + "digest": "eed79364754eec97855e3c7b584f347ae139d9ddb4eb7fb66c00867610b8f1c1" }, { "name": "golfer", "unicode": "1F3CC", - "digest": "6458295a5e4a6e4323c32a7f1f7182fb2d3918083839efc380d995860ce360b1" + "digest": "7d7ecc6e226596f646030a4109c2b0001ef0cc690e4863e450bf5d29e7a90344" }, { "name": "grapes", "unicode": "1F347", - "digest": "7f6873d65180ab476f49d207ac2d1f7dbaf6c8b0b561d50b64325e192cf97a86" + "digest": "74d1a09ab411234a84d025a2e717e7ec5791bc02aad29853896d21c0f0283c50" }, { "name": "green_apple", "unicode": "1F34F", - "digest": "effc3fe60f2ab704a034c794bfccfa023b41332f8f16ca44cc8ea41698f03873" + "digest": "457490e9b2b20894f50768262d63f1021717079da104d4847076b3fa779e9a21" }, { "name": "green_book", "unicode": "1F4D7", - "digest": "6652c4d2ccfa4a287a5d45007bd06cadc16d34b0a1ca4b6b13b46f976c8d8319" + "digest": "370f635b200efe5e4a9f17da58bd22500e258e61d17795cef375f19c9a45468f" }, { "name": "green_heart", "unicode": "1F49A", - "digest": "f4bcb660a1d3cf3692238359d8b9de9a725a9af81f166253e487d61b8ccf9d86" + "digest": "f71e30416d9019873f2ed38ef375c48386424ff60b5a07b89b15dc9e0a3970f9" }, { "name": "grey_exclamation", "unicode": "2755", - "digest": "ac8cdab7496d133e7bc9475f2fdb0cf59b3ccba20f2f156c8b693e72b5948078" + "digest": "2fa1d356e12c17cc4025e43afb6c3070385f677102a35223302fda46c47a9b03" }, { "name": "grey_question", "unicode": "2754", - "digest": "c173e1b2a16ab62b0abd7a58deb7a6df709b072d30d001627b92d0123a3a3e4a" + "digest": "e1035bcbf0f66d238ef478ba451f5cf2c51627fbf101ed03bad3b2bf38db8aa2" }, { "name": "grimacing", "unicode": "1F62C", - "digest": "8c54b73f5d2c1c6347e2c0ab01616519e0fb34490daa9c36664d442c6851c57e" + "digest": "2cedad13b8b2a1d4385ca6fa88a251eb7757a4c65dd6d362267864a01247846b" }, { "name": "grin", "unicode": "1F601", - "digest": "916eabdabd8b7ca698e638bbbd14affff97464ec11a3b59c0cb96cd7705600d8" + "digest": "634b2f37e32e57ed6edc7f371993a92e34137dd21ba393de5227cfbbe2422815" }, { "name": "grinning", "unicode": "1F600", - "digest": "3d8665c03f272ca3063e96145989926355a7ac315ed1a032d30fcefa6f0c3923" + "digest": "cef76aa41771db9fd1d6bd9b4233c22c1fb1931494af54cab29e6347ed9b678d" }, { "name": "guardsman", "unicode": "1F482", - "digest": "ebbd29fa138005232d64fca4a8ec015d097fa14e6ded57b35ac257b4570b3c36" + "digest": "17bc7fad6b8c8dbd015bb709380d129f8b8e1e971062d15e6ab0b2e63e500564" }, { "name": "guardsman_tone1", "unicode": "1F482-1F3FB", - "digest": "b6082c8fee5dbc3ce2540f3939d5e344b5366c9f07827345facaba438e7017ff" + "digest": "c531ecb101bdf9ce1db18e1567882e6db927410237100b0a2492a1401860246e" }, { "name": "guardsman_tone2", "unicode": "1F482-1F3FC", - "digest": "2b813afe1c2bbdaf9a47493393a0e6c400a16e453ed25a9a9c0035197927b56e" + "digest": "602168c5204af0f1de8b4aa5863b192ef20c19d263999377aa5eb60f98311732" }, { "name": "guardsman_tone3", "unicode": "1F482-1F3FD", - "digest": "49b2fa1ad0bc50a5ef6d73fb140aa1876506b9ebb9d45782ccb8dbb6818f8dde" + "digest": "d0a85de46dd02c7bd6cb14bff0f22d2db9083d4b171a8806c83363b49f3dd9ef" }, { "name": "guardsman_tone4", "unicode": "1F482-1F3FE", - "digest": "a584e1e3a8ad7be4871a6bdb7996d4f649abeaa77eb5d1cae998058d8b23ca0f" + "digest": "1c9d4d72b6b50bdac8271613b6d2a38340ec2067bc344e8ee2a3c863fd5c23a1" }, { "name": "guardsman_tone5", "unicode": "1F482-1F3FF", - "digest": "e853b67ee13fda99e98f47083529ca80c404df1b19352c78b9c69850eb8f2c76" + "digest": "9899a796d01842e495d716fbe737a16d85724f7d3e23f50807ec2bc70f057318" }, { "name": "guitar", "unicode": "1F3B8", - "digest": "8c041b961649cc5917f56f2fb543f9a5280724647ed2fc67bc94a05eff9da805" + "digest": "a1027ceae4dd3ea270740587c9d373329e5677e375c9e00af6ae3275e0b67500" }, { "name": "gun", "unicode": "1F52B", - "digest": "d7f5aa657cc0ba04d878511820632b89c305a9b4d6c4a4b90ff691dad9906607" + "digest": "fc12b577df2283e7b336f23774f9cfe5b79f1d26ddd28a64a560519b28d94ca5" }, { "name": "haircut", "unicode": "1F487", - "digest": "369dbab1b138c31d3eca04c950fdab4ec9f085272268c241f100d44e7b0f229e" + "digest": "b243a04f5ca889accd45e7abe095ac5caa92274ed95103f5966a36b415fff412" }, { "name": "haircut_tone1", "unicode": "1F487-1F3FB", - "digest": "c56f32d7c1d8a92d22429133f87f31a159818939cfdc570cb48b6d243cc58cf2" + "digest": "a58d0cff1427b80dfd7a9ea5267b4a181e9faaac6a51a0165db522f668b4cf91" }, { "name": "haircut_tone2", "unicode": "1F487-1F3FC", - "digest": "e916e040ffb8e869e930d1256343af2ad2bbaa683f01a11564d0777019944bec" + "digest": "675083ff40001405f8de99268477d50dd8594ff6ca40ddfd442dd42ad76e8216" }, { "name": "haircut_tone3", "unicode": "1F487-1F3FD", - "digest": "f07cdfbea964ac42a9a050f832107ef0f2fa8115b27689f93d1be954de07b7c1" + "digest": "70d7581e49c315a3771dd61a3713229886db32aaaeb3af078a69cc042f809150" }, { "name": "haircut_tone4", "unicode": "1F487-1F3FE", - "digest": "32ec7f5e999f7c43676768c8320ffaa346c713d340a94b948b1f564b345a2d11" + "digest": "ec5e3e909eb3bc375ef9cc0fe0e0f90b33f44f273ada91ccf415bbc43b8ffbfc" }, { "name": "haircut_tone5", "unicode": "1F487-1F3FF", - "digest": "5aad997d09e7975700927906d41a10bae774356ccddbe5197980bde670272262" + "digest": "7c89739ee458546a808fded7f96d9354c47a76883ebb262d5f5abeafd021260e" }, { "name": "hamburger", "unicode": "1F354", - "digest": "24ebae9a69cf283ab198499cb38d0cdcd82bac74c8e8d1e769ad78eb320a4294" + "digest": "48204235238bd89d3a69f319f65135102f3d6b181eec241d4d86b302bbffa9bf" }, { "name": "hammer", "unicode": "1F528", - "digest": "a43a66b0efdc4cd2c84fd0ccc2cb8e9ede1f89c5d62eefa6ae521d3aed9d81b3" + "digest": "d0e7830539d935fcd82820c4e0c1d724f0756dfc83a51171fe0f4b36b69fac42" }, { "name": "hammer_pick", "unicode": "2692", - "digest": "2e4fe33406ca03fbb0df1596d63e903d8ee6bd78ecc3ec38a67dd2cecbc584e2" + "digest": "aa0445f43bca58d17afa7f3577632ca7775f5a28336385b3020b268b15b18142" }, { "name": "hammer_and_pick", "unicode": "2692", - "digest": "2e4fe33406ca03fbb0df1596d63e903d8ee6bd78ecc3ec38a67dd2cecbc584e2" + "digest": "aa0445f43bca58d17afa7f3577632ca7775f5a28336385b3020b268b15b18142" }, { "name": "hamster", "unicode": "1F439", - "digest": "f47da088ff5792532a382b6e3a47d2dd7c5e6fc19abd5ff6c5ba3ce420b4192e" + "digest": "a7e7582e8b1bccd5b7df27ccb05e353a3f0e39bdeb40877732706b9d74a70de1" }, { "name": "hand_splayed", "unicode": "1F590", - "digest": "a43e52f7cdec5e9d51497888b0988d7bbd42846ad7e492b196293fbce576d197" + "digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15" }, { "name": "raised_hand_with_fingers_splayed", "unicode": "1F590", - "digest": "a43e52f7cdec5e9d51497888b0988d7bbd42846ad7e492b196293fbce576d197" + "digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15" }, { "name": "hand_splayed_reverse", @@ -5747,52 +5747,52 @@ { "name": "hand_splayed_tone1", "unicode": "1F590-1F3FB", - "digest": "73cceec7117280d330f8a149979190f0f355dd8d0a92821be89fb70344bb8dfe" + "digest": "c31fb44a982ed8808e1c311ec1b0b9c5afcb47f16bb1fc731dc483adf8f0d049" }, { "name": "raised_hand_with_fingers_splayed_tone1", "unicode": "1F590-1F3FB", - "digest": "73cceec7117280d330f8a149979190f0f355dd8d0a92821be89fb70344bb8dfe" + "digest": "c31fb44a982ed8808e1c311ec1b0b9c5afcb47f16bb1fc731dc483adf8f0d049" }, { "name": "hand_splayed_tone2", "unicode": "1F590-1F3FC", - "digest": "b06fac698128f4c3a7b8ea56e8bc4de088bb5461aa0f9c84553f16b43d347145" + "digest": "56a236881184e9ffad54613fa08a67368c432af738f5254fb1cd87b20368acdf" }, { "name": "raised_hand_with_fingers_splayed_tone2", "unicode": "1F590-1F3FC", - "digest": "b06fac698128f4c3a7b8ea56e8bc4de088bb5461aa0f9c84553f16b43d347145" + "digest": "56a236881184e9ffad54613fa08a67368c432af738f5254fb1cd87b20368acdf" }, { "name": "hand_splayed_tone3", "unicode": "1F590-1F3FD", - "digest": "a94ee9a2f8cdec6d2f7dd6887d1c7b8e064fcad63030c2c7c001742d72b5603e" + "digest": "9242ca97dfd2bbc1947228f6535029afb31f8feb72c14ff4b7f2deea30217425" }, { "name": "raised_hand_with_fingers_splayed_tone3", "unicode": "1F590-1F3FD", - "digest": "a94ee9a2f8cdec6d2f7dd6887d1c7b8e064fcad63030c2c7c001742d72b5603e" + "digest": "9242ca97dfd2bbc1947228f6535029afb31f8feb72c14ff4b7f2deea30217425" }, { "name": "hand_splayed_tone4", "unicode": "1F590-1F3FE", - "digest": "501792b4126c6f32e755accee0fc8b4d1915e1d36c4ceaa40f3bd0066efe76c3" + "digest": "43348d9fd3d43b3c45cebaf663bf181bcad3b6df841a5aeed838180db2cdd481" }, { "name": "raised_hand_with_fingers_splayed_tone4", "unicode": "1F590-1F3FE", - "digest": "501792b4126c6f32e755accee0fc8b4d1915e1d36c4ceaa40f3bd0066efe76c3" + "digest": "43348d9fd3d43b3c45cebaf663bf181bcad3b6df841a5aeed838180db2cdd481" }, { "name": "hand_splayed_tone5", "unicode": "1F590-1F3FF", - "digest": "22ed533d587cf44f286e2d6ad77be20b4b5f133c422af4ca51e9af86a75002d8" + "digest": "4b3a0aba7829772fec09f26d6facc19a2f822d2998015297b18b5cab85190ee2" }, { "name": "raised_hand_with_fingers_splayed_tone5", "unicode": "1F590-1F3FF", - "digest": "22ed533d587cf44f286e2d6ad77be20b4b5f133c422af4ca51e9af86a75002d8" + "digest": "4b3a0aba7829772fec09f26d6facc19a2f822d2998015297b18b5cab85190ee2" }, { "name": "hand_victory", @@ -5807,7 +5807,7 @@ { "name": "handbag", "unicode": "1F45C", - "digest": "f1e2822c67f659b52c76821dd9db001332215a8566fc1846c89b6019c9758038" + "digest": "45410a3eed0c2e3f68748d7649fa9e33a90f4e80d5291206bdd0b40380c6da45" }, { "name": "hard_disk", @@ -5817,67 +5817,67 @@ { "name": "hash", "unicode": "0023-20E3", - "digest": "5bd5c7180485fa71accdec5378bdc196ce0602f594f91e4eadc1e7514d5d0f90" + "digest": "01c8b577953010bff0c20f797c2c96ab5d98d4e6ac179c4895a78f34ea904655" }, { "name": "hatched_chick", "unicode": "1F425", - "digest": "7995c3eb503a8b9662694eba80a9b551216473a31928091e35cd6ebc21cee083" + "digest": "006571b9e9e839ec9fcb1a911b935c8ca71eb8bcdce9775bee6a2a4c7c927277" }, { "name": "hatching_chick", "unicode": "1F423", - "digest": "22905b42fa65dbc9aad8940d2db13691cacc62014f54e0960978ee0002178e1b" + "digest": "fd7f69fa186407f80de59dec5116e318325a5743ee0e8bba1db541f1e57e7f74" }, { "name": "head_bandage", "unicode": "1F915", - "digest": "d690b740ff4f58e89dfc764c6411a4e84cfedffd7694eb5efa839a642dbabd08" + "digest": "d09019a73e203b38cc43729a96163147de88e09eab8adb073888e55366854c72" }, { "name": "face_with_head_bandage", "unicode": "1F915", - "digest": "d690b740ff4f58e89dfc764c6411a4e84cfedffd7694eb5efa839a642dbabd08" + "digest": "d09019a73e203b38cc43729a96163147de88e09eab8adb073888e55366854c72" }, { "name": "headphones", "unicode": "1F3A7", - "digest": "219da138032c01c97a94f02b211049418191a3beb3d159804b9033f5916fd3c8" + "digest": "34f9d5598158d5d6f978a5ea5c5aa9948bb2990625565a3afad7710f864fbe2f" }, { "name": "hear_no_evil", "unicode": "1F649", - "digest": "8120060238eaca645809dd113862a144f10395afcb3837ab60c0f04009b49a2f" + "digest": "53b030b6d6f4ed1a734fa7d48b46f42eb1b2b01653202c1838b742082f08c4bf" }, { "name": "heart", "unicode": "2764", - "digest": "a646a25a36f431cadc7e56afd1a4d1b7cbae5292a25d7783bd31462d0d3d719b" + "digest": "92be652ec3e50c6e7393440b5d52b88a367f98a28dffe12660095ed3253aa6c0" }, { "name": "heart_decoration", "unicode": "1F49F", - "digest": "a83989669347c98cb74065d4f0befedbc37f82c91214e773245cb6810ab359b4" + "digest": "6ec5bbf3aa75c6f43eb3dc05e9204366936e8b6b4219310bacdc2fc45f51e245" }, { "name": "heart_exclamation", "unicode": "2763", - "digest": "9751c89dcf10805f2011949ff3ddcb6bcb13de8c32ae5de9e03955e8a4235df2" + "digest": "5985ea4d82232a2a07052a59db268aed9ac943895d0c82f637595bb5386329a6" }, { "name": "heavy_heart_exclamation_mark_ornament", "unicode": "2763", - "digest": "9751c89dcf10805f2011949ff3ddcb6bcb13de8c32ae5de9e03955e8a4235df2" + "digest": "5985ea4d82232a2a07052a59db268aed9ac943895d0c82f637595bb5386329a6" }, { "name": "heart_eyes", "unicode": "1F60D", - "digest": "335ea73efca4824e623a5a51ccdb494c8b1f5f10b4139b39b250a2a771876b0d" + "digest": "0eff616517a6252ec89d47d9b4ad85589bcf2bdc7f490578934350acb84b2fcc" }, { "name": "heart_eyes_cat", "unicode": "1F63B", - "digest": "9346b85afb80f7b498cc255426ea15a287f81d8fb3c26dab61337635f439d3ce" + "digest": "8a1f28b97d661ca4cff5ee13889ca61b5fa745ccb590e80832b7d7701df101d6" }, { "name": "heart_tip", @@ -5892,257 +5892,257 @@ { "name": "heartbeat", "unicode": "1F493", - "digest": "cd6921ce55c155873220a09416d695c4bcca1556007066d6d185e93d6561e825" + "digest": "c9ec024943439d476df6f5ec3a6b30508365a7af3427671a80de3ef2f4f95ffe" }, { "name": "heartpulse", "unicode": "1F497", - "digest": "f869357b9e678d9671ec38c569fc88efec48006c159b69297277cee795dc4dc9" + "digest": "281d8aebfea37db5b7fe82d9115be167006881fe29ab64a5b09ac92ac27a2309" }, { "name": "hearts", "unicode": "2665", - "digest": "17dc9b2941561f58ca0f04d0754b1eff3490b63b17241580b3d4aa4638fa85e8" + "digest": "271429d12c40be921897005b7bdd08f9518960af1e1e6f56bb0060f1f183651e" }, { "name": "heavy_check_mark", "unicode": "2714", - "digest": "b5fa24f6e0f1dcbd6278e9125154522f2efd79e6dd0836ccb792a1f3aeeff2b2" + "digest": "e347728e1290eb9e7b0742d628e2fd124fc049e0774f8a6ddf8e5286e7318718" }, { "name": "heavy_division_sign", "unicode": "2797", - "digest": "59a6983d788f347c64eecb3df6f7d3b36779d92df6cc811820993ff9e18d77e1" + "digest": "c1e8c40f0788f140b1c5fcb81ed9b5ce1bcfa5988bb8140ed2808e9cb7e0d651" }, { "name": "heavy_dollar_sign", "unicode": "1F4B2", - "digest": "d2e89c54b3fdeda4d1fd4d29454b69dcf750181110894e6e71a40df99c95bfe8" + "digest": "7cdeef38348654b93d566e01a48973281cb404a63d0b75b3bad51032887f3f55" }, { "name": "heavy_minus_sign", "unicode": "2796", - "digest": "dd5ab3722fe49cfdbc5e1fbab5b342dc960de7b412d4fba59d66e06ce3dc3bcd" + "digest": "e5335cc6b22abdce49a6127c34269b65a4a6643ddd3253d9baac425089143e7d" }, { "name": "heavy_multiplication_x", "unicode": "2716", - "digest": "7d77742f91377785675802f40bd8dde9bd1feeb513735760a58ea9bee8a65d44" + "digest": "64bbe9e9716a922e405d2f6d3b6d803863a53fac80ff8cd775899971046cb1ca" }, { "name": "heavy_plus_sign", "unicode": "2795", - "digest": "9aa9dcdbba120a4b485c21f67589609b789c6e3edf08479ff8268fa0db973ad7" + "digest": "d0d8ade2020ceb252205180b85c66e665856e6cb505518d395b9913b0b24b746" }, { "name": "helicopter", "unicode": "1F681", - "digest": "b259ea8d2bdca36766075894da650b1d3ff4c8602259cd0d30cb8214cd585340" + "digest": "4bd6fd13650fbe3a19cfffeffe6c21b1cda74bd6af64c5dc5999185e35444bc3" }, { "name": "helmet_with_cross", "unicode": "26D1", - "digest": "affbe9dd87b87ff9235b4858c59c2a73e9ed30dd5221e5b666b8d7747378a9c4" + "digest": "8286107391d44b9cd7fce5dc83bfdebbcdcf5a8214c46a8990732ec40263ed77" }, { "name": "helmet_with_white_cross", "unicode": "26D1", - "digest": "affbe9dd87b87ff9235b4858c59c2a73e9ed30dd5221e5b666b8d7747378a9c4" + "digest": "8286107391d44b9cd7fce5dc83bfdebbcdcf5a8214c46a8990732ec40263ed77" }, { "name": "herb", "unicode": "1F33F", - "digest": "3c452106b1966f643751bf161fa7d1762a33e6fff381b2109bb53b55c4fdd129" + "digest": "9fe8ed65515ede59d0926dcf98f14e2498785e1965610aa0dd56eca9b4bedad9" }, { "name": "hibiscus", "unicode": "1F33A", - "digest": "268963a1f3cdad9050d9ae31c558e010f33812e3b09bbf9088ba876c033d8b2f" + "digest": "c442e8eacbd8727bd154bd39692a9a2a03ea2f674b9670ad8361f78a038afe49" }, { "name": "high_brightness", "unicode": "1F506", - "digest": "d607f6269d95dd16c2a7932e49ac09e44f4c19e0a34f6c0f21ecb945a2316361" + "digest": "35ced42426dcfd5214c2c6c577dce84bb708156433945e6b6adaff7ea530cc57" }, { "name": "high_heel", "unicode": "1F460", - "digest": "5c320d5954bf4f4dacacddd562c1598ab101731077a6656ac5d2bfd41405483e" + "digest": "1e7c7aba50eb1d02cf1d9aa372caca741a6005cf47f68dfa75b7310c3cb18f05" }, { "name": "hockey", "unicode": "1F3D2", - "digest": "008904c1b8db139215492a6d96c09f2c3eeda769f858a9bbae13f8c54d439d0e" + "digest": "2d00fb17baa617e799db8e9b1771cc365bb4545c7633df0123e66e1a6e2ed25d" }, { "name": "hole", "unicode": "1F573", - "digest": "36bbafa5e89b1410ec74919aaf60b09ac3525a421cb5b475b9bb2f20357db8de" + "digest": "8b5539f6f24f09d5d68ffd56be5aa2a8a2f753a8dfbf64892fb02c8f2703e920" }, { "name": "homes", "unicode": "1F3D8", - "digest": "9980d6dd6cbd23b820747ecac4cb10974dd24b0c94b4acfe21fa87793ad065c9" + "digest": "cd512f2b4ce747325607d47da48e083dbfe38a44b85b2522bc372bd105afd25f" }, { "name": "house_buildings", "unicode": "1F3D8", - "digest": "9980d6dd6cbd23b820747ecac4cb10974dd24b0c94b4acfe21fa87793ad065c9" + "digest": "cd512f2b4ce747325607d47da48e083dbfe38a44b85b2522bc372bd105afd25f" }, { "name": "honey_pot", "unicode": "1F36F", - "digest": "94cb1624491076b5cb145e7a309f91a7be3d4c0bed712af6a51d641eb73edee7" + "digest": "f6eec8c32fbd1b461446dc6c5d5031c43e6ee9685dc9b1ea1b839114e48c4eee" }, { "name": "horse", "unicode": "1F434", - "digest": "624ad9dc9ed7af3f6e1a2f9d4ed483702ae64ed5fbcf5e9918af6bfef24e76f9" + "digest": "e377649a9549835770a2a721a92570f699255f88efa646029638eb8ec5f10e3d" }, { "name": "horse_racing", "unicode": "1F3C7", - "digest": "c2702b7225e9839a789dda7c43f0cc86dced2b4d5d3787116106396633362de6" + "digest": "3b98e94e9c028ad85b9a750cc61db5ee3ac23cf5ad9243ea3e996b1f772bad54" }, { "name": "horse_racing_tone1", "unicode": "1F3C7-1F3FB", - "digest": "a7ed284f9d5cd8a4fe4a09cb91c3f99e5db99c7e31c5f525c14de97b06857d92" + "digest": "382d8e4502ed34fc1bbf1779ce483bc2e22b83f89c91746c11a5d7aea656d446" }, { "name": "horse_racing_tone2", "unicode": "1F3C7-1F3FC", - "digest": "20b4d61b21ee6ba860b029f0ad0e38f5ecb6dd2c774f7b7801fba07ed33f96be" + "digest": "198df9973b492ea63e5cfc210dd9591750ccce04a6380adc1dc5b4cb0462a8cd" }, { "name": "horse_racing_tone3", "unicode": "1F3C7-1F3FD", - "digest": "dd65f7bb96ee44507d26e524202d567d2d7679d571245299a2a84f68bd5def4c" + "digest": "a67f95fc92c366750ebad3c4db92982893d67a5ed78163c8cc809ac40d2ab9a3" }, { "name": "horse_racing_tone4", "unicode": "1F3C7-1F3FE", - "digest": "36afaad218a4c820b19c7c9bbbc187119d47b41273d8f48ab14cc3e32dd7c21f" + "digest": "986b1706c4a3395b58a8ae3b7609ffdd4424dfefcbf26c88c8085f4f6379734e" }, { "name": "horse_racing_tone5", "unicode": "1F3C7-1F3FF", - "digest": "2e0efd501a4471428533ce7909972a49ff045369261c27e4abb97ee2aede2f47" + "digest": "66656b5e3d0f43f16f983f9db6214b07aac73b143eeff6475782f98aa5b9ba53" }, { "name": "hospital", "unicode": "1F3E5", - "digest": "df5c774fa36b2601e6960a7b81cdfac71c1d2d71f04dea88068d1c9043e313bb" + "digest": "034573e76df444f5b0eb7aff3a4103e4b49a1813869155ab3ae29a6fc0c6c8a2" }, { "name": "hot_pepper", "unicode": "1F336", - "digest": "62e4dade3c793f6d83530bd1f60f3e3e26c1e10a41786c3a15f5aec0ff2b8e76" + "digest": "0b05777d42698196a10db17d04030175b1dfa772d06288f71d666d5f8d3fddbc" }, { "name": "hotdog", "unicode": "1F32D", - "digest": "58b829e26b5c4642942898d9c7873cb08e048fd7deaacba8292899d5d895cb2b" + "digest": "7a25bbd1a7531fd34a22c654c0931d9e74bea2bbe7baa9f9cbd88f43baa79fb5" }, { "name": "hot_dog", "unicode": "1F32D", - "digest": "58b829e26b5c4642942898d9c7873cb08e048fd7deaacba8292899d5d895cb2b" + "digest": "7a25bbd1a7531fd34a22c654c0931d9e74bea2bbe7baa9f9cbd88f43baa79fb5" }, { "name": "hotel", "unicode": "1F3E8", - "digest": "428120a35b38a217901e10d704751eb8fdbc9f805e6eccd8aab070f4311b2085" + "digest": "2d78e0ad4cfb0caad778c7de49fefd6e8356afe902a43e3f1c40bceb6b0be422" }, { "name": "hotsprings", "unicode": "2668", - "digest": "df4f946218445f97a6f28c6abe4c1d1dac56ff97a8cd81df59f1b3c320e0092f" + "digest": "4c10c3a974b44693e8cbe91365c8b8d7f14f62db234cc516b6e54c08a6bacaed" }, { "name": "hourglass", "unicode": "231B", - "digest": "07aece9413e6898717b4f0757e073d7a593f3e8044c56855127033b796207ccb" + "digest": "f0bae8392aaf6f75a83f5d8914936b8650665b24ba1b232fa546b71545dd9acd" }, { "name": "hourglass_flowing_sand", "unicode": "23F3", - "digest": "92dbc68e9d16fb9f706236367e1882f0d2b6817b83ca490820a000021f2c6483" + "digest": "2d077729f40fc04007a933e97356bd511cbd8be76b8c55962ca3fa0d8b828e23" }, { "name": "house", "unicode": "1F3E0", - "digest": "a6221fc84a9b0e11ae71bfa1e0020982b55ff8c89a374a6d755dba710b4e058c" + "digest": "b4ac25979fbe161ada0d2a75769aa7552d2371d37d78cddba4ffdc7f076d3279" }, { "name": "house_abandoned", "unicode": "1F3DA", - "digest": "e404631e3a296bdeae3de7510da8934c32327bc0fa0f7ae4e676b61932165668" + "digest": "6e1a58533fbfe88a0eb03668c9f17c5c654a6cc7734ed798d4a885400f823610" }, { "name": "derelict_house_building", "unicode": "1F3DA", - "digest": "e404631e3a296bdeae3de7510da8934c32327bc0fa0f7ae4e676b61932165668" + "digest": "6e1a58533fbfe88a0eb03668c9f17c5c654a6cc7734ed798d4a885400f823610" }, { "name": "house_with_garden", "unicode": "1F3E1", - "digest": "22d0d911da96b7ae3bf6692d3cf3590afbca959fc99c13e7a088f7194f43a35d" + "digest": "817463f23ec0a849393ba75c333e822b4d253cd4db998c127e90d1b924f35d20" }, { "name": "hugging", "unicode": "1F917", - "digest": "68ed6c4e0eae9071cf67770a39e07a2290b4f7763170f765b3cd3ac67ae43240" + "digest": "69810a98b1247e1f1e496aa757e428189ef5cc086764fabd8189cf1eef82234f" }, { "name": "hugging_face", "unicode": "1F917", - "digest": "68ed6c4e0eae9071cf67770a39e07a2290b4f7763170f765b3cd3ac67ae43240" + "digest": "69810a98b1247e1f1e496aa757e428189ef5cc086764fabd8189cf1eef82234f" }, { "name": "hushed", "unicode": "1F62F", - "digest": "69faa8e0b170ee8cf41977ca4a5154406360ed9699d5c62ecdaa01f50e8e4276" + "digest": "22586107f7399eff64538a52929dade152633aa268fc5ec4e6fe1c0e00a7bd89" }, { "name": "ice_cream", "unicode": "1F368", - "digest": "d48ec98a8789148b96c30f19595201a0f85ed899659d97d1d3596091162909ff" + "digest": "d1a8e685f2ecf83dead28733859e369d6ce120a2669cdab97dc4423547d472ac" }, { "name": "ice_skate", "unicode": "26F8", - "digest": "6fb044d9fbe62605f6728062c35c345ddd3ae4cc51203c925b0e69f1b3ef2dbf" + "digest": "41ef65c143bc068868fa64080ffd447d91aa3fe2a39e69ecaa97022820af4dcd" }, { "name": "icecream", "unicode": "1F366", - "digest": "abd5774157575dd304dc1a393244757853972c863861a654ca29b2d528e48b28" + "digest": "22cfe17b80cbd2a0377ee90da45bd40d33533c914b2639d363fbb1f00714e194" }, { "name": "id", "unicode": "1F194", - "digest": "860ffb36d37d84e2c1cf0ab991b95c1cf73e458bef0e4d85bb0c1e26115cb2d1" + "digest": "bcf0922e083821d3be7951893084ea0d72a0110ef0b20d11dfec24dd70633893" }, { "name": "ideograph_advantage", "unicode": "1F250", - "digest": "37892a5642cd49ef7828646f36f48b5a83dc02437624c05da428579256118030" + "digest": "0b6bf59f63fda1afa92d652814a778a056c3f4abdd9cf3f6796068bd71783051" }, { "name": "imp", "unicode": "1F47F", - "digest": "f8c93d03bd9f1d5ef86738541e11695d6811bf6fef06759eba98321b6d038814" + "digest": "52598cf2441988f875ccb4e479637baefc679e3ca64e9a6400e56488b0fde811" }, { "name": "inbox_tray", "unicode": "1F4E5", - "digest": "066a2d75633eb50329496f6866b5b0645c2e48135a03118f1bf53244f8529043" + "digest": "d5d9497022b5318fcfbfdfcd56df9c65dd8f4a4cb5e6283ca260836df57da301" }, { "name": "incoming_envelope", "unicode": "1F4E8", - "digest": "ef6e5c5aa679d174181dae77113717f26e295778dde1e2c3bdf1d64de8a4af8c" + "digest": "310b7bdcca93452fe10c72c03d0aafa12b98e5d3408896d275d06d3693812c7a" }, { "name": "info", @@ -6157,97 +6157,97 @@ { "name": "information_desk_person", "unicode": "1F481", - "digest": "acae6d272e348aee87dd60360f16ac58cea7cb4e1ea962cc1655005c7f4aed27" + "digest": "9f12a4a58a650e8e1d3836ef857003c3ccd42ad4203a2479eb95100bf6559064" }, { "name": "information_desk_person_tone1", "unicode": "1F481-1F3FB", - "digest": "709ebb0481ca981d76ece2d4fc68db693ddf18b9c1aaa0b6ac5d3c42e71bf07f" + "digest": "6674f2e059eff7cfd7fd6abc800da37c4f1087feb4ff26c9e4e31aa29fdf9921" }, { "name": "information_desk_person_tone2", "unicode": "1F481-1F3FC", - "digest": "d5bc3563bc721d66b73850db93ac827be3715e7ca6420dc0051396ffe26bef47" + "digest": "9983412ecd130b7e9cfb078167016c06fd043b6f9f3c26d21733ca3f059fd109" }, { "name": "information_desk_person_tone3", "unicode": "1F481-1F3FD", - "digest": "af67fd4ef2fc402bec2d446b2e8ff5e9f636b5a9bbb6639587cdb88bd780d265" + "digest": "d8907bf47af5722127afca8fc0da587eab33044a6c60a94890983deb8d6f7a66" }, { "name": "information_desk_person_tone4", "unicode": "1F481-1F3FE", - "digest": "fd3174d1adfe13e8c0d6b6ae9c3a26ea35bb40f98f0728f91d1798809a74933b" + "digest": "3be086d4edfe9ca8e4a364b4e8d09b81b5b594b5eeb9ffdf6370179fb3118658" }, { "name": "information_desk_person_tone5", "unicode": "1F481-1F3FF", - "digest": "4b773c443830a02de8b4d6471077b5d1387b560b537cabba7cdc667110cbde69" + "digest": "2fde4e98dd11c5c29c89cad7cbb7bd2d5077dfad07913b20e01955b2d0dfad40" }, { "name": "information_source", "unicode": "2139", - "digest": "50cd8bf46d20b7c18d5f00a69fc79452aa32934245ba8d0929e51632d73876bd" + "digest": "b6bf3cce86d42c2e3c46470baab4af01e900b8ae337b605c3da07c3eba671269" }, { "name": "innocent", "unicode": "1F607", - "digest": "a3510fd51c17093ebe2371cfde7611aa44aed2d120a0e5500cfaae0f1d3486a4" + "digest": "20f8d856bc3e46f4b1173cea05d4577e1c61f06b2daba46e57db90f4066bb428" }, { "name": "interrobang", "unicode": "2049", - "digest": "1f843ff672486154f9f3df549bb1b528a5eac8d15264f447649ba57f45ee4d00" + "digest": "92a2d5b4c0bd6714e402f6f12fe19774cb41d081b5e9c23c415ce794224d8117" }, { "name": "iphone", "unicode": "1F4F1", - "digest": "be6f96c02ddae557f700fd20fe7b3f94c9e1c928acb82b2b8b214d231273fece" + "digest": "1ebc54215713cd4bf1c1e50770999f2512bb4fea29e37d0bb3a8aa2460ff875d" }, { "name": "island", "unicode": "1F3DD", - "digest": "17f02b309b62ed9542b1d8943168302846040e420f413e56d799bb5fba7064fa" + "digest": "7f9eb5c0cd865762f7a0f187e09c1be442de7010e7c2e113d56aae998597c90d" }, { "name": "desert_island", "unicode": "1F3DD", - "digest": "17f02b309b62ed9542b1d8943168302846040e420f413e56d799bb5fba7064fa" + "digest": "7f9eb5c0cd865762f7a0f187e09c1be442de7010e7c2e113d56aae998597c90d" }, { "name": "izakaya_lantern", "unicode": "1F3EE", - "digest": "ddb20f475aa119c3a64a55dff40f7a9dbc3a14f7ffc6cfbac89210c652f10d02" + "digest": "fbdc290e666d43d0776a73b955c26df4518692b35e72742e073705fc4ca2ae88" }, { "name": "jack_o_lantern", "unicode": "1F383", - "digest": "62a701ac472619bcb3859e0d9a61b98c7f5c32150d2d04ca8c3e8fc3bec4dbd5" + "digest": "78d666c2e80f64bfb6796f53e5ba4960a83ec36192110e8661031bee2b5e370a" }, { "name": "japan", "unicode": "1F5FE", - "digest": "2535300fff2b2e4b75fc73c187be6c0ea4bc4753e443db498ea55e268e627ab7" + "digest": "e7d9d6ebf9047fdd3c52e074ba259659c6d8e51a6abae3cdb8d6cf6dbf9a93fe" }, { "name": "japanese_castle", "unicode": "1F3EF", - "digest": "70645aa05599e23a9ac4327e4a2e78bffe7ea06c38ec1935c15ae420619c5c1c" + "digest": "938ae132c403330288223b88d28c19a47224d4f254fbc2366ecef73d9633112c" }, { "name": "japanese_goblin", "unicode": "1F47A", - "digest": "59b6901dc6eedc6509c25b4eef6702bf461ded06c5ff12fe2a02a5b3301577c0" + "digest": "63d4bcf58b9d0c29612994432aad2ae35819fdd2890674e60a2f1d51601b742e" }, { "name": "japanese_ogre", "unicode": "1F479", - "digest": "dab7e68cd4cbf99c13d64792c7104c4f0a846bc63aa12950fa8fab028dca301d" + "digest": "434ceedd102e7dcbc07e086811673dd63659ddf8c3ec4d029a3d759a0abfcbdb" }, { "name": "jeans", "unicode": "1F456", - "digest": "ddd032ac77cdfe49152a0e0a0eaaaea9f183590fb1f493ec30e9e39f679e3914" + "digest": "f986ad32e419cca81c995f8371f0189d1490172a97ebbeac60054a1af08949c5" }, { "name": "jet_up", @@ -6262,37 +6262,37 @@ { "name": "joy", "unicode": "1F602", - "digest": "f90cfbcb14f906f8d786b61f022c978f381fc99ca422805f605631314e101805" + "digest": "75d7a05043523d290c46d3b313b19ed3c95271f1110bcf234cf13d4273625b08" }, { "name": "joy_cat", "unicode": "1F639", - "digest": "6ca24a94490de66d1ca2cbc080bcd805f54ca295051d8e6588cae3fe6658c80a" + "digest": "a65c999604147e5e20170fcb14f80a1ff0a633f991492e1f790b2ad4caec7b7e" }, { "name": "joystick", "unicode": "1F579", - "digest": "ec172df88ef8e8a5512d6d906c13296875b7057ed0cca79f4ac8cddd9e1de34b" + "digest": "671ee588f397a96f27056a67e6a06d6e8d22c2109ec57b2859badb5fec9cf8dd" }, { "name": "kaaba", "unicode": "1F54B", - "digest": "30f1a27a148399bbb811586eff795eff858701c42055c23e4d5bef7ae77f5f32" + "digest": "a4618782f9583f077bd383965f1c91b9985a949bb7b6cec7af22914e7f5e9ab6" }, { "name": "key", "unicode": "1F511", - "digest": "c68ed648350d3976c8d27a709020c8873ecf553929e66453acff96231684a1a2" + "digest": "66719fa77a50a0827c8d47237e2704c03e38186e6fef80627a765473b2294c2e" }, { "name": "key2", "unicode": "1F5DD", - "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d" + "digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e" }, { "name": "old_key", "unicode": "1F5DD", - "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d" + "digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e" }, { "name": "keyboard", @@ -6327,132 +6327,132 @@ { "name": "keycap_ten", "unicode": "1F51F", - "digest": "7593aa7ffe7192a2e35c6ccec76522f6243777783c9152c7c03419835ea58c03" + "digest": "c7c9491021740d2c17edddb856f79579b0b943d8dc85a2f48dbaac84f35b8a40" }, { "name": "kimono", "unicode": "1F458", - "digest": "e92bea044fe013f1993c2229d86e9cca9d43f14aab00564ce6ff559bdc5ce93a" + "digest": "637182590e256c8fb74ce4c0565f5180c07f06e3bdebf30138ed3259b209c27f" }, { "name": "kiss", "unicode": "1F48B", - "digest": "c060eb09af2a0d0f77d307b995c15719b0e59c9162a490b8a553fac9b779c8f0" + "digest": "62f9b9ffcb01558cd5bb829344a1d1d399511663ff5235405c1f786c9416a94d" }, { "name": "kiss_mm", "unicode": "1F468-2764-1F48B-1F468", - "digest": "381364ad988ec07cc3708fd60f71838092224009088fff587069b4e8ab01ee63" + "digest": "6b0ae32ecb7ec0f0f43dc7a1350711185cce114c52752395f364ddbfb4f1fff4" }, { "name": "couplekiss_mm", "unicode": "1F468-2764-1F48B-1F468", - "digest": "381364ad988ec07cc3708fd60f71838092224009088fff587069b4e8ab01ee63" + "digest": "6b0ae32ecb7ec0f0f43dc7a1350711185cce114c52752395f364ddbfb4f1fff4" }, { "name": "kiss_ww", "unicode": "1F469-2764-1F48B-1F469", - "digest": "7705ca707b73f44c856ea324bdfe30ed05244c8d192d1111f6e1d62ab3f2f8a5" + "digest": "6de420cf752e706b1b7e9522b1b9be62eda069cb028c8fd587caf39f6a142e6a" }, { "name": "couplekiss_ww", "unicode": "1F469-2764-1F48B-1F469", - "digest": "7705ca707b73f44c856ea324bdfe30ed05244c8d192d1111f6e1d62ab3f2f8a5" + "digest": "6de420cf752e706b1b7e9522b1b9be62eda069cb028c8fd587caf39f6a142e6a" }, { "name": "kissing", "unicode": "1F617", - "digest": "3142617e8b9488689bd9efc67c0e4cc71a1870df8ffc308f949eedc5c3684051" + "digest": "b4a505f9e3d7fbd0ac60111f0e678cf425a5fd1abc65a3e9db59ae4abcfb8e85" }, { "name": "kissing_cat", "unicode": "1F63D", - "digest": "ed26cee8c438ba41365b55c48457cdad3e8d43bf90db3128ac5b277718b82ed3" + "digest": "a00431bf10601db4998e78433279167e52cbd36aed885399482529d5cdab8636" }, { "name": "kissing_closed_eyes", "unicode": "1F61A", - "digest": "22d3369d21b4c2cb4c0c2cab9551cd848dd4f9adecfa64977d3f1a80fc0c8b53" + "digest": "ae474db7daf80fe0b82ae1f2a11672cfcd9f9126e100f6e6d4b8a0d135dce39d" }, { "name": "kissing_heart", "unicode": "1F618", - "digest": "1f089b07447bdcc1baada6a2a9607d4ef4f2de9a6093fcab47a553a64b9acb76" + "digest": "bce372573bd3b347b555c1cd22087e03e650df73c8e0284ab668bf6633251632" }, { "name": "kissing_smiling_eyes", "unicode": "1F619", - "digest": "e37d282861669adfa3953b9af833acfab7d55e787621d4318d77de7e3529d5c5" + "digest": "f0f8636cb1a02b93cc72ce1b194b890fca823d91e35926b889be3ecfae79207f" }, { "name": "knife", "unicode": "1F52A", - "digest": "3fef068a6ada61630dc868e47d25e0e0550b44bc7cf530afe88ca63dc7ab2a39" + "digest": "e6189e4843c6e80875b4952fcddb0c858f7c6039b9214bbec6a261a1358425df" }, { "name": "koala", "unicode": "1F428", - "digest": "fe020ab9048f3c2a881474f8b1335db6bfaf37d115ff9b2d264f668d136122dd" + "digest": "c58f7e0abae42c2218a85efed0e04151df67187815bebca7f3db6f435e0dab4d" }, { "name": "koko", "unicode": "1F201", - "digest": "734a5cb296826a598e02be3f4ec22f318633ede2ce274914586256421e2df97b" + "digest": "5f45eb49bbf298e1fadedfe6cccc297850fcaaa4535e4cc911d48d979af55807" }, { "name": "label", "unicode": "1F3F7", - "digest": "9fe8195c3efab4d905b1cfcba0ae58cda12496030b0908de8076ff5e6777742e" + "digest": "9550ed50cedbc56eb1bd22a8a0809d837048a33d6e2e6e7d65c50d95fa05a85d" }, { "name": "large_blue_circle", "unicode": "1F535", - "digest": "ba4d0f84a9c2be9a65b25c8cfa78f30d4856d021b1853154dd1d2fd0c5bcfb6a" + "digest": "0df3fb3b09a6269459a3d9a1fe78db572190a948680844cfe758f53b6a482ff4" }, { "name": "large_blue_diamond", "unicode": "1F537", - "digest": "d5aa5e315126859c10c83507be6b9e11cbf423f7a27145de089468cff9b94a94" + "digest": "7f646b4e9de2788ed09e45f72cb512c269dda4989029b39bf9a2556659321651" }, { "name": "large_orange_diamond", "unicode": "1F536", - "digest": "108600badd0ef267842325c0fbf326cb3504306332c64f6f5694de2b54c9438a" + "digest": "80ae005ef9d79190c777f00de0993f8b3cb783f7051d76e971640c8c0827c338" }, { "name": "last_quarter_moon", "unicode": "1F317", - "digest": "68315b85bc1cb17bb82629bd1a6024a5124f3641b9878a732a8aad016c587546" + "digest": "3d1f276607c685d50f4b70d00a57750a57ad9ad84256dafd2dc8eef8c72300c3" }, { "name": "last_quarter_moon_with_face", "unicode": "1F31C", - "digest": "146a419109b7f662bf87cf9de299e47d025a8758c8970b7dabf3483e1956b559" + "digest": "d516825ba52dc67f5a01433fb9df2aa77742d38efde4225983ebc4882cbdfe5d" }, { "name": "laughing", "unicode": "1F606", - "digest": "f22d3be77f1daf058d04c3cbc1fd7f76b4dc069d2d300b45e63e768b08d269c5" + "digest": "e9ea994b39650740c4961f070ed492d86b3acf6e6a830a6dadaa3a6872e81b81" }, { "name": "satisfied", "unicode": "1F606", - "digest": "f22d3be77f1daf058d04c3cbc1fd7f76b4dc069d2d300b45e63e768b08d269c5" + "digest": "e9ea994b39650740c4961f070ed492d86b3acf6e6a830a6dadaa3a6872e81b81" }, { "name": "leaves", "unicode": "1F343", - "digest": "f65e2db125564eb04fc427a49fff175d6e2dae847bd12314d5e6a131610d5ccd" + "digest": "56a7a0e767a6f214d340d1b5989efd99fec52c6aa306ec5c3328e32234a1631b" }, { "name": "ledger", "unicode": "1F4D2", - "digest": "62df1772cec10c035ae0646e6cca4ba7d75b10636a520d091c5b42c2dc36b742" + "digest": "e58cb714353e96a2891a5d97910ff79660e637af909b81c49c919d3735db55b4" }, { "name": "left_luggage", "unicode": "1F6C5", - "digest": "62292758715115e55ab6239805b7f99b7b35bdfa8d40da07fe391424f1f083d8" + "digest": "6625077767a51163ea20cbc299f3c13fd5ccf1b5ce365ee702ef1fef6be3dadf" }, { "name": "left_receiver", @@ -6467,107 +6467,107 @@ { "name": "left_right_arrow", "unicode": "2194", - "digest": "28a6945972451b1f4dadec5c55310b8868ffd9f3b0a07803287bc4e07a56e7d4" + "digest": "560fcf1b794eb0d5269c73b3f8da57540cbb8a6f1a9af7a9d10b202252247e34" }, { "name": "leftwards_arrow_with_hook", "unicode": "21A9", - "digest": "d672afc39fd50f78d7370be243173fe76ba50292f0c401305b562898939a8b7f" + "digest": "504714c5559b1bd35aa469be83069a923d1a25f364cac08c10df0195749e7b26" }, { "name": "lemon", "unicode": "1F34B", - "digest": "e0e293a8b8c1b3c87534f5e05cf006671eb3c6d52b4d17d40f2e23bce215a8be" + "digest": "ccca25bb6ac47770dba3aaf75144128f9a73299061969b25a35ad1733dcde5fe" }, { "name": "leo", "unicode": "264C", - "digest": "b0fd4e5f4637de530b62323521c6edcd80312d67ea4043eedd959acb6763474a" + "digest": "f2ed930e279699962f189e0cac519cc29d339b3e82debfdc90c5b0935a7543bb" }, { "name": "leopard", "unicode": "1F406", - "digest": "ede891be8484a17e6277431c64ec1bfd6b742544a41947ebc85005bc2d558bb1" + "digest": "d4a8964b6f2cdf6ddf074d0f1f2f65783a1a43eb4af426905fad0e60899939c7" }, { "name": "level_slider", "unicode": "1F39A", - "digest": "49777cf160d9130d723e3bfef765c3de54033e6b059000fb0e22fb559b5ed190" + "digest": "48842324f54d971ebf548a89a82ac7f29e235702081c91b477b1a92d427290e7" }, { "name": "levitate", "unicode": "1F574", - "digest": "3e4e9a5ac6a8dbd7909c58a9d915f16f1a0fc59cc019714ae5935f18e4704044" + "digest": "453c24bf2544ed3ef3c710a7fabbd5fdace4dc65cddd377274d30d921523b50b" }, { "name": "man_in_business_suit_levitating", "unicode": "1F574", - "digest": "3e4e9a5ac6a8dbd7909c58a9d915f16f1a0fc59cc019714ae5935f18e4704044" + "digest": "453c24bf2544ed3ef3c710a7fabbd5fdace4dc65cddd377274d30d921523b50b" }, { "name": "libra", "unicode": "264E", - "digest": "ec8e2e7a735abc9f2bddb115fc0e09f4bdc7a164679e2b57d127f58eee1155c2" + "digest": "e330ba05bb449db074bc23d1514246ca5e249110f44ddb5804e5510eef6deac1" }, { "name": "lifter", "unicode": "1F3CB", - "digest": "f64db037fd21e5918e5de35d6a561ef4b44668e307ed351338de00fcf3e771e3" + "digest": "d6c94a32eb863d14a2a01add8ab95040f42a55d9e3f90641a0fe143d58127558" }, { "name": "weight_lifter", "unicode": "1F3CB", - "digest": "f64db037fd21e5918e5de35d6a561ef4b44668e307ed351338de00fcf3e771e3" + "digest": "d6c94a32eb863d14a2a01add8ab95040f42a55d9e3f90641a0fe143d58127558" }, { "name": "lifter_tone1", "unicode": "1F3CB-1F3FB", - "digest": "f9e0d161b12c4908ac3409b11c1a77ee38f33ba018f12416545876214bfb7c01" + "digest": "870acf2f554fce360b58d3e98b4c0558d7ec7775587776c0f9d40c6fb1bdacf9" }, { "name": "weight_lifter_tone1", "unicode": "1F3CB-1F3FB", - "digest": "f9e0d161b12c4908ac3409b11c1a77ee38f33ba018f12416545876214bfb7c01" + "digest": "870acf2f554fce360b58d3e98b4c0558d7ec7775587776c0f9d40c6fb1bdacf9" }, { "name": "lifter_tone2", "unicode": "1F3CB-1F3FC", - "digest": "631eb6ed5bd147dc6f1f8b94149abe44d62a0f78e7809e37a4bfe127c40ed98f" + "digest": "1a7ece8512e42241cdd95c85ccc509bc0ff9c7c6ffaff2be343c77f417a27576" }, { "name": "weight_lifter_tone2", "unicode": "1F3CB-1F3FC", - "digest": "631eb6ed5bd147dc6f1f8b94149abe44d62a0f78e7809e37a4bfe127c40ed98f" + "digest": "1a7ece8512e42241cdd95c85ccc509bc0ff9c7c6ffaff2be343c77f417a27576" }, { "name": "lifter_tone3", "unicode": "1F3CB-1F3FD", - "digest": "406b5707a47d9066f016acf0b64fa695e3505acc2453758a0428de21efd7eb6d" + "digest": "4bc633ee82a0fb59feba379fb6901a489e4ac849d758f9c8e7a1a0a26eaa380c" }, { "name": "weight_lifter_tone3", "unicode": "1F3CB-1F3FD", - "digest": "406b5707a47d9066f016acf0b64fa695e3505acc2453758a0428de21efd7eb6d" + "digest": "4bc633ee82a0fb59feba379fb6901a489e4ac849d758f9c8e7a1a0a26eaa380c" }, { "name": "lifter_tone4", "unicode": "1F3CB-1F3FE", - "digest": "d917164ed8c4bb1ffcc887ca256ec329e7fa1b9516eaf8c159f8b43fdb071ed6" + "digest": "d086fe5577b5ba80676f2224d886f8ebe4588314f429f12a34c52c971ed71b5c" }, { "name": "weight_lifter_tone4", "unicode": "1F3CB-1F3FE", - "digest": "d917164ed8c4bb1ffcc887ca256ec329e7fa1b9516eaf8c159f8b43fdb071ed6" + "digest": "d086fe5577b5ba80676f2224d886f8ebe4588314f429f12a34c52c971ed71b5c" }, { "name": "lifter_tone5", "unicode": "1F3CB-1F3FF", - "digest": "f79ea93e8a40b3c895b693bf49eb4ce6e7b3f4413595e5881ea44839fd7fe8e5" + "digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55" }, { "name": "weight_lifter_tone5", "unicode": "1F3CB-1F3FF", - "digest": "f79ea93e8a40b3c895b693bf49eb4ce6e7b3f4413595e5881ea44839fd7fe8e5" + "digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55" }, { "name": "light_check_mark", @@ -6582,27 +6582,27 @@ { "name": "light_rail", "unicode": "1F688", - "digest": "7c2be55456f1332e849ff6699a26dda2e1641c280f45c9ec88dedf6d9b7b7fe2" + "digest": "2f30b23a738371690b2f00d96ddb5ceb90a1442b5478754626a3dfa263ed2fc1" }, { "name": "link", "unicode": "1F517", - "digest": "cc4873f8a612dd721dddcd507a4430b4fb6c4abc15a8848456f0ffd97811b163" + "digest": "7bf567aabd1fc38b3d70422f9db3a13b50950cf6207e70962c9938827c196ccb" }, { "name": "lion_face", "unicode": "1F981", - "digest": "935b1076815f51fafcd860a395d0a03c536acfcea61ffcf542a377da046fa7d9" + "digest": "dd24f2668e973ec973e97dc111f59a2cc14e9b608387401191dd53368d28d4fa" }, { "name": "lion", "unicode": "1F981", - "digest": "935b1076815f51fafcd860a395d0a03c536acfcea61ffcf542a377da046fa7d9" + "digest": "dd24f2668e973ec973e97dc111f59a2cc14e9b608387401191dd53368d28d4fa" }, { "name": "lips", "unicode": "1F444", - "digest": "e3bc20f9e210fa1711271234fe61bf1c9ddf36dd6ffc5b832c6c3a769a1e59a8" + "digest": "8740d8086525c7a836d64625a6915cc1c59af69ba143456dbb59e0179276895e" }, { "name": "lips2", @@ -6612,477 +6612,477 @@ { "name": "lipstick", "unicode": "1F484", - "digest": "335b912e163020df3d6d9f0a19a55d6547bd59b471c5a3e374c2968e49911ccc" + "digest": "751dcb22706a796033b13a2ccb94304236ec13207ad4d011e02d230ae33ab5c1" }, { "name": "lock", "unicode": "1F512", - "digest": "c20eacfb8ccd9bb85919a837c0d4650ee608edb48c85bff46945f613e95d7038" + "digest": "043b4fc0b8c79d47a07d91308e628e1ac262aea6c1ec05e6b84bf7bcdf89dc83" }, { "name": "lock_with_ink_pen", "unicode": "1F50F", - "digest": "5cab25cea08e22d9c3f5de16de6d0ab658ca15cc93d7830f29b0f3e9348ec45f" + "digest": "7b5e959b26cf7296c7b230fc2be9feb9e38391c5001951a019d16b169a71aba9" }, { "name": "lollipop", "unicode": "1F36D", - "digest": "33d2334a00bf0e15869ccc75fadc36f27f89abf0525bb71f859aad9e1dc4ad66" + "digest": "17b6a0df47ec758a2f9c087b46a6902cee344d39407ef4c321e408505cbb72ca" }, { "name": "loop", "unicode": "27BF", - "digest": "fa1174ddc44e317d0796e07868c7ac8ac9c9274fbc8a6c3d0ec78d543c3c6bf0" + "digest": "9f20ecc34b3c871789ba7d0712aa31e7a74b6c1558ac8bea385bc40590056726" }, { "name": "loud_sound", "unicode": "1F50A", - "digest": "fb70229e13b690ffc1031d2e631123f8c908035a15218c297c1c4a3ff3624aa0" + "digest": "64b12db9ddd8adf74a9fc2bd83c7979ea865113347f7ce8666e9ccf5019e715f" }, { "name": "loudspeaker", "unicode": "1F4E2", - "digest": "e2d6cf9ec6412ee62f3128a1afd8c63ec74755c4833f01a4f99722407fe154d6" + "digest": "1e1f35d16dd2898ebaa6f2b2868203df6e09c8a70df069c92d6d1b5cb2ac0976" }, { "name": "love_hotel", "unicode": "1F3E9", - "digest": "184670ebc4045043a7b18d576da3255d216551da522a11cde7df34524e9c7d50" + "digest": "ff8966a50fd47a216855488eb09a367d231fea21f49e7e5325191d32fb494473" }, { "name": "love_letter", "unicode": "1F48C", - "digest": "9a4c52e2622fc7d364995ebc93ca530d972134621d117b72053a659dffc90ffc" + "digest": "037261c8ca4d72f7205e51664591696da2ae7ceb19f1c1c9f6123da5a5979d29" }, { "name": "low_brightness", "unicode": "1F505", - "digest": "c177b7fa9fdbef959cc47e7d16becd71117470b767a81ed6d15f80f464776c02" + "digest": "a065d00a416e297c168b0a675cafcf492fedf94865cb21801a1be5a3914593d4" }, { "name": "m", "unicode": "24C2", - "digest": "2eaf011e74d69613923dad424daaec4c13b592388dbcc5757b645bc058eedecb" + "digest": "54588ac2b7fcd53a96f17124e9de69b617613fcd5af9ad2930a094cb795bb9f4" }, { "name": "mag", "unicode": "1F50D", - "digest": "029427bd73d2c79fffc5194ded01f6011952ec0124b7634c6230e0afa7ad7c95" + "digest": "a6e31a2efa7d9427aaa30b45d9f4181ee55c44be08aea2df165a86e0e6d9eaa1" }, { "name": "mag_right", "unicode": "1F50E", - "digest": "f99de50bb59ec3bf1d4ccb8584ca09d4a7ceb5bf9f600ea8d3f84930efbf01b8" + "digest": "c7d8ceeb05db261e5eaab31dc4da432d0d5592a2ed71e526c5a542daa230bbaf" }, { "name": "mahjong", "unicode": "1F004", - "digest": "da5d1fa980c38e092d414516161ca26046aa65ace3261999ea750f72e676ac6e" + "digest": "755d69f988434ce1c17531a8b7ac92ead6f5607c2635a22f10e0ad70f09fc3e6" }, { "name": "mailbox", "unicode": "1F4EB", - "digest": "14217df8f39a95fc0a0c527f97db1ca8564764034e921614decc5be705629352" + "digest": "2069091be90a530a43ef29d5ec7688c351bf4d5b08d63a0d20d72b67d639ec62" }, { "name": "mailbox_closed", "unicode": "1F4EA", - "digest": "e0c7beb205ec548a66d8afc7f103b64c6c79c08417ab550f19c36cc6d1a62bc4" + "digest": "d88d65bfebb8216535fd055c69f319564b2cf0b0901820f8312f581864557ed4" }, { "name": "mailbox_with_mail", "unicode": "1F4EC", - "digest": "6d381c0c4181be628d9409df1d85f8a9438c21ef5b92d82ef8ae1ff0079236de" + "digest": "69e966b4659128991a70c6a2dd4d647551bedb91bdf5ce688958686bbec56381" }, { "name": "mailbox_with_no_mail", "unicode": "1F4ED", - "digest": "74843d5ea9e03b48323f2252bdd000585f549b7fffe1fe181a25c38b99b5e23d" + "digest": "9e92d8ee88f660ce56da61077c80ec26c5d8f54ebd2306c4cfa16f6c1b981f83" }, { "name": "man", "unicode": "1F468", - "digest": "0275935258b4c832c3fcb06531d3e6972e2c3d46bab2973004750a9f00bd4cb6" + "digest": "42b882d2c6aa095f1afcf901203838d95c1908bdc725519779186b9c33c728d7" }, { "name": "man_tone1", "unicode": "1F468-1F3FB", - "digest": "1f6603d040f4a025f49d384170dd16b8da169663fc3282af1dc8710d9c1a7adf" + "digest": "7053e265fa7d2594de54a6c5d06c21795b9a7dfb36a1c5594ca43c4c6cc56504" }, { "name": "man_tone2", "unicode": "1F468-1F3FC", - "digest": "d65bb03071b483946c69c61769d19b29a2af76fa7e43020e55f0bbc046492221" + "digest": "7ebc64de40d3ac60fb761be5cf94f53fa10b4f03fb66add46c90f5d98eaf71eb" }, { "name": "man_tone3", "unicode": "1F468-1F3FD", - "digest": "9af8ede7211b19a7dc0c60db083dd2bdc4897dda4d71e57feadf2e39d847f060" + "digest": "77ceef4d3740ed4751acb83dd45b6b754cf625c522c6757309cd4d61202d7149" }, { "name": "man_tone4", "unicode": "1F468-1F3FE", - "digest": "6555de60976aafeb024db78addb44eab2a412dd7277013f44d06757d03b6a252" + "digest": "41e6037c393f61cca61b9a81b27ed14a95d75fe380e3a00153c33a371a836ffd" }, { "name": "man_tone5", "unicode": "1F468-1F3FF", - "digest": "b58b97a28a6adc1777acc05194cd917c730f90e37441124c384ded12e9a7d2a4" + "digest": "a8cebfd39a5b9c79af7cc37f205e1135376056fee287af967c9f55d415572d99" }, { "name": "man_with_gua_pi_mao", "unicode": "1F472", - "digest": "88663173a6ccbebec5e24883c90d965447e022c6688773273110fe544d5b1607" + "digest": "3dae285e900c69986a48db0fa89d4f371a49f38608059cdae52be098030c5ac4" }, { "name": "man_with_gua_pi_mao_tone1", "unicode": "1F472-1F3FB", - "digest": "3c8bad3923a619f888e14544d357499a26a517e8fbe7a51027117b960c9eb842" + "digest": "35404d8e266920c78edd9e7143fb052b42f65242a5698494c4f4365e9183cc67" }, { "name": "man_with_gua_pi_mao_tone2", "unicode": "1F472-1F3FC", - "digest": "da125a3310fab19c9282497d53e2fc71ad07920ce60a0ef52dcdb31500023f09" + "digest": "82d4f968665a93c7543372c8a1eeb0f25d0ea6842d5e518bd91c226c6c3ab8c2" }, { "name": "man_with_gua_pi_mao_tone3", "unicode": "1F472-1F3FD", - "digest": "1d5842558847367966bf3ea473ff80fe744359bc5d969f4cc06cf2e452ed2fb6" + "digest": "f44159f0c672b9b833449382896180e799abf574f5b3c6cd9541caa992fa18ce" }, { "name": "man_with_gua_pi_mao_tone4", "unicode": "1F472-1F3FE", - "digest": "92be490f3ba602a43e2be8160d8bfd8a0691b2f81fe017b06df10f476a89ffab" + "digest": "c79060188f9461ca34eaa225b7682d8c410883609509fb731c992db69bfeeb50" }, { "name": "man_with_gua_pi_mao_tone5", "unicode": "1F472-1F3FF", - "digest": "669f6b31bc7a8bf50b169d0600f14e00addaeb24144a1bace8b94950372839b0" + "digest": "de9e4acdb10f7abddeeabc0b48d91139fc8b544a601c530db811f099991b0d38" }, { "name": "man_with_turban", "unicode": "1F473", - "digest": "87d30d35ba40ee39c2df8ce19d975ce34a9c54688bafeac7377d7d481e55f1a4" + "digest": "db72c944e93983f38d00e3e936ebb5b243c6069f1f1236d46f6a9f1beb8d6634" }, { "name": "man_with_turban_tone1", "unicode": "1F473-1F3FB", - "digest": "33b8b8154e0691e2ad66177dbf1e0101411fd8b3a16bf4e54c36d4a874f2a275" + "digest": "b6d7489c4cd151af09fff48b62c54c336303e14866e6ef38f94cd834b085d09e" }, { "name": "man_with_turban_tone2", "unicode": "1F473-1F3FC", - "digest": "1a6b83faa8d6e6a7d12a04898a6f22243287330a1faa081d2626b17dfb07174d" + "digest": "7854ef973c21847f452d7e78e5c460ea300e12b539ce92c69dabe8f1bf3a4382" }, { "name": "man_with_turban_tone3", "unicode": "1F473-1F3FD", - "digest": "5d43da5109e688ff8ca0675f33ebbaf930e206f1f01e3ee773f2844663fe572b" + "digest": "1dbd9bd78f5263cbadee7d0d5754c14cfbc914f7329e25fbd97d9f5b8ce0737e" }, { "name": "man_with_turban_tone4", "unicode": "1F473-1F3FE", - "digest": "bfaf7293c5ea75d0ecdc6fe5afe8f48e7b29b2e0df06ef974d3e1732f5db5dd4" + "digest": "4f4804da4a7c98ad4f9db3ae3eaf674c8977c638e73414e33ef1f65098e413a3" }, { "name": "man_with_turban_tone5", "unicode": "1F473-1F3FF", - "digest": "fba2404dd3d7eab5268519894cc0b386e1b17fdf14a04760c346014aa0e25acd" + "digest": "240282aa346ef9b1d0d475ea93a02597697f0f56f086305879b532b0b933210a" }, { "name": "mans_shoe", "unicode": "1F45E", - "digest": "45dc13ac44c922b4c4b8ecb2e1a870a78e09d53da86843431ab0e9ec96ebcd97" + "digest": "f53fe74abd9906cd3e2dd7e7bddbe1feb9f8f7be28b807fabe452f1f60ca1b84" }, { "name": "map", "unicode": "1F5FA", - "digest": "f56116d09996d6d08fb5cdfb46622b545253f2649008170fc2011a9713fa875b" + "digest": "84f496a062b5c3ae1e8013506175a69036038c8130891bcf780a69ce7fcbe4de" }, { "name": "world_map", "unicode": "1F5FA", - "digest": "f56116d09996d6d08fb5cdfb46622b545253f2649008170fc2011a9713fa875b" + "digest": "84f496a062b5c3ae1e8013506175a69036038c8130891bcf780a69ce7fcbe4de" }, { "name": "maple_leaf", "unicode": "1F341", - "digest": "40c5ee93396301911391cf6e70454b6fa8020fe5c85d3364136bcedb5d052cdb" + "digest": "72629a205e33f89337815ad7e51bb5c73947d1a9f98afe5072bdf4846827ae72" }, { "name": "mask", "unicode": "1F637", - "digest": "e0301cd27eb8c74c9772ff05b880215fc031ac1ae7f3177cd24ba0acb43b3834" + "digest": "1b58af9ae599308aabf41bbd38f599fa896bd9fe5df7a40be9f2dc7e0e230600" }, { "name": "massage", "unicode": "1F486", - "digest": "856d0fb1144ee91c58dfad74f9a2cababf6bae4b3ceba2a95c03ecd44ae3aa21" + "digest": "6ee48b4d8cec0bf31e11d7803ad9fc1f909457c8c00cb320b5671395af3c170c" }, { "name": "massage_tone1", "unicode": "1F486-1F3FB", - "digest": "fd53b06eb0967303c0914ebb79fd872900ec0f71b2852c7238517e192e5023e1" + "digest": "9da162c2f39628156b87db986a6ada59372a9e9a6b3f0488d21c9e65ec3309bb" }, { "name": "massage_tone2", "unicode": "1F486-1F3FC", - "digest": "7ef57359a339ae1ca4488f9a6195a352e74daf5b67d8e1ae1e91fe866921c40c" + "digest": "ac259188549b5b429b8c4929e1da2314859e8857ee49720551467aedfcc96567" }, { "name": "massage_tone3", "unicode": "1F486-1F3FD", - "digest": "e4fb643b6242bedb395e503ae337a88b2a255b5fda88b4aaa93396f948614a6e" + "digest": "cfd9c105b6debc10448f172afcb20d4192899f7ae5aa8af54c834153a5466364" }, { "name": "massage_tone4", "unicode": "1F486-1F3FE", - "digest": "94f007c2daf9455fa8d2b10cc7ccff7db9bc9daf835ef5c3699be091938db833" + "digest": "38ab715c621c58454f3cb09153a96380118cf082568554b6edc5f83fb62e9297" }, { "name": "massage_tone5", "unicode": "1F486-1F3FF", - "digest": "d18e800b728bf45b500f492062dc81312ca1ad7b1a0277a3d5bc150e4632ea1c" + "digest": "32480457734121b0c83e9be6d693ae379c95535f43f963c0c2f0f20434ee12c6" }, { "name": "meat_on_bone", "unicode": "1F356", - "digest": "674a2a58e174b7681eef3b6c5b39c098ed9374cc610d037166c0092ee5269a97" + "digest": "d71a8e0b118d5e6ca60690793ce9649afb78e707fcbd7be890a75564c94434fd" }, { "name": "medal", "unicode": "1F3C5", - "digest": "270d438b6e2155e944dc734ea3e4d02409e51f59db2db636398fbf96e5edb0e6" + "digest": "9600cbe57e08da090c60629bcafd2821c87322e738c2454f8e883ceb756e7391" }, { "name": "sports_medal", "unicode": "1F3C5", - "digest": "270d438b6e2155e944dc734ea3e4d02409e51f59db2db636398fbf96e5edb0e6" + "digest": "9600cbe57e08da090c60629bcafd2821c87322e738c2454f8e883ceb756e7391" }, { "name": "mega", "unicode": "1F4E3", - "digest": "540ab4fd5bab041a681749b85e6de598ebcbfc4fbf5c3cdbd9ca1e8256191733" + "digest": "4b1def6b5b051c5045514063f0ac006222ad81fbfe56d840e14bb950713e331b" }, { "name": "melon", "unicode": "1F348", - "digest": "39dd0ecb23e2d3da6cbb7309333fed5d7e2cb38c0afc526ade78520eca11b5f4" + "digest": "0cdd663e6f2129808856cdf0746e6571b62aac641f224adb553baf3bb63ba3bd" }, { "name": "menorah", "unicode": "1F54E", - "digest": "5f81bc2e5a34bf76481d2958fdb0b4e4540c599aa837a6453609a39023885d8c" + "digest": "49fca8c3bc00ea69653ee2f8d4e21e561856ba39716c13e9d107db3e805a2997" }, { "name": "mens", "unicode": "1F6B9", - "digest": "5ed56cff80e8ee7ed581f2a2e365915db5cb29df89e850e0add0b68db4b0c788" + "digest": "7d92292586ee12a5d1a557c37da4d14708dc3ce701cf32d3280dcc83d91e5df8" }, { "name": "metal", "unicode": "1F918", - "digest": "45e5fac0b9b019cf217dcfd1380cafb0d03063454612178278dac1ca5f8476a6" + "digest": "ffb750caf187f5d821c990108e2699ac3e216492bcff6ee543f4a7aa55b9fd29" }, { "name": "sign_of_the_horns", "unicode": "1F918", - "digest": "45e5fac0b9b019cf217dcfd1380cafb0d03063454612178278dac1ca5f8476a6" + "digest": "ffb750caf187f5d821c990108e2699ac3e216492bcff6ee543f4a7aa55b9fd29" }, { "name": "metal_tone1", "unicode": "1F918-1F3FB", - "digest": "9b3596fe7c063df838f0a43fb680ce10fb88e2b73c5c3324abfa357a224c17aa" + "digest": "5505f0b0340f9ba572db8897e40adf598cfa784686ad5ee360a7351bf44ddc1d" }, { "name": "sign_of_the_horns_tone1", "unicode": "1F918-1F3FB", - "digest": "9b3596fe7c063df838f0a43fb680ce10fb88e2b73c5c3324abfa357a224c17aa" + "digest": "5505f0b0340f9ba572db8897e40adf598cfa784686ad5ee360a7351bf44ddc1d" }, { "name": "metal_tone2", "unicode": "1F918-1F3FC", - "digest": "e15a4898a0efca4354ac48d6b01ff0618ce8b110b1246a4f5d78e19b54658be6" + "digest": "8f9eee3ad5fc7eeeb30118d16d27467b16fd87297e0ecf02656db77e701f5aeb" }, { "name": "sign_of_the_horns_tone2", "unicode": "1F918-1F3FC", - "digest": "e15a4898a0efca4354ac48d6b01ff0618ce8b110b1246a4f5d78e19b54658be6" + "digest": "8f9eee3ad5fc7eeeb30118d16d27467b16fd87297e0ecf02656db77e701f5aeb" }, { "name": "metal_tone3", "unicode": "1F918-1F3FD", - "digest": "c159e8179cb1907c246b432d87c5253b914fd7cebb6ac05292c4e38eff4815b0" + "digest": "8270a7ecf5eb11431a07ef04cc476c2651ac8aacb0d4768e5cb69355f8a5e84e" }, { "name": "sign_of_the_horns_tone3", "unicode": "1F918-1F3FD", - "digest": "c159e8179cb1907c246b432d87c5253b914fd7cebb6ac05292c4e38eff4815b0" + "digest": "8270a7ecf5eb11431a07ef04cc476c2651ac8aacb0d4768e5cb69355f8a5e84e" }, { "name": "metal_tone4", "unicode": "1F918-1F3FE", - "digest": "a8a43a88028c97074321e3da56df1045db41ede58bf286c21d7ae90f222f2011" + "digest": "f24f7b137dd6c7899dc0a8794204bbde7ad43ec1e63b419c90dd70a8b77871e8" }, { "name": "sign_of_the_horns_tone4", "unicode": "1F918-1F3FE", - "digest": "a8a43a88028c97074321e3da56df1045db41ede58bf286c21d7ae90f222f2011" + "digest": "f24f7b137dd6c7899dc0a8794204bbde7ad43ec1e63b419c90dd70a8b77871e8" }, { "name": "metal_tone5", "unicode": "1F918-1F3FF", - "digest": "e6611e826e867e2c73a8cadb138e4aa6365e3583dd229ff24b3e8f161904bf56" + "digest": "07b0726a632653b980df775f460cd3fe1ea8d4a7b0b46fe29e089b66579482d2" }, { "name": "sign_of_the_horns_tone5", "unicode": "1F918-1F3FF", - "digest": "e6611e826e867e2c73a8cadb138e4aa6365e3583dd229ff24b3e8f161904bf56" + "digest": "07b0726a632653b980df775f460cd3fe1ea8d4a7b0b46fe29e089b66579482d2" }, { "name": "metro", "unicode": "1F687", - "digest": "532378cf385f9a7fafe2f5c8203e675be6d38798871f4c8e2c50498a1529f956" + "digest": "b380247b61b5e2ca1b9b70fabff65907b2c3a5191a14b169ae094af94659b9b1" }, { "name": "microphone", "unicode": "1F3A4", - "digest": "46da2b94e4dc233f640249103f09ec915aaa812cce90afe68fedb6774a27ad4b" + "digest": "9ef4fc2e40d5391c4bb2d30f34f59662cff7cbb1b04341c9dac210d0e21b44ae" }, { "name": "microphone2", "unicode": "1F399", - "digest": "f9df32cd207808f67a895d3460a215d1ecc42e377907bcd64731c02b697d4f32" + "digest": "8a30464d51f7f101335778444c43270ac0679900f49463e6556682d9db1cb4dc" }, { "name": "studio_microphone", "unicode": "1F399", - "digest": "f9df32cd207808f67a895d3460a215d1ecc42e377907bcd64731c02b697d4f32" + "digest": "8a30464d51f7f101335778444c43270ac0679900f49463e6556682d9db1cb4dc" }, { "name": "microscope", "unicode": "1F52C", - "digest": "79918f5fe0a39f31f270a481f4c6e00ea49fc09d64b1ae78770971293c2b1ed8" + "digest": "4ca4322c6ba99b8c15acdb8b605f84f87398769e504b262b134c1f3868b2692f" }, { "name": "middle_finger", "unicode": "1F595", - "digest": "c6320b236a4a9593aeade511b52dd3114207e947458cb3b818c78737a505fdf6" + "digest": "0c3f1cc0ec7323f6d19508ad22fa90050845f7b5cc83f599ab2cacb89cf5dd0e" }, { "name": "reversed_hand_with_middle_finger_extended", "unicode": "1F595", - "digest": "c6320b236a4a9593aeade511b52dd3114207e947458cb3b818c78737a505fdf6" + "digest": "0c3f1cc0ec7323f6d19508ad22fa90050845f7b5cc83f599ab2cacb89cf5dd0e" }, { "name": "middle_finger_tone1", "unicode": "1F595-1F3FB", - "digest": "93c7aa994856185519d576cb779bdcff3a33f7077eef98e70968125f92f02448" + "digest": "4ebecf1058a3059aaa826eaad39c1a791120f115f65dde6d6ae32fc5561f60f7" }, { "name": "reversed_hand_with_middle_finger_extended_tone1", "unicode": "1F595-1F3FB", - "digest": "93c7aa994856185519d576cb779bdcff3a33f7077eef98e70968125f92f02448" + "digest": "4ebecf1058a3059aaa826eaad39c1a791120f115f65dde6d6ae32fc5561f60f7" }, { "name": "middle_finger_tone2", "unicode": "1F595-1F3FC", - "digest": "a0de802294717b80e08d9d30f5fd64eacb90b5b3b9d7a0c27d6226a22822597f" + "digest": "85ff506a08c38663c2dfa2e3a90584c02a36aa3dda33af47cdb49834bf9baf83" }, { "name": "reversed_hand_with_middle_finger_extended_tone2", "unicode": "1F595-1F3FC", - "digest": "a0de802294717b80e08d9d30f5fd64eacb90b5b3b9d7a0c27d6226a22822597f" + "digest": "85ff506a08c38663c2dfa2e3a90584c02a36aa3dda33af47cdb49834bf9baf83" }, { "name": "middle_finger_tone3", "unicode": "1F595-1F3FD", - "digest": "8bbbab07c838257416bbf8377904362c07019fca9d5abf9fd048ccf6370178da" + "digest": "cac697ff5207bf8a4e091912f3127f4e73c88ef69b5c6561d1d7b12ed60be8f1" }, { "name": "reversed_hand_with_middle_finger_extended_tone3", "unicode": "1F595-1F3FD", - "digest": "8bbbab07c838257416bbf8377904362c07019fca9d5abf9fd048ccf6370178da" + "digest": "cac697ff5207bf8a4e091912f3127f4e73c88ef69b5c6561d1d7b12ed60be8f1" }, { "name": "middle_finger_tone4", "unicode": "1F595-1F3FE", - "digest": "d9eed8db540fdb669c6ae5ef168b77659660589f5ddd9b66062274d335a3ef04" + "digest": "9324a5a4e3986b798ad8c61f31c18fb507ca7a4abfd6e9ae1408b80b185bf8c7" }, { "name": "reversed_hand_with_middle_finger_extended_tone4", "unicode": "1F595-1F3FE", - "digest": "d9eed8db540fdb669c6ae5ef168b77659660589f5ddd9b66062274d335a3ef04" + "digest": "9324a5a4e3986b798ad8c61f31c18fb507ca7a4abfd6e9ae1408b80b185bf8c7" }, { "name": "middle_finger_tone5", "unicode": "1F595-1F3FF", - "digest": "0519c3298040e57db202294476df239edb9b23b44848bab296bc45eda7cf8664" + "digest": "078f917cd4d8be08a880724e9400449980d92740ccbee4a57f5046a9cf7f6575" }, { "name": "reversed_hand_with_middle_finger_extended_tone5", "unicode": "1F595-1F3FF", - "digest": "0519c3298040e57db202294476df239edb9b23b44848bab296bc45eda7cf8664" + "digest": "078f917cd4d8be08a880724e9400449980d92740ccbee4a57f5046a9cf7f6575" }, { "name": "military_medal", "unicode": "1F396", - "digest": "bd1da0004768f404c6bb4db85d4b748f766a77ab3edb74e709d0c0064509a043" + "digest": "5da18351dc14b66cfc070148c83b7c8e67e6b1e3f515ae501133c38ee5c28d3d" }, { "name": "milky_way", "unicode": "1F30C", - "digest": "598b4e641c1081bb03ce38a29f9711fc8616373216a833e4daa14fbe97a358f5" + "digest": "17405ff31d94b13a1fb0adcda204b8adb95ca340bc3980d9ad9f42ba1e366e7d" }, { "name": "minibus", "unicode": "1F690", - "digest": "3d15791ca96349c3abb5bd5d1014b6b33b984db19609f56f5fd1e8d2fc551809" + "digest": "08ccb4b1bf397b7c9aed901e2b5dcdd6cb8ca5c5487ef26775bb3120f7b92524" }, { "name": "minidisc", "unicode": "1F4BD", - "digest": "83c4bfda4e0a80785fa1c3f2bbf3c15aca2bda8ea3727ce78bc4236e1e377a36" + "digest": "bebf82c0b91ef66321e7ae7a0abf322e59b2f7d8e6fbf9a94243210c00229c59" }, { "name": "mobile_phone_off", "unicode": "1F4F4", - "digest": "cfe6dfd766b9e0b4768df25d6e943c9abc0e910ff5e5c7a8a0f425c786bbab8d" + "digest": "6f9d8d6a32fc998f5d8144a5ff7e2ad00de37ad464cd97285e7c72efb09a1feb" }, { "name": "money_mouth", "unicode": "1F911", - "digest": "3ac2f9b5409e1426eef6966938ca04cf78aeffefd43f44b6c86af4af7836e22f" + "digest": "5a43973dadf48a89201b1816fea9972c5cfe501a26fe457b6f7eee0a6362018e" }, { "name": "money_mouth_face", "unicode": "1F911", - "digest": "3ac2f9b5409e1426eef6966938ca04cf78aeffefd43f44b6c86af4af7836e22f" + "digest": "5a43973dadf48a89201b1816fea9972c5cfe501a26fe457b6f7eee0a6362018e" }, { "name": "money_with_wings", "unicode": "1F4B8", - "digest": "f7f1fa502d2f6804169869aeb5ca7f0ea64bc2d6a0204f08875d65da4f8cb332" + "digest": "15fcf0595021374ba091ca00efdb4167770da4d421eab930964108545f4edab9" }, { "name": "moneybag", "unicode": "1F4B0", - "digest": "442db49cda27360d2eb781489c9879730a6094c3267bb0a0a8687d84f8fed078" + "digest": "02d708e2f603b0df6f6c169b5c49b3452e1c02e7d72e96f228b73d0b0a20bff4" }, { "name": "monkey", "unicode": "1F412", - "digest": "3141c971aacbadaba21f970a515e192740212be2a49fa1f5eb0fc4dc576e209f" + "digest": "3588a544d6d9e9995b45d60327a1a42002fa1faa4d48224b140facd249af1c67" }, { "name": "monkey_face", "unicode": "1F435", - "digest": "e2397431d2befe44bf5298fa81d865d80722bf954113bceacc2aa98b84d856e2" + "digest": "9e263ef5ca42bb76d1b1d1e3cbf020bcf05023a6e9f91301d30c9eb406363a2a" }, { "name": "monorail", "unicode": "1F69D", - "digest": "b546153200d6fbe8d65b1b34f62ff4a19b1b6a159eb1b536c5c2ecb56dab0ec9" + "digest": "2c9f185babcb4001fcef2b8dfc4a32126729843084d0076c3e3ccdc845ab23ad" }, { "name": "mood_bubble", @@ -7112,102 +7112,102 @@ { "name": "mortar_board", "unicode": "1F393", - "digest": "cb59edb08f75c374088b65284e4d0f77b9bc9573de3e6a5127f865431011e54c" + "digest": "d7fbe41d4b340d3564e484aec46a22c9613521414b2ba6eece2180db4d23e410" }, { "name": "mosque", "unicode": "1F54C", - "digest": "a08ddb74342dea8f79063db6f98ba03eb08fe99481de8ce9123827ca7f17c7f3" + "digest": "5f3d3de7feac953a70a318113531c2857d760a516c3d8d6f42d2a3b3b67ed196" }, { "name": "motorboat", "unicode": "1F6E5", - "digest": "9dbea67bbe2e95dcc68c049a58f87390a44350b32308342615d75214af3d1cef" + "digest": "81c156643528c5a94a12d6d478e52a019f5a4e3eb58ee365cdd9d2361a7fdb01" }, { "name": "motorcycle", "unicode": "1F3CD", - "digest": "8429fb6dfeb873abdffcc179c32d4f23e91c9e6b27b06cd204fd2e83cc11189e" + "digest": "354aa8157732184ad50eff9330f7a8915309dc9b7893cc308226adb429311a62" }, { "name": "racing_motorcycle", "unicode": "1F3CD", - "digest": "8429fb6dfeb873abdffcc179c32d4f23e91c9e6b27b06cd204fd2e83cc11189e" + "digest": "354aa8157732184ad50eff9330f7a8915309dc9b7893cc308226adb429311a62" }, { "name": "motorway", "unicode": "1F6E3", - "digest": "fc05a36c917637c135b0a60db8afcd58cee2b335070fe3888697f8026c9d11a5" + "digest": "148c3c13c7c4565453d16e504e0d4b8d007e4f2cad1ab56b1b51fefe39162d17" }, { "name": "mount_fuji", "unicode": "1F5FB", - "digest": "22bfffef033637b3c9b2fe7e539c74a659d2a49e594d2b33be894da00654d059" + "digest": "f8093b9dba62b22c6c88f137be88b2fd3971c560714db15ec053cf697a3820bc" }, { "name": "mountain", "unicode": "26F0", - "digest": "486cf4e9d5f3913d138fdb7878fe869b39caa3fca53876365957a89dc8f7edb8" + "digest": "07423804ad79da68f140948d29df193f5d5343b7b2c23758c086697c4d3a50da" }, { "name": "mountain_bicyclist", "unicode": "1F6B5", - "digest": "b547b96951b6837df8ae3be1e846f15e7e2ac06d976e1fe7f1442dcc5d3a0942" + "digest": "91084b6c887cb7e34f3d7ec30656ecb82c36cc987f53a6c83ccb4c6f7950f96a" }, { "name": "mountain_bicyclist_tone1", "unicode": "1F6B5-1F3FB", - "digest": "68ce0d55163c7b89ee1d87b752ece127bb25ca9deb3421b31df549a00ac5f69d" + "digest": "5d57fcfad61bca26c3e8965eb57602a1993a3117ebdda0f24569af730310ab6e" }, { "name": "mountain_bicyclist_tone2", "unicode": "1F6B5-1F3FC", - "digest": "5bfa82180bfb8bc4444cf301688aff02884895574a7ba66b398aaf20bde0f101" + "digest": "c0da7fb85d99aa01a665f64063cd7e2d994f8a16d3f6fbf52df5d471e771a98a" }, { "name": "mountain_bicyclist_tone3", "unicode": "1F6B5-1F3FD", - "digest": "33cb64a792123b81a05080465a0ea1035a2cdfdab01c71f5f725a5f92251c3e8" + "digest": "b099e7ee84eae44ebc99023fa06bdf37ffa0d69767c7c0163a89f7ced2a26765" }, { "name": "mountain_bicyclist_tone4", "unicode": "1F6B5-1F3FE", - "digest": "9c3fa4e65dcb0ad69b963292e77c7a75853ae3c1d18a90670f81ffb65b5d020c" + "digest": "9d09f7b3899ea44e736f237a161ef8d5170dccfa162a872c59532ceaf65ee007" }, { "name": "mountain_bicyclist_tone5", "unicode": "1F6B5-1F3FF", - "digest": "871de9e3fddb49b305e5f91000143878b0288c107a125c4e60acf2b6cf8b7f3f" + "digest": "71e374981d955056748a60c6d1820b45e9688a156b55318b4ea54a3a67ca801c" }, { "name": "mountain_cableway", "unicode": "1F6A0", - "digest": "f248ed5bf864f4a81e365b30d2825d2e6fc15a200c4ccf69e9f797341529f955" + "digest": "e261c3292758b1c0063c5a0d0c7f5c9803306d2265e08677027e1210506ced94" }, { "name": "mountain_railway", "unicode": "1F69E", - "digest": "7dd08745ab56c95c3dfcebcca517ff231cef61b670cedf9d7c53f3244c34e30b" + "digest": "b0987f8f391b3cbc7a56b9b8945ebfca240e01d12f8fd163877ebebe51d6b277" }, { "name": "mountain_snow", "unicode": "1F3D4", - "digest": "9939aade3d4d972ba3af16fcc6cc2454978f5426e4c92838734a44db065ce0ff" + "digest": "49aac2b851aa6f2bd2ca641efa8060f93e89395357f49d211658d46f5a2b0189" }, { "name": "snow_capped_mountain", "unicode": "1F3D4", - "digest": "9939aade3d4d972ba3af16fcc6cc2454978f5426e4c92838734a44db065ce0ff" + "digest": "49aac2b851aa6f2bd2ca641efa8060f93e89395357f49d211658d46f5a2b0189" }, { "name": "mouse", "unicode": "1F42D", - "digest": "fb20b3a82f407a6316bbbac68d58018c3d5b93a9a6ae968f44ace18d1c5698d9" + "digest": "007dd108507b45224f7a1fad3c1de6ecc75f38d71fc142744611eb13555f5eff" }, { "name": "mouse2", "unicode": "1F401", - "digest": "87be4099523ec32440e6d091f1193a8ed90730b9fbecaafed4912585bfe7818c" + "digest": "f3ed37b639b7c16aae49502bd423f9fdeabaf15bc6f0f74063954b189e176b5d" }, { "name": "mouse_one", @@ -7222,132 +7222,132 @@ { "name": "mouse_three_button", "unicode": "1F5B1", - "digest": "6a5629fee01145211cc8f4e8f59c5f1e61affed38c650502213d76c7d8861b01" + "digest": "3724341ac5ad0d01027ef1575db64f1db7619f590ca6ada960d1f2c18dc7fc6a" }, { "name": "three_button_mouse", "unicode": "1F5B1", - "digest": "6a5629fee01145211cc8f4e8f59c5f1e61affed38c650502213d76c7d8861b01" + "digest": "3724341ac5ad0d01027ef1575db64f1db7619f590ca6ada960d1f2c18dc7fc6a" }, { "name": "movie_camera", "unicode": "1F3A5", - "digest": "d6633b89a637b64d617c3032eed74bb82d3fa732dd9975486b2b5841b473808a" + "digest": "f7e285eda35b4431c07951e071643ddc34147cd76640e0d516fbfd11208346e9" }, { "name": "moyai", "unicode": "1F5FF", - "digest": "bf948c26cd98e2f5e48da363f2924a9d7c217232115a00cec372d0d5293402a8" + "digest": "2c1d0662c95928936e6b9ab5a40c6110ff1cea5339f2803c7b63aabc76115afb" }, { "name": "muscle", "unicode": "1F4AA", - "digest": "c85147efb786bdea3e7d53e2edf6b827280cd9fa881661a6102a614bf5b3579f" + "digest": "e4ce52757b2b7982e2516e0e8bf2e2253617cc9f3e6178f1887c61c9039461ba" }, { "name": "muscle_tone1", "unicode": "1F4AA-1F3FB", - "digest": "38d071df2b25031b61f3605b03c34d2e5d3e35d29f3c4aada14be37e19750eb8" + "digest": "4a2fa226a05bb847b62cdd163eb6c2d514d3c2330a727991cf550c0d32b0e818" }, { "name": "muscle_tone2", "unicode": "1F4AA-1F3FC", - "digest": "dcf11b76c8ffb58dc7e4f9ecd32a4c291d9772d51df2853d41081e041e7e0876" + "digest": "a8d5ecce335c782ca5f5e55763c06cfefa1c16c24cd6602237cf125d4ff95e47" }, { "name": "muscle_tone3", "unicode": "1F4AA-1F3FD", - "digest": "a3d5f8f2dbfc28f9713ee657428ea3292c47d0b22f11a51c13594be22b0f5204" + "digest": "070354b443faec3969663b770545fc4cf5ec75148557b2b9d6fc82ab22b43bd1" }, { "name": "muscle_tone4", "unicode": "1F4AA-1F3FE", - "digest": "eb220fc19be58d16cacc6b721e1011078b03256c0245756f251a4c2bcf50586c" + "digest": "8eafcdb6a607aeafa673c257df0d2a1b20f00fc0868d811babcbe784490a0dd3" }, { "name": "muscle_tone5", "unicode": "1F4AA-1F3FF", - "digest": "4e18708cbd61eaad288f913c86ad2d45108dd4484bc35879c5dcdd075eeb09fd" + "digest": "85a1e2b5c89907694240e9c5b9d876a741fa7ba38918c5718273e289cbc40efe" }, { "name": "mushroom", "unicode": "1F344", - "digest": "a2b252cd759244409d9a8066470059948e2c50b8cc86b59821c1c86b5190f640" + "digest": "aaca8cf7c5cfa4487b5fef365a231f98be4bbf041197fc022161bcc8ce6f57c8" }, { "name": "musical_keyboard", "unicode": "1F3B9", - "digest": "dcb3e84d27bfe373e5ea7ede457908de52002f0fd6105e9f3f5525c54d2a43dd" + "digest": "fb0a726728900377d76d94aac9c94dce29107e8e3f1dcb0599d95bce7169b492" }, { "name": "musical_note", "unicode": "1F3B5", - "digest": "76a0f598f8e251a9dab44f2e14f2b7a6fb0c0c351e0f37862c8c99d380f1c261" + "digest": "41288e79b4070bb980281d0e0d1c14d8b144b4aedb2eaadb9f2bebcb4ef892b4" }, { "name": "musical_score", "unicode": "1F3BC", - "digest": "a132c6b35236005b45c830a42fa97b454d3061c14991c6320f34807f10ba6a4a" + "digest": "f0f91b9fa4a2bff7a5a1a11afa6f31cfe7e5fa8b0d6f3cce904b781a28ed0277" }, { "name": "mute", "unicode": "1F507", - "digest": "73a99b7f9e00f92cab78cd304dee4e893a112c3a6f2285c13d44916ea547458e" + "digest": "def277da49d744b55c7cdde269a15aa05315898f615e721ee7e9205d7b8030d6" }, { "name": "nail_care", "unicode": "1F485", - "digest": "62f721d3610d1647dba4b3f53cd4f2bc4180dae298314c2cca2a6a8ab1664525" + "digest": "48b33b1dbbd25b4f34ab2ca07bb99ddaaaa741990142c5623310f76b78c076f9" }, { "name": "nail_care_tone1", "unicode": "1F485-1F3FB", - "digest": "11b82ed2e6b6619c9b74702fdacfb0ddc91310191c8b89f355c7c69a72673f8f" + "digest": "a9ac92a34f407e7dd7c71377e6275e66657f7f42e4b911c540d1a66a02d92ac5" }, { "name": "nail_care_tone2", "unicode": "1F485-1F3FC", - "digest": "5195c76bccb9149d9080347d785dae2cce947bada5b198fae8c23e42f5553154" + "digest": "f295ec85980aaa75818fad619c3d25042146ecbbf361db9e9bb96e7bc202bc73" }, { "name": "nail_care_tone3", "unicode": "1F485-1F3FD", - "digest": "50eab0bf825c5e00db07a3f5ad26b1bb221f54efb5c55549f392b2f5aec09e5a" + "digest": "02ec373052a250977298bae85262177910126cc10de9480f1afa328ac2f65a95" }, { "name": "nail_care_tone4", "unicode": "1F485-1F3FE", - "digest": "d05a9ccfad02191c89e4cbd00aa48fdaf908c0de6681f4a587d500be448e528f" + "digest": "f3d95390ab59caedfda66122bbd0acf3aabedc142fc48352d68900766a7e6f5c" }, { "name": "nail_care_tone5", "unicode": "1F485-1F3FF", - "digest": "62466354dcf6717a8b9e942ca2c5ad15a26aa815c213e3b01faba9a2e302ecdd" + "digest": "009423c97f2aafd24fb8c7c485c58b30bbf9ae6797cc14b80d472b207327b518" }, { "name": "name_badge", "unicode": "1F4DB", - "digest": "0a1cb0f7d489d3356a4d3e01f9faf78449d82d8ec4595c8639a55c3606c97c40" + "digest": "f9f6a4895ff0be8fb2ccc7ad195b94e9650f742f66ead999e90724cfb77af628" }, { "name": "necktie", "unicode": "1F454", - "digest": "029e1140391ef559a9316021c2db94f05653751fdf9d8f366446467a70fee6df" + "digest": "01bb18dc8bfe787daa9613b5d09988cd5a065449ef906099ce3cb308c8a7da68" }, { "name": "negative_squared_cross_mark", "unicode": "274E", - "digest": "0ba0e705fdeac99edd712db31a8846320b9d2cf53c9cb4d4bcfd22ba4e1488ea" + "digest": "1cdaf4abc9adafa089c91c2e33a24e9e647aea0f857e767941a899a16ec53b74" }, { "name": "nerd", "unicode": "1F913", - "digest": "94efd551700aae8909b8dd7a78a54a33e070d24b2e0a10534353645084614e98" + "digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66" }, { "name": "nerd_face", "unicode": "1F913", - "digest": "94efd551700aae8909b8dd7a78a54a33e070d24b2e0a10534353645084614e98" + "digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66" }, { "name": "network", @@ -7362,157 +7362,157 @@ { "name": "neutral_face", "unicode": "1F610", - "digest": "df01da8501e1f588049c8ed66e504e9abcce83f74ce5790f4d3dc547408f77ee" + "digest": "7449430a60619956573e9dc80834045296f2b99853737b6c7794c785ff53d64e" }, { "name": "new", "unicode": "1F195", - "digest": "24e80abd29750d8b297335cdd4751b6250bb820560cf0392a6cc8783d34db63a" + "digest": "e20bc3e9f40726afd0cfb7268d02f1e1a07343364fd08b252d59f38de067bf06" }, { "name": "new_moon", "unicode": "1F311", - "digest": "2d697e431eac53d6e1ea367b5da03c15fc535cd7e8c214f801fe595b768a8e11" + "digest": "dbfc5dcae34b45f15ff767e297cba3a12cb83f3b542db8cfc8dbd9669e0df46c" }, { "name": "new_moon_with_face", "unicode": "1F31A", - "digest": "ea469a4668ded071f35e5898ae229fdb5d02b0730ce233169b83e22f81292baa" + "digest": "c66d347d2222ac8d77d323a07699aff6b168328648db4f885b1ed0e2831fd59b" }, { "name": "newspaper", "unicode": "1F4F0", - "digest": "0aaf6747a43fb60cd15e6e64ca0eccaade331b376c6fe6712fd5e8294e9868cc" + "digest": "c05e986d9cdac11afa30c6a21a72572ddf50fc64e87ae0c4e0ad57ffe70acc5c" }, { "name": "newspaper2", "unicode": "1F5DE", - "digest": "0ca6b5850091f23295c970815a8e64a52e3c3dae492029ecb1e0726c2693f9bf" + "digest": "63db7bcf51effc73e5124392740736383774a4bcfbc1156cf55599504760883d" }, { "name": "rolled_up_newspaper", "unicode": "1F5DE", - "digest": "0ca6b5850091f23295c970815a8e64a52e3c3dae492029ecb1e0726c2693f9bf" + "digest": "63db7bcf51effc73e5124392740736383774a4bcfbc1156cf55599504760883d" }, { "name": "ng", "unicode": "1F196", - "digest": "4994c9b795033ed788e98c4af571a1dffe28c0a1479e3b42dcae21bb08381b5f" + "digest": "34d5a11c70f48ea719e602908534f446b192622e775d4160f0e1ec52c342a35c" }, { "name": "night_with_stars", "unicode": "1F303", - "digest": "56bb4a59a897c1836ee1a49cc99f468891b790b0f8bce203c201c13bb7b8ae9a" + "digest": "39d9c079be80ee6ce1667531be528a2aa7f8bd46c7b6c2a6ee279d9a207c84a4" }, { "name": "nine", "unicode": "0039-20E3", - "digest": "7e3644a98cb6417a351530c9ce6b368e637a22c847a8c04133897dc1c5d7419f" + "digest": "8bb40750eda8506ef877c9a3b8e2039d26f20eef345742f635740574a7e8daa6" }, { "name": "no_bell", "unicode": "1F515", - "digest": "f4fb42836132000101624fecef8b9358736a0fc76beae460e6986aaa479204fd" + "digest": "6542a9a5656c79c153f8c37f12d48f677c89b02ed0989ae37fa5e51ce6895422" }, { "name": "no_bicycles", "unicode": "1F6B3", - "digest": "b3c258bea7d6988640e3348598c03c97632ca00a11cbf0352995b801ff4a296b" + "digest": "af71c183545da2ff4c05609f9d572edb64b63ccba7c6a4b208d271558aa92b0a" }, { "name": "no_entry", "unicode": "26D4", - "digest": "ac807d54092efdc3aea417790a7d0c50b59800c9ea49b37f1aec6d2e453c5f6d" + "digest": "dc0bac1ed9ab8e9af143f0fce5043fe68f7f46bd80856cdec95d20c3999b637d" }, { "name": "no_entry_sign", "unicode": "1F6AB", - "digest": "5a17d677ec1c7595a7970a1cbe0d20909341b30d3ab31471ced590f51fff1ff7" + "digest": "2c1fceef23b62effca68e0e087b8f020125d25b98d61492b1540055d1914fdc3" }, { "name": "no_good", "unicode": "1F645", - "digest": "8ce921e5e13e1203cf43fdc3e7c5ec1fb2a1f9ff79f21539cff542c80af2e5fe" + "digest": "6eb970b104389be5d18657d7c04be5149958c26855c52ea68574af852c5f85c4" }, { "name": "no_good_tone1", "unicode": "1F645-1F3FB", - "digest": "aab4d354aaac06e8348eb354487c6381e475b44651cb2716660904a36c47a1b6" + "digest": "c20a24a1e536240b4dcf90ecb530796de621d7ba1fb9e3fa0f849d048c509c03" }, { "name": "no_good_tone2", "unicode": "1F645-1F3FC", - "digest": "8fb66b1a7b8f72062794281294515d47471a8c59de300b99d656c3412ca19d64" + "digest": "f31a4628c1f2e6a39288fda8eb19c9ec89983e3726e17a09384d9ecc13ef0b4c" }, { "name": "no_good_tone3", "unicode": "1F645-1F3FD", - "digest": "aeecf73fb9dca24b4002db2802fc9b5a483644c49f834c19f143d4e56ec46c1a" + "digest": "959dec1bfdaf37b20a86ab2bcbdbacd3179c87b163042377d966eab47564c0fb" }, { "name": "no_good_tone4", "unicode": "1F645-1F3FE", - "digest": "fadeb23307d5ccabbf08c848cf81c66c05b152aa32b85f86061caf14760f8eb9" + "digest": "efd931f0080adf2e04129c83a8b24fda0ae7a9fa7c4b463686c0b99023620db8" }, { "name": "no_good_tone5", "unicode": "1F645-1F3FF", - "digest": "cf26d5d6463d0febf4e1f08e343308742ffe0811cfc30c459b87d4cc812f5d04" + "digest": "f35df2b26af9baef47c1f8cc97a1b28a58aa7fcb2a13fdac7b2d9189f1e40105" }, { "name": "no_mobile_phones", "unicode": "1F4F5", - "digest": "3b4ead88beca33f1e303d0a45268849be7aaaff7830b761732c7a5afc5a2de3a" + "digest": "a472decd6ac7f9777961c09e00458746b2c04965585e3bee4556be3968e55bcd" }, { "name": "no_mouth", "unicode": "1F636", - "digest": "2af81a3e07a8b7827a1e58f6f5036ccff2f6e7b0027a4f934c9fa34c6a780963" + "digest": "72dda8b1e3ad4b05d9b095f9bd05e95d7ba013906c68914976a4554e8edf5866" }, { "name": "no_pedestrians", "unicode": "1F6B7", - "digest": "9f9ed90bb8f9964fa8cb0048fc092ecc0612a1994c98d19ef0b5a58607888476" + "digest": "062b4a71b338fe09775e465bfba8ac04efbb3640330e8cabe88f3af62b0f4225" }, { "name": "no_smoking", "unicode": "1F6AD", - "digest": "fb90290ff5c917b7307a97c8ba793d20c61042525cf2f7bfd4cd2a7878aeefa5" + "digest": "ae2ebb331f79f6074091c0ee9cd69fce16d5e12a131d18973fc05520097e14ee" }, { "name": "non-potable_water", "unicode": "1F6B1", - "digest": "c4ddca2ab1a97260e9b2c2aa33fb03455c0e8174541c3a9416fc44143a3ee567" + "digest": "32eba0a99b498133c2e4450036f768d3dccaaf5b50adc9ad988757adc777a6a1" }, { "name": "nose", "unicode": "1F443", - "digest": "308e28b15b7f734f6f184ae367789d7cf258656b24861cf8d5935127d810aa3f" + "digest": "9f800e24658ea3cebe1144d5d808cf13a88261f1a7f1f81a10d03b3d9d00e541" }, { "name": "nose_tone1", "unicode": "1F443-1F3FB", - "digest": "392d24b38ac3edc2d7b83945a60bbe9115a6a97658d8af35281a7cbef79449e8" + "digest": "a2d0af22284b1d264eb780943b8360f463996a5c9c9584b8473edf8d442d9173" }, { "name": "nose_tone2", "unicode": "1F443-1F3FC", - "digest": "409a790339c405770492e49fdb0b5ba34087c27e2f9018ecd845ab078e61476a" + "digest": "244dcaa8540024cf521f29f36bd48f933bf82f4833e35e6fa0abf113022038f3" }, { "name": "nose_tone3", "unicode": "1F443-1F3FD", - "digest": "92b52b479a935f31e460257d809c531edad1a6bb4583ad18233b12c4e45202fe" + "digest": "c935b64866f0d49da52035aa09f36ff56d238eb7f5b92205386451056e8ea74f" }, { "name": "nose_tone4", "unicode": "1F443-1F3FE", - "digest": "78ad2e857792e86cded6ba5620f634f7d1f79a92c82c266e48fab9bd73df3688" + "digest": "a87e95fd9319c49e66b6dea0e57319d0ed9921b8d94df037767bf3d5dc7c94f3" }, { "name": "nose_tone5", "unicode": "1F443-1F3FF", - "digest": "dbef6813c1965d3e93f70f33f118f9950130af21c622cea97ea215a36b4fa73f" + "digest": "1e0f9842e0f8ad5805eabd3f35a6038b7a2e49d566a1f5c17271f9cdf467ca60" }, { "name": "note", @@ -7537,12 +7537,12 @@ { "name": "notebook", "unicode": "1F4D3", - "digest": "64bd4a3e7ca7b22fc704c7b7bd4d13540c16bc69b9d8dd76e69e6ad573ab3823" + "digest": "fc679d3728f86073d1607a926885dd8b0261132f5c4a0322f1e46ea9f95c8cb8" }, { "name": "notebook_with_decorative_cover", "unicode": "1F4D4", - "digest": "4b45f28fbde1be5c214a6bc2413abc91db02bccd86f74c21b7f4a4da8b75a46f" + "digest": "d822eda4b49cbfa399b36f134c1a0b8dcfdd27ed89f12c50bc18f6f0a9aa56ef" }, { "name": "notepad", @@ -7567,297 +7567,297 @@ { "name": "notepad_spiral", "unicode": "1F5D2", - "digest": "c181b6c1cc6063ec1848e46cbbf1d8b890c53b59cdc5218311ce06889570e727" + "digest": "c6a8e16aa62474cef13e5659fddb4afc57e3f79635e32e6020edbee2b5b50f18" }, { "name": "spiral_note_pad", "unicode": "1F5D2", - "digest": "c181b6c1cc6063ec1848e46cbbf1d8b890c53b59cdc5218311ce06889570e727" + "digest": "c6a8e16aa62474cef13e5659fddb4afc57e3f79635e32e6020edbee2b5b50f18" }, { "name": "notes", "unicode": "1F3B6", - "digest": "bf3868386e17eac40ac7fbabea027042027ff061daafe406c869cdd8ce94641d" + "digest": "98467e0adc134d45676ef1c6c459e5853a9db50c8a6e91b6aec7d449aa737f48" }, { "name": "nut_and_bolt", "unicode": "1F529", - "digest": "fdb9d7408202fad7a52ff21608042c08c3b0beb195999fff233df36a29dc9e96" + "digest": "a77bd72f29a7302195dcec240174b15586de79e3204258e3fb401a6ea90563b3" }, { "name": "o", "unicode": "2B55", - "digest": "8e119dba4130bd33b3ee5c862fb4fa5a691173911ffee51cb9359fee3398e330" + "digest": "2387e5fd9ae4c2972d40298d32319b8fa55c50dbfc1c04c5c36088213e6951dd" }, { "name": "o2", "unicode": "1F17E", - "digest": "00d751124c25633611055bd61e74fc3f3d1779f0d09e1e707837686f613367b4" + "digest": "6a9ccb0bf394e4d05ffda19327cee18f7b9ed80367fc7f41c93da9bb7efab0bf" }, { "name": "ocean", "unicode": "1F30A", - "digest": "9b1fbfd2a64f417d0c2cb91085b29a12d14e15844bc21798bdee938bb7bf6222" + "digest": "1a9ca9848d4fb75852addfc10bf84eccf7caa5339714b90e3de4cb6f2518465e" }, { "name": "octopus", "unicode": "1F419", - "digest": "3fdfbc02f47ad434bdeb7f3a15cd4e8f8118ee1cd754627e358f1c2f4616f5e3" + "digest": "0fcc65c12f4b29ea75a8c4823d20838a7e6db6978fdcb536943072aa1460bc59" }, { "name": "oden", "unicode": "1F362", - "digest": "afed1c5166943e5803602ffacc67652e3b29ee4222a6c36aba2daf88bd21ad3c" + "digest": "089974cb13a0bef6a245fc73029c5ed5153fd4caae0177b835f779e32200b8aa" }, { "name": "office", "unicode": "1F3E2", - "digest": "dc1836ef152d88fd628df18db770594f5dbc8d7f20d6ce982588b25b78b19c92" + "digest": "3633a2e91036362e273eef4e0cfbdbbb4cb1208afe2cfa110ebef7b78109a66f" }, { "name": "oil", "unicode": "1F6E2", - "digest": "f8b7626cb09e229203105b9c8c7f3fbb38c0650021092fc50115ad517248644a" + "digest": "00b94d33bcc9b9e8a5d4bd6e7f7e2fced9497ce05919edd5e58eafbc011c2caa" }, { "name": "oil_drum", "unicode": "1F6E2", - "digest": "f8b7626cb09e229203105b9c8c7f3fbb38c0650021092fc50115ad517248644a" + "digest": "00b94d33bcc9b9e8a5d4bd6e7f7e2fced9497ce05919edd5e58eafbc011c2caa" }, { "name": "ok", "unicode": "1F197", - "digest": "6b05bbab4a7104541c2f4bce553884d17ae0ad07589b19d6b53b6949c14f2269" + "digest": "5f320f9b96e98a2f17ebe240daff9b9fd2ae0727cd6c8e4633b1744356e89365" }, { "name": "ok_hand", "unicode": "1F44C", - "digest": "9981f32ef200b011a10f6bfa2066c41b6b5e7bcd6c3c21647980b640bc1fa93b" + "digest": "d63002dce3cc3655b67b8765b7c28d370edba0e3758b2329b60e0e61c4d8e78d" }, { "name": "ok_hand_tone1", "unicode": "1F44C-1F3FB", - "digest": "e5933a9b64b03ce0634f15f02ff7b6424530dbdc0e283461e0c9992d0c2ca2ad" + "digest": "ef1508efcf483b09807554fe0e451c2948224f9deb85463e8e0dad6875b54012" }, { "name": "ok_hand_tone2", "unicode": "1F44C-1F3FC", - "digest": "4c04741c9f2c8731da8df3015e9aae00061a01848c2d22aab1e9853c271deed3" + "digest": "1215a101a082fd8e04c5d2f7e3c59d0f480cb0bedd79aeab5d36676bfe760088" }, { "name": "ok_hand_tone3", "unicode": "1F44C-1F3FD", - "digest": "216dc5a72f9e34bbb7b39f680c388bd5b52abf9b41b843342e53e285b7933076" + "digest": "6fe0ed9fb42e86bb2bed4cb37b2acacacda1471fb1ee845ad55e54fb0897fbf4" }, { "name": "ok_hand_tone4", "unicode": "1F44C-1F3FE", - "digest": "7139de7ec9d5a962cf87b9fbbeef3a53aa482bb840ab3b64d8d0da81bdc19886" + "digest": "bfb9041c49d95e901a667264abaf9b398f6c4aa8b52bf5191c122db20c13c020" }, { "name": "ok_hand_tone5", "unicode": "1F44C-1F3FF", - "digest": "e18b0a1bc5d970cc63466bd6da6e9f855db37d1eada3230d19f600c1f5a402a3" + "digest": "1c218dc04d698da2cbdd7bea1ca3f845f9b386e967b7247c52f4b0f6ec8f5320" }, { "name": "ok_woman", "unicode": "1F646", - "digest": "3b2fa732d9c9addb056f136192428e99d805d4cb1c7dab724fd552c7e93197e4" + "digest": "3f8bd4ce2c4497155d697e5a71ebdc9339f65633d07fa9a7903e1bd76cfa4ba1" }, { "name": "ok_woman_tone1", "unicode": "1F646-1F3FB", - "digest": "017aca3797701b043a44f22e67dcad8b531a3ca14e629ae0d2fbc601ed3e49cb" + "digest": "1660cd904ccd2ecdc6f4ba00527f7d4ec8c33f3c6183344616f97badae4c3730" }, { "name": "ok_woman_tone2", "unicode": "1F646-1F3FC", - "digest": "036bed032bc5a616668775cda0d5640c810e2836aa28009c8e8bf2b487259c59" + "digest": "7ba5fddd1e141424fac6778894dfc5af28e125839c58937c69496f99cd2c4002" }, { "name": "ok_woman_tone3", "unicode": "1F646-1F3FD", - "digest": "d9a4414caddda43d1a36828cfbecce5f2b7e5c1b67b4a47991b2ae0a34cf7ab7" + "digest": "1d972b8377c52f598406f59ab1e5be41aaf8f027e1fefba3deda66312ccd6a9b" }, { "name": "ok_woman_tone4", "unicode": "1F646-1F3FE", - "digest": "942e1b9aa495c4c4de0804e4d4348422201299d649e5d65829ba4a308880df1c" + "digest": "a176328d8f53503aa743448968afd21d72ffd3510555526a3fb38d6b30ee7c15" }, { "name": "ok_woman_tone5", "unicode": "1F646-1F3FF", - "digest": "e8d0fb5b999d5d63404493aa505b5af2260c76001023431d5e788773d0a9e2de" + "digest": "13cfc1b589c57e81f768ee07a14b737cafc71407a7eb0956728b2ec4b1df14c4" }, { "name": "older_man", "unicode": "1F474", - "digest": "620f763325827acbeb9d57798ef55d87827d0dfc77b84d942e25bc5057f2cbfe" + "digest": "4c0462b199bf26181c9e4d2d4cb878a32b0294566941212efc67362d0645f948" }, { "name": "older_man_tone1", "unicode": "1F474-1F3FB", - "digest": "e0f35c12362eae503d1c30a345c3a4978196d351d8a1eb9d5f107c60ea4bbf52" + "digest": "99baa083f78cb01166d0a928d0b53682be14be04c29fc17bef14aac1a73a61e6" }, { "name": "older_man_tone2", "unicode": "1F474-1F3FC", - "digest": "671766ce9fa47c3fa009d4f138344c87d73032a1c38e48614c663f8ea5d0f673" + "digest": "5b4ce713e8820ba517fe92c25f3b93e6a6bf3704d1f982c461d5f31fc02b9d3d" }, { "name": "older_man_tone3", "unicode": "1F474-1F3FD", - "digest": "6ff4885ef8c416b8970780a691fef74c8d89421ab11e0aa8c522c33e1c67fbe8" + "digest": "0eff72b3226c3a703c635798ee84129a695c896fa011fe1adbc105312eecc083" }, { "name": "older_man_tone4", "unicode": "1F474-1F3FE", - "digest": "0ae7d4e316dcd4d27a5a6cdaabab88a4f992bd1b75f6ceaeb5b906ed1eb5269c" + "digest": "ad9ba82b0c5d3b171b0639ee4265370dbddff5e0eeb70729db122659bb8c8f84" }, { "name": "older_man_tone5", "unicode": "1F474-1F3FF", - "digest": "abe2757bd5e35f30d2a6daec09637ea5382a46d14d239b77282e9bf874229b57" + "digest": "5eb0a7467cc40e75752e11fd5126b275863dc037557a0d0d3b24b681e00c2386" }, { "name": "older_woman", "unicode": "1F475", - "digest": "3ed599443eed25399aac999fc234c9e97f8fb6ec567e37a553c26e01021b097c" + "digest": "c261fdf3b01e0c7d949e177144531add5895197fbadf1acbba8eb17d18766bf6" }, { "name": "grandma", "unicode": "1F475", - "digest": "3ed599443eed25399aac999fc234c9e97f8fb6ec567e37a553c26e01021b097c" + "digest": "c261fdf3b01e0c7d949e177144531add5895197fbadf1acbba8eb17d18766bf6" }, { "name": "older_woman_tone1", "unicode": "1F475-1F3FB", - "digest": "7421c5dba67cfd1eeabb2fa8faf4aa0d615d23f191cf7d7c0ad9c1fa884edfda" + "digest": "1f2bb9e42270a58194498254da27ac2b7a50edaa771b90ee194ccd6d24660c62" }, { "name": "grandma_tone1", "unicode": "1F475-1F3FB", - "digest": "7421c5dba67cfd1eeabb2fa8faf4aa0d615d23f191cf7d7c0ad9c1fa884edfda" + "digest": "1f2bb9e42270a58194498254da27ac2b7a50edaa771b90ee194ccd6d24660c62" }, { "name": "older_woman_tone2", "unicode": "1F475-1F3FC", - "digest": "65edeef25648ac7f8be535df06af1286441691fa15176e99a6e83fc779aa2cde" + "digest": "2e28198e9b7ac08c55980677ed66655fd899e157f14184958bebd87fcd714940" }, { "name": "grandma_tone2", "unicode": "1F475-1F3FC", - "digest": "65edeef25648ac7f8be535df06af1286441691fa15176e99a6e83fc779aa2cde" + "digest": "2e28198e9b7ac08c55980677ed66655fd899e157f14184958bebd87fcd714940" }, { "name": "older_woman_tone3", "unicode": "1F475-1F3FD", - "digest": "5d27bbcc5796227a9caec1c7612d3f691055655b96f7303e420839463d76c269" + "digest": "c968be0170f7e0c65d4f796337034cfb1daba897884da6fad85635ab5b6edf67" }, { "name": "grandma_tone3", "unicode": "1F475-1F3FD", - "digest": "5d27bbcc5796227a9caec1c7612d3f691055655b96f7303e420839463d76c269" + "digest": "c968be0170f7e0c65d4f796337034cfb1daba897884da6fad85635ab5b6edf67" }, { "name": "older_woman_tone4", "unicode": "1F475-1F3FE", - "digest": "75b858e910175fc0233503d672120fd43ac035ba3fd2052fbb44df39f6e3695c" + "digest": "3596a6fa9a643bf79255afcd29657b03850df8499db9669b92ce013af908af44" }, { "name": "grandma_tone4", "unicode": "1F475-1F3FE", - "digest": "75b858e910175fc0233503d672120fd43ac035ba3fd2052fbb44df39f6e3695c" + "digest": "3596a6fa9a643bf79255afcd29657b03850df8499db9669b92ce013af908af44" }, { "name": "older_woman_tone5", "unicode": "1F475-1F3FF", - "digest": "9da1cf10a605c470877d7f4a840f99344b1ec2e7b1ec7db61e930cde77025e3b" + "digest": "c8998cb3dbd15e22bd1d6dad613d109ce371d9ffca3657e1a8afe5aeb30c1275" }, { "name": "grandma_tone5", "unicode": "1F475-1F3FF", - "digest": "9da1cf10a605c470877d7f4a840f99344b1ec2e7b1ec7db61e930cde77025e3b" + "digest": "c8998cb3dbd15e22bd1d6dad613d109ce371d9ffca3657e1a8afe5aeb30c1275" }, { "name": "om_symbol", "unicode": "1F549", - "digest": "c8c1c9d445b1fc50a627b71bee21fba978e04532e4685ec032a0174f51fc12bb" + "digest": "5ead73bea546ba9ba6da522f7280cc289c75ff5467742bdba31f92d0e1b3f4e6" }, { "name": "on", "unicode": "1F51B", - "digest": "08e1159a68d3334a87ffa75b9e70826cb557d0f73a2c1d08f4c3d60476ecacc8" + "digest": "9cc61a6b31a30c32dab594191bf23f91e341c4105384ab22158a6d43e6364631" }, { "name": "oncoming_automobile", "unicode": "1F698", - "digest": "6bff7f40fe223df6d16c7512532b8aa6f83e8c13e1007b63eb9aabf774c1a322" + "digest": "557c9cacdc3f95215d4f7a6f097a2baa7c007cb9c519492a6717077af4ca6b56" }, { "name": "oncoming_bus", "unicode": "1F68D", - "digest": "127a357fcd96ce4b9ab11c3dba95d8ff811bab193dd8ba38efb7067a44752ce8" + "digest": "059f28ce6bfb337e107db5982cbd2004844450ef20b4a54b9ca3cb738360ab05" }, { "name": "oncoming_police_car", "unicode": "1F694", - "digest": "57cb70e05e70c1f68ab42259f307ed9782c2b9d6e35d2dff2895aa23d7eb6b04" + "digest": "aee79306a0d129cfc1980f58db80391eb46d2d7d5f814bf431414dc7680cab72" }, { "name": "oncoming_taxi", "unicode": "1F696", - "digest": "174967ae4c3d5881d2408c71c020f704e933190af4caef5d2908e9ac382f35ea" + "digest": "84351489fc86d980b8d3eb9ec4e81120fe700b3ac01346daebe2b7aeb9607a55" }, { "name": "one", "unicode": "0031-20E3", - "digest": "113b9d87c3e37c9c54e49cecccbfc40c15fb97fd03a51505df85e48b78702b2b" + "digest": "d5d3fff04e68a114ff6464ee06fc831f3f381713045165f62a88d5e8215c195b" }, { "name": "open_file_folder", "unicode": "1F4C2", - "digest": "def93715203aed464211798d773732895a19389a94a2e7ed43e7f229b2aab7da" + "digest": "96cfc322ee4903ae8cec07604811742245fd7d14f00bb70276d39d29c48bed28" }, { "name": "open_hands", "unicode": "1F450", - "digest": "7c60a37ae11727c998908199b8709e52593b931843aef942f37b306b1edca12a" + "digest": "a6c131da2040b48103cea14f280e728675da50fa448d2b3f3438fcbb5bf5596a" }, { "name": "open_hands_tone1", "unicode": "1F450-1F3FB", - "digest": "09ffa9b3f28fc56a71e4e711bdfc87ce1a56721229377e71f1c00224523f8b9b" + "digest": "867128dff2fa9b860c10c6b792f989f0c057928783696062378f834c0ef89d85" }, { "name": "open_hands_tone2", "unicode": "1F450-1F3FC", - "digest": "21ecaba9f086bcb7eb07c17c2b2621bcd1ca28c57f79032d5e0eba356494cc85" + "digest": "487ff2745b03d49bb3b1d0acd86ba530fd8cc3f467ca3fa504f88f0ef1cbbc01" }, { "name": "open_hands_tone3", "unicode": "1F450-1F3FD", - "digest": "c7dbb8c44f78f7793b202ec215fee42b7e1e555d659fbf402383500217b89656" + "digest": "cb8cddc8b8661f874ac9478289d16cc41406b947bb87f3363df518a588a53e16" }, { "name": "open_hands_tone4", "unicode": "1F450-1F3FE", - "digest": "867451d42492ab2277687447f421f744530b9ea057312326353fec39c94b18fd" + "digest": "17dcc2c07230846a769f3c79ce618a757c88b9b58c95c6c5b2d7f968814d447d" }, { "name": "open_hands_tone5", "unicode": "1F450-1F3FF", - "digest": "56335506cf68e29150cb68d7ebbb4a92aed390018966669a8144d20ae0d6cfe3" + "digest": "36b2493d67c84cea4f3f85a3088c6abcfd35cf99f7aeaeedfafa420ee878e3d2" }, { "name": "open_mouth", "unicode": "1F62E", - "digest": "f05fdf998e8b5c0b00ebd8b5ab17a67f5c0a45275f31a201af74e8ab0c2f7ba9" + "digest": "1906c5100ae0c8326ca5c4f9422976958a38dadd8d77724d68538a25d9623035" }, { "name": "ophiuchus", "unicode": "26CE", - "digest": "98c61bb0c36d60c476d42d5e074297662e8d141dcab7004a5bd63c359eed3b84" + "digest": "6112e2a1656b1cb8bd9a8b0dfa6cbf66d30cae671710a9ef75c821de344aab2b" }, { "name": "optical_disk", @@ -7872,27 +7872,27 @@ { "name": "orange_book", "unicode": "1F4D9", - "digest": "86d150ea3d62183ab7dfe2851cf7f4d1ae769b7ecbb1987b0f463e639e429598" + "digest": "41141b08d2beceded21a94795431603c47fd7d42a3a472a2aa8b2bb25fa87ebf" }, { "name": "orthodox_cross", "unicode": "2626", - "digest": "9c861285ca6d699cd2c72b6df44ec2b1e64138152f19c66e32df1ce770ff2e83" + "digest": "c16372102f0169dd6d32eb2b27a633aaee74e4e0fddcf723c15ad97f9dc6075c" }, { "name": "outbox_tray", "unicode": "1F4E4", - "digest": "b6a6015d5d7d528af485de23ff4518dc35408def1cc49bc6c9b01d880d613985" + "digest": "e47cb481a0ffcb39996f32fd313e19b362a91d8dda15ffca48ac23a3b5bb5baf" }, { "name": "ox", "unicode": "1F402", - "digest": "cbcfe5c8c4d6b939e24e18e610785f171bb9410441e02c2eeb1bceb0a6246daf" + "digest": "d13bc60552190bb9936bf32d681bdc742439b702a09cfc62137ea09a98624aed" }, { "name": "package", "unicode": "1F4E6", - "digest": "4023cffce85384217a73609f457aec013876e689c44bcfff0bcc35f3e4e1ab00" + "digest": "e82bf5accebb65136e897c15607eef635fb79fd7b2d8c8e19a9eb00b6786918c" }, { "name": "page", @@ -7902,17 +7902,17 @@ { "name": "page_facing_up", "unicode": "1F4C4", - "digest": "71a0872bf1b13c58746f9b41655227c75be107ab6083c0dce13cb16444af22e7" + "digest": "3884868bdcb2f29615b09a13a30385cbc5269379094a54b5a7e8a5f4e8ce905a" }, { "name": "page_with_curl", "unicode": "1F4C3", - "digest": "cb4210464faea946c7b07db7067c7fc98920f778cf57721388f5362942ba3029" + "digest": "3d6257670189f841ad1fa45415c34feb2433b2cb35bb435c4ee122ce89b39669" }, { "name": "pager", "unicode": "1F4DF", - "digest": "209dbdc19aa650ecacc0569e17a9123c9a1e39df59c9b4120f3b0888b63cd6f1" + "digest": "e21c756cc1c58ebc1b37ebcd38e22a25b31e2e81306c6f18285d6a7671f9eb12" }, { "name": "pages", @@ -7922,132 +7922,132 @@ { "name": "paintbrush", "unicode": "1F58C", - "digest": "73eb33184f5f495d6c2699fafc1a8680069f82a70fbe519290c3a2ce30d1aee9" + "digest": "fc0da7a25b726b8be9dd6467953e27293d2313a21eeff21424c2a19be614fff2" }, { "name": "lower_left_paintbrush", "unicode": "1F58C", - "digest": "73eb33184f5f495d6c2699fafc1a8680069f82a70fbe519290c3a2ce30d1aee9" + "digest": "fc0da7a25b726b8be9dd6467953e27293d2313a21eeff21424c2a19be614fff2" }, { "name": "palm_tree", "unicode": "1F334", - "digest": "1589ff4b1b87296edc0118e4aa67b3b504ed85a5b8d47e7d0c3e309d0bbf8cd6" + "digest": "90fedafd62fe0abf51325174d0f293ebb9a4794913b9ba93b12f2d0119056df1" }, { "name": "panda_face", "unicode": "1F43C", - "digest": "050ee87892f56ff485f460bc6c3846d98a0ca7083d2cf0b8ab24772b672273f2" + "digest": "56a4b84abe983bd6569be1b81ac5e43071015fd308389a16b92231310ae56a5b" }, { "name": "paperclip", "unicode": "1F4CE", - "digest": "1463607a59345973f009fa53a719e2264b95743560adb99737bef29b1d133a95" + "digest": "d1e2ce94a12b7e8b7a9bba49e47ddc7432ec0288545d3b6817c7a499e806e3f0" }, { "name": "paperclips", "unicode": "1F587", - "digest": "7071e031f4a100c3cb3573fbfa375360043f0276289a0818f2ffaf71b3580040" + "digest": "70cefa0d0777f070e393e9f95c24146fe2dd627f30fa3845baa19310d9291fe2" }, { "name": "linked_paperclips", "unicode": "1F587", - "digest": "7071e031f4a100c3cb3573fbfa375360043f0276289a0818f2ffaf71b3580040" + "digest": "70cefa0d0777f070e393e9f95c24146fe2dd627f30fa3845baa19310d9291fe2" }, { "name": "park", "unicode": "1F3DE", - "digest": "d257f0f1b1a0134573f80ba1a5f522a91c320ee7f93a1cb64877c077e7e19b50" + "digest": "444dce8014e0817ddd756c36a38adfbbf7ae4c6aa509e4cae291828f0716d5e7" }, { "name": "national_park", "unicode": "1F3DE", - "digest": "d257f0f1b1a0134573f80ba1a5f522a91c320ee7f93a1cb64877c077e7e19b50" + "digest": "444dce8014e0817ddd756c36a38adfbbf7ae4c6aa509e4cae291828f0716d5e7" }, { "name": "parking", "unicode": "1F17F", - "digest": "e1d2cfd1c57ea85003ca4df066cbba4e506bf6c4d6c790e27b2f78ad8443fabf" + "digest": "9f1da460a7dd58b26beab8cf701be2691fb812208fbc941c71daa35be1507c2f" }, { "name": "part_alternation_mark", "unicode": "303D", - "digest": "b3cc2e803b255e858417345ba6ba52a1c22f511b483fec11b5d68c4432f759b6" + "digest": "956da19353bb38fd4dfe0ab5360679a9035d566858fb5de62887b85c75fb8eef" }, { "name": "partly_sunny", "unicode": "26C5", - "digest": "484990f5e1a3b14c731e7bd4b0b4a1c10cd5fb54ac7cf2751f40c8bf59d7e2b4" + "digest": "8fb9a6d2caf9e0cce58447762f0dfd6aa0b581b2e83fea6411348e0cbc8cf3c4" }, { "name": "passport_control", "unicode": "1F6C2", - "digest": "224e8ef60d4d6587721727555de324948fb5b6c1cb5cc4b546960983d1ec85c4" + "digest": "d9be6eed2c90e1c89171c42d70a06485fdf86a4c68833371832cc1f6897fadd0" }, { "name": "pause_button", "unicode": "23F8", - "digest": "edd605ffaa39a7905ed0958b7cc69f00f5b271e579198d2df1746ad1b3648272" + "digest": "143221d99e82399ed7824b6c5e185700896492058b65c04e4c668291de78b203" }, { "name": "double_vertical_bar", "unicode": "23F8", - "digest": "edd605ffaa39a7905ed0958b7cc69f00f5b271e579198d2df1746ad1b3648272" + "digest": "143221d99e82399ed7824b6c5e185700896492058b65c04e4c668291de78b203" }, { "name": "peace", "unicode": "262E", - "digest": "e0ee8a5c9fb18d5db6841b21527ed8fd955abdff9ffdb7b2684dca22107015fc" + "digest": "65181429e373c1f0507bbd98425c1bec0c042d648fb285a392460cbce60f44d4" }, { "name": "peace_symbol", "unicode": "262E", - "digest": "e0ee8a5c9fb18d5db6841b21527ed8fd955abdff9ffdb7b2684dca22107015fc" + "digest": "65181429e373c1f0507bbd98425c1bec0c042d648fb285a392460cbce60f44d4" }, { "name": "peach", "unicode": "1F351", - "digest": "a3f4fd5ff02e0a03104ab54456ee1a7521858ee68443856ee10e0972e5b6aaa5" + "digest": "768d1f4f29e1e06aff5abb29043be83087ded16427ce6a2d0f682814e665e311" }, { "name": "pear", "unicode": "1F350", - "digest": "7a7a72568d53677cd1fff4d9e58e63327a742fa16d22a2bef03b4a6fa378d3b3" + "digest": "b7c9cf90bb979649b863d2f4132f1b51f6f8107d42e08fb8b4033fea32844948" }, { "name": "pen_ballpoint", "unicode": "1F58A", - "digest": "6becdc6f622c774bb09b7e7592bba2123ecccc9de32a35f0b18b50d7d54109cb" + "digest": "aacb20b220f26704e10303deeea33be0eec2d3811dcba7795902ca44b6ae9876" }, { "name": "lower_left_ballpoint_pen", "unicode": "1F58A", - "digest": "6becdc6f622c774bb09b7e7592bba2123ecccc9de32a35f0b18b50d7d54109cb" + "digest": "aacb20b220f26704e10303deeea33be0eec2d3811dcba7795902ca44b6ae9876" }, { "name": "pen_fountain", "unicode": "1F58B", - "digest": "8c78cf0c2bd1d5e309d2d3356ff207e3fc76ca18dd6b90762cb62f6afbc95c6a" + "digest": "3619913eab2b6291f518b40481bb3eca0820d68b0a1b3c11fb6a69c62b75a626" }, { "name": "lower_left_fountain_pen", "unicode": "1F58B", - "digest": "8c78cf0c2bd1d5e309d2d3356ff207e3fc76ca18dd6b90762cb62f6afbc95c6a" + "digest": "3619913eab2b6291f518b40481bb3eca0820d68b0a1b3c11fb6a69c62b75a626" }, { "name": "pencil", "unicode": "1F4DD", - "digest": "62b7ee5d9352114d09ee6f2c9a4c5e8b79f775a6c509e82ddfcdd61e13716249" + "digest": "accbc3f1439b7faa4411e502385f78a16c8e71851f71fc13582753291ffb507c" }, { "name": "memo", "unicode": "1F4DD", - "digest": "62b7ee5d9352114d09ee6f2c9a4c5e8b79f775a6c509e82ddfcdd61e13716249" + "digest": "accbc3f1439b7faa4411e502385f78a16c8e71851f71fc13582753291ffb507c" }, { "name": "pencil2", "unicode": "270F", - "digest": "aa2c572772187fee1f9125bb0950f5ce8a61f7dd2647258c40b4077ee5feb498" + "digest": "9ca1b56b5726f472b1f1b23050ed163e213916dac379d22e38e4c8358fe871e0" }, { "name": "pencil3", @@ -8062,7 +8062,7 @@ { "name": "penguin", "unicode": "1F427", - "digest": "095de34b3f6a2521a342c21f5f2551a0092bf47429801c15b7bbf0913924f412" + "digest": "a1800ab931d6dc84a9c89bfab2c815198025c276d952509c55b18dd20bd9d316" }, { "name": "pennant_black", @@ -8087,147 +8087,147 @@ { "name": "pensive", "unicode": "1F614", - "digest": "2d9e7f1eed14dcc86674cec78e992567a40d0f223fc67d722b91eebcd1251269" + "digest": "d237deff9f5ead8a0b281b7e5c6f4b82e98cc30c80c86c22c3fdc6160090b2f2" }, { "name": "performing_arts", "unicode": "1F3AD", - "digest": "a202755bab6427433975589bb8b63e61e5d7f55c6242676d8000e91eedabc55e" + "digest": "d7c7bc9213e308ca26286cbbd8012e656b0f9b00293758faf1bfccc4c5ceabed" }, { "name": "persevere", "unicode": "1F623", - "digest": "686ef3fc70ce8294d02a764ebd75b69f25cca6bff6b92e7905130366d22f6d8a" + "digest": "c361509c9b8663af19a02a1ffff61b1b0d0b4bd75d693ce3d406b0ca1bde1ca0" }, { "name": "person_frowning", "unicode": "1F64D", - "digest": "16e8fbf22c0b4c237d0d45202fa32d1ebd04760a5b6975c9c9b477321ccb0e12" + "digest": "b37be8bd95f21a6860ad3f171b8086125ab37331b382d87bcdb4cd684800546b" }, { "name": "person_frowning_tone1", "unicode": "1F64D-1F3FB", - "digest": "a143b865976ce3cf307db854cfd1ca58c3832df0eee5e9b0ab307cf4f24ba3db" + "digest": "3d5e78a367f9673baed2a86bc11cf04fd44394aadb65291fa51ade8dca318427" }, { "name": "person_frowning_tone2", "unicode": "1F64D-1F3FC", - "digest": "4e7050d8a38019ba2293f66b9930e6a7e35dacf3b3bc9431edb586a0d9ea8054" + "digest": "7456c414c65ad6b6f11855f68a2eedc18113526f86862c4373202397cb1bed2c" }, { "name": "person_frowning_tone3", "unicode": "1F64D-1F3FD", - "digest": "0750015d3ac1b5954d31e36cd59c70b6ed9f4df698082484b7ac59eb0b9964b0" + "digest": "c86cf2d6951f1e6a7c786a74caaf68a777cf00e88023e23849d4383f864ae437" }, { "name": "person_frowning_tone4", "unicode": "1F64D-1F3FE", - "digest": "18d6cc92d0990624218d38d6eeed60bccb371d0fc9f1c889e9476b3b0c44b5e8" + "digest": "944e96ced645ced8db6bb50120c7e37ed46b6960d595cbfe964c81803efa83aa" }, { "name": "person_frowning_tone5", "unicode": "1F64D-1F3FF", - "digest": "4a898199cbaf083d37511f51d8a1d2560b7a20c62a1b09087831da7010fbd093" + "digest": "4bd0ea571be6ef9f0493784ef0d12d5e47bc2d6ac610fb42c450bf3d87fb2948" }, { "name": "person_with_blond_hair", "unicode": "1F471", - "digest": "67d95a0801c65f62db55fa80ab35dec65c239601a44bf5f5902e4645f126770e" + "digest": "a7f94ede2e43308108c2260d83fc10121dda09a67f94a0a840e6d7bba7fd5616" }, { "name": "person_with_blond_hair_tone1", "unicode": "1F471-1F3FB", - "digest": "e79717bfe30a26eafc082a75fa7547d8f2ad3c123fb2d75a95e75f0ce7ecbd0c" + "digest": "00a116357a7878554c83e5bade4bddfa9cfabf76a229efa19cbb58e0d216219c" }, { "name": "person_with_blond_hair_tone2", "unicode": "1F471-1F3FC", - "digest": "c4a1961c292149ab6e1fd54a7894398599bf855de97a05ee4e836a86a400deb3" + "digest": "df509ebe92ed3138b9d5bd4645eff4b13f77f714cf62bb949c59eff1adc00019" }, { "name": "person_with_blond_hair_tone3", "unicode": "1F471-1F3FD", - "digest": "e2707d0cf778bee5b72d861ec76430eb1cf9f9820f066ee6327574d5697f445e" + "digest": "6f328513f440a0c8cd1dc44596a5028fd8f306bdaf57c1e6f3aa94a3aa262b3c" }, { "name": "person_with_blond_hair_tone4", "unicode": "1F471-1F3FE", - "digest": "94da43f0b12ef4a98dabec096ff1184b0a9b5b6ee55824d257e5112cc7e88730" + "digest": "32df1a577815b009696643ad80d063cc97b35d54add6d4e5517fc936f6da9ee8" }, { "name": "person_with_blond_hair_tone5", "unicode": "1F471-1F3FF", - "digest": "9e096a210ea720d32bc6a7005cd77f8b314ccf817fc3060da2e1796de39e9d60" + "digest": "2e270bb39187d8e36a33f4aa4d6045308189595fafc157cf7993e82d7ce93442" }, { "name": "person_with_pouting_face", "unicode": "1F64E", - "digest": "8c3199a422250d2db9a163156191ed2c6697d7f31699e2efe19e05ca26e5d225" + "digest": "57e9a6e5f82121516dc189173f2a63b218f726cd51014e24a18c2bdfeeec3a0b" }, { "name": "person_with_pouting_face_tone1", "unicode": "1F64E-1F3FB", - "digest": "3e1f09bbf607381c992739ea92dd35cbd26b1bbc705a7d21b7c3156f50e9d8b3" + "digest": "d10dadb1ac03fc2e221eff77b4c47935dc0b4fe897af3de30461e7226c3b4bbc" }, { "name": "person_with_pouting_face_tone2", "unicode": "1F64E-1F3FC", - "digest": "b5fc1cf3fdc5ff01105ee2452db90baa6a52c1e42f3795b2836c3e35197ece1f" + "digest": "efface531537ab934b3b96985210a2dac88de812e82e804d6ec12174e536d1cc" }, { "name": "person_with_pouting_face_tone3", "unicode": "1F64E-1F3FD", - "digest": "e8ec2539c458a8283c8c1050634c432b6363f3e64b68ba4c977994782f09b564" + "digest": "7ff26ece237216b949bfa96d16bd12cfd248c6fd3e4ed89aa6c735c09eafaeff" }, { "name": "person_with_pouting_face_tone4", "unicode": "1F64E-1F3FE", - "digest": "5cab7a29699decd45682583446c2bf56ddcd69cd16e14db661b526a4076dfa17" + "digest": "045c04105df41d94ff4942133c7394e42ff35ef76c4ccb711497ab77ae6219f2" }, { "name": "person_with_pouting_face_tone5", "unicode": "1F64E-1F3FF", - "digest": "3caebd3626fd77d849859d1c99a747f80a2b59bfa5c1854494f1ce0485539a94" + "digest": "783ee37f146fcf61d38af5009f5823cf6526fe99ed891979f454016bce9dd4ba" }, { "name": "pick", "unicode": "26CF", - "digest": "24a3e8f592435b97272e6d134ea5503dce3012811659c4aadbad4e45d9fba679" + "digest": "7f0ec5445b4d5c66cf46e2a7332946cce34bd70e9929ac7a119251a7f57f555d" }, { "name": "pig", "unicode": "1F437", - "digest": "50b55fc74e8f6c89c6e04609381c99a660748908f0ef015f5da37089678ad0c3" + "digest": "51362570ab36805c8f67622ee4543e38811f8abb20f732a1af2ffbff2d63d042" }, { "name": "pig2", "unicode": "1F416", - "digest": "e8189fb678608e8b9d69e11d2566f9a4765cbdff99ec8e66df30c7a2dabf742f" + "digest": "67010e255f28061b9d9210bcdab6edc072642ad134122a1d0c7e3a6b1795a45b" }, { "name": "pig_nose", "unicode": "1F43D", - "digest": "7e299cb49a771884f5065c68733a5a1fe354a54cff009127230177f1717af4a5" + "digest": "0b21cac238bf4910939fbea9bed35552378c1b605a3867d7b85c1556dbda22a9" }, { "name": "pill", "unicode": "1F48A", - "digest": "53ae3379cc6721744979122569f157a5a13aa6b48e081a89f17b2d90134efe9e" + "digest": "cb00be361aaba6dbcf8da58bd20b76221dd75031362ecae99496b088ed413a7f" }, { "name": "pineapple", "unicode": "1F34D", - "digest": "ceda8ffa4a41594f28a4e69d03f8a1daeb2ba20740f0b8c56447cae833eea035" + "digest": "621d4d4c52b59e566c2e29ed7845c8bd2d1da0946577527342097808d170dd70" }, { "name": "ping_pong", "unicode": "1F3D3", - "digest": "dd2a84716c93410a285ff759bfbc2dc31a10f90b203c7a657b908e5949e89a39" + "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1" }, { "name": "table_tennis", "unicode": "1F3D3", - "digest": "dd2a84716c93410a285ff759bfbc2dc31a10f90b203c7a657b908e5949e89a39" + "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1" }, { "name": "piracy", @@ -8242,322 +8242,322 @@ { "name": "pisces", "unicode": "2653", - "digest": "75f11b9a094196b54a242420362fa7c0aeba7cfc497b187e1aaaba96d93684a7" + "digest": "453c3915122a4b6b32867056d2447be48675a84469145c88d52f8007fcb0861a" }, { "name": "pizza", "unicode": "1F355", - "digest": "ac94ae1c034f7b854ce2a483e1c219d101a84336f5065342f4824ff32ba705c4" + "digest": "169bc6c1e1d7fdab1b8bf2eab0eeec4f9a7ae08b7b9b38f33b0b0c642e72053a" }, { "name": "place_of_worship", "unicode": "1F6D0", - "digest": "4fabc307b7e35f94288f6d53985485662a4814b11a9a382f0a3873d41b1290d3" + "digest": "daf271d36a38ee8c0f8b9de84c128ab8b25a5b7df8f107308d0353c961f2c644" }, { "name": "worship_symbol", "unicode": "1F6D0", - "digest": "4fabc307b7e35f94288f6d53985485662a4814b11a9a382f0a3873d41b1290d3" + "digest": "daf271d36a38ee8c0f8b9de84c128ab8b25a5b7df8f107308d0353c961f2c644" }, { "name": "play_pause", "unicode": "23EF", - "digest": "d69e8cdec33447283cf65d343b986115e27681d781b721db7894e5c587ca18ad" + "digest": "af1498f34a3d6e0da8bbd26ebaa447e697e2df08c8eb255437cf7905c93f8c42" }, { "name": "point_down", "unicode": "1F447", - "digest": "685f46a643be7f3033896e59a822f87d61ce50db6969bcdbacc743215a96bb7a" + "digest": "4ecdb3f31c16dc38113b8854ec1a7884613b688a185ebdf967eab9a81018f76d" }, { "name": "point_down_tone1", "unicode": "1F447-1F3FB", - "digest": "d3dd2608fe17d5649c960fcf8dbdb68466908d80fa349b7947b457da2a27ebb1" + "digest": "c74a7c94367cddbfa840542dc0924adeb0d108be0c7fde8c25fb95d69115d283" }, { "name": "point_down_tone2", "unicode": "1F447-1F3FC", - "digest": "67ab236a14f6d63abcdb26433a66a183d223186c21ebc9f978fab50165ebe271" + "digest": "dc4bda0726d85418b974addb42738f437fbb9cf16e5815cdbab3859c4ada6cae" }, { "name": "point_down_tone3", "unicode": "1F447-1F3FD", - "digest": "c8a2368f2cedb5bbb5cc0195b97fbf3787747637bf6e77bdc9a4edf4a3f22a04" + "digest": "e460f81a501376d2f0ed1d45e358c5ed03ba049e8f466e4298afb4f3ca6d24dc" }, { "name": "point_down_tone4", "unicode": "1F447-1F3FE", - "digest": "6a92eab3bc8f950fa423e690f54a352887bda92f01e91c62eb3f3a9544c10cd8" + "digest": "4bc91cd771f24e0f897a9d8b18f323fec9a82da0fc2429c4a7e4e6a9d885a0a3" }, { "name": "point_down_tone5", "unicode": "1F447-1F3FF", - "digest": "6ad329f156414f421d6f8cf5e2a68d34b7a41f90d80e8e66b15bcbd3788126c7" + "digest": "7e47c6bc73250f36dc7ae1c1c09e7b41f30647b9d0ff703a53a75cc046b5057d" }, { "name": "point_left", "unicode": "1F448", - "digest": "cb520d6bba4c2b3bd7911315c9efce3261d048ff090437d7e24c9c5a7255043e" + "digest": "b5a7e864a0016afbadb3bec41f51ecf8c4af73cc20462e1a08b357f90bca6879" }, { "name": "point_left_tone1", "unicode": "1F448-1F3FB", - "digest": "81813901bdaa8d261277f79aff9e9a21beb80a5855899941820b25f70786ec21" + "digest": "9f1868272a10a2b738c065be5d30241643324550cfd47baf01c7a09060e66d31" }, { "name": "point_left_tone2", "unicode": "1F448-1F3FC", - "digest": "ecdc3dea0d644290aa7e0dab758c215822482a482ba35d825a33152453593c1e" + "digest": "bf0d58c68178a2c2c01d4a6235a1a66b90073cea170f9f6fe2668b6dd68424f7" }, { "name": "point_left_tone3", "unicode": "1F448-1F3FD", - "digest": "84e73b6a37755016271c255eba164f349dbd2a2badf5d9ac1c6f4cbfcae589f0" + "digest": "34d28c97bc8f9d111d14e328153c4298fc32cf18e39e20aacaec17846645ed90" }, { "name": "point_left_tone4", "unicode": "1F448-1F3FE", - "digest": "d16800499b6c6ede94256796b1de8a8f723879f636849856b3bd8b7a092b5576" + "digest": "c40c8436316915d516c53bb1c98a469528cefd98baa719be7e748c4608cbbcc9" }, { "name": "point_left_tone5", "unicode": "1F448-1F3FF", - "digest": "18b7108066cebf2d4090f29e595a2f01db94bd210f3b1d61dc269ec249a749b9" + "digest": "c410fe32e4ce0ded74845a54b86090e59e5820d457837b16e175b36cc71ecb46" }, { "name": "point_right", "unicode": "1F449", - "digest": "866180bf31e92de32aba336d5b5ce81773a29cdaadada1d93c944cf9ad9783bc" + "digest": "44d9251ab41f2f48c2250c44a47f92b3476a71f13fbbbfb637547db837fd5a49" }, { "name": "point_right_tone1", "unicode": "1F449-1F3FB", - "digest": "ebe2e4bf6bd46a5798b9a845a4ed055911c4fe58dbeacc4d39d6ea63e28e7cc9" + "digest": "9fcce259eb81c0b52ec7796b98a1653194e3a9021a1d338df1dbbab7522fc406" }, { "name": "point_right_tone2", "unicode": "1F449-1F3FC", - "digest": "b638662a67b1c6adde4f5abc789aae010b178404cdd1b71fcc982cdf8307c655" + "digest": "9d00a0b1cfc435674dc56065b3d28d28839196977504cf20581205351d8708f2" }, { "name": "point_right_tone3", "unicode": "1F449-1F3FD", - "digest": "32c6ca2f992416ab2c36672dfbc1c0de8f102c77a13496dd8d63736a7b0261d2" + "digest": "e3026a70630ba73d76892a055a80cac2f78d509faddce737f802d2abefa074ba" }, { "name": "point_right_tone4", "unicode": "1F449-1F3FE", - "digest": "89bd6828e9b82408a3829d49fa43332e2599f7d10bc6e5b14b750ef03267b173" + "digest": "ea508fde90561460361773b4e1b8e80874667b19ac115926206e7c592587cb76" }, { "name": "point_right_tone5", "unicode": "1F449-1F3FF", - "digest": "390525048a12b0efa22de550c800e439b0deaad03f1f31155d179aef093354af" + "digest": "d59cdb2864eb2929941ecd233f8b8afcddc30fbd4594e5f9acf6386ae06ac12c" }, { "name": "point_up", "unicode": "261D", - "digest": "31b5ca1303c1afabe1db322b24f73b23f3568c87a364f61c82f6e0c858c090e9" + "digest": "b69ff4f650989709f2185822d278c7773672bd9eb4a625da80f3038a2b9ce42b" }, { "name": "point_up_2", "unicode": "1F446", - "digest": "55c237054aa347c9847f3f3f577eb755db55dfcf793aa7de0f8f868574d70e8f" + "digest": "e83cd9eff2af5125a25f5a306c3ee3cfea240add683b5c36a86a994a8d8c805c" }, { "name": "point_up_2_tone1", "unicode": "1F446-1F3FB", - "digest": "dc07e7732d973de96ae3b08b14c19e20b6c1aea7f5a30e7198679b750422e914" + "digest": "b02ec3e7e04a83bfb769cffb951cbf32aa78e56fa5a51c097f9326df9e08ed33" }, { "name": "point_up_2_tone2", "unicode": "1F446-1F3FC", - "digest": "af2211fc4a1bd51d1e76f7bc43a6fa87bdd24e4295c52fdbdb01c1ca670a6cd7" + "digest": "32994b85c8b4a1383ca985ebc3382be88866cea1ff1315adfb71fb05e992a232" }, { "name": "point_up_2_tone3", "unicode": "1F446-1F3FD", - "digest": "917701169b3fb3e1b6e14a68e9572b25998ef2e38abac9ad8cf30100f8ea0dac" + "digest": "9e263bcfb82ada34ff85291f36e64e66b86760fb11a4e0c554e801644d417d6d" }, { "name": "point_up_2_tone4", "unicode": "1F446-1F3FE", - "digest": "20843904764c6c3e55792cce0c55c76f72b97788c5229cad655ebf1f2873b439" + "digest": "3edc92130a0851ac7b5236772ce7918d088689221df287098688e1ed5b3ff181" }, { "name": "point_up_2_tone5", "unicode": "1F446-1F3FF", - "digest": "1d0cca546027c717da50f90da65757af46fe7cd4e397da9b8e203446f707208d" + "digest": "cabb3b7da9290840ef59d0c8b22625bdb2e94842f01b0a575ccbc348f3069d77" }, { "name": "point_up_tone1", "unicode": "261D-1F3FB", - "digest": "5ede60379dee23166c6b834d73da8b55268e330f67058843b8a3705dca6ed71a" + "digest": "e496fda349072f8b321ceb7a251175f7244c3076661f5ede48ea75ba1acf8339" }, { "name": "point_up_tone2", "unicode": "261D-1F3FC", - "digest": "c94a15ef848d410aa5d32b8d0e453b59682fde6f39e6705cbb81cf0829833a81" + "digest": "5a8081323f3baa67e6431e21e16a36559b339f5175d586644e34947f738dd07a" }, { "name": "point_up_tone3", "unicode": "261D-1F3FD", - "digest": "d319ce72876d97a3b1d4bc7c0679e546a983f02145d723a0da5ed0b73a51cfe7" + "digest": "07bf0cea812eb226b443334e026e13d1ec23e013478f4af862a3919703107842" }, { "name": "point_up_tone4", "unicode": "261D-1F3FE", - "digest": "9171a27f86f27fd144347a17153fb56e30bd32e67a8f10f8c1f32a40cad4e009" + "digest": "1fbbd71433108143ee157d0fdadd183f7f013bafa96f0dd93b181e1fd5fd4af2" }, { "name": "point_up_tone5", "unicode": "261D-1F3FF", - "digest": "a894f87da4c3d33d5e6e74d003a33ec60c453db6507fe05d22235f807ead27d6" + "digest": "ad068ef32df32f8297955490a9a90590a0f93ed5702a052cd0d8f6484c6cc679" }, { "name": "police_car", "unicode": "1F693", - "digest": "7999869cb75be404fc34942b6f9d8e84fa7e259aa892a1e8e1652a5f02cceea6" + "digest": "0909be1bd615ae331a7cce71e16dee3ca663c721d5170072c593cb7c76f9f661" }, { "name": "poodle", "unicode": "1F429", - "digest": "8a568d8688bf19b440b7c1b49fcfe6672b8f75af0031d89ab6212623430acadb" + "digest": "f1742fdf3fd26a8a5cfeaba57026518dacaad364cbd03344c4000a35af13e47a" }, { "name": "poop", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "shit", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "hankey", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "poo", "unicode": "1F4A9", - "digest": "140ce75a015ede5e764873e0ae9a56e7b2af333eddca0fe2796b14545c620258" + "digest": "857a61c872138d359a7fe8257bb26118afa49d75186eca2addb415d07c92b3ec" }, { "name": "popcorn", "unicode": "1F37F", - "digest": "12264cb16fca9317e3ba8d5924a2c8f15f790e36d2f29e7b12aaaf77e1beb73d" + "digest": "684f1b7ef34ea7ca933aed41569bc6595a19ef0d546a1b7b9e69f8335540b323" }, { "name": "post_office", "unicode": "1F3E3", - "digest": "5e2d896cd646a2eecd5596af9e44ca1fa2745de5cedaf0f6d193b8243201c6cc" + "digest": "54398ee396c1314a7993b1cb1cba264946b5c9d5a7dbb43fd67286854d1d1a0f" }, { "name": "postal_horn", "unicode": "1F4EF", - "digest": "339aa61fa1567a1d159bb8204d15db889fbb6cc1106f6e1991b4a184d1bc1fc7" + "digest": "0ea12f44f3bae9a14bde3b37361b48bd738d2f613bb1b53a9204959b70e643f8" }, { "name": "postbox", "unicode": "1F4EE", - "digest": "ef1a6543fccb9f1009cc3782c51883e51167721a0b49e8ba21e8e6049b216906" + "digest": "bbc424ae8d46de380d7023a43ea064002fd614657d00330d3503275827ac87e2" }, { "name": "potable_water", "unicode": "1F6B0", - "digest": "4a2379835660dfa8b6780d662a10d1effab710f471eb9b5e6ade4772ba7e5aeb" + "digest": "dbe80d9637837377cc2a290da2e895f81a3108cc18b049e3d87212402c1c2098" }, { "name": "pouch", "unicode": "1F45D", - "digest": "cbd47ec1a65f5c642773d8ea2e7e57f7041a2d7ed9df05fbdd7bc8743c6dece6" + "digest": "9f012b90310b4a072b6a8fa2c64def087b5f7ffffaafc36e1856ba943a170351" }, { "name": "poultry_leg", "unicode": "1F357", - "digest": "d416e9464bd58073bd3e32eb06c0da96905609f47b9d667acdc0810e94237584" + "digest": "1445ec4f5e68a19e5a84e5537dca8190d62409070c954d112e6097f1a6b7f054" }, { "name": "pound", "unicode": "1F4B7", - "digest": "1ac491bb8a91613b2b1faaac4e7b4bc794d2abef69ac79de17d54c824c3ef826" + "digest": "eb11b83eb52adb0a15e69a3bc15788a2dc7825dedee81ac3af84963c9dd517b5" }, { "name": "pouting_cat", "unicode": "1F63E", - "digest": "ba28d75401d5bb98773acd35aaf173356bae4d5a5520a226559478138364ebdf" + "digest": "8822abedf3499cf98278d7eeea0764d1100ec25cad71b4b2e877f9346f8c8138" }, { "name": "pray", "unicode": "1F64F", - "digest": "fb0df9c1566014bd2df2a1afd59366b896f20c03ca3516e02e4be44ea556c8ea" + "digest": "735b79dab34ac2cf81fd42fdcd7eb1f13c24655e5e343816d5764896c03edeea" }, { "name": "pray_tone1", "unicode": "1F64F-1F3FB", - "digest": "c6d8cb46e65ad13a92e85f97e018176fd89513f23e899e15d1ad09e3b4009f4b" + "digest": "e8b6103450215e8566797f150978355e297deade4eb47a6371f7a7bc558fed9d" }, { "name": "pray_tone2", "unicode": "1F64F-1F3FC", - "digest": "2cd68cbe1ba3254f173ec8136af79cae64873bd0f20480158c3e6babd5a1a442" + "digest": "ee8baacd95d7e8dbad8a1f2d9a12e36c98f3d518db5d3b117d0a18290815e62b" }, { "name": "pray_tone3", "unicode": "1F64F-1F3FD", - "digest": "d2e81863f74a87b96335fb108e7b206f28ed18185362ab4d42a3b0523801398b" + "digest": "ae8c0caa9aca0a6c44069e76a7535c961d0284cd701812f76bbd2bd79ce2bd53" }, { "name": "pray_tone4", "unicode": "1F64F-1F3FE", - "digest": "ad1b91254b101d872325c325ebd1f2a6257cfe22e83de88e29dd16ffac191979" + "digest": "64f7b3178b8cd6f6a877ed583539eefe068fa87a0dd658fdcd58c8bc809f7e17" }, { "name": "pray_tone5", "unicode": "1F64F-1F3FF", - "digest": "23f40a11321decbdc6a1d274b9ad571041d261d364d13d1063c306e73ad52254" + "digest": "5bc8cdce937ac06779c87021423efcec4f602aa4a39dba90b00de81033005332" }, { "name": "prayer_beads", "unicode": "1F4FF", - "digest": "cb6f8700154f75749cf2642a25c03e255dc18428baf8b57f6bd807c92b83e28d" + "digest": "80177091264430cbcf7c994fbe5ee17319d1a58d933636cc752a54dafcf98a05" }, { "name": "princess", "unicode": "1F478", - "digest": "47b93eb52d757c3c000d9760391ecb942776d883b28050d833fa11612483d8ee" + "digest": "efabd28480a843c735f0868734da2f9ce28133933b02ab07b645498f494f3f80" }, { "name": "princess_tone1", "unicode": "1F478-1F3FB", - "digest": "1e4073c2abdf51a61a1a85a3e063541fe96e9b9ec36ec6f7fb9c98deeb230869" + "digest": "52b88b99ba64f82e8f36e2a1827c85145e4fcd6863478c2345fe9fa9e8901cdf" }, { "name": "princess_tone2", "unicode": "1F478-1F3FC", - "digest": "6a0a5dc447cd887798f908c15972e7a12d28d81f168b92bcb105786ac253bea0" + "digest": "7e44289404693668f20e681fcdc2e516512d54a69c627eedae958f69dfe6eea9" }, { "name": "princess_tone3", "unicode": "1F478-1F3FD", - "digest": "2f08d22fdfc7a7d66fcd87ae716b811f43077f5bb17fef87f5b7e2aa93700d70" + "digest": "96c9a9857348d7a1a8be899c50d55b352b9a9fd5c65e4777bfa199fe7929d41c" }, { "name": "princess_tone4", "unicode": "1F478-1F3FE", - "digest": "02129211bf7bf7ff6de35913b7069aee151532d878b8c4f7e24c012e5b09d4b4" + "digest": "67696f96be60f2a36598072172d2db197d007e6c1ac3acef526a5ce6d59bf3f7" }, { "name": "princess_tone5", "unicode": "1F478-1F3FF", - "digest": "d676f103600b69dbfdb469469a77b9d561ec460ff862befa58ab30ddc909c9f7" + "digest": "007f624e2fad91bb57ce32ecd35213a796d71807f3b12f3f1575bf50e6a50eeb" }, { "name": "printer", "unicode": "1F5A8", - "digest": "c44402c87071f8d31d3997abab53ab9f8f7c11434e747380928814ceb6b0a417" + "digest": "5e5307e3dc7ec4e16c9978fb00934c99c4adefca7d32732a244d1f2de71ce6f8" }, { "name": "prohibited", @@ -8572,57 +8572,57 @@ { "name": "projector", "unicode": "1F4FD", - "digest": "fc361282f367926254c08150b02cb8fda7fa8d2c9c939d9360c78bf19a4f982e" + "digest": "7f8e1fdb89584849a56ee34c62cab808af48b7bd4823467d090af4657a2e0420" }, { "name": "film_projector", "unicode": "1F4FD", - "digest": "fc361282f367926254c08150b02cb8fda7fa8d2c9c939d9360c78bf19a4f982e" + "digest": "7f8e1fdb89584849a56ee34c62cab808af48b7bd4823467d090af4657a2e0420" }, { "name": "punch", "unicode": "1F44A", - "digest": "5759db1d7093744c74b840bbb4761fb025d6633f8fa539bcb35dcf54fc05ceb6" + "digest": "c7e7edf6d64f755db3f02874354f08337b3971aff329476d19ac946e0b421329" }, { "name": "punch_tone1", "unicode": "1F44A-1F3FB", - "digest": "793b3fa2a43c23b2c1e1b48b86ae35e8c4024cd065fac0a0a5ada87cb78d6de3" + "digest": "c9ba508b0c36041047473782acfedab5af40dd7946b33daf4d8d54c726e06a11" }, { "name": "punch_tone2", "unicode": "1F44A-1F3FC", - "digest": "6fc2467e99982ab00b0c352c6f7793d34faf17b16a0312082c9bd1f0709e3938" + "digest": "d53011cd2f3334c7b3fffdfe1e2b8cc1c832c74306e1ac6d03f954a1309d7d0b" }, { "name": "punch_tone3", "unicode": "1F44A-1F3FD", - "digest": "bf747b29952550c5b4d3807b9ed85b5e5d4bcc3265b0e214791f7db547f861fb" + "digest": "f7522347094e0130ed8e304678106574dbd7dd2b6b3aeb4d8a7a0fef880920b2" }, { "name": "punch_tone4", "unicode": "1F44A-1F3FE", - "digest": "3b6c0ccb682552f32d6744c438e3af04a1732c67a74bcafb14c723cf526fed87" + "digest": "3e62bdd426f3e6ff175ce3b8dd6f6d3998d9c1506128defa96b528b455295b47" }, { "name": "punch_tone5", "unicode": "1F44A-1F3FF", - "digest": "945bae1aa3587cd1dc57d1ec4da18c67a59e0e7150dcc8735e5357b4ea1234ac" + "digest": "7d9bff777dc4ec41ac132b1252fa08cf92a398c8dc146c4a5327b45d568982d8" }, { "name": "purple_heart", "unicode": "1F49C", - "digest": "e0eb886e74f22d40d059ff3a089d472af53c6c53de380f428cca140dfd046345" + "digest": "a6bf01de806525942be480e45a4b2879f91df8129b78a1b8734d4f917bcab773" }, { "name": "purse", "unicode": "1F45B", - "digest": "67d82ff9a4d76148b9d98538d4b786f880058a556e650ec3f93e1632aa42aaa7" + "digest": "2b785f36e01875d66cfda2192c8c53606e7224a7c869a4826b62cb61613d60c8" }, { "name": "pushpin", "unicode": "1F4CC", - "digest": "c4de129d5d8744caffeb2f499fcc0bc6b551843938f8166ffecd0de00bda66e3" + "digest": "c3f7d7008be6bab8dc02284d4d759abf7aafbb3dbbe3a53f0f5b2ff685af88f8" }, { "name": "pushpin_black", @@ -8632,202 +8632,202 @@ { "name": "put_litter_in_its_place", "unicode": "1F6AE", - "digest": "b26d3b68bd62d30ecfe75cfaf309a7a0f91e92db0aa18b0b97b97baf0609d4e6" + "digest": "f52a57d6f1bada7b6e6b9a6458597d70cb701c01e1120d8cb1d7ff65e01d405c" }, { "name": "question", "unicode": "2753", - "digest": "258e3169bae177fb0f01ed5f9b933f7f02dd2673e12a316af44a0c3729a78a2c" + "digest": "40050a1fd29bed321fd601d13dc33de5d6084121f1d873b29bde9dc3d823a310" }, { "name": "rabbit", "unicode": "1F430", - "digest": "9817a7454aeda77d28f63eb13c0dc0a6d9e6c9abe3dcf538b4b3477e494cddb6" + "digest": "678ad953a7ab8f618c59051449a67c965d1f04f42dd6f6669adaf3fadebd080c" }, { "name": "rabbit2", "unicode": "1F407", - "digest": "67ba57a31b0768a2118faabdcb088f96f1441e1132397f65b6937d523ff7dabb" + "digest": "19b1f5108292472434cc7a49efac4ea9275779735c7aeb0f15c36021d5998ca0" }, { "name": "race_car", "unicode": "1F3CE", - "digest": "2e9828e3884c79ad7e9e1173d3470790f3f56cfa08ef4e38deff45db0728c66c" + "digest": "46f4814259d3d17ff35c04110e73e5327aee99f4711cd459ca1ee951508da3a6" }, { "name": "racing_car", "unicode": "1F3CE", - "digest": "2e9828e3884c79ad7e9e1173d3470790f3f56cfa08ef4e38deff45db0728c66c" + "digest": "46f4814259d3d17ff35c04110e73e5327aee99f4711cd459ca1ee951508da3a6" }, { "name": "racehorse", "unicode": "1F40E", - "digest": "36aa3c7123ee7e15600657166032b21b8edeb192cf6d3ada39b5c65001f7fc40" + "digest": "a57b7aca35347ada8225eeee06b70cfd040484104963b4df56ea8fec690576b0" }, { "name": "radio", "unicode": "1F4FB", - "digest": "b1403f9a883405b909208f52c9474c2d3923681ea0b02609a6e9dc12460319a5" + "digest": "9245951dd779cdd141089891b15a90d3999a6358acf1fc296aa505100f812108" }, { "name": "radio_button", "unicode": "1F518", - "digest": "9bcdac17b3620331a32f9bb876812231a701eb5a7f696e7d875f877ab92159fc" + "digest": "565bec59198df2592e96564c6e314d3cde33c47b453db1bec6c5d027b5cb4fd9" }, { "name": "radioactive", "unicode": "2622", - "digest": "5ad8e8594617c0153672a76421deb836e05c6098020c33af3f975f8fcfe216e4" + "digest": "0ed6634057824e0cfd10b2533753e3632b0624341a7eac8d9835706480335581" }, { "name": "radioactive_sign", "unicode": "2622", - "digest": "5ad8e8594617c0153672a76421deb836e05c6098020c33af3f975f8fcfe216e4" + "digest": "0ed6634057824e0cfd10b2533753e3632b0624341a7eac8d9835706480335581" }, { "name": "rage", "unicode": "1F621", - "digest": "02ac70551fc51478884c133b29539cae58b463c760db38c0aeec1bdf5b282312" + "digest": "d97ba6bd08eec46dbc7199f530c945b73a87a878e35397b0a3e4f2b45039e89e" }, { "name": "railway_car", "unicode": "1F683", - "digest": "8490e2ecf94c7c1d1e22fea0d80cc18a49648741009e51984f583b17bbd022e2" + "digest": "2cddc08d555e7fc24e312c3d255ed013fbf9cd2974a6918369c32554049ba2be" }, { "name": "railway_track", "unicode": "1F6E4", - "digest": "63ee881cc775d5b2711082b6c96ab44d5204c5d390afd6d8ee97e52aeeaa5e5e" + "digest": "0da351b6d4e75c6beeaef1225e151d9580d4b5c41dfa1cf192715bf3cec981d7" }, { "name": "railroad_track", "unicode": "1F6E4", - "digest": "63ee881cc775d5b2711082b6c96ab44d5204c5d390afd6d8ee97e52aeeaa5e5e" + "digest": "0da351b6d4e75c6beeaef1225e151d9580d4b5c41dfa1cf192715bf3cec981d7" }, { "name": "rainbow", "unicode": "1F308", - "digest": "bbd8ecc8d0737948969a3539d2d202e599404e509f1a21bdbb0a0c41c2540522" + "digest": "a93aceb54e965f35e397e8c8716b1831614933308d026012d5464ee42783ed4d" }, { "name": "raised_hand", "unicode": "270B", - "digest": "4192881a0d613b4fcb19b1c2d8b83aadee6f0b12170721c8dd7b1ccef6540199" + "digest": "5cf11be683aea985d5ba51fbd44722c2327311bfe26b61c3d441c90f5d5a195a" }, { "name": "raised_hand_tone1", "unicode": "270B-1F3FB", - "digest": "df2e046c99dceb9184c50a777b403d72bfb25ff473d6a4e20bb9a731db64ed8d" + "digest": "865afca29b57577fed8fe8c2be57b74254a008c8cf34194680be2759239b5f5d" }, { "name": "raised_hand_tone2", "unicode": "270B-1F3FC", - "digest": "ed179299a1c397cd51cf6067d6795d71a3831d35e1ec9eacbf0286c8992c1e7a" + "digest": "832169a0b626a682a58a3b998f68413657b4962c1fab05f1fdc2668e82727210" }, { "name": "raised_hand_tone3", "unicode": "270B-1F3FD", - "digest": "cacbd0ddef65bc01a41bd921ea159f8cd89050309b10f15780d6199f79434a54" + "digest": "3959a873ad7671de82c615c4ed840b011e67baafb2bab7dd16859608d3e83cb1" }, { "name": "raised_hand_tone4", "unicode": "270B-1F3FE", - "digest": "04c934c7a55b83bcfa7f3880fc1f6aa0f188090c37b9670e6775a512a1cf59e9" + "digest": "db542f65d076ccf3dbfca27cb7c2f135a8bf7a487a81a04873e70172bdfcd579" }, { "name": "raised_hand_tone5", "unicode": "270B-1F3FF", - "digest": "da0c4283b7b19861237c023234c6db28045b8f5a5971acb015733e08e2940e86" + "digest": "88ca884d14baaae48df21d75c22d82fb15bdc395e42026f5ca34cd65e5ae8674" }, { "name": "raised_hands", "unicode": "1F64C", - "digest": "308e475f38558e73bd66e28693d77478caa5bca4360cffaffc2a97b5858c56ba" + "digest": "2ee73466a3f5079e542857fe6f5497e9f87753a81854985ce3356a8d3da1d8b8" }, { "name": "raised_hands_tone1", "unicode": "1F64C-1F3FB", - "digest": "e39b9bc49dccc127e44f543e98961fcf5bcd44d6e216741bcd10ec3667263c84" + "digest": "43e73c60f040a66374b8ec98f3629a90d13ae9f472446ed7676cd5573e824f4b" }, { "name": "raised_hands_tone2", "unicode": "1F64C-1F3FC", - "digest": "f376ab13071ffdc11888ec221ef5b4de546ca0f60bd9ae30bf3da4066c220462" + "digest": "fcc5255bb2b06dc82d6878e74cf34e8ce118c70004a06d39a980683772b98c52" }, { "name": "raised_hands_tone3", "unicode": "1F64C-1F3FD", - "digest": "67694325a43e629c00fa9bd2ff7e19f84f216b2855ae2cf097762dfa7aca25e6" + "digest": "3ee3e0aafef486e766a166935e8147fb75a7329cfebc96dec876cc45e83a8754" }, { "name": "raised_hands_tone4", "unicode": "1F64C-1F3FE", - "digest": "a2254fe75a0770708916a4ddd5db4420221c6ea9db9f74068d14eadfc0f3772c" + "digest": "78a8cbf6b2b85be4d6b18f0ff6a77f197963117955725fb7e57e0441effb928f" }, { "name": "raised_hands_tone5", "unicode": "1F64C-1F3FF", - "digest": "bd7c9897cefb454ccdc46027bf56d6587565bdd345d7d0f081b7b671a53f6c99" + "digest": "2a5ed7334a17172db0cd820a559e7f75df40ec44de6c25d194c76e1b58c634cb" }, { "name": "raising_hand", "unicode": "1F64B", - "digest": "d57178fc77e9fa140682634da35f9ab12a65d9b4c506b7cd8a9697f1b5910bdb" + "digest": "512750b00704f1ccefd3c757743540b785ad7670dbbe4a2c4dca8d93e6701920" }, { "name": "raising_hand_tone1", "unicode": "1F64B-1F3FB", - "digest": "f46b34361ef79743f3187d6860182bbe1ae411031db7fe5c0f7292fa472b9c16" + "digest": "2897722f091c273dd3714cff7423c2475bc3070416c28014ca03322b9ece48bc" }, { "name": "raising_hand_tone2", "unicode": "1F64B-1F3FC", - "digest": "20b85a2ebca150b2020a04b41d34884c78c22f42c251e2b9d23fd3724574143b" + "digest": "59199b334b3845911382c1f29bd7c0d5ef9d2486417345e265b166ead7d3e1c1" }, { "name": "raising_hand_tone3", "unicode": "1F64B-1F3FD", - "digest": "5e0401b528c2b8edff766d39cdcedbe9abebe4c940df7a36ace61f59c08d508a" + "digest": "f95b338d5efcf14ef12f415a2c1bba93df48628ddc94f34f70c31e1b3c2e1d28" }, { "name": "raising_hand_tone4", "unicode": "1F64B-1F3FE", - "digest": "e4f5624264269ad09cde207cd7d4eb0fd46de816880daeec457ac8cd51cc1b7b" + "digest": "951ddbfdb57d5a60551b59b3d0f7ca00a64912f4a101a73afaebd68445cd6cec" }, { "name": "raising_hand_tone5", "unicode": "1F64B-1F3FF", - "digest": "eb34b6c037bee5bbc4222f6aab421aa785f527ebf1b5e971769e5102244d60e1" + "digest": "9370f93704d8f89ca6dc946715eab5e7dba82bf04dd68c00f5c0abb8bc16371e" }, { "name": "ram", "unicode": "1F40F", - "digest": "b71950d7a286a4c4909c5ec7c35211c2a5c20b6bad341bd863c6a85c4bcf9c80" + "digest": "2875ab28e1018b39062aeb0c5ce488c48a98f13e9f2364470a0a700b126604f2" }, { "name": "ramen", "unicode": "1F35C", - "digest": "7dd185b24852b577913edc78647cd53b27d42e225fde29aa2f3aba25c980b5c4" + "digest": "425662a49c4c13577c0de8d45d004e5ba204aaadbaabae62a5c283ecd7a9a2c5" }, { "name": "rat", "unicode": "1F400", - "digest": "7a10d9ba5ee1010d421d9cf73d7966507302a69617a32fe9f1a00d57a31f7bd7" + "digest": "14380d65498c6ce037c02a93bca2b24f25a368d85278d6015b8c9f7cd261f8e2" }, { "name": "record_button", "unicode": "23FA", - "digest": "c2ba672994ad0f99d7fdc449f3fee45a2dca68a58f9fe95825b38465a30ef44e" + "digest": "92be12161ba206bb2e06a39131711c7b17368d55b4aae0b48f0ac5b6b1cde76b" }, { "name": "recycle", "unicode": "267B", - "digest": "74a54ed62a40dfbdcace1f08b085658a77d45c62570273927ad270bf9a8a2f4d" + "digest": "c377e8537367b05b5de9be860a0fcabd7aed2bf4ba146eefc423671a21530369" }, { "name": "red_car", "unicode": "1F697", - "digest": "558730d6418aa5d85b73af58c8041efd12cff906e26ea47c50963f66d33d6eb8" + "digest": "8a99832a195263c0e922af53d52dea37aa3e07032b3c2a1977f8527b4a144b9c" }, { "name": "red_circle", @@ -8837,72 +8837,72 @@ { "name": "registered", "unicode": "00AE", - "digest": "ed924107384461aabb4924c401c6c087ffa047bc2ef735823e7c2be67804707c" + "digest": "9661b1df529ecb752d130820c55c403e5de263748eb02f7fea327818bc282d94" }, { "name": "relaxed", "unicode": "263A", - "digest": "65072f7b9bfaaa92b8a0ed012dffe2cfd2efa3748264aaf450aa31ba6bd44045" + "digest": "2d5aed4fb8504c6d6660ef8d3bfe0cc053dcd6099c2f53748c202dc970c639bc" }, { "name": "relieved", "unicode": "1F60C", - "digest": "1f2c7ae6a9d74a112de89403be6eca3d8155d70395e7fce51032fc961f235c7d" + "digest": "b4ce2ba6c220d887fe5e333c05ed773df9b6df0ac456879fd8f5103ff68604a5" }, { "name": "reminder_ribbon", "unicode": "1F397", - "digest": "e4a2afc7dce40589657f7043ba8acc9638fd4117252278233ea89f84cddad387" + "digest": "c3de2a7c9350b77a0b86c0dcce9dcd9953ea8a97aa1e7aed149755924742f54d" }, { "name": "repeat", "unicode": "1F501", - "digest": "27b6dad9215e58e24c607a39dbf398ecf66ccb692c81e08eb2f5f4912db30522" + "digest": "b9512d508613ed0eb3181eb1030f7f6fd6b994476ecdfa308733c6df975fb99e" }, { "name": "repeat_one", "unicode": "1F502", - "digest": "052d13f2b08eaf70b31252aa78f95d06fbe22c58945c19381b13cbeb1c855651" + "digest": "53409cf24dd4bb0d7b50ae359f15d06b87b7f4a292ed5c3a09652fa421a90bf2" }, { "name": "restroom", "unicode": "1F6BB", - "digest": "b77fbc4247c241362e5ef9e6eb58b1b437aa9d16b65886cec0c55ceb55c1440e" + "digest": "2e7a1bfc9a9d49b0272230a91db7369e24d54bf1de8e683d36b85f1d8c037f77" }, { "name": "revolving_hearts", "unicode": "1F49E", - "digest": "2b8925d3e78df2dba8534252fe60bf03285346f6b3697be7668bd568e6d85931" + "digest": "c43d3197cb4cf06659f643638f6c4e91a2889e0f6531b7d81ea826c2a8b784fc" }, { "name": "rewind", "unicode": "23EA", - "digest": "91a95b26d12ca76111556096f4d96484c9f1d7e1b20ccff5a3291b36e529a6d1" + "digest": "d20c918c1e528ff0947312738501ca9a6fb6ff4016aad07db7a8125d00fd65cd" }, { "name": "ribbon", "unicode": "1F380", - "digest": "9c0296d8c2baa84c99347c431bf79b288d98b5f17b1ce7605ad7ce1da265d5aa" + "digest": "74315fe907f9f0203afe139cd4552aa442eecfa2a64fac12db3e1292fc5a8828" }, { "name": "rice", "unicode": "1F35A", - "digest": "e34849496a79e71ae4700df94f2a54895bf6de758a92edeae33fe78295a3ba21" + "digest": "f544f12606de59d28739798003f14ebd8869856add8e24496ec5dda3e131daf4" }, { "name": "rice_ball", "unicode": "1F359", - "digest": "52df5da8b0edbdeb56d66e0f30ad4549abdd81c064f7269d920dcac66a3df2e4" + "digest": "2cba6f5364cd366859bc8948897b65fc97b225ea7973d9be3b24aba388fed8e8" }, { "name": "rice_cracker", "unicode": "1F358", - "digest": "d55f8f9d807f4619eb243c510938067a7417a64bd9435b05dfeb2a36fdb2b6a0" + "digest": "ac0f805d41d4f322154c1968bd3ce3e9aabcd39d908182e52fd7d28458dbef92" }, { "name": "rice_scene", "unicode": "1F391", - "digest": "482d854d8d30edfc1ecd48a4ce476e6498606321405bf5a0b4ff74489a092af8" + "digest": "b942a06d3da0570aca59bab0af57cd8c16863934f12a38f70339fd0a36f675f5" }, { "name": "right_speaker", @@ -8932,7 +8932,7 @@ { "name": "ring", "unicode": "1F48D", - "digest": "ae2a93e7895b9b89f5a39f01d356ffed988f219ef8b658a56c55285826a4533b" + "digest": "b5322907222797b5e1786209cda88513e76cd397a40f0a7da24847245c95ef9d" }, { "name": "ringing_bell", @@ -8942,47 +8942,47 @@ { "name": "robot", "unicode": "1F916", - "digest": "cc0e363774b86e21a5b2cea7f7af85bca9e92c124ebcd39c6067c125048baa60" + "digest": "4d788e6ec89279588b036fca6b17f5a981291681df8f90306ecf5c039de40848" }, { "name": "robot_face", "unicode": "1F916", - "digest": "cc0e363774b86e21a5b2cea7f7af85bca9e92c124ebcd39c6067c125048baa60" + "digest": "4d788e6ec89279588b036fca6b17f5a981291681df8f90306ecf5c039de40848" }, { "name": "rocket", "unicode": "1F680", - "digest": "65d8bd005ceac41904237b7a8c5f55f16713a55d971522f0bbe63a1d548e515d" + "digest": "b82e68a95aa89a6de344d6e256fef86a848ebc91de560b043b3e1f7fd072d57d" }, { "name": "roller_coaster", "unicode": "1F3A2", - "digest": "907baab1f3d7becf3f8a3b1264642b395bd73b4af49e23058b3abb5c69e9106a" + "digest": "a65e9ace1d7900499777af1225995f17af90a398bb414764c20b6e09a8c23a2c" }, { "name": "rolling_eyes", "unicode": "1F644", - "digest": "f596f203030b6c9bd743848512aa3fc7919447020d35ae5c2bf13ccb16fa2dbe" + "digest": "23dea8100da488a05721a4e82823eb438393b0ea762211c9ecef011d127aa1b7" }, { "name": "face_with_rolling_eyes", "unicode": "1F644", - "digest": "f596f203030b6c9bd743848512aa3fc7919447020d35ae5c2bf13ccb16fa2dbe" + "digest": "23dea8100da488a05721a4e82823eb438393b0ea762211c9ecef011d127aa1b7" }, { "name": "rooster", "unicode": "1F413", - "digest": "6cefdaa45631ed8c9480e15f578c793d95af81b42687164fd7900eee325ccf07" + "digest": "2b90c5cf6fa46da13eb77285443d600afcea0c48bd1d215d60167e7dc510da5d" }, { "name": "rose", "unicode": "1F339", - "digest": "584909a4a2ece625c688f8479a39692bb8e816b692e6eb7dfd40cb045259b1b2" + "digest": "73799e459dba188de4de704605d824242feeb65d587c5bf9109acf528d037146" }, { "name": "rosette", "unicode": "1F3F5", - "digest": "0ce3b85ca05124ab99d57ebc9aa17bb246ee614d2fcda1ef62bf42ac7e616148" + "digest": "2537def4deef422d4e669b28b1a0675259306ab38601019df3ec3482b14e52d5" }, { "name": "rosette_black", @@ -8992,542 +8992,542 @@ { "name": "rotating_light", "unicode": "1F6A8", - "digest": "369e069e0bfecc7413e75f4015e9c1de527a33c7cce3f6c2b4adb60a0d9d338c" + "digest": "91fcdb85a752ae904d335a978c7e7936aed4c75d414b35219b5a74430e51555f" }, { "name": "round_pushpin", "unicode": "1F4CD", - "digest": "1bc5fe5a90a6e56ea00246f1b008a0e0cce0d77c226dc0300bf9a2804b543877" + "digest": "8ffca77bbdc6f1f726daf3abd6eff338a5ad1aa9b09dbbd8782c1e7ef5452f30" }, { "name": "rowboat", "unicode": "1F6A3", - "digest": "c10e09bf8be8b1a8ef3113edd9327126d6a4644f3bc81c7ada2922851e4d1cfb" + "digest": "83715d83a061926d4ad3bb569b21f5d337e3ebd4c9bcdfe493e661c12adc0a16" }, { "name": "rowboat_tone1", "unicode": "1F6A3-1F3FB", - "digest": "a84fc1b30d1a284dcd3899dc4de8f11e7b65c258528eb41c7dbf8f82425fee12" + "digest": "e279ac816442c0876fba1f42c700b80f2fb6de671e1a8a9e9d11b71eed5c58e8" }, { "name": "rowboat_tone2", "unicode": "1F6A3-1F3FC", - "digest": "85f001430a2ad607a15901f7c2dcf8381471f42d6cc0775e76a2ff1f457151c1" + "digest": "6a48eba352ed4971d26498b6c622e5772389c89c5205ed02acde8e995dddcc3b" }, { "name": "rowboat_tone3", "unicode": "1F6A3-1F3FD", - "digest": "adf8b1e45a46a13f3db40c29df0312216558e9d0c615aa46a8e913cee5003a81" + "digest": "875948f6d8354ebd95ce9a66fde30f06a8366dcd89d5ca3e660845f8801e9305" }, { "name": "rowboat_tone4", "unicode": "1F6A3-1F3FE", - "digest": "05482749ec40bdf02e53fc42d316c51f4f3ed643f21e8fc16b81930e4a884bda" + "digest": "8c7ac7346b0020d0ff5e2f4a1efb1b7785eac637f17556663ec33e2335083f0a" }, { "name": "rowboat_tone5", "unicode": "1F6A3-1F3FF", - "digest": "d4bb337d948996d4a23d87f99988f02fc207815b862082ffd2eef5f0c1016aa9" + "digest": "a399dbb647892b22323e0bf17bc36a9b5f1708ebedf9ba525233ee7b9d48339a" }, { "name": "rugby_football", "unicode": "1F3C9", - "digest": "e14aebbded78d4a5e9b4028f79a8ca840d02798c6758cb9e926e992e2a35a4f3" + "digest": "cc6f00ade3e0bbb7899e7bfb138b57216dd66de26d7967d5ffa501f382ed09f4" }, { "name": "runner", "unicode": "1F3C3", - "digest": "58a884f06d37b0ce78197bebcd3f0e102dd90022ebd86ec70a2ef5a5cdf9683b" + "digest": "e9af7b591be60ade2049dbada0f062ba2d3e17f02bec76cbd34ce68854a2a10c" }, { "name": "runner_tone1", "unicode": "1F3C3-1F3FB", - "digest": "65f1633d1517803de23686d2dbcc75a5787874266db4981138ccdbe4badc773c" + "digest": "21091cbb09c558712ecf63548bf28b7995df42bdb85235088799a517800e52f5" }, { "name": "runner_tone2", "unicode": "1F3C3-1F3FC", - "digest": "2bc81f3fb77445cdc75c34806ab0ce912bacfe47f63b5d2011a4f5d370cf7064" + "digest": "1fe3d194f675a46fe67799394192e66c407dd81163363692c5e7da32ddb9af2b" }, { "name": "runner_tone3", "unicode": "1F3C3-1F3FD", - "digest": "beaf5f254cba2991fdd0c38ce2ddd1b4c1110e15b2b7bc026d32f162e295c4ef" + "digest": "8cea1bf4ef3be71f42dc5bae978d5b7a197a3851543225349ef0dda29a370537" }, { "name": "runner_tone4", "unicode": "1F3C3-1F3FE", - "digest": "21d531ba9b3d13747ad636b8f7a6f184c974bf61d9f529975a64f9629263c407" + "digest": "c33f0b8b5a71d295fb6ba322e79446964a8eca9e4573efd591e4273808b088a0" }, { "name": "runner_tone5", "unicode": "1F3C3-1F3FF", - "digest": "b02a5bcc58cc45f8219262ec44c77764172fd8f2624d9122ded4a5a5db04c0ed" + "digest": "9f59e6dd0fdf2f17bceb41f5c355b4e6f3c8bb8cbd8af0992f0b5630ff8892e8" }, { "name": "running_shirt_with_sash", "unicode": "1F3BD", - "digest": "431bed35f4a55175bf99af769e74a81e8650c6ab34af6ecddaa1417ff7e437e6" + "digest": "7542307d3595aca45e8ccae66b6e58b6e92870144b738263d5379ec6dc992b76" }, { "name": "sa", "unicode": "1F202", - "digest": "a47a480631f874e8a2cd69b5d513f90a1e81a96bfa2f6025bf244a82baca3656" + "digest": "6042bcabd1516ef3847d695aba22851c49421244432d256e24eba04e8a223dab" }, { "name": "sagittarius", "unicode": "2650", - "digest": "14871e6681c35e4a63a0b19613f77b3674d00cb78d06975e02ca29e61b5cea8c" + "digest": "a02593e025023f2e82a01c587a8c0bbb1eff88cbcabf535a1558413eb32ed1d5" }, { "name": "sailboat", "unicode": "26F5", - "digest": "6f742dde6c180a174b771aa3942b558e98a3dc1eb212dd31add86c5fa5620865" + "digest": "c95ef4dc939cbdcb757ef3cd90331310e8c0a426add8cc800bae2540148a3195" }, { "name": "sake", "unicode": "1F376", - "digest": "aa1392790c805950779dde7778292c937f8c1aaecb522876171d5ee542ec51f8" + "digest": "0a786075f3d9da48ae91afccf6ae0d097888da9509d354ee1d3cb99afcc88fe4" }, { "name": "sandal", "unicode": "1F461", - "digest": "14f1e9003a6acd90a55f23c48ed87a758fca586f2e0b0edc4dc9d1deef9eb067" + "digest": "03c3077cb4bd900934f9bdf921165b465e5cc9a6bee53e45a091411bceb8892d" }, { "name": "santa", "unicode": "1F385", - "digest": "12feddd84eb49ce30ae68d4f93d66e2c0dd11297a4d1275c9a50d4f35bea83a9" + "digest": "178513e3d815917e59958870f5885b3414b43a16b8056980c863a468dfe00179" }, { "name": "santa_tone1", "unicode": "1F385-1F3FB", - "digest": "a75813770efe27d5b4c80ad892d0c796d88d1a0dbb1bd02d5f68882d7abad479" + "digest": "bf900bbc19bbd329229add9326e28e8197b69d6ddceb69f42162b0200fde5d16" }, { "name": "santa_tone2", "unicode": "1F385-1F3FC", - "digest": "90f8072fdde5f4a275cbd1902d6c94689d453b1bee0336213dc9d6f7e1d038e1" + "digest": "7340f2171adab97198e3eecac8b0d84c4c2a41f84606301a0d10e9fe655c93d1" }, { "name": "santa_tone3", "unicode": "1F385-1F3FD", - "digest": "0973053e7b77d268080126a50b95b45429630e5d49f62210e7b71840794c7dc5" + "digest": "7368ab75454ec28d8f7d6baef6ad69b5278445a9f50753f6624731bffde32054" }, { "name": "santa_tone4", "unicode": "1F385-1F3FE", - "digest": "5cd49c0d199a42846b400b3c1244d448ed6fe5ce993d379817cb2a5f7c0b609b" + "digest": "0ee60188353e0ee7772079c192bebbc6d49e74e63906f840c66da4eb35f4f245" }, { "name": "santa_tone5", "unicode": "1F385-1F3FF", - "digest": "a54c36dfa99b39549fb1d3dd7f0021a7aee28112960172ed466dacc67961c525" + "digest": "e4378a0cc5d21e9b9fe6e35c32d1ebc6fb8c2e1c09554cd096aeaefd3a6eb511" }, { "name": "satellite", "unicode": "1F4E1", - "digest": "3b9797c8161526edce0bd8e9b8563055166f9307761c367ab3e2ad7645b6dee0" + "digest": "c9d63118dcb445856917bb080460ab695cc78e715dcbba30ba18dffa9e906b27" }, { "name": "satellite_orbital", "unicode": "1F6F0", - "digest": "104b135e3736a4bcfd51a42dadb53bf3e00d7f85d77a94bcb86c6704fbfacd01" + "digest": "beb2f50e7f2b010e76bed9daa95d7329a93c783d3ebc4f0b797dd721c5e3d32d" }, { "name": "saxophone", "unicode": "1F3B7", - "digest": "1090da174ce8aa4f7d35025f65d5ac235e09310abde998d2a725ef3a989a2b75" + "digest": "dfd138634f6702a3b89b5a2a50016720eef3f800b0d1d8c9fe097808c9491e96" }, { "name": "scales", "unicode": "2696", - "digest": "b2984caa182b691a33650344708f47c61d6d319fd067760d7594c2ef60c1e27b" + "digest": "2280c026f16c6b92e0daa00bc14e718770f8d231c571ab439bde84d837cf31cc" }, { "name": "school", "unicode": "1F3EB", - "digest": "caf35260dc465a833521e4a0034201978fed41bbf72cd770756b3340c60e8a0c" + "digest": "af198b068a86ccad3daec4c6873e6b4735086c1ecbb3848182e70bae9aa3ee24" }, { "name": "school_satchel", "unicode": "1F392", - "digest": "a89a2cc46d24d57c2d6b95ed7a56ed829ae2f97b9e6201b2d5adc78c2b78518b" + "digest": "f670ae8aea67eb9d8aaa0bf2748c1cc3e503dcc1dbe999133afcdf21af046b24" }, { "name": "scissors", "unicode": "2702", - "digest": "a4e91127ac83acf5ebc64fbeca768cbbf24f2f0a484861c9c8104bee377b97ae" + "digest": "95225be28f05d8b5a6b6e6bf58d973f61f183ad4fef55a558dc1b810796b85c8" }, { "name": "scorpion", "unicode": "1F982", - "digest": "a090a96731bc1171b054b51abec4c9b36faa62708fd51ac48277ccf5e55d9d12" + "digest": "d41119d1ea5daf727c17dbea7dadec1718c72fc9f98ae88252161df5fde0938a" }, { "name": "scorpius", "unicode": "264F", - "digest": "1ad9bc1030a8f58f3f3223bac52c954cc7a0350805a9df7a42a26972c3b74728" + "digest": "a36404b408814c2ecb8fa8b61f5c5432dfcf54cae8c09cc67b8d0fadf7cbdc03" }, { "name": "scream", "unicode": "1F631", - "digest": "75d613786737ee9c0a74da7394b9ae190eacc7182164627ad8205ac64e4cc09a" + "digest": "916e4903a4b694da4b00f190f872a4e100e7736b7a2e6171fa1636f46bf646e6" }, { "name": "scream_cat", "unicode": "1F640", - "digest": "eee04ff27c2c6b57d698cb87b0af8064ba8313ffc13aa090e38cd5aa8c3d2f76" + "digest": "f1d3a6ff538064e7d5e0321bbc33aba44e8da703dc1894ef1403c0cd6d63d781" }, { "name": "scroll", "unicode": "1F4DC", - "digest": "b8205847649e3ce6b946f1d1da972ed015adde3841c62971b8169235f4b41c1f" + "digest": "9b2cb00860bcc2d20017cafb2ed9681b6232dc07273d489d75d53ce29e4ba3ab" }, { "name": "seat", "unicode": "1F4BA", - "digest": "054c4db0bc8939e9dd951a3f73e9ae4b3c31652784f4d304b509c2bd32f98e31" + "digest": "ae68d86fc2a07cae332451b23bd1ceba3f6526a6c56d8c1089777fa4632850e1" }, { "name": "secret", "unicode": "3299", - "digest": "77daef6e5c91d55228781ddec954a7089d1851297ec81daef6e813cd22915b5e" + "digest": "1d0b9adde2657f41421b135962de20820cf4b4eb0204044f9859522ab9d211b0" }, { "name": "see_no_evil", "unicode": "1F648", - "digest": "aa5883fe605aeaa172d16640b8347580f9cb7d85a596da1b13955f27b0b79297" + "digest": "3ff66d2e84b36d071d0a34f8e41cfd620a56b83131474ea50ed7803b635551ed" }, { "name": "seedling", "unicode": "1F331", - "digest": "a75ec929402de1e653fd6bc89e5be2f92fe5fe52f39e4b6c290eae3c59172b56" + "digest": "c0ec5e6d20e1afdc4e78eeddb1301c8b708ad6278e7287a4e4e825417c858e75" }, { "name": "seven", "unicode": "0037-20E3", - "digest": "c6a34020f6bb25871164fad44302a45c5bffced87f51dfbb816c2985ad7f6a1c" + "digest": "ae85172d2c76c44afb4e3b45d277d400abb2dc895244b9abfbd1dac1cd7c53c2" }, { "name": "shamrock", "unicode": "2618", - "digest": "530e6b987ecb9bcbf0d6e0e11bd075e7949873c784da4f9e1e1b47efd37e5058" + "digest": "68ed70c26e04a818439a1742d2da6bc169edd02db86b6e6f8014b651f3235488" }, { "name": "shaved_ice", "unicode": "1F367", - "digest": "fc22c3568f6be56771e83fd0e67b7eb3750041304d5d4979d3ec417f5201230e" + "digest": "54048e77268b7548d03088517bf8558d11324db901ca57f9bec93f1873663a74" }, { "name": "sheep", "unicode": "1F411", - "digest": "3e3656b82784164ca02c5d775db7245260f0119d2c1d35ba552a6dc75ef02544" + "digest": "c867c8e6e51768f1f51f4fe5abd3fbd5c1d69b01a3cb48b5fb94b6e2338a271c" }, { "name": "shell", "unicode": "1F41A", - "digest": "ff2f4f574b61bffd85c63bc2315c80d3cbcaba37a7c15a1f00783d312bd441d4" + "digest": "8983652d33ad6ab91195518cecb5a268a1c0ae603d271f0ddd756ff50058ddb3" }, { "name": "shield", "unicode": "1F6E1", - "digest": "062aec4a325da7b637c5710846c7e7319229be49b7e59f50428442a7ef725d60" + "digest": "763d0a56a62c51c730ccb0fbea38ab597cbf41a85ab968198e6ec35630d50aa5" }, { "name": "shinto_shrine", "unicode": "26E9", - "digest": "9768fe94142a7dc169703d3707b203f285a546455e29fe2bbf185d44f160d6d0" + "digest": "38a6d756c5aa9703510afa5076d75192f7814bbb6632394d4b8253d9ceda7f8c" }, { "name": "ship", "unicode": "1F6A2", - "digest": "f8d5b0c8ec66287b732d9171ac1913be02efb656de11501213a207d8a6c801e1" + "digest": "79c680845892a3e81ec6af2160ee07c29147155943e5daba6c76d04252014c20" }, { "name": "shirt", "unicode": "1F455", - "digest": "e2e72c323f3bfaea02e8cf52201aa144dc56ec0f25ec97d5f04ee6c2ee99104e" + "digest": "46c7253e15d7cac03699ddb1550fbb7565bbe487310f7e218c0583aa69f9d3c5" }, { "name": "shopping_bags", "unicode": "1F6CD", - "digest": "0194ba540c47e4fc6403be2df68f785d56810efc2dc011dfbf700f3778cb704a" + "digest": "95a3f03c675207bb1354270d02a630c204455c47b3edca23c48523a40cf3ea3b" }, { "name": "shower", "unicode": "1F6BF", - "digest": "c945120182392510348de9a957c2b77a4645d118691298a2ad660dafa62a859c" + "digest": "6b3c767c0eb472d4861c6c3cc2735a5e2c09681872ef42a11dc89f3c80b9da01" }, { "name": "signal_strength", "unicode": "1F4F6", - "digest": "7876ed9d602e1be746ca0629f072d85668d1f9715e9135745e803bdf89819a3c" + "digest": "2c6f04ba4ecd2d2d423e19eb52cfbfd253f4db6e0908d91c1af4ea6192597447" }, { "name": "six", "unicode": "0036-20E3", - "digest": "b409f23b73e46393c7a814442816b5880c38ef12a7feb5505e71276c195e8ca9" + "digest": "cede9324261208d0fd5d00fcdfc0df0331944bd9cff4f40b30a582a641526c1c" }, { "name": "six_pointed_star", "unicode": "1F52F", - "digest": "4bc294dcbf4185250873b52b2fb5453fb7d80df912db929add6e4b7efc066363" + "digest": "9203e3b4f08af439ae0bfb6a7b29a02dceb027b6c2dc5463b524dfd314cbff4e" }, { "name": "ski", "unicode": "1F3BF", - "digest": "7ee81a2e2f7ff4e32dbf3d64b034e7542ec0c86d32e25eb125052e674943d75f" + "digest": "80f0ca8660ba373fef823af9e98e148c4ddb1e217eb6d0a0ea2bae2288b57570" }, { "name": "skier", "unicode": "26F7", - "digest": "49df9a4206ae0c7c2dbfc8a8b13fd3e14e6f7e750bd5a8581ab6a1626d4c165e" + "digest": "4fff0aa155367f551a59aed9657b8afa159173882b25db9cd8434293d1eed76d" }, { "name": "skull", "unicode": "1F480", - "digest": "dfd169764b192ac7c6e5101277dd9f1e010e86bdd32ad37e00ed4499fc0a5dd6" + "digest": "cdd2031164281bf2b0083df4479651d96bc16d11e44bac4deaf402a9c0d6f40a" }, { "name": "skeleton", "unicode": "1F480", - "digest": "dfd169764b192ac7c6e5101277dd9f1e010e86bdd32ad37e00ed4499fc0a5dd6" + "digest": "cdd2031164281bf2b0083df4479651d96bc16d11e44bac4deaf402a9c0d6f40a" }, { "name": "skull_crossbones", "unicode": "2620", - "digest": "e2acf0f36b6a6800c1829a1c6551b5d0eb6dcdef4b7f02070cf69570aeab608c" + "digest": "ae764ba21a1fcc4409f4cc9e75a261d70b87548f64158dbd3451374ad5724123" }, { "name": "skull_and_crossbones", "unicode": "2620", - "digest": "e2acf0f36b6a6800c1829a1c6551b5d0eb6dcdef4b7f02070cf69570aeab608c" + "digest": "ae764ba21a1fcc4409f4cc9e75a261d70b87548f64158dbd3451374ad5724123" }, { "name": "sleeping", "unicode": "1F634", - "digest": "4ead95079b1a542eedd0e5a0e93fddb318a002bdaffaa2fe5d8d7f20bf8143ed" + "digest": "1050a011509b56735c9f30a6fccc876256e2a4546dc6052e518151c8aca4b526" }, { "name": "sleeping_accommodation", "unicode": "1F6CC", - "digest": "10ee8cd925a75d7977b7cf004e08b5a8147b509ee4281e879a8b57c4a7c2cb04" + "digest": "2ce42c027d1d0947abc403c359fd668a7bc44f5ead2582e97f3db7dd4e22e5d5" }, { "name": "sleepy", "unicode": "1F62A", - "digest": "dea3b246bb8af1b28e200358e3d5d59c8bba1813f35a7f4a57ec568ef43591db" + "digest": "2ee9bb1f72ef99e0e33095ec2bbf7a58ffea0ff7d40b840f4cdba57be9de74b0" }, { "name": "slight_frown", "unicode": "1F641", - "digest": "3ae82b38b58ffa50eddebd87153428d880ca181f4f4178a9ca3bd813ea15ccbc" + "digest": "d71d564a6c2d366a8e28a78ef4e07d387a77037fe8c99aa0ea1571299dc490c9" }, { "name": "slightly_frowning_face", "unicode": "1F641", - "digest": "3ae82b38b58ffa50eddebd87153428d880ca181f4f4178a9ca3bd813ea15ccbc" + "digest": "d71d564a6c2d366a8e28a78ef4e07d387a77037fe8c99aa0ea1571299dc490c9" }, { "name": "slight_smile", "unicode": "1F642", - "digest": "5eee09f634a4e2031927d008a6530a258a00e611ead0c386dd5b7ebb5e75a306" + "digest": "10f4b66a755f5c78762a330f20d1866e4a22f3f1d495161d758d3bab8d2f36fe" }, { "name": "slightly_smiling_face", "unicode": "1F642", - "digest": "5eee09f634a4e2031927d008a6530a258a00e611ead0c386dd5b7ebb5e75a306" + "digest": "10f4b66a755f5c78762a330f20d1866e4a22f3f1d495161d758d3bab8d2f36fe" }, { "name": "slot_machine", "unicode": "1F3B0", - "digest": "9d516b389299431b608c89d3f02ac68d28cb8df2a780f2048923bbcfbb49f416" + "digest": "914184788f8cd865cd074dca25c22acee31f5498117bd9a6e78cae67e6601652" }, { "name": "small_blue_diamond", "unicode": "1F539", - "digest": "97389e82755dc43015089dee635072357ec347f0117b2d3e9b006c46514948ee" + "digest": "0b56d8e6b5ddf1f49fcc76e45e5fb2ee9f99ae6ffe682c26eaea4d9b7faac36c" }, { "name": "small_orange_diamond", "unicode": "1F538", - "digest": "67442d3b707501b7768f606115688373d13617ecf0b3b03ace0f1a6d38f66ddf" + "digest": "a2235830550e289c1608f2dcf5ede48f5c1a0eff45570699c39708c9677ab950" }, { "name": "small_red_triangle", "unicode": "1F53A", - "digest": "e0a556a3dd5bbf0290ed7c00eb6f6307dc2ea98d1fb3111fd85a7f46242a3638" + "digest": "8c2985c4e9ce42d2f3b35539b879bc36206c5ef749f39fbd1eac51bd2676e1e5" }, { "name": "small_red_triangle_down", "unicode": "1F53B", - "digest": "7a11dcb8a517df220493d471759e4f4bca0db3769e2d942bbf596a88a3e57f72" + "digest": "46bd328df2fbf5d0597596bbf00d2d5f6e0c65bcb8f3fb325df8ba0c25e445b5" }, { "name": "smile", "unicode": "1F604", - "digest": "46a7c3545b0038dfce6825d97544f6665f28512ad05c404d668e32ac599c7ecb" + "digest": "14905c372d5bf7719bd727c9efae31a03291acec79801652a23710c6848c5d14" }, { "name": "smile_cat", "unicode": "1F638", - "digest": "c1db961f0fa261532b842816aca7ea7f6d8b461c7e930a1a1c91f96efd9db515" + "digest": "c35b76d6df100edb4022d762f47abfeb9f5e70886960c1d25908bd5d57ccb47e" }, { "name": "smiley", "unicode": "1F603", - "digest": "deeaaee64ebdd9fc0bcb719db75c3f7e0c33ddbcc97f6cd51f9f84377a4368ce" + "digest": "a89f31eb9d814636852517a7f4eadec59195e2ac2cc9f8d124f1a1cc0f775b4a" }, { "name": "smiley_cat", "unicode": "1F63A", - "digest": "85ad852cb3881c4b754af172fdfc6231af42578033ea9f2981ceae944c41e72f" + "digest": "3e66a113c5e3e73fb94be29084cb27986b6bdb0e78ab44785bf2a35a550e71bf" }, { "name": "smiling_imp", "unicode": "1F608", - "digest": "e777bdf186d89921df106d23bf002967b69afffd7e981b3cbb19f89630a06e87" + "digest": "3e02131d16525938f6facc7e097365dec7e13c8a0049a3be35fc29c80cc291b3" }, { "name": "smirk", "unicode": "1F60F", - "digest": "2e7fddd8bed33ef4b7d8c13320302b87a28203e576ef87bd43716952cf0b5ace" + "digest": "3c180d46f5574d6fca3bb68eb02517da60b7008843cb3e90f2f9620d0c8ee943" }, { "name": "smirk_cat", "unicode": "1F63C", - "digest": "9ca0721f4c18592b4b809ade8f716b95fa30cd31dd87d1e41db29a319becd705" + "digest": "0683c7f73e1f65984e91313607d7cca21d99acd4b2e9932f00e0fffd0ce90742" }, { "name": "smoking", "unicode": "1F6AC", - "digest": "3d14b3f0c57eb7a6a31ff371b0a454986533b79dbbeac78a76e4063478911b8d" + "digest": "baa9cb444bf0fe5c74358f981b19bc9e5c0415ced7f042baf93642282476ea61" }, { "name": "snail", "unicode": "1F40C", - "digest": "57d946c7ec84dfad71bc4f7a042927ec5712aef50c66d21af892b6c8a7faf5e1" + "digest": "5733bf3672ae4b2b3e090fa670aeac70dcbcc04ca5b13abc8c8e53b8b3d4ff33" }, { "name": "snake", "unicode": "1F40D", - "digest": "d084da540162288721364992f3b8059cbf2efd9f5b48f49a196ddbe23a073870" + "digest": "18da2d97c771149ef5454dd23470e900903a62ab93f9e2ce301aad5a8181d773" }, { "name": "snowboarder", "unicode": "1F3C2", - "digest": "de9e1767526de606f4908743af94cc17e89fdb0a2a44167d3d021ef09d033ab9" + "digest": "c6e074139b851aa53b1ba6464d84da14b3da7412fc44c6c196a8469d76915c19" }, { "name": "snowflake", "unicode": "2744", - "digest": "e476863ccd7d7b549c6191fb25c121c6a467b4baef4683b7dc3e0a793c2e5d76" + "digest": "6556c918e181df01ba849e76c43972d5310439971e5d8fc2409d112c05bf0028" }, { "name": "snowman", "unicode": "26C4", - "digest": "792946b8446f2243d11b89d07c73a774be3abd36573f3918640b1ba8714270b5" + "digest": "6137456b2335e88e09c1859615eb22bb636355ef438f7a3949ad2f3d54478dd3" }, { "name": "snowman2", "unicode": "2603", - "digest": "571acabaa4d55782c4529b762423a7e34cb1fb6bb7852cbd013e2e846d8311d1" + "digest": "33ec75c22a13c81fa3c6b24a77ac1a08dc0dbe70b3716cf17b6702014d8a63fe" }, { "name": "sob", "unicode": "1F62D", - "digest": "562f02ab584bcbcf9ba73cf7fa7d7129965266abd28db2c73913b8c42f2f5aca" + "digest": "d1ed4b31861f9f9fd4e9c95a9c17530e2320a1b4cad6ececb1545ce25d65e4ce" }, { "name": "soccer", "unicode": "26BD", - "digest": "5fd0d534659b63dc862c65a80561b255bece0b76708fe8ecbae8e01b08d8cad0" + "digest": "6a3f2e6a9a0b64c3fbf8705995792091daf386a4112dba75507a1f556f662f84" }, { "name": "soon", "unicode": "1F51C", - "digest": "d2a1ab16a4056d80c827ea23f9332bb73235fc841b857cbf545062ff8aeed81d" + "digest": "a49d1bcfbac3e6ccc05b9a9863eff74b0eb8b4d4b22b8b0f7b2787fcba1c73cc" }, { "name": "sos", "unicode": "1F198", - "digest": "fadfe8337e133a6f05d205d0807f288e5c230db04cb09f3547ce0cb73cfcf48a" + "digest": "2fa7e0274383aeed6019eb9177e778d7aab8b88575b078b0ffeb77cd18df14b3" }, { "name": "sound", "unicode": "1F509", - "digest": "c0074b338fd461f1f9d1143b7f9b3781ddb3fd501ea79b2410630433a8e87b83" + "digest": "faaca7b315b2495cbc381468580d25f1d11362441c35bb43d8a914f2ec8202d2" }, { "name": "space_invader", "unicode": "1F47E", - "digest": "d264390004bd28d664dfda0069104be6db32ce477e23a95ac595bac2e29fd4e7" + "digest": "e75379cb5063f9a8861d762ad1886097c1697fbb61f2e4e8f531047955a4a2dd" }, { "name": "spades", "unicode": "2660", - "digest": "d1ad99a4fc20dfea881a9062a9f2109e483dbb5dea3b29e9653cb27ec57b4800" + "digest": "2c4d20f6a4893cfc62498d3f1f8f67577f39ed09f3e6682d8cb9cd8f365d30da" }, { "name": "spaghetti", "unicode": "1F35D", - "digest": "ac63f9ad143e236ce6068098e5330a333ade9cddfb3dd6b1457ea47ce9dcf7e9" + "digest": "6d3451dc0faa1913539edb99261448f51735f269b61193c53dfe63466c0191e8" }, { "name": "sparkle", "unicode": "2747", - "digest": "95b8f4f1bb6080cd1d7bd333c4724dbba43ed196dce72a2bbaab46c4a1bc0e48" + "digest": "7131163cd6c2f879110c86e9f068c33cf580f7c4b619449c41851fe6083402ee" }, { "name": "sparkler", "unicode": "1F387", - "digest": "3a296e4d0081ad1a566e111d218e352e1439bba9fd04e8a1eb9a8e36bd438cb7" + "digest": "88539ed8a13bd66e0c265c0913bd3ec2ddc4d95484323595713beb102221a1f6" }, { "name": "sparkles", "unicode": "2728", - "digest": "5ab280ea10c30e0e0b5a26ef52b8f47ad44a983330f7ef62ac0c0888752bbdb6" + "digest": "cf84d16b1c0a381d5a7ae79031872747c9a6887eab6e92cc4a10a4b8600ef506" }, { "name": "sparkling_heart", "unicode": "1F496", - "digest": "f145dab6b597c07e5a851176fabaf56dd857209645483d1acc1490d12c969113" + "digest": "b80b1ddef83b6528b309a194f6f2faf5acab603daeb9254523efc2b941bcb6d2" }, { "name": "speak_no_evil", "unicode": "1F64A", - "digest": "6eae2d066d39c4ba81e58a8327ed875c68bc9b1297c18dc0f5243e477a81040f" + "digest": "d2d7cfb4d471928a496bdc146890adc8422a68500b68115630b24c125d18e81f" }, { "name": "speaker", "unicode": "1F508", - "digest": "ea59c5a9d994808ff7937c300303e644b5f1ad41097e82f9e73ea6e1c718936c" + "digest": "dbca5f7181728d2ad67ff76fd566ffbdf53e333e7eeed341f54668bd47969413" }, { "name": "speaking_head", "unicode": "1F5E3", - "digest": "d92cfe1200887300b2f05f9576448a2f2a79d0accd51f323a65ce3db0aa5639b" + "digest": "4be1af79b4506c00af4df64663413bcbae195dab0bc63c5011feb8f9663ed544" }, { "name": "speaking_head_in_silhouette", "unicode": "1F5E3", - "digest": "d92cfe1200887300b2f05f9576448a2f2a79d0accd51f323a65ce3db0aa5639b" + "digest": "4be1af79b4506c00af4df64663413bcbae195dab0bc63c5011feb8f9663ed544" }, { "name": "speech_balloon", "unicode": "1F4AC", - "digest": "5dccfda46fc984583bc9eaece66e7e884f2a9eb12a69dbd3493035e3c862edd0" + "digest": "817100d9979456e7d2f253ac22e13b7a2302dc1590566214915b003e403c53ca" }, { "name": "speech_left", "unicode": "1F5E8", - "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed" + "digest": "912797107d574f5665411498b6e349dbdec69846f085b6dc356548c4155e90b0" }, { "name": "left_speech_bubble", "unicode": "1F5E8", - "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed" + "digest": "912797107d574f5665411498b6e349dbdec69846f085b6dc356548c4155e90b0" }, { "name": "speech_right", @@ -9562,122 +9562,122 @@ { "name": "speedboat", "unicode": "1F6A4", - "digest": "553a288ab8eeb3dee7b9d1c92eba38016caef7658beaa828136ba1d6ba8ed08a" + "digest": "a523b2320f0b24be1e9fdbc1ff828e28d8fd9a64d51e5888ab453ef0bc9f0576" }, { "name": "spider", "unicode": "1F577", - "digest": "519f7243b5574102ce3f8953e5480812830a1feb32ae51e8573724c864338481" + "digest": "8411eac0c1b80926fd93cc1d6423e00b05d04c485b79ee232da8f1714e899a37" }, { "name": "spider_web", "unicode": "1F578", - "digest": "42959fae08a2162d6ee8c8706f823c5932f3801bc90da30d2ca9a48c3ff25572" + "digest": "2434bdfbe56dcc4a43699dd59b638af431486b52fb1d6d685451f3b231b2be23" }, { "name": "spy", "unicode": "1F575", - "digest": "eaa570a36d83119d0a596228e74affe84d7355714ff6901d88a89410d26dec2a" + "digest": "99fe3cdeff934726ee5855b0e401bf32570084aaad4eb10df837fd410ca742aa" }, { "name": "sleuth_or_spy", "unicode": "1F575", - "digest": "eaa570a36d83119d0a596228e74affe84d7355714ff6901d88a89410d26dec2a" + "digest": "99fe3cdeff934726ee5855b0e401bf32570084aaad4eb10df837fd410ca742aa" }, { "name": "spy_tone1", "unicode": "1F575-1F3FB", - "digest": "abdc066d4cad6a17047faf7806c45feb43ae1e2056cf500536f08f4173dbfa94" + "digest": "1720a99064061c43c7647b6bd517efa2ee2621b355a644adfb347d62849366a2" }, { "name": "sleuth_or_spy_tone1", "unicode": "1F575-1F3FB", - "digest": "abdc066d4cad6a17047faf7806c45feb43ae1e2056cf500536f08f4173dbfa94" + "digest": "1720a99064061c43c7647b6bd517efa2ee2621b355a644adfb347d62849366a2" }, { "name": "spy_tone2", "unicode": "1F575-1F3FC", - "digest": "72a3313ef12364105e764cc3deabd47eb6bd086f261c435682ae1cd29dc8230b" + "digest": "23ff0026723f2b5a46fbfb55e24c4a4a33af2bd96808b3ea3af76aae99965d68" }, { "name": "sleuth_or_spy_tone2", "unicode": "1F575-1F3FC", - "digest": "72a3313ef12364105e764cc3deabd47eb6bd086f261c435682ae1cd29dc8230b" + "digest": "23ff0026723f2b5a46fbfb55e24c4a4a33af2bd96808b3ea3af76aae99965d68" }, { "name": "spy_tone3", "unicode": "1F575-1F3FD", - "digest": "2a1108d3d2e778f88aa5b3ae36705c877b84d0bf6b421409582ba748aeb2aee7" + "digest": "1d0cb3d54fb61e4763a4f0642ef32094bdd40832be0d42799ce9ba69773616df" }, { "name": "sleuth_or_spy_tone3", "unicode": "1F575-1F3FD", - "digest": "2a1108d3d2e778f88aa5b3ae36705c877b84d0bf6b421409582ba748aeb2aee7" + "digest": "1d0cb3d54fb61e4763a4f0642ef32094bdd40832be0d42799ce9ba69773616df" }, { "name": "spy_tone4", "unicode": "1F575-1F3FE", - "digest": "1d4fe62912384bc0d687bcf4565752caf0ed6146c903a156d1c6ba6ea239b154" + "digest": "e36a4b52df6cb954fab9d9128111f1301c6d46bdeacf51993ffb5bb354cd0ad3" }, { "name": "sleuth_or_spy_tone4", "unicode": "1F575-1F3FE", - "digest": "1d4fe62912384bc0d687bcf4565752caf0ed6146c903a156d1c6ba6ea239b154" + "digest": "e36a4b52df6cb954fab9d9128111f1301c6d46bdeacf51993ffb5bb354cd0ad3" }, { "name": "spy_tone5", "unicode": "1F575-1F3FF", - "digest": "69c1baac73783edb9e2d0c951f922dc7dddac34d0a9c818fee8d1021bc17db0d" + "digest": "ffc6fefd9a537124ebf0a9ddf387414dce1291335026064644f6cf9315591129" }, { "name": "sleuth_or_spy_tone5", "unicode": "1F575-1F3FF", - "digest": "69c1baac73783edb9e2d0c951f922dc7dddac34d0a9c818fee8d1021bc17db0d" + "digest": "ffc6fefd9a537124ebf0a9ddf387414dce1291335026064644f6cf9315591129" }, { "name": "stadium", "unicode": "1F3DF", - "digest": "4356db5d2cdef8c40830638debaf1f50831130c12ae8d8dc3d9a6bd28fdaa1f7" + "digest": "73bf955e767ba1518c9c92b2ba59a2aa1ec4b018652dffd97bcd74832a33789f" }, { "name": "star", "unicode": "2B50", - "digest": "13240b8fada84e7555892996e9f9652503bf9b9a002056c2bae428d543abe2da" + "digest": "d78e5c1b78caed103e100150c10b08a9ca3ee30c243943d6fc3cc08f422122e9" }, { "name": "star2", "unicode": "1F31F", - "digest": "9b56c7548f6a222499d4e848576ea25eab837db72b207ebf8a62a451b35f758f" + "digest": "f91ac4afe3f5d4a52847ae8b4a9704b591e00399aebba553d150d7e34ee939fa" }, { "name": "star_and_crescent", "unicode": "262A", - "digest": "10b8a0771e415aa6610fa62185137aa1836c2bb3e82f1a3f601470e94f784923" + "digest": "1bf3d29e50034f5e7c0dccff0a3a533b74bfa9b489e357b2739a473311f1332a" }, { "name": "star_of_david", "unicode": "2721", - "digest": "5bc4d1038b8316281e01a9c575ded7ede0fc24c7593db5b5d36ca2e188aa5614" + "digest": "28a0bd0eeac9d0835ceb8425d72c2472464e863dd09b76a0ddc1c08cf1986402" }, { "name": "stars", "unicode": "1F320", - "digest": "23605eafc949feead3eca145a7ff5ee3b211a8bfd95621bd35dd05df532b97c6" + "digest": "837d9045316b8fb5e533457eac61241534f641eb78d8cb75f688f80fb8e8a7f0" }, { "name": "station", "unicode": "1F689", - "digest": "c346f12fff64161041af8492550c3541a6304e53f30288224ddd0c6fe08c4d6b" + "digest": "27a163ac0aea4ed247a121cae826eafc475977c68b0d888e9405bea14326ff56" }, { "name": "statue_of_liberty", "unicode": "1F5FD", - "digest": "56fa27ab059a9fd1f53aec47d9108277a3bf04a73186f36297cd1207c832ee31" + "digest": "f5a43599ab3f24ed3a78a745e06e2ac3e33107a292386ad81c67935ee5b22493" }, { "name": "steam_locomotive", "unicode": "1F682", - "digest": "d0ec2eb3d761ab6157e17eab1b8b4dec3a69f9becc4251592cbb67d71825e661" + "digest": "52ad0073f37b978faf3884fb193046f2b0614e1557bbcc9de1b020e42aff2dba" }, { "name": "stereo", @@ -9692,7 +9692,7 @@ { "name": "stew", "unicode": "1F372", - "digest": "12e6e4bf48a7296700e07a053d831dd67b70c308ca9522ca96e933a4d1ef6c5e" + "digest": "c16f61236db314ad8d9f2dd241ec1e15c8d64e5872cce93ec4d0996490dd39df" }, { "name": "stock_chart", @@ -9702,212 +9702,212 @@ { "name": "stop_button", "unicode": "23F9", - "digest": "57310962c7738a7da4f2a62cbd5e0b26d7aec357978267a0d8ca8e6cbd7ffb02" + "digest": "83f9d0da3ad845fef41b4e8336815d30e9c8f042ab2a8340894ade2f428fc98a" }, { "name": "stopwatch", "unicode": "23F1", - "digest": "c8e69c24f9da98dcb41c9c6355922d08a702f12a35667fbc5beb3f659430333d" + "digest": "9b6b9491a24d8ab4f896eb876da7973f028bd5e7c51a3767ba7e61bb6fbb2be0" }, { "name": "straight_ruler", "unicode": "1F4CF", - "digest": "55ff7182a3696461df52e3000708083f803bc8bf0f3c25dacb34175cc104b51d" + "digest": "cee31101767bd3f961363599924dc3790675d05a1285a8396428d2f91771c111" }, { "name": "strawberry", "unicode": "1F353", - "digest": "fd501e1fefb70242ac7c4dc30ad3d8c3ae200b263a832daedaa984906114afaf" + "digest": "5750a15e12f21259286ddbc3a8222a385b3b97a9f368897f42dd000060343174" }, { "name": "stuck_out_tongue", "unicode": "1F61B", - "digest": "1b49956cec511ee382177d95da77c8b6a9214a02c86bf7c6c6fd6cc9df3e9331" + "digest": "92dc42980a6dfdd7204fc874a762d6a0bbf0fdbfb5a7c0698fca04782e99fde6" }, { "name": "stuck_out_tongue_closed_eyes", "unicode": "1F61D", - "digest": "60a4d5d92550c6ad4db901d42c9f6434fe94fa3ddb353b6019a93d374d9485e9" + "digest": "434d25ac24cad7ba699eae876a25d9a99b584449cca50b124bf6aa7f20a83d51" }, { "name": "stuck_out_tongue_winking_eye", "unicode": "1F61C", - "digest": "d9c15ad1c4782a0391a79aeda2745127527385b0b5fc01c8d96c3f3b637a74ae" + "digest": "dbacd6428a2a2933212e6a4dc0c7f302177fb23b963626ccb26f27f91737f03d" }, { "name": "sun_with_face", "unicode": "1F31E", - "digest": "56b14e92f68f8701fdc42763e1f4695ed352845f22bd5d412f827e5cf98dd83b" + "digest": "7256ff5263006c64c03f1eb66e3ddb56d67d785d65dacc37aa886d0cd4be63be" }, { "name": "sunflower", "unicode": "1F33B", - "digest": "817dea222a75bb6492c32b4b144d07f48295d7dd113e21760f90b18277612ebb" + "digest": "27d1161f50f932a6b26c404cf2e8f7083683ed0f2382d62b7472acccaa6eb695" }, { "name": "sunglasses", "unicode": "1F60E", - "digest": "16003cc5256397389889f52e0a5e14daea8d8c72f2ea660b8174529868cba9cd" + "digest": "966684382e5c59e98319e4c0ea7c304c61c2638ad5408faa49ce2c83c4416757" }, { "name": "sunny", "unicode": "2600", - "digest": "f68a774b7d574fc711111e17368b57c40d973d263c7e857544a09051d4592ab9" + "digest": "460fea4cbbdd1595450c1033a2ee5de7fea2e2f147822efa49f7e204812415aa" }, { "name": "sunrise", "unicode": "1F305", - "digest": "ce06a9321bc04605538a59f9fca8536d6209d7ded03120e5d2a0be955bb17ddf" + "digest": "7718a49636b0cdd1862ed67c7a9d6e72f471c2591ff0d912485b1be55d1ea115" }, { "name": "sunrise_over_mountains", "unicode": "1F304", - "digest": "286244ac2bec8c5c41cf8c7c439702fa525c57fab623f7f9bd7687db0adf75b2" + "digest": "743d0701cdbe2a814962363813c3153d3c5e62c3e410349f56d49dbb9581f356" }, { "name": "surfer", "unicode": "1F3C4", - "digest": "d17c7ea185ca5ef5a2950ef126ee14103bf7769acb419a20d08cc023f619e459" + "digest": "bb440775e9213430942015c37db8de58b5a561ee971b2a0f3993fc3f1d2554d4" }, { "name": "surfer_tone1", "unicode": "1F3C4-1F3FB", - "digest": "af66f2f26071b3ba8d7c795139055a58a857212f8cb1f51a507242ad7d2c49c7" + "digest": "a4937b030aca30b68bb644f37cf63c38aebce3c00b57d1c8a0ffe596b57d2f1e" }, { "name": "surfer_tone2", "unicode": "1F3C4-1F3FC", - "digest": "7a34e8b1fdad0a89bbb10333d241583ef018517fdd90f171ad7121de53776a3f" + "digest": "1c2a954a9c5284dedf0327d6f3c954c9fdd3953b848076d298874775ad8bf0a3" }, { "name": "surfer_tone3", "unicode": "1F3C4-1F3FD", - "digest": "b2f4cbd59a0aa93c7ee2bbb14ce55c8306dc25884377982a5f132ce6c074fa1d" + "digest": "418a3408b9ab026124f067c8597b500217e56bc28d9844a29eea5eee6f604ff8" }, { "name": "surfer_tone4", "unicode": "1F3C4-1F3FE", - "digest": "b16a02cfcc3606524cca9408e69c654fb83a162eaec8faae8dfd8ec67fe391c5" + "digest": "530870b9ac9f4d45ff750e264feb90b44fb93ca2852f323987b06f5f12fb5a4d" }, { "name": "surfer_tone5", "unicode": "1F3C4-1F3FF", - "digest": "b9a156e1aa57544b703db4e4a7773e244a3139e82c2c808c2e5a804fb524f512" + "digest": "40e11b1ae652cfd085d083377f1da24160065ed1b67403c6fa4655e6e44169ec" }, { "name": "sushi", "unicode": "1F363", - "digest": "d2709b51ee92997c7fafa1b1517259cb896819c8dc9ba98ae26e1d44ec810d4f" + "digest": "b924c621236ca3284b349b0509ae1043f2fc2c7f6d67615716f9717ada78c992" }, { "name": "suspension_railway", "unicode": "1F69F", - "digest": "48903e103ef00a068b0100b28319b1e41c6a4485cb564f0ca59422ec9d3b259c" + "digest": "cd3d21da79864f0c018b863e82fb0561fff3c5e3c065303cfcb89c3663d638ba" }, { "name": "sweat", "unicode": "1F613", - "digest": "8d684fa882bcbf07f4e91ea02a48cd61f22e7aa206162b8352c26fc19361ed4e" + "digest": "1aa771479aa1ac5eeea4bafbe93ebd85a0f692f6d869034f31e25b689c2e264d" }, { "name": "sweat_drops", "unicode": "1F4A6", - "digest": "fca48e255dff08dab97ef98b75c67f7504a13be8b90afac88b69a7b7e887e445" + "digest": "b575b85415bc9852cf6415d417ebf799167fde03c6819ebcaa24ae1b3dde8dab" }, { "name": "sweat_smile", "unicode": "1F605", - "digest": "0c8156554eec2396b5fee908da46484945db980d2ebc6dee57b4069a86826182" + "digest": "171b0d0845d46c33bedb6d3b39fb1ff366e22ba90685eedabebd91bb2b0680de" }, { "name": "sweet_potato", "unicode": "1F360", - "digest": "3ce74ea9bc14906a3d29a9592c0657aee8f7961d406992752f7580b16ca6bdd0" + "digest": "4b91920f0b87d42763313bc476f4c821a74e4c12dc1c92165a859dddeaaf8844" }, { "name": "swimmer", "unicode": "1F3CA", - "digest": "05f3aa8544e3b15837bb06ae47344633b3e60d64c572dc6638c4cee19d6e5506" + "digest": "2c4ed4a51aad99d9957ae11a219d5164db9748fc3a65002c6085a9f15adfa9e2" }, { "name": "swimmer_tone1", "unicode": "1F3CA-1F3FB", - "digest": "85a266a9131f6a1b37e758305ca43ffb46e3e07b0a465c5faefbdb5e5adeb7a4" + "digest": "48588f129ee4af52ca2e0f4594213391978601087cd607896b2f979ca077284b" }, { "name": "swimmer_tone2", "unicode": "1F3CA-1F3FC", - "digest": "f2afdc4d05a2694e663a420d5ad82bd48c92aedc4137d0fd3725bf08c41bd12a" + "digest": "fff209448524bd1ef4d6decabf6c1ead94c8d3d5b1bfb5e54f20cc8e139232fc" }, { "name": "swimmer_tone3", "unicode": "1F3CA-1F3FD", - "digest": "b87ecc38fb9e8eeeef8b120164d758d3f6a68a407053b03261354fd7f90f43b6" + "digest": "2003932cb2cf4ae9a10b23338bf375a9293fb18c0ecf91bdfae73be6eebb3800" }, { "name": "swimmer_tone4", "unicode": "1F3CA-1F3FE", - "digest": "a08629cf3484953b851b357c6a04891fb97ac15e70c376bbb82af47479835e1c" + "digest": "20b4bff9baa1c694ad98067dde834c56092f023b9664bec382c2e512232bd480" }, { "name": "swimmer_tone5", "unicode": "1F3CA-1F3FF", - "digest": "21d83f66b2ef3e348f9e14ec108b9a90262d9934039ebd573471d2bdcde68974" + "digest": "0ff8eb57c2be8e80a1bc6ba75b8d9ffb9bd8d3be636150c4c03399ec1886f218" }, { "name": "symbols", "unicode": "1F523", - "digest": "f33c3ce58374e23b8957c759016fdb5c56ef7fe812bd4e693ae8ff7574cf6bbf" + "digest": "2a2a79816c4d0751a0d73586eec5e63b410653d3c85cc968906bf1fc03d89b94" }, { "name": "synagogue", "unicode": "1F54D", - "digest": "b13402c3c5793ebf924335a87a9f69befb7a6c152fc2a288261b2c2d49842eb6" + "digest": "98569cdd7c61528963b67b7891dfa46025c5e810cbb22ee18ddb3bd85de2da69" }, { "name": "syringe", "unicode": "1F489", - "digest": "39e5e7530255ccf2ff35ec5c653568c8645a4711170c573117f796ea3438c44a" + "digest": "e1538e645ccc571227c994b71b3d1be2c4d072d8bd9c944a42ff4a11c91a34a6" }, { "name": "taco", "unicode": "1F32E", - "digest": "6b004ce7129e00abcc10278bba1b9c3d5ac71888b99bf353f9878d8e494e3e0d" + "digest": "e1e45aefdb7445faeae75c3831df6a3d6f2590fcdd48a20d847593c246df613b" }, { "name": "tada", "unicode": "1F389", - "digest": "956a180a1f18e3a1252761e5b3713324f63975ee1fe32168b59b60aa4dd8b72b" + "digest": "1d2e6cbb2a3244240bc70209715d2213d1efee2e370cccfbcc046c333ae2d650" }, { "name": "tanabata_tree", "unicode": "1F38B", - "digest": "d074457ba347687bfc8397ec62edee6325c411356216e7d43acd3f60628a0bb8" + "digest": "592f2907ffc1b914390e1a106c15120ff3607e99192158b94d237975647c5540" }, { "name": "tangerine", "unicode": "1F34A", - "digest": "1b46bb690458914220cba18c43d7ae0f6914adfee6dba7cf2bb58ed4e1854ad8" + "digest": "40c9ddcde1b0bcfaeb466629a87825eb8c2037835720cbee5e2fda04be3c8d0a" }, { "name": "taurus", "unicode": "2649", - "digest": "ea87fb3baa32605107d63b60847e4873ad9e21b7e7b652e3721cde777168670d" + "digest": "21cf24cb6410ab6596e2df8b3e242cc07f9dbb247eabc00c590fe184b373d068" }, { "name": "taxi", "unicode": "1F695", - "digest": "f44249c643a96d924e1eb35f67a133f3ca61128e610a880afaa09a73c7bcaf9d" + "digest": "c546cc743831cfbf0c15452767cf2a4faf3775066797e997ae7c1fcbe4eca479" }, { "name": "tea", "unicode": "1F375", - "digest": "56ab8c291de8320c5b339e1cfbe972696e4ea31c592cefa240eda9a3abdf4fa3" + "digest": "00e3f1e389fa58c4fcd8c53ebbf83d25872f4315845ab1984b35410ae65553d9" }, { "name": "telephone", "unicode": "260E", - "digest": "609104588e00039199a2fef3190ee6a7be5fca7cb09b36ffe5a7d800aac69d8d" + "digest": "3a53851e641f8ad938ce3597b1afca2ea63c9314ff81f62563b99937496a13d7" }, { "name": "telephone_black", @@ -9922,7 +9922,7 @@ { "name": "telephone_receiver", "unicode": "1F4DE", - "digest": "e3bf6034de6cf2160893ba4990eba198185a6a3f9cd5767a63b048e41c297640" + "digest": "1614d67f3d8814b0d75f39d55f9149e4d28ef57b343498625e62fcfff8365046" }, { "name": "telephone_white", @@ -9937,52 +9937,52 @@ { "name": "telescope", "unicode": "1F52D", - "digest": "abe0aca5f2c78105b0e9e4c8ee7a40adcd9bb013e7c49d568076459bade73556" + "digest": "4adf40387870276c4f59fb050d441023e8dac784365b6a8c0282fb519780b495" }, { "name": "ten", "unicode": "1F51F", - "digest": "7593aa7ffe7192a2e35c6ccec76522f6243777783c9152c7c03419835ea58c03" + "digest": "c7c9491021740d2c17edddb856f79579b0b943d8dc85a2f48dbaac84f35b8a40" }, { "name": "tennis", "unicode": "1F3BE", - "digest": "0a5fad3f7f35da0f37761e2279c148dbe154fa14c0e2a0749209b8b2b213a388" + "digest": "dc1600b4d8dce3d26259eb0d1c6ab042566565e3c1f2c96112210f1550a716fd" }, { "name": "tent", "unicode": "26FA", - "digest": "7ddf437d8d186e4e3c3e818d137518d590fa06098813c7fe20e1f2a9704feab2" + "digest": "30d9b17ac3219d4970ddf54d7c1a288b0ae50f7f3b82ed232c0b1b19ef585662" }, { "name": "thermometer", "unicode": "1F321", - "digest": "597d1714442698a22187fee4d57a2580322f7206c7d51e4519023824598ec08f" + "digest": "66616babbcaef256d7b652796c760e8e893cb950c073348a408fe70904f80f25" }, { "name": "thermometer_face", "unicode": "1F912", - "digest": "f19c489d89dd2d39770a6c8725a20f3e98f9e5216774af60c0665fd6a03a7687" + "digest": "ac2b5caddd128563711a9dcc7f690cf210f684d5e8b64b09c0431d6902437126" }, { "name": "face_with_thermometer", "unicode": "1F912", - "digest": "f19c489d89dd2d39770a6c8725a20f3e98f9e5216774af60c0665fd6a03a7687" + "digest": "ac2b5caddd128563711a9dcc7f690cf210f684d5e8b64b09c0431d6902437126" }, { "name": "thinking", "unicode": "1F914", - "digest": "f64a9a18dca4c502b46f933838753a818b604a9d0268aa32eda26cbd31abc58c" + "digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3" }, { "name": "thinking_face", "unicode": "1F914", - "digest": "f64a9a18dca4c502b46f933838753a818b604a9d0268aa32eda26cbd31abc58c" + "digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3" }, { "name": "thought_balloon", "unicode": "1F4AD", - "digest": "76c8513191641f0a79e878ccc0d83c4576984609810633f596db2f64cc684b7d" + "digest": "bf59624560c333561d636aedf2c8827089e275895cf434974daaabb3d5cea46e" }, { "name": "thought_left", @@ -10007,7 +10007,7 @@ { "name": "three", "unicode": "0033-20E3", - "digest": "ca0147a8f67cea3bc2516fa8deef4325188359559786c94ff0b27f90eef04b88" + "digest": "d3f85828787799c769655c38a519cad0743ab799ab276c7606e6e6894cc442e6" }, { "name": "thumbs_down_reverse", @@ -10032,192 +10032,192 @@ { "name": "thumbsdown", "unicode": "1F44E", - "digest": "a98f742c9773e0d95c0de5e1c10d1ab373fa761378a205f27d095e85debe69a3" + "digest": "5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61" }, { "name": "-1", "unicode": "1F44E", - "digest": "a98f742c9773e0d95c0de5e1c10d1ab373fa761378a205f27d095e85debe69a3" + "digest": "5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61" }, { "name": "thumbsdown_tone1", "unicode": "1F44E-1F3FB", - "digest": "5d0a7c63d52eafe6267c552168c5557a66622009d565c3cf7b5378c1f6e84bce" + "digest": "3c2853491473fd7ae2d1b5415a425cc390d26a8754446f8736c1360e4cb18ba3" }, { "name": "-1_tone1", "unicode": "1F44E-1F3FB", - "digest": "5d0a7c63d52eafe6267c552168c5557a66622009d565c3cf7b5378c1f6e84bce" + "digest": "3c2853491473fd7ae2d1b5415a425cc390d26a8754446f8736c1360e4cb18ba3" }, { "name": "thumbsdown_tone2", "unicode": "1F44E-1F3FC", - "digest": "ca5c15dc516660b2989a1c717bf3745fdfb6964c7acf3b938285ff6c7caf2ca2" + "digest": "4e0f8f86a06b69e423df8d93f41ec393f12800633acc82c4cb6dff64ca0d8507" }, { "name": "-1_tone2", "unicode": "1F44E-1F3FC", - "digest": "ca5c15dc516660b2989a1c717bf3745fdfb6964c7acf3b938285ff6c7caf2ca2" + "digest": "4e0f8f86a06b69e423df8d93f41ec393f12800633acc82c4cb6dff64ca0d8507" }, { "name": "thumbsdown_tone3", "unicode": "1F44E-1F3FD", - "digest": "05740e3568795270674dac9134198bf75b1b778c11daa71649c88c231859ec16" + "digest": "e08fa35575f59978612d4330bbc35313eca9c4dfa04f4212626abc700819effe" }, { "name": "-1_tone3", "unicode": "1F44E-1F3FD", - "digest": "05740e3568795270674dac9134198bf75b1b778c11daa71649c88c231859ec16" + "digest": "e08fa35575f59978612d4330bbc35313eca9c4dfa04f4212626abc700819effe" }, { "name": "thumbsdown_tone4", "unicode": "1F44E-1F3FE", - "digest": "5ee93bcc2f515806462a7b303064beade2b22a3f43a8162e39fd65d15d772e27" + "digest": "7c6d118d20d5add8ca003e4a53e42685a1f9436b872ed10d79f67ad418fb2a44" }, { "name": "-1_tone4", "unicode": "1F44E-1F3FE", - "digest": "5ee93bcc2f515806462a7b303064beade2b22a3f43a8162e39fd65d15d772e27" + "digest": "7c6d118d20d5add8ca003e4a53e42685a1f9436b872ed10d79f67ad418fb2a44" }, { "name": "thumbsdown_tone5", "unicode": "1F44E-1F3FF", - "digest": "5c9ef8d53cf6f755668ab6dabfbfcdfd4b95fd59db3b3dd60290efefe9c33994" + "digest": "8697c4a4ee4d6669dc2d47aa97699c42012ca59b80818ad6845878b37b4a9c58" }, { "name": "-1_tone5", "unicode": "1F44E-1F3FF", - "digest": "5c9ef8d53cf6f755668ab6dabfbfcdfd4b95fd59db3b3dd60290efefe9c33994" + "digest": "8697c4a4ee4d6669dc2d47aa97699c42012ca59b80818ad6845878b37b4a9c58" }, { "name": "thumbsup", "unicode": "1F44D", - "digest": "28b31df963773ba42a1a089f43cd89d0ce1ab0981e5410f41242e9a125fc1aee" + "digest": "59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61" }, { "name": "+1", "unicode": "1F44D", - "digest": "28b31df963773ba42a1a089f43cd89d0ce1ab0981e5410f41242e9a125fc1aee" + "digest": "59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61" }, { "name": "thumbsup_tone1", "unicode": "1F44D-1F3FB", - "digest": "f6365942738d2128b6959d6672b3d295757dc8240703cb84a2b014ad78d67de3" + "digest": "f57e6c525e8830779ea5026590eec3ca10869dc438a0c779734b617d04f28d21" }, { "name": "+1_tone1", "unicode": "1F44D-1F3FB", - "digest": "f6365942738d2128b6959d6672b3d295757dc8240703cb84a2b014ad78d67de3" + "digest": "f57e6c525e8830779ea5026590eec3ca10869dc438a0c779734b617d04f28d21" }, { "name": "thumbsup_tone2", "unicode": "1F44D-1F3FC", - "digest": "771d30146e4dc947a69057b05d32c765c8457ab02b5342889c5489acf27ef356" + "digest": "980eeeb1d8f5d79dae35c7ff81a576e980aa13a440d07b10e32e98ed34cbf7f1" }, { "name": "+1_tone2", "unicode": "1F44D-1F3FC", - "digest": "771d30146e4dc947a69057b05d32c765c8457ab02b5342889c5489acf27ef356" + "digest": "980eeeb1d8f5d79dae35c7ff81a576e980aa13a440d07b10e32e98ed34cbf7f1" }, { "name": "thumbsup_tone3", "unicode": "1F44D-1F3FD", - "digest": "0bb7bbfb654c6139260e1786e7ffa5a33f31e19410c1d4d15737fdf5dd4c721d" + "digest": "b3881060569e56e1dd75ca7960feab0e58ae51f440458781948d65d461116b4e" }, { "name": "+1_tone3", "unicode": "1F44D-1F3FD", - "digest": "0bb7bbfb654c6139260e1786e7ffa5a33f31e19410c1d4d15737fdf5dd4c721d" + "digest": "b3881060569e56e1dd75ca7960feab0e58ae51f440458781948d65d461116b4e" }, { "name": "thumbsup_tone4", "unicode": "1F44D-1F3FE", - "digest": "df0927c5342f0075fbf4ea83b724e6f70c0466c54769c9ce4a5c2deb602b28aa" + "digest": "86fbe2c95414bce5e38fb5c33da31305d7942fca2c9c79168dcffdbd895e9ad6" }, { "name": "+1_tone4", "unicode": "1F44D-1F3FE", - "digest": "df0927c5342f0075fbf4ea83b724e6f70c0466c54769c9ce4a5c2deb602b28aa" + "digest": "86fbe2c95414bce5e38fb5c33da31305d7942fca2c9c79168dcffdbd895e9ad6" }, { "name": "thumbsup_tone5", "unicode": "1F44D-1F3FF", - "digest": "0683ae08c50aaf186c6406680a60617679c7b4bccd0817f24b15911dbb06866f" + "digest": "49fa63ff725c746a18649df16c8fab69bad88bbb564884df79d1d15f553b7343" }, { "name": "+1_tone5", "unicode": "1F44D-1F3FF", - "digest": "0683ae08c50aaf186c6406680a60617679c7b4bccd0817f24b15911dbb06866f" + "digest": "49fa63ff725c746a18649df16c8fab69bad88bbb564884df79d1d15f553b7343" }, { "name": "thunder_cloud_rain", "unicode": "26C8", - "digest": "dd836f06b41a10d6ed9bcbdae291d2886847ff66dc3ede2427382e469f60674c" + "digest": "dacc20b4f6b68e5834aa1b8391afa5e83b5e6eb28e2d2174d3a68186a770506d" }, { "name": "thunder_cloud_and_rain", "unicode": "26C8", - "digest": "dd836f06b41a10d6ed9bcbdae291d2886847ff66dc3ede2427382e469f60674c" + "digest": "dacc20b4f6b68e5834aa1b8391afa5e83b5e6eb28e2d2174d3a68186a770506d" }, { "name": "ticket", "unicode": "1F3AB", - "digest": "a7654a5529535120da3c377e72cd1f7997bdc2dabf1d44b584f7df7852b158f9" + "digest": "b4326fe7761940216e6c76ee2928110a6b37bf913da9d694e96557e7c7c10420" }, { "name": "tickets", "unicode": "1F39F", - "digest": "ccafcc9583a84e847ff1eaa3d53187c5ab150a7d27c6a19363e59b9bc046b567" + "digest": "fb73358c3697c04fcfde6a1e705b1c3b47635b93b9cadfe31d5657566c7d190a" }, { "name": "admission_tickets", "unicode": "1F39F", - "digest": "ccafcc9583a84e847ff1eaa3d53187c5ab150a7d27c6a19363e59b9bc046b567" + "digest": "fb73358c3697c04fcfde6a1e705b1c3b47635b93b9cadfe31d5657566c7d190a" }, { "name": "tiger", "unicode": "1F42F", - "digest": "9ebe3117f5f1b589ff8164f8d87dcc275923e0db87121d2cee0fdb9b56dfc4ac" + "digest": "e139531e6c930bc46242dc0ed274661229de026b5419d8ea8f99fdb0f8a719ab" }, { "name": "tiger2", "unicode": "1F405", - "digest": "212c95dc60d52420a6320917fe3fdd0683b4edc1a2a2c4a1c60920d1f90f4bc3" + "digest": "f930cc8714198310d9b0edca6baff243ac5a3320f75fadb56fa5acc6fe34ff24" }, { "name": "timer", "unicode": "23F2", - "digest": "c48199312ed42ff53a33bb2791db19e2e2521223cd49d8f758ea95b9b379c5ff" + "digest": "69b33f219523d89d81cbbc070ad7e528711e4b34e124a50acb12a0280a34d0b0" }, { "name": "timer_clock", "unicode": "23F2", - "digest": "c48199312ed42ff53a33bb2791db19e2e2521223cd49d8f758ea95b9b379c5ff" + "digest": "69b33f219523d89d81cbbc070ad7e528711e4b34e124a50acb12a0280a34d0b0" }, { "name": "tired_face", "unicode": "1F62B", - "digest": "ad687a956388ec53ca1e301a0abe2f1e2cfb9f73cd543dd61a21c7335a42e332" + "digest": "775739bc9324517e614878ca0960d793df97775feeb62b14dbfb311a42a21802" }, { "name": "tm", "unicode": "2122", - "digest": "1156c8b0af40b336bbb6534b3302ac63eab009c4cd0476adcf1fc4669f04b647" + "digest": "7d9fafdb72d91860478fc185719f289f359eab2c368a132cb936a269e2ab6a24" }, { "name": "toilet", "unicode": "1F6BD", - "digest": "a4a24529c21e00e0861f4160c771f0e90aae8f6aee7550ad30d3dbb3fabbd4be" + "digest": "0d1b0dd0078f51104e8632a0726e1b3f075561a1ffa8a2546602de15798415d0" }, { "name": "tokyo_tower", "unicode": "1F5FC", - "digest": "6324f154f5f5c722044129e5bca03484aca1439911585e42c1c181ffa30b480c" + "digest": "73eaf6fd59d16396673afef620c6d928857d5cf616e95a40eaf2861686e0956a" }, { "name": "tomato", "unicode": "1F345", - "digest": "41bb6de095b27815eacb74a70aea8f7d4fe1ff947182b112001dd47ae7e45fbb" + "digest": "d092d8ad381d542e59b6a82b4f1ef0d10fc1ed48460952375c6c5c6258cea111" }, { "name": "tone1", @@ -10247,72 +10247,72 @@ { "name": "tongue", "unicode": "1F445", - "digest": "bf9dd7c65a8dc5d77eb013658a0a12a13f7b224a784e65e203d9584bb6b41427" + "digest": "286e9d2583c371431d6fc979dd4ab48981676da26baada51a846657a3654c19b" }, { "name": "tools", "unicode": "1F6E0", - "digest": "9b0a36dfdb475621d326359662b22cbdb80563c4f476aa5e7d7c00cdba605bd9" + "digest": "bf08d60dedc06de73d04dab05703bb8ad81989c72b5035d1a07821e51096f158" }, { "name": "hammer_and_wrench", "unicode": "1F6E0", - "digest": "9b0a36dfdb475621d326359662b22cbdb80563c4f476aa5e7d7c00cdba605bd9" + "digest": "bf08d60dedc06de73d04dab05703bb8ad81989c72b5035d1a07821e51096f158" }, { "name": "top", "unicode": "1F51D", - "digest": "d645030099aeb433307569e8e1c4342c1c411a8fefe50fdca7a3207a1a0db671" + "digest": "c9a9f25b17db014e76b6be54aa07ef89bb18f8adb41b3199d180a559ff1d9ea5" }, { "name": "tophat", "unicode": "1F3A9", - "digest": "1082fb2ee2e98fe65d21081b74ca59b07adef85043e2d36f25cac69db2d31fd3" + "digest": "43a45dfb5d6b57a63a0491f4e3ec780774c0301b53ed39a303a0bd803d16ed71" }, { "name": "track_next", "unicode": "23ED", - "digest": "d5415ed140933f345fea8023a3d8fca30dcfcf7d19d9dc9771fa2cae9df62a3b" + "digest": "88592ef6c720a32aeb752322fb4c794bf5110a72408e21e898630452115c731c" }, { "name": "next_track", "unicode": "23ED", - "digest": "d5415ed140933f345fea8023a3d8fca30dcfcf7d19d9dc9771fa2cae9df62a3b" + "digest": "88592ef6c720a32aeb752322fb4c794bf5110a72408e21e898630452115c731c" }, { "name": "track_previous", "unicode": "23EE", - "digest": "97ff4a59a236e5cf506fa3577b20715b3b0197e0f343a50615b36185d5b835f1" + "digest": "98c1b3d643768d94857fb762f6d26cfb87282b449a67792242e8b7068643ac87" }, { "name": "previous_track", "unicode": "23EE", - "digest": "97ff4a59a236e5cf506fa3577b20715b3b0197e0f343a50615b36185d5b835f1" + "digest": "98c1b3d643768d94857fb762f6d26cfb87282b449a67792242e8b7068643ac87" }, { "name": "trackball", "unicode": "1F5B2", - "digest": "8332503454ce42059d720c285fe2b15eb0562a0a4b234dccb0f3159bb30a91aa" + "digest": "32a819a3129429f797ad434d0c40e263dc236808e34878c599ed2304b43702f5" }, { "name": "tractor", "unicode": "1F69C", - "digest": "a41d304c41a85d966f6a7c301735fdbe2ae41f4471dd7dcd72023046ca2546d0" + "digest": "5e4686290f1a4c9953ae208340b7d276f25b3b2197a43e52469aeb6450e93997" }, { "name": "traffic_light", "unicode": "1F6A5", - "digest": "005f68d028fec8d9ae389cc2b23e1343a82c028eb32820d5e56f5c84eba315d1" + "digest": "d96aacade33d1ad3e0414f8a920513010f36eb7e5889774251c1d91148917ead" }, { "name": "train", "unicode": "1F68B", - "digest": "bf32893b7b9ecd248e8afe840624061746ac6ceb741e3e861ebfa46014f4bed4" + "digest": "7423d17e131df7aadaa350b5d39dcbce3b28de331ff8b6703a3b2d0093963f4b" }, { "name": "train2", "unicode": "1F686", - "digest": "08a9732453a0b4f68dd2d3d3879f04ee538f65897913b5a5157c0585132a374a" + "digest": "06e65d549e771632f3c64287a38ba67236f9800ccb6a23c3b592bc010e24e122" }, { "name": "train_diesel", @@ -10327,7 +10327,7 @@ { "name": "tram", "unicode": "1F68A", - "digest": "5a86d31f7ab677d967fecd75babc900b5169766d0228961912314c4c4d1d64ee" + "digest": "21a7699f1a94f06dcb4d1e896448b98a4205f8efe902a8ac169a5005d11ab100" }, { "name": "triangle_round", @@ -10342,62 +10342,62 @@ { "name": "triangular_flag_on_post", "unicode": "1F6A9", - "digest": "d824c973d84cd62c845d64e546de87b094fda8f9972b6a33acd75e1a5ac19f75" + "digest": "1f5ce3828a42f5b1717bac1521d0502cf7081ad9f15e8ed292c1a65f0d1386da" }, { "name": "triangular_ruler", "unicode": "1F4D0", - "digest": "5576802d8bcb8836f473d9c7641ff666250c23c8476c676b253e577695025959" + "digest": "a0367dcf663ec934f1fc7c88bfaccc02b229a896f60930a66bb02241c933e501" }, { "name": "trident", "unicode": "1F531", - "digest": "70c1e8254da5b0e4552673b487503a20feeb249484d4596836b75de70220be82" + "digest": "ee45920845d3b35c2e45b934cf30ce97bfe2f24c5d72ef1ac6e0842e52b50fc1" }, { "name": "triumph", "unicode": "1F624", - "digest": "b09262121b0d3d9d017ded22d0fbb1acaa6ee8c9d38e9ac34292b390d97408fe" + "digest": "4aa44b8e1682c1269624a359f4b0bf613553683b883d947561ab169d7f85da0f" }, { "name": "trolleybus", "unicode": "1F68E", - "digest": "5af943836cc30c3b79160c70b6488c984fa63c104dce08c436597a93d30ff6f4" + "digest": "f610b4fd1123f06778a8e3bb8f738d5b0079aeb0b0926b6a63268c0dd0ee03ed" }, { "name": "trophy", "unicode": "1F3C6", - "digest": "c249938815042716db2b39cdece6715fabf9e56ed583270c451925e6c91f9191" + "digest": "50cfbedac18bf0fa5dec727643e15ec47f64068944b536e97518ee3be4f08006" }, { "name": "tropical_drink", "unicode": "1F379", - "digest": "352d903e813a27d2a74803322539b50a50aec0ca2ed7ab4a92ec480b1c226cb6" + "digest": "54144fce60d650f426b1edf09e47c70b2762222398c1fe40231881f074603a69" }, { "name": "tropical_fish", "unicode": "1F420", - "digest": "13a104ca9c326238ab8d85b60759629b4efaa836946fbe58d78d779443475f7b" + "digest": "fd92100aaa9328da35e6090388824921b9726b474d1432a926d2cf9c45ad6528" }, { "name": "truck", "unicode": "1F69A", - "digest": "13d381d6b43b42350a1e24c02296904b8fdc38c1bf0939fc7037850127e91f21" + "digest": "0d1571e58e900abc453df0ff683fe7acb5906ecbdd52ab35b7101074359faf18" }, { "name": "trumpet", "unicode": "1F3BA", - "digest": "df7fb48920ac0919ee2d7b30102016479f747a5d4dd25b3e18d9f17121d232d1" + "digest": "cea3614c309f5573f328f4603120dbe930016a35f0dfa400b0d968fe9fff2d55" }, { "name": "tulip", "unicode": "1F337", - "digest": "519a84336464b5dc8db57eecef3e5b8ed82ccfdaa0ed0fa9ef7bcf0e8acea1f8" + "digest": "e744e8dbbdc6b126bd5b15aad56b524191de5a604189f4ab6d96730dfef4d086" }, { "name": "turkey", "unicode": "1F983", - "digest": "e87bff52ad3e301dc62f6832b8a6fcaf99db260a96263e4203a55ce3abda8cf8" + "digest": "bf5daef15716b66636a5fdb6d059420521443c0603e2d56bd7c99c791a7285f4" }, { "name": "turned_ok_hand", @@ -10412,442 +10412,442 @@ { "name": "turtle", "unicode": "1F422", - "digest": "388b3e75b931638a09f65b842d26e2cc87b200ba782dec871f84cddd71aaeaf3" + "digest": "588c35fb42c9502a908e9805517d4cc8c4ba4e74c9beed4035779fea1efe14f8" }, { "name": "tv", "unicode": "1F4FA", - "digest": "dba03be6482d6291599c7393b0f749c0de5c873d45c96a20ccc53b3e104a6a24" + "digest": "1279f3f3955a58dbbf74e248fc914b0bdba9c4c6b6a5176e9d12bf2750ecfeb4" }, { "name": "twisted_rightwards_arrows", "unicode": "1F500", - "digest": "5fcad0247576e10e683f353008749975e9371a4f66c0901a73c3a0c7803c63c7" + "digest": "fed07eebc2cf0d977ca0826bbd80defafbbcf118508444148f47b58949ebe27c" }, { "name": "two", "unicode": "0032-20E3", - "digest": "20ad722532a5073fff8aef0a5e890421da0ae97f0723a8a2cc503c13d24ba597" + "digest": "b346f51f6523b02ebcbd753256804e2f9cc1574c96aa634362bf9401dac2c661" }, { "name": "two_hearts", "unicode": "1F495", - "digest": "160cb11e3ed2ae1b20957d445c6c4b4bd604d067294818dfeeefba4562425eb9" + "digest": "6ded120a59aed790b441ec8fbbdea6f5cbfb4fa48e9e4b224cc29c9fde2d2e4c" }, { "name": "two_men_holding_hands", "unicode": "1F46C", - "digest": "923734704e544f7484fdb424bfe26f51ee07754db712cd151f8fbe955023a1ab" + "digest": "bfcf9e20a67d00262cdf6e85f1acd545dda91f2e370d68bfd41ce02f232a2987" }, { "name": "two_women_holding_hands", "unicode": "1F46D", - "digest": "58a40e7819cab3589ac81bb4fdc485b7196ee355544b54c6b00169028c260130" + "digest": "9d9d2b37a7f8e16fde1468dd8b5645003ea81ae4bf8bcf68471e2381845dd0dd" }, { "name": "u5272", "unicode": "1F239", - "digest": "b7e8ad52629a1f1fca77a5c9a51da87ce2b9a81f6af9bcbe9bec9552d398e9bf" + "digest": "01e6cb8f74ea3c19fdade59c2d13d158b90dc6b4b293421b2014b7478bf20870" }, { "name": "u5408", "unicode": "1F234", - "digest": "f359799d206cff6aae3af26eb8ad153abd38e817d4c70b2e5e5e8cf2f46e645e" + "digest": "084cdbd5436670ea4dc22010e269c1ab7b0432897b8675301e69120374bcdd14" }, { "name": "u55b6", "unicode": "1F23A", - "digest": "c40293bea0f148e76ca5152e830b1b474380fe259180fbf74fece1ccc9afd8a3" + "digest": "c1017023d20d4aae78d59342dd3bfc5282716ea0601d9a8c2476335cbf7a2e12" }, { "name": "u6307", "unicode": "1F22F", - "digest": "45449f7ae29da9e507c19d0f2b22f17f7cbd763f2ec87eb893be5bae49c7f78e" + "digest": "f459b092b974f459db1fb9cc13617a448b2e4f2b4dc46cc316d8c46af6e7d8bd" }, { "name": "u6708", "unicode": "1F237", - "digest": "b897ead8c952013975ce6f381cdb8c584ebe4015311ef87f2a332c8a9e155d75" + "digest": "928815abf5b30f92efe5168de0c7e6cf8c17899a03e358ab42f42667e0a4a04c" }, { "name": "u6709", "unicode": "1F236", - "digest": "8b2f792abc1313a1a58f2fb8b37ad68a964004c962535f7739131257b1331a05" + "digest": "f63a48ee06c892d24acec8b5634c021658d2ebde67a42d8faa86f27804a9f26d" }, { "name": "u6e80", "unicode": "1F235", - "digest": "fd982a56d4c492e63526b427bb948d7f155b0d5c414a68c7177698a71e72269b" + "digest": "489181d90a5e43068459530673a153e4af04fdad8514ec341ff7afbcfd366c3b" }, { "name": "u7121", "unicode": "1F21A", - "digest": "334f87a5254b58503d9f7a8ecc3d971a99839ec9c22c443469d72caca1750a48" + "digest": "9c50fd2ba14221affd2dcd3746322c2137dd75458493f4d385b544eb5bd8d6cd" }, { "name": "u7533", "unicode": "1F238", - "digest": "3c8e743ae9960e43b9fa0cc698018fcb2a52ae34d143f0561298191f9def019c" + "digest": "2b05819b380a2ea47cc5fde8fcce3d53922fd223d6f5bd83d696d44175b69f18" }, { "name": "u7981", "unicode": "1F232", - "digest": "a08bf39be3a54c076de79478c09b79c5c4d221853722870dd6e81abb78a4b64a" + "digest": "adbe12601b22972003ddebcb0bd1532b979aa9c78bfdc147511854b5014eabc0" }, { "name": "u7a7a", "unicode": "1F233", - "digest": "5dfb74a534a6490df989f84eac271c79d52f29313b6d43662dd0ff029794367c" + "digest": "b9ee0ec7bb0b86c3eb73d4dbbb91848c427bf356ae30a263b9b44bd9bd784482" }, { "name": "umbrella", "unicode": "2614", - "digest": "ff1191f6c11b82f5337f78aadb58af50c69abaf676a384b0473bf49004e4018f" + "digest": "0328a2f48b7df47905e2655460e524c0794ef12d3d7c32a049a10892d5662f77" }, { "name": "umbrella2", "unicode": "2602", - "digest": "aa7db9d6ed42dff847a8e5ee48a8eeff7a6e7f30de155a28951407f5aaa3dae2" + "digest": "2f6a58110dc590480a822a3ffa2b5bc86f295e0c994a4a632837d25d4cf9fc58" }, { "name": "unamused", "unicode": "1F612", - "digest": "efbbcaee6f3178afe509d74d13243ec6befe3112620a01e5079171eac4b32417" + "digest": "0d597088e3e7880918d0166e5c69243b18fe64afa31685c39bfdbc71494aa132" }, { "name": "underage", "unicode": "1F51E", - "digest": "ae9a300fa400a57b7216a0a040fb8a5f02236fbceeeceed58bfd953c87ad51fe" + "digest": "b6b194614ca714ac2b1c2c17b75fe5922c7fdadb3d1157ba89ab2a5d03494a67" }, { "name": "unicorn", "unicode": "1F984", - "digest": "1b1e9c209dabe619db76fd346c3fb51b28ace0e4102697fe0973fe2d46aa9f08" + "digest": "f71bb485a7c208e999dd45f2b36d7b7d517898c0627947926b05aa28603804ca" }, { "name": "unicorn_face", "unicode": "1F984", - "digest": "1b1e9c209dabe619db76fd346c3fb51b28ace0e4102697fe0973fe2d46aa9f08" + "digest": "f71bb485a7c208e999dd45f2b36d7b7d517898c0627947926b05aa28603804ca" }, { "name": "unlock", "unicode": "1F513", - "digest": "63dbef0855399254ae01cf4ef0676adebc1432ae1ee260b569c23ae8152deaf8" + "digest": "9554ef3a6a315938b873e77970d9b0212e61f13c6cc36e4f17f87acc930a9a53" }, { "name": "up", "unicode": "1F199", - "digest": "902a3ecbcd73099a28476b49bc9e7b06da6cc002ee584e0501e5b625fb515088" + "digest": "ff2554ccf08c7208b38794c5fa3d9a93a46ff191a49401195d8f740846121906" }, { "name": "upside_down", "unicode": "1F643", - "digest": "763fe2baf07a9b04f96958adf38a43c7dd2bc70d57398f49604307bd835cbb53" + "digest": "5129121f0a28f5b334268c28565de26a5907559568deca11de6ec620b097dfe1" }, { "name": "upside_down_face", "unicode": "1F643", - "digest": "763fe2baf07a9b04f96958adf38a43c7dd2bc70d57398f49604307bd835cbb53" + "digest": "5129121f0a28f5b334268c28565de26a5907559568deca11de6ec620b097dfe1" }, { "name": "urn", "unicode": "26B1", - "digest": "dbfd5b90709d1b812d2fff71a5cfa10f84a4579866c2d7cd0e80759a22b2ba0e" + "digest": "9bebf589eed8dd361f6a03cd1b325078f2cd0e82270ef63a7dd1b6aee08cd1e6" }, { "name": "funeral_urn", "unicode": "26B1", - "digest": "dbfd5b90709d1b812d2fff71a5cfa10f84a4579866c2d7cd0e80759a22b2ba0e" + "digest": "9bebf589eed8dd361f6a03cd1b325078f2cd0e82270ef63a7dd1b6aee08cd1e6" }, { "name": "v", "unicode": "270C", - "digest": "df85ad1a3ff365c3232a010701c9b25cd824d19fa2511422dee60ac231f457e3" + "digest": "9825bf440df289a8edf8ede494e8c778dc63c95f967f4d7bbea3245cf4f558ec" }, { "name": "v_tone1", "unicode": "270C-1F3FB", - "digest": "ce45db8de862b6f37d9208920d7c7c19335fac2cbff59b52be1ccbc01e3249da" + "digest": "76e358250d9ca519b60b8d7b6a32900700d784433dcc609e9442254a410f6e37" }, { "name": "v_tone2", "unicode": "270C-1F3FC", - "digest": "9036c8d793b02b4d2e6a4752b8ec319ec50efd6fcd6feef7b0671a63e5659acc" + "digest": "4081b674be8416136022523fa9f29ec70a0f7e3aa05ca13152606609f3fd003c" }, { "name": "v_tone3", "unicode": "270C-1F3FD", - "digest": "a94b95f7656d62b442c99f2643b96b0c6114683401a94cdda68405c37efecc4c" + "digest": "b6afb3a4c78384280610b953592d378241c75597a82aa6d16c86a993f8d8f3b0" }, { "name": "v_tone4", "unicode": "270C-1F3FE", - "digest": "5c75f74993856f2faeeaee68df7689056e60d30e8c573039db8303167f7d0a80" + "digest": "7ddc3cdd0138da2c8d7f6d8257ffdb8801496043e8a2395f93b0663447ac7fce" }, { "name": "v_tone5", "unicode": "270C-1F3FF", - "digest": "bb899672adb3c11f65983fbf9581de7f0a1bbac86fde146e799cea1126fe241e" + "digest": "a85dc5c589f0d1cf32f8bfa5c82e5c11c40b35439636914686a2f06f7359f539" }, { "name": "vertical_traffic_light", "unicode": "1F6A6", - "digest": "36296e03620f16d35e5cec195cd97f5b358dfdedcd43bc1b3f7988ff7e85ab47" + "digest": "8cfd49a8f96b15a8313ef855f2e234ea3fa58332e68896dea34760740de9f020" }, { "name": "vhs", "unicode": "1F4FC", - "digest": "f4be55f4c23a85e0caacbf569742c117c8fd52c189465a6560cbd2f8873ad74f" + "digest": "3fb1acaf25805cf86f8d40ee2c17cf25da587b7ca93b931167ab43fce041eee8" }, { "name": "vibration_mode", "unicode": "1F4F3", - "digest": "b9b8dfa3160c22f78b7d627cb52636d81ca6230a196cee5e94028e32e06b9a98" + "digest": "c9a8899222f46fe51dd8cee3e59f77c48268f0b7cfae2bcb34a791213acb1755" }, { "name": "video_camera", "unicode": "1F4F9", - "digest": "3bfaa24e5fb00145e3e4dd07ecf569dabbb3f211551e46085ef23cf23002cfc3" + "digest": "62e56f26c286a7964ef1021f0f23fcb4b38cdcfb5b5af569b472340c412c619a" }, { "name": "video_game", "unicode": "1F3AE", - "digest": "4dcbd76030e37d0f7429852991a5f3f126cbdedfc124ecad0ba29d227375f6e2" + "digest": "2787e302aa9e6fd7e9dc382c9bc7f5fbf244ef4940e08a4f9e80d33324f3032e" }, { "name": "violin", "unicode": "1F3BB", - "digest": "8ab7adc6e1e934f9e05009cd0a6d4da3136092c8f11c0606b91914be182206f5" + "digest": "1e69d531ce2b5d5bf1dd9470187dbbe76f479d14428834b6a9e2bf5296dc0ec9" }, { "name": "virgo", "unicode": "264D", - "digest": "aaa19752756d0cac949445de1d2b8bf1f75a071368ae0acf5002f4acdc34826f" + "digest": "0f75e9c228bc467fd0cec0f93f0e087c943bc5fb1d945fb0d4de53d07718388e" }, { "name": "volcano", "unicode": "1F30B", - "digest": "86c17d61d66bfa868c02f1d31daca22f077c096368ef53cd9bfb9914a2f0b273" + "digest": "41c92ef88ca533df342a0ebe59d2b676873bfa944c3988495b8a96060a9b8e16" }, { "name": "volleyball", "unicode": "1F3D0", - "digest": "b505684b13f814fbc08dc8ff652849328f46068276e0a24ae1961e2aff15868f" + "digest": "774a83357f7aee890b4d4383236f0a90946dbd7c86aaabadc5753dcc9b4c9d69" }, { "name": "vs", "unicode": "1F19A", - "digest": "e31bd8b48b88c21d717964d1360a7751684dd1e0b63fdd655f1a9ec10a952dfb" + "digest": "ac943e4c737459c2e1adbac8b71d3fdaebb704dbaf5713012e7a77beb09db1ef" }, { "name": "vulcan", "unicode": "1F596", - "digest": "ca800fce797e652c5f47bf44992e8fbe19554688a36423fdf7c29ca6defae1e0" + "digest": "b4d409a0b019e7b06333cefd15ea46cb54aef5132d86e8ba361c1c3b911fe265" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers", "unicode": "1F596", - "digest": "ca800fce797e652c5f47bf44992e8fbe19554688a36423fdf7c29ca6defae1e0" + "digest": "b4d409a0b019e7b06333cefd15ea46cb54aef5132d86e8ba361c1c3b911fe265" }, { "name": "vulcan_tone1", "unicode": "1F596-1F3FB", - "digest": "84bafdaca43426b053f5caa4e868ca109d99113a28ea9799db09d3c5d5f645c8" + "digest": "cc6072c85031b5081995f98a57f09ab177168318f69a51f3acc63251760499a4" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone1", "unicode": "1F596-1F3FB", - "digest": "84bafdaca43426b053f5caa4e868ca109d99113a28ea9799db09d3c5d5f645c8" + "digest": "cc6072c85031b5081995f98a57f09ab177168318f69a51f3acc63251760499a4" }, { "name": "vulcan_tone2", "unicode": "1F596-1F3FC", - "digest": "e7cedf63ead957ee5c287e4cb0828ba70673e17b604f92b529875c32d094e7e3" + "digest": "858bd5a1ac91dc4d7735f57ba4dd69d39138aa6dac1c80cfc05de30a59a5bc33" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone2", "unicode": "1F596-1F3FC", - "digest": "e7cedf63ead957ee5c287e4cb0828ba70673e17b604f92b529875c32d094e7e3" + "digest": "858bd5a1ac91dc4d7735f57ba4dd69d39138aa6dac1c80cfc05de30a59a5bc33" }, { "name": "vulcan_tone3", "unicode": "1F596-1F3FD", - "digest": "e124fef20f289921553274cf834f6dcc1a012889d30d9874dc5ad01afb8235b8" + "digest": "2f74b6f3eab2a75063591b66f1c7350af0d23153e1427af91de20c48a5f4a54a" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone3", "unicode": "1F596-1F3FD", - "digest": "e124fef20f289921553274cf834f6dcc1a012889d30d9874dc5ad01afb8235b8" + "digest": "2f74b6f3eab2a75063591b66f1c7350af0d23153e1427af91de20c48a5f4a54a" }, { "name": "vulcan_tone4", "unicode": "1F596-1F3FE", - "digest": "ea2115f549e4680467521bbf362b229f4a8f0fdadbfaf231378d801f9b369f08" + "digest": "87cf8b87d3610f742857a9704b658462df32b4924d8f1ddba26f761e738c4e11" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone4", "unicode": "1F596-1F3FE", - "digest": "ea2115f549e4680467521bbf362b229f4a8f0fdadbfaf231378d801f9b369f08" + "digest": "87cf8b87d3610f742857a9704b658462df32b4924d8f1ddba26f761e738c4e11" }, { "name": "vulcan_tone5", "unicode": "1F596-1F3FF", - "digest": "1b322e1252491f35ae02f0b279b6529dad867f2a6b3c2c3e77f981bed07e447d" + "digest": "11e9ff62f2385edeb477dbf66c63734536531def5771daf80b66a3425ac71493" }, { "name": "raised_hand_with_part_between_middle_and_ring_fingers_tone5", "unicode": "1F596-1F3FF", - "digest": "1b322e1252491f35ae02f0b279b6529dad867f2a6b3c2c3e77f981bed07e447d" + "digest": "11e9ff62f2385edeb477dbf66c63734536531def5771daf80b66a3425ac71493" }, { "name": "walking", "unicode": "1F6B6", - "digest": "8ec0b2207d4368422261bc58944c17dff2554b2356becfb18f21dd87425cd67b" + "digest": "ae77471fe1e8a734d11711cdb589f64347c35d6ee2fc10f6db16ac550c0557fa" }, { "name": "walking_tone1", "unicode": "1F6B6-1F3FB", - "digest": "9ee2224226326833fb0c9598c737fbd2f6bca1c81f082537e9f22ea1de4ff48e" + "digest": "3de871c234e1340ccf95338df7babd94d175cfcb17a57b5a74d950e0a31f03b1" }, { "name": "walking_tone2", "unicode": "1F6B6-1F3FC", - "digest": "4855d521e937d10d58eeb2bbada493699e31e1098128f81a9e3303bcf3edeb49" + "digest": "620eb7bfb753a331a5822b02bdaf08d8dde7b573efd210287a3d3dfdd84a40b9" }, { "name": "walking_tone3", "unicode": "1F6B6-1F3FD", - "digest": "82669cf7167054a3615add01059f87dbb809edac3889ee171d5994de90448000" + "digest": "ff39545acc2256006128f8c186433c28052b8c9aaec46fe06f25cff02c71f6b8" }, { "name": "walking_tone4", "unicode": "1F6B6-1F3FE", - "digest": "c11f03aa96248272f831f68b93c5b21b2ecbffeb1b4c1c13373bf539ee7db8f8" + "digest": "a9499d142392977a9b9e54fb957952359e9bdffce7ec2f1e8320523d185fb066" }, { "name": "walking_tone5", "unicode": "1F6B6-1F3FF", - "digest": "18238ee121a64211f6bcdbd475cee4ad6debe2bf421daba53d125aa005c26d10" + "digest": "b47a4c48ce40298f842f454fc1abccae70f69725d73ee2c80e4018f4c4065d7d" }, { "name": "waning_crescent_moon", "unicode": "1F318", - "digest": "96ef03ff85247877255a5ca3e8a8bb63f7d41f66531e8db61cbcd863e3ad7355" + "digest": "2ec7896eefcf821e0ea013556a17af59e997503662c07f080d0a84ab13ef4cf1" }, { "name": "waning_gibbous_moon", "unicode": "1F316", - "digest": "994223113ad151e6b42ee317a10dad18f86759a308e61ab88eeb10ab780aae67" + "digest": "ce2f5aca8fccdacaaf174d10da4e493e853e4608cc4d159aa3081d108a8b58d5" }, { "name": "warning", "unicode": "26A0", - "digest": "a702e51efd1a3ab425eada008ccf694f38a71db14bb710edacc2e206d61f5ca3" + "digest": "745f1d203958f42bf37ecb5909cd0819934e300308ba0ff20964c8c203092f90" }, { "name": "wastebasket", "unicode": "1F5D1", - "digest": "afecb31aaf5078298ab9f7c5da29a49ce0cdefe477ee50889be9c0e43ccf1799" + "digest": "221a1b6d9975051038d9d97e18a16556cdf4254a6bca4c29bf1c51f306c79f2a" }, { "name": "watch", "unicode": "231A", - "digest": "410334c87b8552f601f4ea1b7e36582a8b22f11b804d5ab1008d4af2b5a0cbe6" + "digest": "acc0c96751404a789b3085f10425cf34f942185215df459515d2439cde3efc6b" }, { "name": "water_buffalo", "unicode": "1F403", - "digest": "d1becfaea464372c46e5442c6030ea355806ce5864c2435c123a9bb3a2c3c5eb" + "digest": "ba6a840d4f57f8f9f3e9f29b8a030faf02a3a3d912e3e31b067616b2ac48a3d1" }, { "name": "watermelon", "unicode": "1F349", - "digest": "88dd78812520c44080c79fe8cb1825bc713e5155da2ce8c73286333749e7035e" + "digest": "42a3821d2e4dd595c93f5db7a5c70b7af486b8f0ddd3b9d26bc4e743a88e699a" }, { "name": "wave", "unicode": "1F44B", - "digest": "5103c49914ff1a2d76a1ab6db2530ddd9f48b98b708ab15292ceadf28873c939" + "digest": "cddbd764d471604446cbaca91f77f6c4119d1cfc2c856732ca0eaac4593cb736" }, { "name": "wave_tone1", "unicode": "1F44B-1F3FB", - "digest": "ef2d79f377d09dedd1e900b2f4e4a2412bf562cd88484f71c52d465053f8aae9" + "digest": "cf40797437ddf68ec0275f337e6aac4bed81e28da7636d56c9f817ddf8e2b30a" }, { "name": "wave_tone2", "unicode": "1F44B-1F3FC", - "digest": "d323e6e2e9ce035bc11b98226d46ab393dfdf3909d99e7a828b51950e6574656" + "digest": "12c8a3e82c03ee35a734c642be482ba2d9d5948dacf91ec1fda243316dd4a0d0" }, { "name": "wave_tone3", "unicode": "1F44B-1F3FD", - "digest": "8a8a386d53252455c20d6b235c462fd9cb3b20c9c19c67e67b3dece4621b5cf6" + "digest": "ebcaef43e21b475f76de811d4f4d1a67d9393973b57b03876e02164345a2ba4a" }, { "name": "wave_tone4", "unicode": "1F44B-1F3FE", - "digest": "a8281c2ab9cf6e2b3d3cad24707fe412ec2398195530b716a2617477416c0432" + "digest": "7df7b70cf76766836ba146c3d91b6104930c384450cf2688426e60c1c06a1fc8" }, { "name": "wave_tone5", "unicode": "1F44B-1F3FF", - "digest": "5ccbee95bfc180580c8a02b88146110c4d132b8ea618dd6a58f03c1db921d58d" + "digest": "8dfdba6aeff5d7dfd807467d431a137547726b34d021f1a5a0b74e155d270ea7" }, { "name": "wavy_dash", "unicode": "3030", - "digest": "b5b67fc12938801a98ff22b6f7b566c603f58c183737fa740a500724879f0e99" + "digest": "7b1968474f01d12fd09a1f2572282927138d9e9d6a3642de4bf68af80a8c3738" }, { "name": "waxing_crescent_moon", "unicode": "1F312", - "digest": "20446122d170b18f88ea71524f6747d42b97f9d765c52e676e5163fee58ec379" + "digest": "852d7e55a19074d061fa3aa80d6b1e7e87a9280bdf44d94bbdbbe6d59178b1be" }, { "name": "waxing_gibbous_moon", "unicode": "1F314", - "digest": "4324e43d4d45e6333f7379c9feb8efd3093d76f3920d7dc5ad3c615e76104998" + "digest": "a3a1c7cc72521a3f74929789a90e1c35d81ac86e21225c9f844d718d8940e3b3" }, { "name": "wc", "unicode": "1F6BE", - "digest": "cb7c5d35bf11149d12cda2c0897cb6038e043127055bbe2e8e33c9b422d6d8fc" + "digest": "4b95d54e0b53e4b705277917653503b32d6a143c2eaf6c547bc8e01c2dc23659" }, { "name": "weary", "unicode": "1F629", - "digest": "29a291033a1b67eda3710dffae42d63fcfa663e37dab728c236172f3e877fe8f" + "digest": "3528f85540996cd5b562efe5421c495fc1bb414dc797bc20062783ae1b730847" }, { "name": "wedding", "unicode": "1F492", - "digest": "6c7d874f464c9c76b0d767135aa40ced94089b5f71d373098b47488d7f3ef7c4" + "digest": "980f3522cc4c19c3096e668032ea2cd19e7900cdc4b73bbb1c9b4c4d28dc78af" }, { "name": "whale", "unicode": "1F433", - "digest": "94168acda6ba502b64ea50ff4aaafb7e6258d7c6806e91f090c8a3c46edc5b6d" + "digest": "6368fe4bc4a7f68aa2bd5386686a5f1b159feacbec16d59515f2b6e5d01adfbd" }, { "name": "whale2", "unicode": "1F40B", - "digest": "e1cde2308bd510b2449c96e88ffec796856f98b19ceedc1cd7e9ea009dae1417" + "digest": "ccd3edf88167965f2abc18631ffb80e2532f728da35bc0c11144376685da18e8" }, { "name": "wheel_of_dharma", "unicode": "2638", - "digest": "bbd6927697c22a1c3e56fd0c9933d9e00dbf120505fe48d02cb486bcd67a8b2c" + "digest": "4a0a13fcd507b9621686c8090bf340aa8770c064e0e3eb576fbae1229000d6da" }, { "name": "wheelchair", "unicode": "267F", - "digest": "513f759acf528f6a7e39d9de1d171c3faebe645c9cf3bd86b185123016beef95" + "digest": "f5250f2b4b5b4ffe6a6f77d30865c3f5d7173fc91aee547869589b2a96da91c8" }, { "name": "white_check_mark", "unicode": "2705", - "digest": "a0b3bf7c4fb131e7a9fab5169ea4094e2665e02cedaa091f0d6e78609b2f17ed" + "digest": "45eb17bde6e503f22c8579d6e4d507ad6557a15f9eaad14aa716ec9ba1540876" }, { "name": "white_circle", @@ -10857,142 +10857,142 @@ { "name": "white_flower", "unicode": "1F4AE", - "digest": "a3efea4950e09994f5e9d3d16f0728969238302304a6cce90b293c56e9a3e20c" + "digest": "ace093b310eeefdecf4a4bdaf4fbcbb568457b0191ac80778a466ac5f3f4025a" }, { "name": "white_large_square", "unicode": "2B1C", - "digest": "99c4442a65f2e3c568f45aed9e74590206c517a716557f4d741d967c9f42ed40" + "digest": "0db6957ee9ff7325b534b730fc05345a63d4ed9060f0f816807d0dcf004baa3e" }, { "name": "white_medium_small_square", "unicode": "25FD", - "digest": "a1edfeb4e540dcc020ba5dde19f7a18d90966788baa5382a22a0f9038d593f01" + "digest": "d79689981a7b38211c60a025a81e44fd39ac6ea4062e227cae3aab8f51572cd4" }, { "name": "white_medium_square", "unicode": "25FB", - "digest": "794c2339ca71bb6d65ac488fb7b5dc4f0a2412f30890d2c4ece53cdbf52ba78b" + "digest": "6c4ce26d3f69667219f29ea18b04f3e79373024426275f25936e09a683e9a4fc" }, { "name": "white_small_square", "unicode": "25AB", - "digest": "9c4c308070a0c4524993cc36feaa778aad8f0df9f209b82d28b1f3811c441bc4" + "digest": "ae0d35a6bbba4592b89b2f0f1f2d183efb2f93cf2a2136c0c195aab72f0bb1c8" }, { "name": "white_square_button", "unicode": "1F533", - "digest": "f46e18c7250c874d1b4d6117eda741d86a081352e76f3d019dd64af2669fa4bb" + "digest": "797f3d9e44e88e940ffc118e52d0f709eec2ef14b13bdf873ad4b0c96cc0b042" }, { "name": "white_sun_cloud", "unicode": "1F325", - "digest": "d8ce416e6bdb0e59e06e2fceac3177dbe59fefc248fd8c6d76b80d1418141070" + "digest": "0e714038bb0a5b091dd4ad8829c5c72dece493e09da6d56ceadcd0b68e1c0fd5" }, { "name": "white_sun_behind_cloud", "unicode": "1F325", - "digest": "d8ce416e6bdb0e59e06e2fceac3177dbe59fefc248fd8c6d76b80d1418141070" + "digest": "0e714038bb0a5b091dd4ad8829c5c72dece493e09da6d56ceadcd0b68e1c0fd5" }, { "name": "white_sun_rain_cloud", "unicode": "1F326", - "digest": "d2b132518261864ac4a95707eaeea335dd8351ed2b8ef4e2272ced456e309bf1" + "digest": "82fb2a91d43c7c511afed216e12f98e32aef4475e7f3c7ccc0f39732d2f7d5e5" }, { "name": "white_sun_behind_cloud_with_rain", "unicode": "1F326", - "digest": "d2b132518261864ac4a95707eaeea335dd8351ed2b8ef4e2272ced456e309bf1" + "digest": "82fb2a91d43c7c511afed216e12f98e32aef4475e7f3c7ccc0f39732d2f7d5e5" }, { "name": "white_sun_small_cloud", "unicode": "1F324", - "digest": "b86a72f1cdb4d24fd3ab180aae9db012ca51fc01f3786aab596c2e330066b185" + "digest": "0a6164cdadf2413555b7ef47b95f823f5a010f36d2dacfb1a38335a0f59e9601" }, { "name": "white_sun_with_small_cloud", "unicode": "1F324", - "digest": "b86a72f1cdb4d24fd3ab180aae9db012ca51fc01f3786aab596c2e330066b185" + "digest": "0a6164cdadf2413555b7ef47b95f823f5a010f36d2dacfb1a38335a0f59e9601" }, { "name": "wind_blowing_face", "unicode": "1F32C", - "digest": "20bdeb8e39dc637792ac9fbee031c5791889f3126e83556ba51f98809c19763c" + "digest": "e4f63149cbc8829118571f6a93487b96d26665fc15d17d578cca4e5c752cd54f" }, { "name": "wind_chime", "unicode": "1F390", - "digest": "1fc26f33ce13b6a969bb76e914de054ec5d1c7c4cd1dc5ee8fea5f3149f794d8" + "digest": "1b1b212fbd74a9edc62aee7ffab9bcf91d3a9f69bffb2be4b7fd527914c14ced" }, { "name": "wine_glass", "unicode": "1F377", - "digest": "7dfcf9c5195a20fd2745b19e102910392b0fc8f1650b98ab81957807841935e0" + "digest": "d99107d6809386bc5e219aa58ee4930d27b7c3a6d2b10deb9f523df369f766d1" }, { "name": "wink", "unicode": "1F609", - "digest": "404ac6c920414ca35894da1d97b3b2fabe92bd09569274eb5798fbb297129036" + "digest": "56e29994a47335a901d0c98fa141d26faae8f647a860517bd3615fa980921885" }, { "name": "wolf", "unicode": "1F43A", - "digest": "ebadd7766c4a314b4027c32435a2f5727a6283123dfb8834e10251cbfc07ca2f" + "digest": "4a983f5ec8ec0872fcde7890e17605b1229064e5e194b6fca1c4259068d1caed" }, { "name": "woman", "unicode": "1F469", - "digest": "9f0dbb5d1e0db4f008141582dcb6413f5aebaa13e191349c976a435b2bee0956" + "digest": "a06a22a48eeb3aeb885321358fe234e97797ed33be17f52d232ce2830cfbcd97" }, { "name": "woman_tone1", "unicode": "1F469-1F3FB", - "digest": "c1f2a503481fdd96cfbfa7d556500f8e0da0cea1c72ed1078ecbb6962221c22a" + "digest": "c2e4b135c1dac6a0b002569a6ccd9d098f6cb18481c68b5d9115e11241a0978d" }, { "name": "woman_tone2", "unicode": "1F469-1F3FC", - "digest": "bf78b3a8f7424037069f8ac337e154ef185f55026c71a6cf6dbe15eb42ef9813" + "digest": "4848e650051214a53c4cd9f6d3d94158f77f65ecb34f891789de34ee0a713006" }, { "name": "woman_tone3", "unicode": "1F469-1F3FD", - "digest": "4ccd70a2052b932b3395ac0a957c05815327dc8082fd461abcd797411db8ce05" + "digest": "b6f751ad47da019cdfb9d6d78f9610adb92120abf204c30df79a9150b57dbdee" }, { "name": "woman_tone4", "unicode": "1F469-1F3FE", - "digest": "71b5efc4a410102e60048ca05f87587384a6db309f3be94109a4f92ea97072dc" + "digest": "fd27d3a669dc34313fbfe518df7dc2ded3ade5dde695f8d773afe87bf8a8b0d4" }, { "name": "woman_tone5", "unicode": "1F469-1F3FF", - "digest": "91a1cd015731f4db501c276a8236eb0665e4dc7aa1891e2a67b8d3e543fbea9c" + "digest": "9ae9b14dfff40fa60a565d89479727feeba4fd6ffea9acb353a81b14aba751d4" }, { "name": "womans_clothes", "unicode": "1F45A", - "digest": "599332c0b863a40fd0c319e4e0f52ae847326a96d180c288e0466b3ac308a27e" + "digest": "d12a27810780fe5cd8118ed4587e0c4e70dbe9bcd014c6866fe6a8c9c7c55698" }, { "name": "womans_hat", "unicode": "1F452", - "digest": "231ff55c3fa56d8fb5731fe41f547e67ffacfdde82286f45d4ca65a2d2821239" + "digest": "52a0255b3483085bd125d39b74516ab6a81003964f44995c2fac821e7ff93086" }, { "name": "womens", "unicode": "1F6BA", - "digest": "f971429456b543804412490af2e27e0b14d0d536a156db898bce67b136e1b563" + "digest": "7e38964006f8b28dfa2b3e9b2b16553bb50c18a63455f556b0bff35ee172137e" }, { "name": "worried", "unicode": "1F61F", - "digest": "e017f636e79b9301f3a06471a5f3513ba7dbb9b97938de1140c1df4c32fd8844" + "digest": "5a073985e1344bc34201ef94a491f7f2b946f5828c9fdbc57eeb2dcd87ac3a6b" }, { "name": "wrench", "unicode": "1F527", - "digest": "c9ded4f7f496bad8691677226310bbd31bb485722ea479bc7a68a2b4ef9d55d9" + "digest": "81aae53bc892035b905bf3ec5b442a8ecc95027c5fa9eb51b7c3e7d8fad3f3f4" }, { "name": "writing_hand", @@ -11007,76 +11007,76 @@ { "name": "writing_hand_tone1", "unicode": "270D-1F3FB", - "digest": "38e64e6dca4847a12aef8a117c113b2025d841501c4bc8188c57d0c8a4f1e34d" + "digest": "2c7e2108e1990490b681343c1b01b4183d4f18fbdef792f113b2f87595e0dad0" }, { "name": "writing_hand_tone2", "unicode": "270D-1F3FC", - "digest": "2b2d0ac2701ae707c31d9c85feb2e3700e11398701e2b0519338897817d53baf" + "digest": "87ec8d44f472d301adbcbd50d8c852b609e46584057f59cc1527401db363c1bf" }, { "name": "writing_hand_tone3", "unicode": "270D-1F3FD", - "digest": "85d67f90ff8bd2e7157f28fd857e6730b660a7eb82eb5350f57671f728ce725b" + "digest": "4a48ddef91f7264e8fa9cca223554db22b3a2e3153e94b88d146644ea6dd661e" }, { "name": "writing_hand_tone4", "unicode": "270D-1F3FE", - "digest": "056c05c201b3d0972433f00910967ad7334e37726e2956fee053ec2e1a9153c7" + "digest": "e5254564a1f91e42ee59f359d8cd26f52abdc04dca8f3b37cb2f140cb7f71390" }, { "name": "writing_hand_tone5", "unicode": "270D-1F3FF", - "digest": "95c59157d301ee08990e4302fd9bdd7953e1d1abed09636d0837d84e44f53ba6" + "digest": "61299bf86d83d323ca3e6052c535ae66c6f7b3d9866a37db0464223b8bc28523" }, { "name": "x", "unicode": "274C", - "digest": "1d256b0015b9cbdeaa4558f9241782c89d86c79a42e507621f7949c56a90b6c0" + "digest": "3e5a7918e31ddefdf1ce73972365e2f0bfd2917d6a450c1a278c108349c9425d" }, { "name": "yellow_heart", "unicode": "1F49B", - "digest": "e869a80266b4379a8d82988fef25e187632bfb076ae619f576e416906cd688a7" + "digest": "a1098f2f04c29754cc9974324508386787d4d803b57cf691d42de414cb2679d6" }, { "name": "yen", "unicode": "1F4B4", - "digest": "8f3d801c687e585e4497123c5c91a8b0c558578deec6a8c1591b25e64a3a8992" + "digest": "944daaeb3f6369c807c0e63b106cee1360040f7800a70c0d942a992f25a55da7" }, { "name": "yin_yang", "unicode": "262F", - "digest": "e8ea4c686518ad6165e15ed67b529f2f1e20d648aa2ecb7e9bff5a6067dd3fea" + "digest": "5ee8d13dacf41306a09237bfcff6abeef110331b40eb7d6e80600628c1327545" }, { "name": "yum", "unicode": "1F60B", - "digest": "d9c97bbf6bdb6e39977437680f0b37c9335306c51e01114056ae1d4c9c85b0e0" + "digest": "31a89088c21bd7a74a3a26d731a907d1bc49436300a9f9c55248703cf7ef44c7" }, { "name": "zap", "unicode": "26A1", - "digest": "37588734c7fe330ae35e6ee99e7cf4183e8fe1bc01f6bbbc6293b21076a338cb" + "digest": "9f8144ae6f866129aea41bbf694b0c858ef9352a139969e57cd8db73385f52c3" }, { "name": "zero", "unicode": "0030-20E3", - "digest": "519c927db8264d5379ab2c6a18656ea6dd1ceb2afc92eb48563bf86af4697571" + "digest": "1b27b5c904defadbdd28ace67a6be5c277ff043297db7cd9f672bbf84e37fa1a" }, { "name": "zipper_mouth", "unicode": "1F910", - "digest": "8396249161b6d865861b56aabd17cae2c821b0d814f4249bf8cab0bb21fa8ee9" + "digest": "81bee5aa1202dfd5a4c7badb71ec0e44b8f75c2cbef94e6fd35c593d8770ae43" }, { "name": "zipper_mouth_face", "unicode": "1F910", - "digest": "8396249161b6d865861b56aabd17cae2c821b0d814f4249bf8cab0bb21fa8ee9" + "digest": "81bee5aa1202dfd5a4c7badb71ec0e44b8f75c2cbef94e6fd35c593d8770ae43" }, { "name": "zzz", "unicode": "1F4A4", - "digest": "f07c56d2d55c0a886c26a8e3d49a9adeab54cc1a0c0354ea8d3bf23aaed3176d" + "digest": "b3313d0c44a59fa9d4ce9f7eb4d07ff71dfc8bb01798154250f27cdcf3c693b5" } ]
\ No newline at end of file diff --git a/lib/api/api.rb b/lib/api/api.rb index f8f680a6311..3d7d67510a8 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -39,7 +39,7 @@ module API mount ::API::Issues mount ::API::Keys mount ::API::Labels - mount ::API::Licenses + mount ::API::LicenseTemplates mount ::API::MergeRequests mount ::API::Milestones mount ::API::Namespaces @@ -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/award_emoji.rb b/lib/api/award_emoji.rb index 985590312e3..c4fa1838b5a 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -11,7 +11,6 @@ module API [ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji", ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji" ].each do |endpoint| - # Get a list of project +awardable+ award emoji # # Parameters: diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 231840148d9..d467eb9d474 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,8 +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 @branch = user_project.repository.find_branch(params[:branch]) @@ -59,8 +58,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 @branch = user_project.repository.find_branch(params[:branch]) @@ -101,7 +99,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/builds.rb b/lib/api/builds.rb index 979328efe0e..d36047acd1f 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -13,7 +13,6 @@ module API # Example Request: # GET /projects/:id/builds get ':id/builds' do - builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) @@ -33,10 +32,10 @@ module API get ':id/repository/commits/:sha/builds' do authorize_read_builds! - commit = user_project.pipelines.find_by_sha(params[:sha]) - return not_found! unless commit + return not_found! unless user_project.commit(params[:sha]) - builds = commit.builds.order('id DESC') + pipelines = user_project.pipelines.where(sha: params[:sha]) + builds = user_project.builds.where(pipeline: pipelines).order('id DESC') builds = filter_builds(builds, params[:scope]) present paginate(builds), with: Entities::Build, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5a23a18fe9c..9076a0c3831 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 @@ -93,6 +104,7 @@ module API class GroupDetail < Group expose :projects, using: Entities::Project + expose :shared_projects, using: Entities::Project end class GroupMember < UserBasic @@ -240,9 +252,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 @@ -272,6 +284,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_type + + 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_id? + + 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 @@ -376,6 +413,7 @@ module API expose :user_oauth_applications expose :after_sign_out_path expose :container_registry_token_expire_delay + expose :repository_storage end class Release < Grape::Entity diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb index ab9b7c602b5..dbe5bb08d3f 100644 --- a/lib/api/group_members.rb +++ b/lib/api/group_members.rb @@ -77,7 +77,7 @@ module API member = group.group_members.find_by(user_id: params[:user_id]) if member.nil? - render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) + render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}", 404) else member.destroy end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 1d361569d59..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 @@ -20,6 +21,20 @@ module API @wiki ||= params[:project].end_with?('.wiki') && !Project.find_with_namespace(params[:project]) end + + def project + @project ||= begin + project_path = params[:project] + + # Check for *.wiki repositories. + # Strip out the .wiki from the pathname before finding the + # project. This applies the correct project permissions to + # the wiki repository as well. + project_path.chomp!('.wiki') if wiki? + + Project.find_with_namespace(project_path) + end + end end post "/allowed" do @@ -32,24 +47,26 @@ module API User.find_by(id: params[:user_id]) end - project_path = params[:project] - - # Check for *.wiki repositories. - # Strip out the .wiki from the pathname before finding the - # project. This applies the correct project permissions to - # the wiki repository as well. - project_path.chomp!('.wiki') if wiki? - - project = Project.find_with_namespace(project_path) + 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.check(params[:action], params[:changes]) + access_status = access.check(params[:action], params[:changes]) + + response = { status: access_status.status, message: access_status.message } + + if access_status.status + # Return the repository full path so that gitlab-shell has it when + # handling ssh commands + response[:repository_path] = project.repository.path_to_repo + end + + response end # diff --git a/lib/api/licenses.rb b/lib/api/license_templates.rb index be0e113fbcb..d0552299ed0 100644 --- a/lib/api/licenses.rb +++ b/lib/api/license_templates.rb @@ -1,6 +1,6 @@ module API - # Licenses API - class Licenses < Grape::API + # License Templates API + class LicenseTemplates < Grape::API PROJECT_TEMPLATE_REGEX = /[\<\{\[] (project|description| diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 0e94efd4acd..4fcdf8968c9 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -233,8 +233,8 @@ module API render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable? - if params[:sha] && merge_request.source_sha != params[:sha] - render_api_error!("SHA does not match HEAD of source branch: #{merge_request.source_sha}", 409) + if params[:sha] && merge_request.diff_head_sha != params[:sha] + render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409) end merge_params = { diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 132043cf3f7..7a0cb7c99f3 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -115,7 +115,6 @@ module API issues = IssuesFinder.new(current_user, finder_params).execute present paginate(issues), with: Entities::Issue, current_user: current_user end - end end end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index ccca65cbe1c..6bb70bc8bc3 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -28,7 +28,6 @@ module API present @hook, with: Entities::ProjectHook end - # Add hook to project # # Parameters: diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb index b703da0557a..6a0b3e7d134 100644 --- a/lib/api/project_members.rb +++ b/lib/api/project_members.rb @@ -4,7 +4,6 @@ module API before { authenticate! } resource :projects do - # Get a project team members # # Parameters: diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 5a22d14988f..0cc1edd65c8 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -341,7 +341,6 @@ module API else not_found!("Source Project") end - end # Remove a forked_from relationship @@ -418,7 +417,6 @@ module API present paginate(projects), with: Entities::Project end - # Get a users list # # Example Request: diff --git a/lib/api/services.rb b/lib/api/services.rb index 203f04a6259..fc8598daa32 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -4,7 +4,6 @@ module API before { authenticate! } before { authorize_admin_project } - resource :projects do # Set <service_slug> service for project # 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). diff --git a/lib/api/todos.rb b/lib/api/todos.rb new file mode 100644 index 00000000000..2a6bfa98ca4 --- /dev/null +++ b/lib/api/todos.rb @@ -0,0 +1,82 @@ +module API + # Todos 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 + TodosFinder.new(current_user, params).execute + end + end + + # Get a todo list + # + # Example Request: + # GET /todos + # + get do + todos = find_todos + + present paginate(todos), with: Entities::Todo, current_user: current_user + end + + # 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 + todo = current_user.todos.find(params[:id]) + todo.done + + present todo, with: Entities::Todo, current_user: current_user + end + + # Mark all todos as done + # + # Example Request: + # DELETE /todos + # + delete do + todos = find_todos + todos.each(&:done) + + present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo, current_user: current_user + end + end + end +end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 7b91215d50b..b9773f98d75 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -2,8 +2,6 @@ require 'yaml' module Backup class Repository - attr_reader :repos_path - def dump prepare @@ -50,10 +48,12 @@ module Backup end def restore - if File.exists?(repos_path) + Gitlab.config.repositories.storages.each do |name, path| + next unless File.exists?(path) + # Move repos dir to 'repositories.old' dir - bk_repos_path = File.join(repos_path, '..', 'repositories.old.' + Time.now.to_i.to_s) - FileUtils.mv(repos_path, bk_repos_path) + bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s) + FileUtils.mv(path, bk_repos_path) end FileUtils.mkdir_p(repos_path) @@ -61,7 +61,7 @@ module Backup Project.find_each(batch_size: 1000) do |project| $progress.print " * #{project.path_with_namespace} ... " - project.namespace.ensure_dir_exist if project.namespace + project.ensure_dir_exist if File.exists?(path_to_bundle(project)) FileUtils.mkdir_p(path_to_repo(project)) @@ -100,8 +100,8 @@ module Backup end $progress.print 'Put GitLab hooks in repositories dirs'.color(:yellow) - cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks" - if system(cmd) + cmd = %W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args + if system(*cmd) $progress.puts " [DONE]".color(:green) else puts " [FAILED]".color(:red) @@ -120,10 +120,6 @@ module Backup File.join(backup_repos_path, project.path_with_namespace + ".bundle") end - def repos_path - Gitlab.config.gitlab_shell.repos_path - end - def backup_repos_path File.join(Gitlab.config.backup.path, "repositories") end @@ -139,5 +135,11 @@ module Backup def silent {err: '/dev/null', out: '/dev/null'} end + + private + + def repository_storage_paths_args + Gitlab.config.repositories.storages.values + end end end diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 81d66271136..d77a5e3ff09 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -160,11 +160,7 @@ module Banzai title = object_link_title(object) klass = reference_class(object_sym) - data = data_attribute( - original: link_text || match, - project: project.id, - object_sym => object.id - ) + data = data_attributes_for(link_text || match, project, object) if matches.names.include?("url") && matches[:url] url = matches[:url] @@ -183,6 +179,14 @@ module Banzai end end + def data_attributes_for(text, project, object) + data_attribute( + original: text, + project: project.id, + object_sym => object.id + ) + end + def object_link_text_extras(object, matches) extras = [] diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index d25de900674..ae7d31cf191 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -61,7 +61,7 @@ module Banzai # Build a regexp that matches all valid :emoji: names. def self.emoji_pattern - @emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ + @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ end def emoji_pattern @@ -69,7 +69,7 @@ module Banzai end def emoji_filename(name) - "#{Emoji.emoji_filename(name)}.png" + "#{Gitlab::Emoji.emoji_filename(name)}.png" end end end diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb index ccd106860bd..f0fb6084a35 100644 --- a/lib/banzai/filter/image_link_filter.rb +++ b/lib/banzai/filter/image_link_filter.rb @@ -8,6 +8,10 @@ module Banzai # of the anchor, and then replace the img with the link-wrapped version. def call doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img| + div = doc.document.create_element( + 'div', + class: 'image-container' + ) link = doc.document.create_element( 'a', @@ -17,7 +21,10 @@ module Banzai ) link.children = img.clone - img.replace(link) + + div.children = link + + img.replace(div) end doc diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 5351272f42d..4042e9a4c25 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -46,6 +46,26 @@ module Banzai end end + def object_link_title(object) + if object.is_a?(ExternalIssue) + "Issue in #{object.project.external_issue_tracker.title}" + else + super + end + end + + def data_attributes_for(text, project, object) + if object.is_a?(ExternalIssue) + data_attribute( + project: project.id, + external_issue: object.id, + reference_type: ExternalIssueReferenceFilter.reference_type + ) + else + super + end + end + def find_projects_for_paths(paths) super(paths).includes(:gitlab_issue_tracker_service) end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index e4d3f87d0aa..e258dc8e2bf 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -13,13 +13,13 @@ module Banzai end def self.references_in(text, pattern = Label.reference_pattern) - text.gsub(pattern) do |match| + unescape_html_entities(text).gsub(pattern) do |match| yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~ end end def references_in(text, pattern = Label.reference_pattern) - text.gsub(pattern) do |match| + unescape_html_entities(text).gsub(pattern) do |match| label = find_label($~[:project], $~[:label_id], $~[:label_name]) if label @@ -66,6 +66,10 @@ module Banzai LabelsHelper.render_colored_cross_project_label(object) end end + + def unescape_html_entities(text) + CGI.unescapeHTML(text.to_s) + end end end end diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index 2d6f34c9cd8..bf058241cda 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -29,7 +29,7 @@ module Banzai def data_attribute(attributes = {}) attributes = attributes.reject { |_, v| v.nil? } - attributes[:reference_type] = self.class.reference_type + attributes[:reference_type] ||= self.class.reference_type attributes.delete(:original) if context[:no_original_data] attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ") end diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 62a79c62e20..536b478979f 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -27,12 +27,17 @@ module Banzai highlighted = "<pre>#{code}</pre>" end - # Replace the parent `pre` element with the entire highlighted block - node.parent.replace(highlighted) + # Extracted to a method to measure it + replace_parent_pre_element(node, highlighted) end private + def replace_parent_pre_element(node, highlighted) + # Replace the parent `pre` element with the entire highlighted block + node.parent.replace(highlighted) + end + # Override Rouge::Plugins::Redcarpet#rouge_formatter def rouge_formatter(lexer) Rouge::Formatters::HTMLGitlab.new( 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/api/builds.rb b/lib/ci/api/builds.rb index 9f270f7b387..260ac81f5fa 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -195,8 +195,7 @@ module Ci not_found! unless build authenticate_build_token!(build) - build.remove_artifacts_file! - build.remove_artifacts_metadata! + build.erase_artifacts! end end end diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb index 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/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index c52d4d63382..01ef13df57a 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, @@ -14,7 +13,7 @@ module Ci ALLOWED_CACHE_KEYS = [:key, :untracked, :paths] ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in] - attr_reader :after_script, :image, :services, :path, :cache + attr_reader :path, :cache, :stages def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) @@ -22,8 +21,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 @@ -42,10 +44,6 @@ module Ci end end - def stages - @stages || DEFAULT_STAGES - end - def global_variables @variables end @@ -60,12 +58,14 @@ module Ci private def initial_parsing - @after_script = @config[:after_script] - @image = @config[:image] - @services = @config[:services] - @stages = @config[:stages] || @config[:types] - @variables = @config[:variables] || {} - @cache = @config[:cache] + @before_script = @ci_config.before_script + @image = @ci_config.image + @after_script = @ci_config.after_script + @services = @ci_config.services + @variables = @ci_config.variables + @stages = @ci_config.stages + @cache = @ci_config.cache + @jobs = {} @config.except!(*ALLOWED_YAML_KEYS) @@ -85,9 +85,14 @@ 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] || [@ci_config.before_script], job[:script]].flatten.compact.join("\n"), + ## + # 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, only: job[:only], @@ -107,12 +112,6 @@ module Ci end def validate! - unless @ci_config.valid? - raise ValidationError, @ci_config.errors.first - end - - validate_global! - @jobs.each do |name, job| validate_job!(name, job) end @@ -120,50 +119,6 @@ module Ci true end - def validate_global! - unless @after_script.nil? || validate_array_of_strings(@after_script) - raise ValidationError, "after_script should be an array of strings" - end - - unless @image.nil? || @image.is_a?(String) - raise ValidationError, "image should be a string" - end - - unless @services.nil? || validate_array_of_strings(@services) - raise ValidationError, "services should be an array of strings" - end - - unless @stages.nil? || validate_array_of_strings(@stages) - raise ValidationError, "stages should be an array of strings" - end - - unless @variables.nil? || validate_variables(@variables) - raise ValidationError, "variables should be a map of key-value strings" - end - - validate_global_cache! if @cache - end - - def validate_global_cache! - @cache.keys.each do |key| - unless ALLOWED_CACHE_KEYS.include? key - raise ValidationError, "#{name} cache unknown parameter #{key}" - end - end - - if @cache[:key] && !validate_string(@cache[:key]) - raise ValidationError, "cache:key parameter should be a string" - end - - if @cache[:untracked] && !validate_boolean(@cache[:untracked]) - raise ValidationError, "cache:untracked parameter should be an boolean" - end - - if @cache[:paths] && !validate_array_of_strings(@cache[:paths]) - raise ValidationError, "cache:paths parameter should be an array of strings" - end - end - def validate_job!(name, job) validate_job_name!(name) validate_job_keys!(name, job) @@ -240,8 +195,8 @@ module Ci end def validate_job_stage!(name, job) - unless job[:stage].is_a?(String) && job[:stage].in?(stages) - raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}" + unless job[:stage].is_a?(String) && job[:stage].in?(@stages) + raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}" end end @@ -305,12 +260,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/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.rb b/lib/gitlab.rb index 37f4c34054f..c3064163e07 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -2,6 +2,11 @@ require_dependency 'gitlab/git' module Gitlab def self.com? - Gitlab.config.gitlab.url == 'https://gitlab.com' + # Check `staging?` as well to keep parity with gitlab.com + Gitlab.config.gitlab.url == 'https://gitlab.com' || staging? + end + + def self.staging? + Gitlab.config.gitlab.url == 'https://staging.gitlab.com' end end diff --git a/lib/gitlab/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/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/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index ab7b811c5d8..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) @@ -22,7 +21,7 @@ module Grack # Need this if under RELATIVE_URL_ROOT unless Gitlab.config.gitlab.relative_url_root.empty? # If website is mounted using relative_url_root need to remove it first - @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'') + @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root, '') else @env['PATH_INFO'] = @request.path end diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 3e3986d6382..34e0143a82e 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -1,3 +1,5 @@ +require 'securerandom' + module Gitlab class Shell class Error < StandardError; end @@ -18,77 +20,82 @@ module Gitlab # Init new repository # + # storage - project's storage path # name - project path with namespace # # Ex. - # add_repository("gitlab/gitlab-ci") + # add_repository("/path/to/storage", "gitlab/gitlab-ci") # - def add_repository(name) + def add_repository(storage, name) Gitlab::Utils.system_silent([gitlab_shell_projects_path, - 'add-project', "#{name}.git"]) + 'add-project', storage, "#{name}.git"]) end # Import repository # + # storage - project's storage path # name - project path with namespace # # Ex. - # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") + # import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://github.com/randx/six.git") # - def import_repository(name, url) - output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '900']) + def import_repository(storage, name, url) + output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', + storage, "#{name}.git", url, '900']) raise Error, output unless status.zero? true end # Move repository - # + # storage - project's storage path # path - project path with namespace # new_path - new project path with namespace # # Ex. - # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new") + # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new") # - def mv_repository(path, new_path) + def mv_repository(storage, path, new_path) Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', - "#{path}.git", "#{new_path}.git"]) + storage, "#{path}.git", "#{new_path}.git"]) end # Fork repository to new namespace - # + # storage - project's storage path # path - project path with namespace # fork_namespace - namespace for forked project # # Ex. - # fork_repository("gitlab/gitlab-ci", "randx") + # fork_repository("/path/to/storage", "gitlab/gitlab-ci", "randx") # - def fork_repository(path, fork_namespace) + def fork_repository(storage, path, fork_namespace) Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project', - "#{path}.git", fork_namespace]) + storage, "#{path}.git", fork_namespace]) end # Remove repository from file system # + # storage - project's storage path # name - project path with namespace # # Ex. - # remove_repository("gitlab/gitlab-ci") + # remove_repository("/path/to/storage", "gitlab/gitlab-ci") # - def remove_repository(name) + def remove_repository(storage, name) Gitlab::Utils.system_silent([gitlab_shell_projects_path, - 'rm-project', "#{name}.git"]) + 'rm-project', storage, "#{name}.git"]) end # Gc repository # + # storage - project storage path # path - project path with namespace # # Ex. - # gc("gitlab/gitlab-ci") + # gc("/path/to/storage", "gitlab/gitlab-ci") # - def gc(path) + def gc(storage, path) Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'gc', - "#{path}.git"]) + storage, "#{path}.git"]) end # Add new key to gitlab-shell @@ -133,31 +140,31 @@ module Gitlab # Add empty directory for storing repositories # # Ex. - # add_namespace("gitlab") + # add_namespace("/path/to/storage", "gitlab") # - def add_namespace(name) - FileUtils.mkdir(full_path(name), mode: 0770) unless exists?(name) + def add_namespace(storage, name) + FileUtils.mkdir(full_path(storage, name), mode: 0770) unless exists?(storage, name) end # Remove directory from repositories storage # Every repository inside this directory will be removed too # # Ex. - # rm_namespace("gitlab") + # rm_namespace("/path/to/storage", "gitlab") # - def rm_namespace(name) - FileUtils.rm_r(full_path(name), force: true) + def rm_namespace(storage, name) + FileUtils.rm_r(full_path(storage, name), force: true) end # Move namespace directory inside repositories storage # # Ex. - # mv_namespace("gitlab", "gitlabhq") + # mv_namespace("/path/to/storage", "gitlab", "gitlabhq") # - def mv_namespace(old_name, new_name) - return false if exists?(new_name) || !exists?(old_name) + def mv_namespace(storage, old_name, new_name) + return false if exists?(storage, new_name) || !exists?(storage, old_name) - FileUtils.mv(full_path(old_name), full_path(new_name)) + FileUtils.mv(full_path(storage, old_name), full_path(storage, new_name)) end def url_to_repo(path) @@ -176,11 +183,26 @@ module Gitlab # Check if such directory exists in repositories. # # Usage: - # exists?('gitlab') - # exists?('gitlab/cookies.git') + # exists?(storage, 'gitlab') + # exists?(storage, 'gitlab/cookies.git') # - def exists?(dir_name) - File.exist?(full_path(dir_name)) + def exists?(storage, dir_name) + File.exist?(full_path(storage, dir_name)) + end + + # Create (if necessary) and link the secret token file + def generate_and_link_secret_token + secret_file = Gitlab.config.gitlab_shell.secret_file + unless File.exist? secret_file + # Generate a new token of 16 random hexadecimal characters and store it in secret_file. + token = SecureRandom.hex(16) + File.write(secret_file, token) + end + + link_path = File.join(gitlab_shell_path, '.gitlab_shell_secret') + if File.exist?(gitlab_shell_path) && !File.exist?(link_path) + FileUtils.symlink(secret_file, link_path) + end end protected @@ -193,14 +215,10 @@ module Gitlab File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}") end - def repos_path - Gitlab.config.gitlab_shell.repos_path - end - - def full_path(dir_name) + def full_path(storage, dir_name) raise ArgumentError.new("Directory name can't be blank") if dir_name.blank? - File.join(repos_path, dir_name) + File.join(storage, dir_name) end def gitlab_shell_projects_path diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb index 997a22779a0..d62bc50ce78 100644 --- a/lib/gitlab/blame.rb +++ b/lib/gitlab/blame.rb @@ -41,7 +41,8 @@ module Gitlab def highlighted_lines @blob.load_all_data!(repository) - @highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines + @highlighted_lines ||= + Gitlab::Highlight.highlight(@blob.path, @blob.data, repository: repository).lines end def project diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index adfd097736e..e6cc1529760 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, to: :@global + delegate :before_script, :image, :services, :after_script, :variables, + :stages, :cache, to: :@global def initialize(config) @config = Loader.new(config).load! 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/cache.rb b/lib/gitlab/ci/config/node/cache.rb new file mode 100644 index 00000000000..cdf8ba2e35d --- /dev/null +++ b/lib/gitlab/ci/config/node/cache.rb @@ -0,0 +1,27 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a cache configuration + # + class Cache < Entry + include Configurable + + node :key, Node::Key, + description: 'Cache key used to define a cache affinity.' + + node :untracked, Node::Boolean, + description: 'Cache all untracked files.' + + node :paths, Node::Paths, + description: 'Specify which paths should be cached across builds.' + + validations do + validates :config, allowed_keys: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 374ff71d0f5..37936fc8242 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -19,35 +19,45 @@ module Gitlab included do validations do - validates :config, hash: true + validates :config, type: Hash end end private def create_node(key, factory) - factory.with(value: @config[key], key: key) - factory.nullify! unless @config.has_key?(key) + factory.with(value: @config[key], key: key, parent: self) + factory.create! end 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]) - define_method(symbol) do - raise Entry::InvalidError unless valid? - @nodes[symbol].try(:value) - end + (@nodes ||= {}).merge!(symbol.to_sym => factory) + end - (@allowed_nodes ||= {}).merge!(symbol => factory) + 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 diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index f044ef965e9..9e79e170a4f 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -9,7 +9,7 @@ module Gitlab class InvalidError < StandardError; end attr_reader :config - attr_accessor :key, :description + attr_accessor :key, :parent, :description def initialize(config) @config = config @@ -34,8 +34,8 @@ module Gitlab self.class.nodes.none? end - def key - @key || self.class.name.demodulize.underscore + def ancestors + @parent ? @parent.ancestors + [@parent] : [] end def valid? @@ -43,12 +43,23 @@ module Gitlab end def errors - @validator.full_errors + - nodes.map(&:errors).flatten + @validator.messages + nodes.flat_map(&:errors) end def value - raise NotImplementedError + if leaf? + @config + else + defined = @nodes.select { |_key, value| value.defined? } + Hash[defined.map { |key, node| [key, node.value] }] + end + end + + def defined? + true + end + + def self.default end def self.nodes diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 025ae40ef94..5919a283283 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -5,13 +5,11 @@ module Gitlab ## # Factory class responsible for fabricating node entry objects. # - # It uses Fluent Interface pattern to set all necessary attributes. - # class Factory class InvalidFactory < StandardError; end - def initialize(entry_class) - @entry_class = entry_class + def initialize(node) + @node = node @attributes = {} end @@ -20,17 +18,27 @@ module Gitlab self end - def nullify! - @entry_class = Node::Null - self - end - def create! raise InvalidFactory unless @attributes.has_key?(:value) - @entry_class.new(@attributes[:value]).tap do |entry| - entry.description = @attributes[:description] + fabricate.tap do |entry| entry.key = @attributes[:key] + entry.parent = @attributes[:parent] + entry.description = @attributes[:description] + end + end + + private + + def fabricate + ## + # We assume that unspecified entry is undefined. + # See issue #18775. + # + if @attributes[:value].nil? + Node::Undefined.new(@node) + else + @node.new(@attributes[:value]) end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 044603423d5..f92e1eccbcf 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -9,8 +9,36 @@ module Gitlab class Global < Entry include Configurable - allow_node :before_script, Script, + node :before_script, Node::Script, description: 'Script that will be executed before each job.' + + node :image, Node::Image, + description: 'Docker image that will be used to execute jobs.' + + node :services, Node::Services, + description: 'Docker images that will be linked to the container.' + + node :after_script, Node::Script, + description: 'Script that will be executed after each job.' + + node :variables, Node::Variables, + description: 'Environment variables that will be used.' + + node :stages, Node::Stages, + description: 'Configuration of stages for this pipeline.' + + node :types, Node::Stages, + description: 'Deprecated: stages for this pipeline.' + + node :cache, Node::Cache, + description: 'Configure caching between build jobs.' + + helpers :before_script, :image, :services, :after_script, + :variables, :stages, :types, :cache + + def stages + stages_defined? ? stages_value : types_value + end end end end diff --git a/lib/gitlab/ci/config/node/image.rb b/lib/gitlab/ci/config/node/image.rb new file mode 100644 index 00000000000..5d3c7c5eab0 --- /dev/null +++ b/lib/gitlab/ci/config/node/image.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a Docker image. + # + class Image < Entry + include Validatable + + validations do + validates :config, type: String + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/key.rb b/lib/gitlab/ci/config/node/key.rb new file mode 100644 index 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/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/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/lib/gitlab/ci/config/node/script.rb b/lib/gitlab/ci/config/node/script.rb index c044f5c5e71..39328f0fade 100644 --- a/lib/gitlab/ci/config/node/script.rb +++ b/lib/gitlab/ci/config/node/script.rb @@ -5,21 +5,12 @@ 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 validations do validates :config, array_of_strings: true end - - def value - @config.join("\n") - end 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..481e2b66adc --- /dev/null +++ b/lib/gitlab/ci/config/node/services.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a configuration of Docker services. + # + class Services < Entry + include Validatable + + validations do + validates :config, array_of_strings: true + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/stages.rb b/lib/gitlab/ci/config/node/stages.rb new file mode 100644 index 00000000000..b1fe45357ff --- /dev/null +++ b/lib/gitlab/ci/config/node/stages.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a configuration for pipeline stages. + # + class Stages < Entry + include Validatable + + validations do + validates :config, array_of_strings: true + end + + def self.default + %w[build test deploy] + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb new file mode 100644 index 00000000000..699605e1e3a --- /dev/null +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -0,0 +1,30 @@ +module Gitlab + module Ci + class Config + module Node + ## + # This class represents an undefined entry node. + # + # It takes original entry class as configuration and returns default + # value of original entry as self value. + # + # + class Undefined < Entry + include Validatable + + validations do + validates :config, type: Class + end + + def value + @config.default + end + + def defined? + false + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index 02edc9219c3..758a6cf4356 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -11,15 +11,29 @@ module Gitlab @node = node end - def full_errors + def messages errors.full_messages.map do |error| - "#{@node.key} #{error}".humanize + "#{location} #{error}".downcase end end def self.name 'Validator' end + + def unknown_keys + return [] unless config.is_a?(Hash) + + config.keys - @node.class.nodes.keys + end + + private + + def location + predecessors = ancestors.map(&:key).compact + current = key || @node.class.name.demodulize.underscore + predecessors.append(current).join(':') + end end end end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index dc9cdb9a220..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 @@ -13,10 +23,43 @@ module Gitlab end end - class HashValidator < ActiveModel::EachValidator + class BooleanValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_boolean(value) + record.errors.add(attribute, 'should be a boolean value') + end + end + end + + class KeyValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_string(value) + record.errors.add(attribute, 'should be a string or symbol') + end + end + end + + class TypeValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + type = options[:with] + raise unless type.is_a?(Class) + + unless value.is_a?(type) + record.errors.add(attribute, "should be a #{type.name}") + end + end + end + + class VariablesValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + def validate_each(record, attribute, value) - unless value.is_a?(Hash) - record.errors.add(attribute, 'should be a configuration entry hash') + unless validate_variables(value) + record.errors.add(attribute, 'should be a hash of key value pairs') 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..5f813f81f55 --- /dev/null +++ b/lib/gitlab/ci/config/node/variables.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents environment variables. + # + class Variables < Entry + include Validatable + + validations do + validates :config, variables: true + end + + def self.default + {} + end + end + end + end + end +end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 28c34429c1f..ffc1814b29d 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -9,10 +9,14 @@ module Gitlab end def ensure_application_settings! - settings = ::ApplicationSetting.cached + if connect_to_db? + begin + settings = ::ApplicationSetting.current + # In case Redis isn't running or the Redis UNIX socket file is not available + rescue ::Redis::BaseError, ::Errno::ENOENT + settings = ::ApplicationSetting.last + end - if !settings && connect_to_db? - settings = ::ApplicationSetting.current settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration? end @@ -44,6 +48,7 @@ module Gitlab akismet_enabled: false, repository_checks_enabled: true, container_registry_token_expire_delay: 5, + user_default_external: false, ) end diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb new file mode 100644 index 00000000000..8406ca4269c --- /dev/null +++ b/lib/gitlab/diff/diff_refs.rb @@ -0,0 +1,36 @@ +module Gitlab + module Diff + class DiffRefs + attr_reader :base_sha + attr_reader :start_sha + attr_reader :head_sha + + def initialize(base_sha:, start_sha: base_sha, head_sha:) + @base_sha = base_sha + @start_sha = start_sha + @head_sha = head_sha + end + + def ==(other) + other.is_a?(self.class) && + base_sha == other.base_sha && + start_sha == other.start_sha && + head_sha == other.head_sha + end + + # There is only one case in which we will have `start_sha` and `head_sha`, + # but not `base_sha`, which is when a diff is generated between an + # orphaned branch and another branch, which means there _is_ no base, but + # we're still able to highlight it, and to create diff notes, which are + # the primary things `DiffRefs` are used for. + # `DiffRefs` are "complete" when they have `start_sha` and `head_sha`, + # because `base_sha` can always be derived from this, to return an actual + # sha, or `nil`. + # We have `base_sha` directly available on `DiffRefs` because it's faster# + # than having to look it up in the repo every time. + def complete? + start_sha && head_sha + end + end + end +end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index d2e85cabf72..b0c50edba59 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -1,47 +1,83 @@ module Gitlab module Diff class File - attr_reader :diff, :diff_refs + attr_reader :diff, :repository, :diff_refs delegate :new_file, :deleted_file, :renamed_file, - :old_path, :new_path, to: :diff, prefix: false + :old_path, :new_path, :a_mode, :b_mode, + :submodule?, :too_large?, to: :diff, prefix: false - def initialize(diff, diff_refs) + def initialize(diff, repository:, diff_refs: nil) @diff = diff + @repository = repository @diff_refs = diff_refs end + def position(line) + return unless diff_refs + + Position.new( + old_path: old_path, + new_path: new_path, + old_line: line.old_line, + new_line: line.new_line, + diff_refs: diff_refs + ) + end + + def line_code(line) + return if line.meta? + + Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) + end + + def line_for_line_code(code) + diff_lines.find { |line| line_code(line) == code } + end + + def line_for_position(pos) + diff_lines.find { |line| position(line) == pos } + end + + def position_for_line_code(code) + line = line_for_line_code(code) + position(line) if line + end + + def line_code_for_position(pos) + line = line_for_position(pos) + line_code(line) if line + end + + def content_commit + return unless diff_refs + + repository.commit(deleted_file ? old_ref : new_ref) + end + def old_ref - diff_refs[0] if diff_refs + diff_refs.try(:base_sha) end def new_ref - diff_refs[1] if diff_refs + diff_refs.try(:head_sha) end - # Array of Gitlab::DIff::Line objects + # Array of Gitlab::Diff::Line objects def diff_lines - @lines ||= parser.parse(raw_diff.each_line).to_a - end - - def too_large? - diff.too_large? + @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a end def highlighted_diff_lines - Gitlab::Diff::Highlight.new(self).highlight + @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight end def parallel_diff_lines - Gitlab::Diff::ParallelDiff.new(self).parallelize + @parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize end def mode_changed? - !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode) - end - - def parser - Gitlab::Diff::Parser.new + a_mode && b_mode && a_mode != b_mode end def raw_diff @@ -53,17 +89,15 @@ module Gitlab end def prev_line(index) - if index > 0 - diff_lines[index - 1] - end + diff_lines[index - 1] if index > 0 + end + + def paths + [old_path, new_path].compact end def file_path - if diff.new_path.present? - diff.new_path - elsif diff.old_path.present? - diff.old_path - end + new_path.presence || old_path end def added_lines @@ -73,6 +107,21 @@ module Gitlab def removed_lines diff_lines.count(&:removed?) end + + def old_blob(commit = content_commit) + return unless commit + + parent_id = commit.parent_id + return unless parent_id + + repository.blob_at(parent_id, old_path) + end + + def blob(commit = content_commit) + return unless commit + + repository.blob_at(commit.id, file_path) + end end end end diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb index 9429b3ff88d..649a265a02c 100644 --- a/lib/gitlab/diff/highlight.rb +++ b/lib/gitlab/diff/highlight.rb @@ -1,11 +1,13 @@ module Gitlab module Diff class Highlight - attr_reader :diff_file, :diff_lines, :raw_lines + attr_reader :diff_file, :diff_lines, :raw_lines, :repository delegate :old_path, :new_path, :old_ref, :new_ref, to: :diff_file, prefix: :diff - def initialize(diff_lines) + def initialize(diff_lines, repository: nil) + @repository = repository + if diff_lines.is_a?(Gitlab::Diff::File) @diff_file = diff_lines @diff_lines = @diff_file.diff_lines @@ -19,7 +21,7 @@ module Gitlab @diff_lines.map.with_index do |diff_line, i| diff_line = diff_line.dup # ignore highlighting for "match" lines - next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline' + next diff_line if diff_line.meta? rich_line = highlight_line(diff_line) || diff_line.text @@ -40,12 +42,12 @@ module Gitlab line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' ' - case diff_line.type - when 'new', nil - rich_line = new_lines[diff_line.new_pos - 1] - when 'old' - rich_line = old_lines[diff_line.old_pos - 1] - end + rich_line = + if diff_line.unchanged? || diff_line.added? + new_lines[diff_line.new_pos - 1] + elsif diff_line.removed? + old_lines[diff_line.old_pos - 1] + end # Only update text if line is found. This will prevent # issues with submodules given the line only exists in diff content. @@ -58,19 +60,12 @@ module Gitlab def old_lines return unless diff_file - @old_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:old)) + @old_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_old_ref, diff_old_path) end def new_lines return unless diff_file - @new_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:new)) - end - - def processing_args(version) - ref = send("diff_#{version}_ref") - path = send("diff_#{version}_path") - - [ref.project.repository, ref.id, path] + @new_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_new_ref, diff_new_path) end end end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index 03730b435ad..c6189d660c2 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -9,6 +9,18 @@ module Gitlab @old_pos, @new_pos = old_pos, new_pos end + def old_line + old_pos unless added? || meta? + end + + def new_line + new_pos unless removed? || meta? + end + + def unchanged? + type.nil? + end + def added? type == 'new' end @@ -16,6 +28,10 @@ module Gitlab def removed? type == 'old' end + + def meta? + type == 'match' || type == 'nonewline' + end end end end diff --git a/lib/gitlab/diff/line_mapper.rb b/lib/gitlab/diff/line_mapper.rb new file mode 100644 index 00000000000..576a761423e --- /dev/null +++ b/lib/gitlab/diff/line_mapper.rb @@ -0,0 +1,64 @@ +# When provided a diff for a specific file, maps old line numbers to new line +# numbers and back, to find out where a specific line in a file was moved by the +# changes. +module Gitlab + module Diff + class LineMapper + attr_accessor :diff_file + + def initialize(diff_file) + @diff_file = diff_file + end + + # Find new line number for old line number. + def old_to_new(old_line) + map_line_number(old_line, from: :old_line, to: :new_line) + end + + # Find old line number for new line number. + def new_to_old(new_line) + map_line_number(new_line, from: :new_line, to: :old_line) + end + + private + + def diff_lines + @diff_lines ||= @diff_file.diff_lines + end + + # Find old/new line number based on its old/new counterpart line number. + def map_line_number(from_line, from:, to:) + # If no diff file could be found, the file wasn't changed, and the + # mapped line number is the same as the specified line number. + return from_line unless diff_file + + # To find the mapped line number for the specified line number, + # we need to find: + # - The diff line with that exact line number, if it is in the diff context + # - The first diff line with a higher line number, if it falls between diff contexts + # - The last known diff line, if it falls after the last diff context + diff_line = diff_lines.find do |diff_line| + diff_from_line = diff_line.send(from) + diff_from_line && diff_from_line >= from_line + end + diff_line ||= diff_lines.last + + # If no diff line could be found, the file wasn't changed, and the + # mapped line number is the same as the specified line number. + return from_line unless diff_line + + diff_from_line = diff_line.send(from) + diff_to_line = diff_line.send(to) + + # If the line was removed, there is no mapped line number. + return unless diff_to_line + + # Because we may not have the diff line with the exact line number + # we were looking for, we need to adjust the mapped line number. + distance = diff_from_line - from_line + + diff_to_line - distance + end + end + end +end diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb index 74f9b3c050a..1c1fc148123 100644 --- a/lib/gitlab/diff/parallel_diff.rb +++ b/lib/gitlab/diff/parallel_diff.rb @@ -15,17 +15,19 @@ 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 + position = diff_file.position(line) next_line = diff_file.next_line(line.index) if next_line next_line = highlighted_diff_lines[next_line.index] - next_line_code = generate_line_code(diff_file.file_path, next_line) + 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 + 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: next_line, - line_code: next_line_code + 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 @@ -108,12 +118,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 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/diff/position.rb b/lib/gitlab/diff/position.rb new file mode 100644 index 00000000000..989fff8918e --- /dev/null +++ b/lib/gitlab/diff/position.rb @@ -0,0 +1,155 @@ +# Defines a specific location, identified by paths and line numbers, +# within a specific diff, identified by start, head and base commit ids. +module Gitlab + module Diff + class Position + attr_reader :old_path + attr_reader :new_path + attr_reader :old_line + attr_reader :new_line + attr_reader :base_sha + attr_reader :start_sha + attr_reader :head_sha + + def initialize(attrs = {}) + @old_path = attrs[:old_path] + @new_path = attrs[:new_path] + @old_line = attrs[:old_line] + @new_line = attrs[:new_line] + + if attrs[:diff_refs] + @base_sha = attrs[:diff_refs].base_sha + @start_sha = attrs[:diff_refs].start_sha + @head_sha = attrs[:diff_refs].head_sha + else + @base_sha = attrs[:base_sha] + @start_sha = attrs[:start_sha] + @head_sha = attrs[:head_sha] + end + end + + # `Gitlab::Diff::Position` objects are stored as serialized attributes in + # `DiffNote`, which use YAML to encode and decode objects. + # `#init_with` and `#encode_with` can be used to customize the en/decoding + # behavior. In this case, we override these to prevent memoized instance + # variables like `@diff_file` and `@diff_line` from being serialized. + def init_with(coder) + initialize(coder['attributes']) + + self + end + + def encode_with(coder) + coder['attributes'] = self.to_h + end + + def key + @key ||= [base_sha, start_sha, head_sha, Digest::SHA1.hexdigest(old_path || ""), Digest::SHA1.hexdigest(new_path || ""), old_line, new_line] + end + + def ==(other) + other.is_a?(self.class) && key == other.key + end + + def to_h + { + old_path: old_path, + new_path: new_path, + old_line: old_line, + new_line: new_line, + base_sha: base_sha, + start_sha: start_sha, + head_sha: head_sha + } + end + + def inspect + %(#<#{self.class}:#{object_id} #{to_h}>) + end + + def complete? + file_path.present? && + (old_line || new_line) && + diff_refs.complete? + end + + def to_json + JSON.generate(self.to_h) + end + + def type + if old_line && new_line + nil + elsif new_line + 'new' + else + 'old' + end + end + + def unchanged? + type.nil? + end + + def added? + type == 'new' + end + + def removed? + type == 'old' + end + + def paths + [old_path, new_path].compact.uniq + end + + def file_path + new_path.presence || old_path + end + + def diff_refs + @diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha) + end + + def diff_file(repository) + @diff_file ||= begin + if RequestStore.active? + key = { + project_id: repository.project.id, + start_sha: start_sha, + head_sha: head_sha, + path: file_path + } + + RequestStore.fetch(key) { find_diff_file(repository) } + else + find_diff_file(repository) + end + end + end + + def diff_line(repository) + @diff_line ||= diff_file(repository).line_for_position(self) + end + + def line_code(repository) + @line_code ||= diff_file(repository).line_code_for_position(self) + end + + private + + def find_diff_file(repository) + diffs = Gitlab::Git::Compare.new( + repository.raw_repository, + start_sha, + head_sha + ).diffs(paths: paths) + + diff = diffs.first + return unless diff + + Gitlab::Diff::File.new(diff, repository: repository, diff_refs: diff_refs) + end + end + end +end diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb new file mode 100644 index 00000000000..4d04f867268 --- /dev/null +++ b/lib/gitlab/diff/position_tracer.rb @@ -0,0 +1,168 @@ +# Finds the diff position in the new diff that corresponds to the same location +# specified by the provided position in the old diff. +module Gitlab + module Diff + class PositionTracer + attr_accessor :repository + attr_accessor :old_diff_refs + attr_accessor :new_diff_refs + attr_accessor :paths + + def initialize(repository:, old_diff_refs:, new_diff_refs:, paths: nil) + @repository = repository + @old_diff_refs = old_diff_refs + @new_diff_refs = new_diff_refs + @paths = paths + end + + def trace(old_position) + return unless old_diff_refs.complete? && new_diff_refs.complete? + return unless old_position.diff_refs == old_diff_refs + + # Suppose we have an MR with source branch `feature` and target branch `master`. + # When the MR was created, the head of `master` was commit A, and the + # head of `feature` was commit B, resulting in the original diff A->B. + # Since creation, `master` was updated to C. + # Now `feature` is being updated to D, and the newly generated MR diff is C->D. + # It is possible that C and D are direct decendants of A and B respectively, + # but this isn't necessarily the case as rebases and merges come into play. + # + # Suppose we have a diff note on the original diff A->B. Now that the MR + # is updated, we need to find out what line in C->D corresponds to the + # line the note was originally created on, so that we can update the diff note's + # records and continue to display it in the right place in the diffs. + # If we cannot find this line in the new diff, this means the diff note is now + # outdated, and we will display that fact to the user. + # + # In the new diff, the file the diff note was originally created on may + # have been renamed, deleted or even created, if the file existed in A and B, + # but was removed in C, and restored in D. + # + # Every diff note stores a Position object that defines a specific location, + # identified by paths and line numbers, within a specific diff, identified + # by start, head and base commit ids. + # + # For diff notes for diff A->B, the position looks like this: + # Position + # base_sha - ID of commit A + # head_sha - ID of commit B + # old_path - path as of A (nil if file was newly created) + # new_path - path as of B (nil if file was deleted) + # old_line - line number as of A (nil if file was newly created) + # new_line - line number as of B (nil if file was deleted) + # + # We can easily update `base_sha` and `head_sha` to hold the IDs of commits C and D, + # but need to find the paths and line numbers as of C and D. + # + # If the file was unchanged or newly created in A->B, the path as of D can be found + # by generating diff B->D ("head to head"), finding the diff file with + # `diff_file.old_path == position.new_path`, and taking `diff_file.new_path`. + # The path as of C can be found by taking diff C->D, finding the diff file + # with that same `new_path` and taking `diff_file.old_path`. + # The line number as of D can be found by using the LineMapper on diff B->D + # and providing the line number as of B. + # The line number as of C can be found by using the LineMapper on diff C->D + # and providing the line number as of D. + # + # If the file was deleted in A->B, the path as of C can be found + # by generating diff A->C ("base to base"), finding the diff file with + # `diff_file.old_path == position.old_path`, and taking `diff_file.new_path`. + # The path as of D can be found by taking diff C->D, finding the diff file + # with that same `old_path` and taking `diff_file.new_path`. + # The line number as of C can be found by using the LineMapper on diff A->C + # and providing the line number as of A. + # The line number as of D can be found by using the LineMapper on diff C->D + # and providing the line number as of C. + + results = nil + results ||= trace_added_line(old_position) if old_position.added? || old_position.unchanged? + results ||= trace_removed_line(old_position) if old_position.removed? || old_position.unchanged? + + return unless results + + file_diff, old_line, new_line = results + + Position.new( + old_path: file_diff.old_path, + new_path: file_diff.new_path, + head_sha: new_diff_refs.head_sha, + start_sha: new_diff_refs.start_sha, + base_sha: new_diff_refs.base_sha, + old_line: old_line, + new_line: new_line + ) + end + + private + + def trace_added_line(old_position) + file_path = old_position.new_path + + return unless diff_head_to_head + + file_head_to_head = diff_head_to_head.find { |diff_file| diff_file.old_path == file_path } + + file_path = file_head_to_head.new_path if file_head_to_head + + new_line = LineMapper.new(file_head_to_head).old_to_new(old_position.new_line) + + return unless new_line + + file_diff = new_diffs.find { |diff_file| diff_file.new_path == file_path } + return unless file_diff + + old_line = LineMapper.new(file_diff).new_to_old(new_line) + + [file_diff, old_line, new_line] + end + + def trace_removed_line(old_position) + file_path = old_position.old_path + + return unless diff_base_to_base + + file_base_to_base = diff_base_to_base.find { |diff_file| diff_file.old_path == file_path } + + file_path = file_base_to_base.old_path if file_base_to_base + + old_line = LineMapper.new(file_base_to_base).old_to_new(old_position.old_line) + + return unless old_line + + file_diff = new_diffs.find { |diff_file| diff_file.old_path == file_path } + return unless file_diff + + new_line = LineMapper.new(file_diff).old_to_new(old_line) + + [file_diff, old_line, new_line] + end + + def diff_base_to_base + @diff_base_to_base ||= diff_files(old_diff_refs.base_sha || old_diff_refs.start_sha, new_diff_refs.base_sha || new_diff_refs.start_sha) + end + + def diff_head_to_head + @diff_head_to_head ||= diff_files(old_diff_refs.head_sha, new_diff_refs.head_sha) + end + + def new_diffs + @new_diffs ||= diff_files(new_diff_refs.start_sha, new_diff_refs.head_sha, use_base: true) + end + + def diff_files(start_sha, head_sha, use_base: false) + base_sha = self.repository.merge_base(start_sha, head_sha) || start_sha + + diffs = self.repository.raw_repository.diff( + use_base ? base_sha : start_sha, + head_sha, + {}, + *paths + ) + + diffs.decorate! do |diff| + Gitlab::Diff::File.new(diff, repository: self.repository) + end + end + end + end +end diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 047c77c6fc2..97701b0cd42 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -33,11 +33,15 @@ module Gitlab end def commits - @commits ||= (Commit.decorate(compare.commits, project) if compare) + return unless compare + + @commits ||= Commit.decorate(compare.commits, project) end def diffs - @diffs ||= (safe_diff_files(compare.diffs(max_files: 30), diff_refs) if compare) + return unless compare + + @diffs ||= safe_diff_files(compare.diffs(max_files: 30), diff_refs: diff_refs, repository: project.repository) end def diffs_count diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 97ef9851d71..1c671a7487b 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -104,15 +104,7 @@ module Gitlab end def create_note(reply) - Notes::CreateService.new( - sent_notification.project, - sent_notification.recipient, - note: reply, - noteable_type: sent_notification.noteable_type, - noteable_id: sent_notification.noteable_id, - commit_id: sent_notification.commit_id, - line_code: sent_notification.line_code - ).execute + sent_notification.create_note(reply) end end end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb new file mode 100644 index 00000000000..b63213ae208 --- /dev/null +++ b/lib/gitlab/emoji.rb @@ -0,0 +1,21 @@ +module Gitlab + module Emoji + extend self + + def emojis + Gemojione.index.instance_variable_get(:@emoji_by_name) + end + + def emojis_by_moji + Gemojione.index.instance_variable_get(:@emoji_by_moji) + end + + def emojis_names + emojis.keys.sort + end + + def emoji_filename(name) + emojis[name]["unicode"] + end + end +end diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 07b856ca64c..9b681e636c7 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -1,6 +1,7 @@ module Gitlab module Git class Hook + GL_PROTOCOL = 'web'.freeze attr_reader :name, :repo_path, :path def initialize(name, repo_path) @@ -14,7 +15,7 @@ module Gitlab end def trigger(gl_id, oldrev, newrev, ref) - return true unless exists? + return [true, nil] unless exists? case name when "pre-receive", "post-receive" @@ -29,19 +30,20 @@ module Gitlab def call_receive_hook(gl_id, oldrev, newrev, ref) changes = [oldrev, newrev, ref].join(" ") - # function will return true if succesful exit_status = false + exit_message = nil vars = { 'GL_ID' => gl_id, - 'PWD' => repo_path + 'PWD' => repo_path, + 'GL_PROTOCOL' => GL_PROTOCOL } options = { chdir: repo_path } - Open3.popen2(vars, path, options) do |stdin, _, wait_thr| + Open3.popen3(vars, path, options) do |stdin, stdout, stderr, wait_thr| exit_status = true stdin.sync = true @@ -60,17 +62,24 @@ module Gitlab unless wait_thr.value == 0 exit_status = false + exit_message = retrieve_error_message(stderr, stdout) end end - exit_status + [exit_status, exit_message] end def call_update_hook(gl_id, oldrev, newrev, ref) Dir.chdir(repo_path) do - system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev) + stdout, stderr, status = Open3.capture3({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev) + [status.success?, stderr.presence || stdout] end end + + def retrieve_error_message(stderr, stdout) + err_message = stderr.gets + err_message.blank? ? stdout.gets : err_message + end end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index d2a0e316cbe..7679c7e4bb8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -3,11 +3,12 @@ module Gitlab DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } - attr_reader :actor, :project + attr_reader :actor, :project, :protocol - def initialize(actor, project) + def initialize(actor, project, protocol) @actor = actor @project = project + @protocol = protocol end def user @@ -49,6 +50,8 @@ module Gitlab end def check(cmd, changes = nil) + return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed? + unless actor return build_status_object(false, "No user or key was provided.") end @@ -164,6 +167,10 @@ module Gitlab Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev) end + def protocol_allowed? + Gitlab::ProtocolAccess.allowed?(protocol) + end + private def protected_branch_action(oldrev, newrev, branch_name) diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb index a15fc84b418..7d2d545b84e 100644 --- a/lib/gitlab/github_import/branch_formatter.rb +++ b/lib/gitlab/github_import/branch_formatter.rb @@ -4,7 +4,7 @@ module Gitlab delegate :repo, :sha, :ref, to: :raw_data def exists? - project.repository.branch_exists?(ref) + branch_exists? && commit_exists? end def name @@ -15,11 +15,15 @@ module Gitlab repo.present? end - def valid? - repo.present? + private + + def branch_exists? + project.repository.branch_exists?(ref) end - private + def commit_exists? + project.repository.commit(sha).present? + end def short_id sha.to_s[0..7] diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index d325eca6d99..043f10d96a9 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -4,26 +4,39 @@ module Gitlab GITHUB_SAFE_REMAINING_REQUESTS = 100 GITHUB_SAFE_SLEEP_TIME = 500 - attr_reader :client, :api + attr_reader :access_token def initialize(access_token) - @client = ::OAuth2::Client.new( - config.app_id, - config.app_secret, - github_options.merge(ssl: { verify: config['verify_ssl'] }) - ) + @access_token = access_token if access_token ::Octokit.auto_paginate = false + end + end + + def api + @api ||= ::Octokit::Client.new( + access_token: access_token, + api_endpoint: github_options[:site], + # If there is no config, we're connecting to github.com and we + # should verify ssl. + connection_options: { + ssl: { verify: config ? config['verify_ssl'] : true } + } + ) + end - @api = ::Octokit::Client.new( - access_token: access_token, - api_endpoint: github_options[:site], - connection_options: { - ssl: { verify: config['verify_ssl'] } - } - ) + def client + unless config + raise Projects::ImportService::Error, + 'OAuth configuration for GitHub missing.' end + + @client ||= ::OAuth2::Client.new( + config.app_id, + config.app_secret, + github_options.merge(ssl: { verify: config['verify_ssl'] }) + ) end def authorize_url(redirect_uri) @@ -56,7 +69,11 @@ module Gitlab end def github_options - config["args"]["client_options"].deep_symbolize_keys + if config + config["args"]["client_options"].deep_symbolize_keys + else + OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys + end end def rate_limit diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 2286ac8829c..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) @@ -167,7 +169,7 @@ module Gitlab def import_wiki unless project.wiki_enabled? wiki = WikiFormatter.new(project) - gitlab_shell.import_repository(wiki.path_with_namespace, wiki.import_url) + gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) project.update_attribute(:wiki_enabled, true) end diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index 498b00cb658..a4ea2210abd 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -11,10 +11,10 @@ module Gitlab description: description, source_project: source_branch_project, source_branch: source_branch_name, - head_source_sha: source_branch_sha, + source_branch_sha: source_branch_sha, target_project: target_branch_project, target_branch: target_branch_name, - base_target_sha: target_branch_sha, + target_branch_sha: target_branch_sha, state: state, milestone: milestone, author_id: author_id, diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index f751a3a12fd..d4f12cb1df9 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -3,7 +3,6 @@ module Gitlab def add_gon_variables gon.api_version = API::API.version gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s - gon.default_issues_tracker = Project.new.default_issue_tracker.to_param gon.max_file_size = current_application_settings.max_attachment_size gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.shortcuts_path = help_shortcuts_path diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb index 2122339d2db..3caf9036459 100644 --- a/lib/gitlab/graphs/commits.rb +++ b/lib/gitlab/graphs/commits.rb @@ -18,7 +18,7 @@ module Gitlab end def commit_per_day - @commit_per_day ||= (@commits.size.to_f / @duration).round(1) + @commit_per_day ||= @commits.size / (@duration + 1) end def collect_data diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 280120b0f9e..41296415e35 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -1,7 +1,7 @@ module Gitlab class Highlight - def self.highlight(blob_name, blob_content, nowrap: true, plain: false) - new(blob_name, blob_content, nowrap: nowrap). + def self.highlight(blob_name, blob_content, repository: nil, nowrap: true, plain: false) + new(blob_name, blob_content, nowrap: nowrap, repository: repository). highlight(blob_content, continue: false, plain: plain) end @@ -10,12 +10,21 @@ module Gitlab return [] unless blob blob.load_all_data!(repository) - highlight(file_name, blob.data).lines.map!(&:html_safe) + highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe) end - def initialize(blob_name, blob_content, nowrap: true) + attr_reader :lexer + def initialize(blob_name, blob_content, repository: nil, nowrap: true) + @blob_name = blob_name + @blob_content = blob_content + @repository = repository @formatter = rouge_formatter(nowrap: nowrap) - @lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText + + @lexer = custom_language || begin + Rouge::Lexer.guess(filename: blob_name, source: blob_content).new + rescue Rouge::Lexer::AmbiguousGuess => e + e.alternatives.sort_by(&:tag).first + end end def highlight(text, continue: true, plain: false) @@ -30,6 +39,14 @@ module Gitlab private + def custom_language + language_name = @repository && @repository.gitattribute(@blob_name, 'gitlab-language') + + return nil unless language_name + + Rouge::Lexer.find_fancy(language_name) + end + def rouge_formatter(options = {}) options = options.reverse_merge( nowrap: true, diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 99cf85d9a3b..588647e5adb 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -2,7 +2,7 @@ module Gitlab module ImportExport extend self - VERSION = '0.1.0' + VERSION = '0.1.1' def export_path(relative_path:) File.join(storage_path, relative_path) diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 78664f076eb..2249904145c 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -28,7 +28,8 @@ module Gitlab end def execute(cmd) - _output, status = Gitlab::Popen.popen(cmd) + output, status = Gitlab::Popen.popen(cmd) + @shared.error(Gitlab::ImportExport::Error.new(output.to_s)) unless status.zero? status.zero? end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 164ab6238c4..05f4ad527ac 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -1,24 +1,29 @@ # Model relationships to be included in the project import/export project_tree: - issues: + - :events - notes: - :author + - :author + - :events - :labels - - :milestones + - milestones: + - :events - snippets: - notes: :author - :releases - - :events - project_members: - :user - merge_requests: - notes: - :author + - :author + - :events - :merge_request_diff + - :events - pipelines: - notes: - :author + - :author + - :events - :statuses - :variables - :triggers 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 92bf7e0a2fc..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', @@ -33,6 +32,7 @@ module Gitlab update_user_references update_project_references reset_ci_tokens if @relation_name == 'Ci::Trigger' + @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] generate_imported_object end diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb index f38229c6c59..6a60b65071f 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(Gitlab::ImportExport::Error.new("Unable to save #{archive_file} into #{@shared.export_path}")) false end rescue => e 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/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb index 8684b4636ea..b75ae512d92 100644 --- a/lib/gitlab/key_fingerprint.rb +++ b/lib/gitlab/key_fingerprint.rb @@ -39,7 +39,7 @@ module Gitlab # OpenSSH 6.8 introduces a new default output format for fingerprints. # Check the version and decide which command to use. - version_output, version_status = popen(%W(ssh -V)) + version_output, version_status = popen(%w(ssh -V)) return false unless version_status.zero? version_matches = version_output.match(/OpenSSH_(?<major>\d+)\.(?<minor>\d+)/) 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/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb index faf0d9b6318..c048fe20ba7 100644 --- a/lib/gitlab/metrics/method_call.rb +++ b/lib/gitlab/metrics/method_call.rb @@ -18,12 +18,12 @@ module Gitlab # Measures the real and CPU execution time of the supplied block. def measure - start_real = Time.now + start_real = System.monotonic_time start_cpu = System.cpu_time retval = yield - @real_time += (Time.now - start_real) * 1000.0 - @cpu_time += System.cpu_time.to_f - start_cpu + @real_time += System.monotonic_time - start_real + @cpu_time += System.cpu_time - start_cpu @call_count += 1 retval diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb index 1cd1ca30f70..f23d67e1e38 100644 --- a/lib/gitlab/metrics/metric.rb +++ b/lib/gitlab/metrics/metric.rb @@ -4,16 +4,15 @@ module Gitlab class Metric JITTER_RANGE = 0.000001..0.001 - attr_reader :series, :values, :tags, :created_at + attr_reader :series, :values, :tags # series - The name of the series (as a String) to store the metric in. # values - A Hash containing the values to store. # tags - A Hash containing extra tags to add to the metrics. def initialize(series, values, tags = {}) - @values = values - @series = series - @tags = tags - @created_at = Time.now.utc + @values = values + @series = series + @tags = tags end # Returns a Hash in a format that can be directly written to InfluxDB. @@ -27,20 +26,20 @@ module Gitlab # # Due to the way InfluxDB is set up there's no solution to this problem, # all we can do is lower the amount of collisions. We do this by using - # Time#to_f which returns the seconds as a Float providing greater - # accuracy. We then add a small random value that is large enough to - # distinguish most timestamps but small enough to not alter the amount - # of seconds. + # System.real_time which returns the nanoseconds as a Float providing + # greater accuracy. We then add a small random value that is large + # enough to distinguish most timestamps but small enough to not alter + # the timestamp significantly. # # See https://gitlab.com/gitlab-com/operations/issues/175 for more # information. - time = @created_at.to_f + rand(JITTER_RANGE) + time = System.real_time(:nanosecond) + rand(JITTER_RANGE) { series: @series, tags: @tags, values: @values, - timestamp: (time * 1_000_000_000).to_i + timestamp: time.to_i } end end diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb index 8e345e8ae4a..aaed2184f44 100644 --- a/lib/gitlab/metrics/subscribers/rails_cache.rb +++ b/lib/gitlab/metrics/subscribers/rails_cache.rb @@ -2,11 +2,21 @@ module Gitlab module Metrics module Subscribers # Class for tracking the total time spent in Rails cache calls + # http://guides.rubyonrails.org/active_support_instrumentation.html class RailsCache < ActiveSupport::Subscriber attach_to :active_support def cache_read(event) increment(:cache_read, event.duration) + + return unless current_transaction + return if event.payload[:super_operation] == :fetch + + if event.payload[:hit] + current_transaction.increment(:cache_read_hit_count, 1) + else + current_transaction.increment(:cache_read_miss_count, 1) + end end def cache_write(event) @@ -21,6 +31,18 @@ module Gitlab increment(:cache_exists, event.duration) end + def cache_fetch_hit(event) + return unless current_transaction + + current_transaction.increment(:cache_read_hit_count, 1) + end + + def cache_generate(event) + return unless current_transaction + + current_transaction.increment(:cache_read_miss_count, 1) + end + def increment(key, duration) return unless current_transaction diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb index a7d183b2f94..82c18bb108b 100644 --- a/lib/gitlab/metrics/system.rb +++ b/lib/gitlab/metrics/system.rb @@ -34,13 +34,29 @@ module Gitlab # THREAD_CPUTIME is not supported on OS X if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID) def self.cpu_time - Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond) + Process. + clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f end else def self.cpu_time - Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond) + Process. + clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f end end + + # Returns the current real time in a given precision. + # + # Returns the time as a Float. + def self.real_time(precision = :millisecond) + Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f + end + + # Returns the current monotonic clock time in a given precision. + # + # Returns the time as a Float. + def self.monotonic_time(precision = :millisecond) + Process.clock_gettime(Process::CLOCK_MONOTONIC, precision).to_f + end end end end diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index 4bc5081aa03..bded245da43 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -30,7 +30,7 @@ module Gitlab end def duration - @finished_at ? (@finished_at - @started_at) * 1000.0 : 0.0 + @finished_at ? (@finished_at - @started_at) : 0.0 end def allocated_memory @@ -41,12 +41,12 @@ module Gitlab Thread.current[THREAD_KEY] = self @memory_before = System.memory_usage - @started_at = Time.now + @started_at = System.monotonic_time yield ensure @memory_after = System.memory_usage - @finished_at = Time.now + @finished_at = System.monotonic_time Thread.current[THREAD_KEY] = nil end diff --git a/lib/gitlab/o_auth/auth_hash.rb b/lib/gitlab/o_auth/auth_hash.rb index 36e5c2670bb..7d6911a1ab3 100644 --- a/lib/gitlab/o_auth/auth_hash.rb +++ b/lib/gitlab/o_auth/auth_hash.rb @@ -66,7 +66,7 @@ module Gitlab # Get the first part of the email address (before @) # In addtion in removes illegal characters def generate_username(email) - email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/,'').to_s + email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/, '').to_s end def generate_temporarily_email(username) diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index 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/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/protocol_access.rb b/lib/gitlab/protocol_access.rb new file mode 100644 index 00000000000..21aefc884be --- /dev/null +++ b/lib/gitlab/protocol_access.rb @@ -0,0 +1,13 @@ +module Gitlab + module ProtocolAccess + def self.allowed?(protocol) + if protocol == 'web' + true + elsif current_application_settings.enabled_git_access_protocol.blank? + true + else + protocol == current_application_settings.enabled_git_access_protocol + end + end + end +end diff --git a/lib/gitlab/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_\-\.]*(?<!\.git|\.atom)\z/.freeze end @@ -42,7 +39,6 @@ module Gitlab "Cannot start with '-', end in '.git' or end in '.atom'" \ end - def file_name_regex @file_name_regex ||= /\A[a-zA-Z0-9_\-\.\@]*\z/.freeze end @@ -59,7 +55,6 @@ module Gitlab "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. " end - def directory_traversal_regex @directory_traversal_regex ||= /\.{2}/.freeze end @@ -68,7 +63,6 @@ module Gitlab "cannot include directory traversal. " end - def archive_formats_regex # |zip|tar| tar.gz | tar.bz2 | @archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze diff --git a/lib/gitlab/saml/auth_hash.rb b/lib/gitlab/saml/auth_hash.rb index 32c1c9ec5bb..67a5f368bdb 100644 --- a/lib/gitlab/saml/auth_hash.rb +++ b/lib/gitlab/saml/auth_hash.rb @@ -1,7 +1,6 @@ module Gitlab module Saml class AuthHash < Gitlab::OAuth::AuthHash - def groups get_raw(Gitlab::Saml::Config.groups) end @@ -13,7 +12,6 @@ module Gitlab # otherwise just the first value is returned auth_hash.extra[:raw_info].all[key] end - end end end diff --git a/lib/gitlab/saml/config.rb b/lib/gitlab/saml/config.rb index 0f40c00f547..574c3a4b28c 100644 --- a/lib/gitlab/saml/config.rb +++ b/lib/gitlab/saml/config.rb @@ -1,7 +1,6 @@ module Gitlab module Saml class Config - class << self def options Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' } @@ -15,7 +14,6 @@ module Gitlab options[:external_groups] end end - end end end diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb index 8943022612c..f253dc7477e 100644 --- a/lib/gitlab/saml/user.rb +++ b/lib/gitlab/saml/user.rb @@ -6,7 +6,6 @@ module Gitlab module Saml class User < Gitlab::OAuth::User - def save super('SAML') end diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb index ae85b294d31..104280f520a 100644 --- a/lib/gitlab/sidekiq_middleware/memory_killer.rb +++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb @@ -25,18 +25,18 @@ module Gitlab Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\ "#{MAX_RSS}" - Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} "\ + Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"\ "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}" + Sidekiq.logger.warn "sending #{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}" Process.kill(SHUTDOWN_SIGNAL, Process.pid) 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 diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 7d02fe3c971..86ed18fb50d 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -6,8 +6,16 @@ module Gitlab 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) - @url = Addressable::URI.parse(url) + @url = Addressable::URI.parse(url.strip) @credentials = credentials end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 40e8299c36b..bc0193a6c32 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -38,12 +38,10 @@ module Gitlab end def send_git_diff(repository, diff_refs) - from, to = diff_refs - params = { 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => from.sha, - 'ShaTo' => to.sha + 'ShaFrom' => diff_refs.start_sha, + 'ShaTo' => diff_refs.head_sha } [ @@ -52,6 +50,19 @@ module Gitlab ] end + def send_git_patch(repository, diff_refs) + params = { + 'RepoPath' => repository.path_to_repo, + 'ShaFrom' => diff_refs.start_sha, + 'ShaTo' => diff_refs.head_sha + } + + [ + SEND_DATA_HEADER, + "git-format-patch:#{encode(params)}" + ] + end + protected def encode(hash) 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 '</span>' end end - lines.join("\n") - else - if @linenos == 'inline' - lines = lines.each_with_index.map do |line, index| - number = index + @linenostart - "<span class=\"linenos\">#{number}</span>#{line}" - end - lines.join("\n") - else - lines.join("\n") + elsif @linenos == 'inline' + lines = lines.each_with_index.map do |line, index| + number = index + @linenostart + "<span class=\"linenos\">#{number}</span>#{line}" end end + + lines.join("\n") end def span(tok, val) diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake index 030ee8bafcb..e930ace1041 100644 --- a/lib/tasks/gemojione.rake +++ b/lib/tasks/gemojione.rake @@ -13,7 +13,7 @@ namespace :gemojione do aliases[real_name] << alias_name end - AwardEmoji.emojis.map do |name, emoji_hash| + Gitlab::AwardEmoji.emojis.map do |name, emoji_hash| fpath = File.join(dir, "#{emoji_hash['unicode']}.png") digest = Digest::SHA256.file(fpath).hexdigest diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 9ee72fde92f..b43ee5b3383 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -33,12 +33,13 @@ namespace :gitlab do unless backup.skipped?('db') unless ENV['force'] == 'yes' - warning = warning = <<-MSG.strip_heredoc + warning = <<-MSG.strip_heredoc Before restoring the database we recommend removing all existing tables to avoid future upgrade problems. Be aware that if you have custom tables in the GitLab database these tables and all data will be removed. MSG + puts warning.color(:red) ask_to_continue puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.color(:yellow) sleep(5) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 12d6ac45fb6..e9a4e37ec48 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -356,97 +356,108 @@ namespace :gitlab do ######################## def check_repo_base_exists - print "Repo base directory exists? ... " + puts "Repo base directory exists?" - repo_base_path = Gitlab.config.gitlab_shell.repos_path + Gitlab.config.repositories.storages.each do |name, repo_base_path| + print "#{name}... " - if File.exists?(repo_base_path) - puts "yes".color(:green) - else - puts "no".color(:red) - puts "#{repo_base_path} is missing".color(:red) - try_fixing_it( - "This should have been created when setting up GitLab Shell.", - "Make sure it's set correctly in config/gitlab.yml", - "Make sure GitLab Shell is installed correctly." - ) - for_more_information( - see_installation_guide_section "GitLab Shell" - ) - fix_and_rerun + if File.exists?(repo_base_path) + puts "yes".color(:green) + else + puts "no".color(:red) + puts "#{repo_base_path} is missing".color(:red) + try_fixing_it( + "This should have been created when setting up GitLab Shell.", + "Make sure it's set correctly in config/gitlab.yml", + "Make sure GitLab Shell is installed correctly." + ) + for_more_information( + see_installation_guide_section "GitLab Shell" + ) + fix_and_rerun + end end end def check_repo_base_is_not_symlink - print "Repo base directory is a symlink? ... " + puts "Repo storage directories are symlinks?" - repo_base_path = Gitlab.config.gitlab_shell.repos_path - unless File.exists?(repo_base_path) - puts "can't check because of previous errors".color(:magenta) - return - end + Gitlab.config.repositories.storages.each do |name, repo_base_path| + print "#{name}... " - unless File.symlink?(repo_base_path) - puts "no".color(:green) - else - puts "yes".color(:red) - try_fixing_it( - "Make sure it's set to the real directory in config/gitlab.yml" - ) - fix_and_rerun + unless File.exists?(repo_base_path) + puts "can't check because of previous errors".color(:magenta) + return + end + + unless File.symlink?(repo_base_path) + puts "no".color(:green) + else + puts "yes".color(:red) + try_fixing_it( + "Make sure it's set to the real directory in config/gitlab.yml" + ) + fix_and_rerun + end end end def check_repo_base_permissions - print "Repo base access is drwxrws---? ... " + puts "Repo paths access is drwxrws---?" - repo_base_path = Gitlab.config.gitlab_shell.repos_path - unless File.exists?(repo_base_path) - puts "can't check because of previous errors".color(:magenta) - return - end + Gitlab.config.repositories.storages.each do |name, repo_base_path| + print "#{name}... " - if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770") - puts "yes".color(:green) - else - puts "no".color(:red) - try_fixing_it( - "sudo chmod -R ug+rwX,o-rwx #{repo_base_path}", - "sudo chmod -R ug-s #{repo_base_path}", - "sudo find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s" - ) - for_more_information( - see_installation_guide_section "GitLab Shell" - ) - fix_and_rerun + unless File.exists?(repo_base_path) + puts "can't check because of previous errors".color(:magenta) + return + end + + if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770") + puts "yes".color(:green) + else + puts "no".color(:red) + try_fixing_it( + "sudo chmod -R ug+rwX,o-rwx #{repo_base_path}", + "sudo chmod -R ug-s #{repo_base_path}", + "sudo find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s" + ) + for_more_information( + see_installation_guide_section "GitLab Shell" + ) + fix_and_rerun + end end end def check_repo_base_user_and_group gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group - print "Repo base owned by #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group}? ... " + puts "Repo paths owned by #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group}?" - repo_base_path = Gitlab.config.gitlab_shell.repos_path - unless File.exists?(repo_base_path) - puts "can't check because of previous errors".color(:magenta) - return - end + Gitlab.config.repositories.storages.each do |name, repo_base_path| + print "#{name}... " - uid = uid_for(gitlab_shell_ssh_user) - gid = gid_for(gitlab_shell_owner_group) - if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid - puts "yes".color(:green) - else - puts "no".color(:red) - puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".color(:blue) - try_fixing_it( - "sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}" - ) - for_more_information( - see_installation_guide_section "GitLab Shell" - ) - fix_and_rerun + unless File.exists?(repo_base_path) + puts "can't check because of previous errors".color(:magenta) + return + end + + uid = uid_for(gitlab_shell_ssh_user) + gid = gid_for(gitlab_shell_owner_group) + if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid + puts "yes".color(:green) + else + puts "no".color(:red) + puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".color(:blue) + try_fixing_it( + "sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}" + ) + for_more_information( + see_installation_guide_section "GitLab Shell" + ) + fix_and_rerun + end end end @@ -473,7 +484,7 @@ namespace :gitlab do else puts "wrong or missing hooks".color(:red) try_fixing_it( - sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')}"), + sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')} #{repository_storage_paths_args.join(' ')}"), 'Check the hooks_path in config/gitlab.yml', 'Check your gitlab-shell installation' ) @@ -785,13 +796,13 @@ namespace :gitlab do namespace :repo do desc "GitLab | Check the integrity of the repositories managed by GitLab" task check: :environment do - namespace_dirs = Dir.glob( - File.join(Gitlab.config.gitlab_shell.repos_path, '*') - ) + Gitlab.config.repositories.storages.each do |name, path| + namespace_dirs = Dir.glob(File.join(path, '*')) - namespace_dirs.each do |namespace_dir| - repo_dirs = Dir.glob(File.join(namespace_dir, '*')) - repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) } + namespace_dirs.each do |namespace_dir| + repo_dirs = Dir.glob(File.join(namespace_dir, '*')) + repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) } + end end end end @@ -799,12 +810,12 @@ namespace :gitlab do namespace :user do desc "GitLab | Check the integrity of a specific user's repositories" task :check_repos, [:username] => :environment do |t, args| - username = args[:username] || prompt("Check repository integrity for which username? ".color(:blue)) + username = args[:username] || prompt("Check repository integrity for fsername? ".color(:blue)) user = User.find_by(username: username) if user repo_dirs = user.authorized_projects.map do |p| File.join( - Gitlab.config.gitlab_shell.repos_path, + p.repository_storage_path, "#{p.path_with_namespace}.git" ) end diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index ab0028d6603..b7cbdc6cd78 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -5,36 +5,36 @@ namespace :gitlab do warn_user_is_not_gitlab remove_flag = ENV['REMOVE'] - namespaces = Namespace.pluck(:path) - git_base_path = Gitlab.config.gitlab_shell.repos_path - all_dirs = Dir.glob(git_base_path + '/*') + Gitlab.config.repositories.storages.each do |name, git_base_path| + all_dirs = Dir.glob(git_base_path + '/*') - puts git_base_path.color(:yellow) - puts "Looking for directories to remove... " + puts git_base_path.color(:yellow) + puts "Looking for directories to remove... " - all_dirs.reject! do |dir| - # skip if git repo - dir =~ /.git$/ - end + all_dirs.reject! do |dir| + # skip if git repo + dir =~ /.git$/ + end - all_dirs.reject! do |dir| - dir_name = File.basename dir + all_dirs.reject! do |dir| + dir_name = File.basename dir - # skip if namespace present - namespaces.include?(dir_name) - end + # skip if namespace present + namespaces.include?(dir_name) + end - all_dirs.each do |dir_path| + all_dirs.each do |dir_path| - if remove_flag - if FileUtils.rm_rf dir_path - puts "Removed...#{dir_path}".color(:red) + if remove_flag + if FileUtils.rm_rf dir_path + puts "Removed...#{dir_path}".color(:red) + else + puts "Cannot remove #{dir_path}".color(:red) + end else - puts "Cannot remove #{dir_path}".color(:red) + puts "Can be removed: #{dir_path}".color(:red) end - else - puts "Can be removed: #{dir_path}".color(:red) end end @@ -48,20 +48,21 @@ namespace :gitlab do warn_user_is_not_gitlab move_suffix = "+orphaned+#{Time.now.to_i}" - repo_root = Gitlab.config.gitlab_shell.repos_path - # Look for global repos (legacy, depth 1) and normal repos (depth 2) - IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find| - find.each_line do |path| - path.chomp! - repo_with_namespace = path. - sub(repo_root, ''). - sub(%r{^/*}, ''). - chomp('.git'). - chomp('.wiki') - next if Project.find_with_namespace(repo_with_namespace) - new_path = path + move_suffix - puts path.inspect + ' -> ' + new_path.inspect - File.rename(path, new_path) + Gitlab.config.repositories.storages.each do |name, repo_root| + # Look for global repos (legacy, depth 1) and normal repos (depth 2) + IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find| + find.each_line do |path| + path.chomp! + repo_with_namespace = path. + sub(repo_root, ''). + sub(%r{^/*}, ''). + chomp('.git'). + chomp('.wiki') + next if Project.find_with_namespace(repo_with_namespace) + new_path = path + move_suffix + puts path.inspect + ' -> ' + new_path.inspect + File.rename(path, new_path) + end end end end diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index 4753f00c26a..dbdd4e977e8 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -2,73 +2,73 @@ namespace :gitlab do namespace :import do # How to use: # - # 1. copy the bare repos under the repos_path (commonly /home/git/repositories) + # 1. copy the bare repos under the repository storage paths (commonly the default path is /home/git/repositories) # 2. run: bundle exec rake gitlab:import:repos RAILS_ENV=production # # Notes: # * The project owner will set to the first administator of the system # * Existing projects will be skipped # - desc "GitLab | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance" + desc "GitLab | Import bare repositories from repositories -> storages into GitLab project instance" task repos: :environment do + Gitlab.config.repositories.storages.each do |name, git_base_path| + repos_to_import = Dir.glob(git_base_path + '/**/*.git') - git_base_path = Gitlab.config.gitlab_shell.repos_path - repos_to_import = Dir.glob(git_base_path + '/**/*.git') + repos_to_import.each do |repo_path| + # strip repo base path + repo_path[0..git_base_path.length] = '' - repos_to_import.each do |repo_path| - # strip repo base path - repo_path[0..git_base_path.length] = '' + path = repo_path.sub(/\.git$/, '') + group_name, name = File.split(path) + group_name = nil if group_name == '.' - path = repo_path.sub(/\.git$/, '') - group_name, name = File.split(path) - group_name = nil if group_name == '.' + puts "Processing #{repo_path}".color(:yellow) - puts "Processing #{repo_path}".color(:yellow) - - if path.end_with?('.wiki') - puts " * Skipping wiki repo" - next - end + if path.end_with?('.wiki') + puts " * Skipping wiki repo" + next + end - project = Project.find_with_namespace(path) + project = Project.find_with_namespace(path) - if project - puts " * #{project.name} (#{repo_path}) exists" - else - user = User.admins.reorder("id").first + if project + puts " * #{project.name} (#{repo_path}) exists" + else + user = User.admins.reorder("id").first - project_params = { - name: name, - path: name - } + project_params = { + name: name, + path: name + } - # find group namespace - if group_name - group = Namespace.find_by(path: group_name) - # create group namespace - unless group - group = Group.new(:name => group_name) - group.path = group_name - group.owner = user - if group.save - puts " * Created Group #{group.name} (#{group.id})".color(:green) - else - puts " * Failed trying to create group #{group.name}".color(:red) + # find group namespace + if group_name + group = Namespace.find_by(path: group_name) + # create group namespace + unless group + group = Group.new(:name => group_name) + group.path = group_name + group.owner = user + if group.save + puts " * Created Group #{group.name} (#{group.id})".color(:green) + else + puts " * Failed trying to create group #{group.name}".color(:red) + end end + # set project group + project_params[:namespace_id] = group.id end - # set project group - project_params[:namespace_id] = group.id - end - project = Projects::CreateService.new(user, project_params).execute + project = Projects::CreateService.new(user, project_params).execute - if project.persisted? - puts " * Created #{project.name} (#{repo_path})".color(:green) - project.update_repository_size - project.update_commit_count - else - puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red) - puts " Errors: #{project.errors.messages}".color(:red) + if project.persisted? + puts " * Created #{project.name} (#{repo_path})".color(:green) + project.update_repository_size + project.update_commit_count + else + puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red) + puts " Errors: #{project.errors.messages}".color(:red) + end end end end diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index 352b566df24..fe43d40e6d2 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -62,7 +62,10 @@ namespace :gitlab do puts "" puts "GitLab Shell".color(:yellow) puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}" - puts "Repositories:\t#{Gitlab.config.gitlab_shell.repos_path}" + puts "Repository storage paths:" + Gitlab.config.repositories.storages.each do |name, path| + puts "- #{name}: \t#{path}" + end puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}" puts "Git:\t\t#{Gitlab.config.git.bin_path}" diff --git a/lib/tasks/gitlab/list_repos.rake b/lib/tasks/gitlab/list_repos.rake index c7596e7abcb..ffcc76e5498 100644 --- a/lib/tasks/gitlab/list_repos.rake +++ b/lib/tasks/gitlab/list_repos.rake @@ -9,7 +9,7 @@ namespace :gitlab do scope = scope.where('id IN (?) OR namespace_id in (?)', project_ids, namespace_ids) end scope.find_each do |project| - base = File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace) + base = File.join(project.repository_storage_path, project.path_with_namespace) puts base + '.git' puts base + '.wiki.git' end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index b1648a4602a..c85ebdf8619 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -12,7 +12,6 @@ namespace :gitlab do gitlab_url = Gitlab.config.gitlab.url # gitlab-shell requires a / at the end of the url gitlab_url += '/' unless gitlab_url.end_with?('/') - repos_path = Gitlab.config.gitlab_shell.repos_path target_dir = Gitlab.config.gitlab_shell.path # Clone if needed @@ -35,7 +34,6 @@ namespace :gitlab do user: user, gitlab_url: gitlab_url, http_settings: {self_signed_cert: false}.stringify_keys, - repos_path: repos_path, auth_file: File.join(home_dir, ".ssh", "authorized_keys"), redis: { bin: %x{which redis-cli}.chomp, @@ -58,10 +56,10 @@ namespace :gitlab do File.open("config.yml", "w+") {|f| f.puts config.to_yaml} # Launch installation process - system(*%W(bin/install)) + system(*%W(bin/install) + repository_storage_paths_args) # (Re)create hooks - system(*%W(bin/create-hooks)) + system(*%W(bin/create-hooks) + repository_storage_paths_args) end # Required for debian packaging with PKGR: Setup .ssh/environment with @@ -73,6 +71,8 @@ namespace :gitlab do File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f| f.puts "PATH=#{ENV['PATH']}" end + + Gitlab::Shell.new.generate_and_link_secret_token end desc "GitLab | Setup gitlab-shell" @@ -87,7 +87,8 @@ namespace :gitlab do if File.exists?(path_to_repo) print '-' else - if Gitlab::Shell.new.add_repository(project.path_with_namespace) + if Gitlab::Shell.new.add_repository(project.repository_storage_path, + project.path_with_namespace) print '.' else print 'F' @@ -138,4 +139,3 @@ namespace :gitlab do system(*%W(#{Gitlab.config.git.bin_path} reset --hard #{tag})) end end - diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index d0c019044b7..ab96b1d3593 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -125,10 +125,16 @@ namespace :gitlab do end def all_repos - IO.popen(%W(find #{Gitlab.config.gitlab_shell.repos_path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| - find.each_line do |path| - yield path.chomp + Gitlab.config.repositories.storages.each do |name, path| + IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| + find.each_line do |path| + yield path.chomp + end end end end + + def repository_storage_paths_args + Gitlab.config.repositories.storages.values + end end diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake index c5666d49e61..21c0e5f1d41 100644 --- a/lib/tasks/test.rake +++ b/lib/tasks/test.rake @@ -6,8 +6,6 @@ task :test do end unless Rails.env.production? - require 'coveralls/rake/task' - Coveralls::RakeTask.new desc "GitLab | Run all tests on CI with simplecov" - task :test_ci => [:rubocop, :brakeman, 'teaspoon', :spinach, :spec, 'coveralls:push'] + task test_ci: [:rubocop, :brakeman, 'teaspoon', :spinach, :spec] end diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index d4291f012d3..41dee5fdc06 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -3,7 +3,6 @@ require "fileutils" # Taken from: Rack::Test::UploadedFile class UploadedFile - # The filename, *not* including the path, of the "uploaded" file attr_reader :original_filename diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png Binary files differindex 7da5f23ed9b..05c8b0d0ccf 100644 --- a/public/apple-touch-icon-precomposed.png +++ b/public/apple-touch-icon-precomposed.png diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png Binary files differindex 7da5f23ed9b..05c8b0d0ccf 100644 --- a/public/apple-touch-icon.png +++ b/public/apple-touch-icon.png diff --git a/rubocop/cop/migration/add_index.rb b/rubocop/cop/migration/add_index.rb new file mode 100644 index 00000000000..d9247a1f7ea --- /dev/null +++ b/rubocop/cop/migration/add_index.rb @@ -0,0 +1,46 @@ +module RuboCop + module Cop + module Migration + # Cop that checks if indexes are added in a concurrent manner. + class AddIndex < RuboCop::Cop::Cop + include MigrationHelpers + + MSG = 'add_index requires downtime, use add_concurrent_index instead' + + def on_def(node) + return unless in_migration?(node) + + new_tables = [] + + node.each_descendant(:send) do |send_node| + first_arg = first_argument(send_node) + + # The first argument of "create_table" / "add_index" is the table + # name. + new_tables << first_arg if create_table?(send_node) + + next if method_name(send_node) != :add_index + + # Using "add_index" is fine for newly created tables as there's no + # data in these tables yet. + next if new_tables.include?(first_arg) + + add_offense(send_node, :selector) + end + end + + def create_table?(node) + method_name(node) == :create_table + end + + def method_name(node) + node.children[1] + end + + def first_argument(node) + node.children[2] ? node.children[0] : nil + end + end + end + end +end diff --git a/rubocop/cop/migration/column_with_default.rb b/rubocop/cop/migration/column_with_default.rb new file mode 100644 index 00000000000..97ee8b11044 --- /dev/null +++ b/rubocop/cop/migration/column_with_default.rb @@ -0,0 +1,50 @@ +module RuboCop + module Cop + module Migration + # Cop that checks if columns are added in a way that doesn't require + # downtime. + class ColumnWithDefault < RuboCop::Cop::Cop + include MigrationHelpers + + WHITELISTED_TABLES = [:application_settings] + + MSG = 'add_column with a default value requires downtime, ' \ + 'use add_column_with_default instead' + + def on_send(node) + return unless in_migration?(node) + + name = node.children[1] + + return unless name == :add_column + + # Ignore whitelisted tables. + return if table_whitelisted?(node.children[2]) + + opts = node.children.last + + return unless opts && opts.type == :hash + + opts.each_node(:pair) do |pair| + if hash_key_type(pair) == :sym && hash_key_name(pair) == :default + add_offense(node, :selector) + end + end + end + + def table_whitelisted?(symbol) + symbol && symbol.type == :sym && + WHITELISTED_TABLES.include?(symbol.children[0]) + end + + def hash_key_type(pair) + pair.children[0].type + end + + def hash_key_name(pair) + pair.children[0].children[0] + end + end + end + end +end diff --git a/rubocop/migration_helpers.rb b/rubocop/migration_helpers.rb new file mode 100644 index 00000000000..3160a784a04 --- /dev/null +++ b/rubocop/migration_helpers.rb @@ -0,0 +1,10 @@ +module RuboCop + # Module containing helper methods for writing migration cops. + module MigrationHelpers + # Returns true if the given node originated from the db/migrate directory. + def in_migration?(node) + File.dirname(node.location.expression.source_buffer.name). + end_with?('db/migrate') + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb new file mode 100644 index 00000000000..7922e19768b --- /dev/null +++ b/rubocop/rubocop.rb @@ -0,0 +1,3 @@ +require_relative 'migration_helpers' +require_relative 'cop/migration/add_index' +require_relative 'cop/migration/column_with_default' diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb index eb82476b179..d5f0b289b5b 100644 --- a/spec/controllers/admin/impersonations_controller_spec.rb +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -22,7 +22,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "doesn't sign us in" do @@ -46,7 +46,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "doesn't sign us in as the impersonator" do @@ -65,7 +65,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "doesn't sign us in as the impersonator" do diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 4cb8b8da150..8eaacef2024 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -11,12 +11,12 @@ describe Admin::ProjectsController do render_views it 'retrieves the project for the given visibility level' do - get :index, visibility_levels: [Gitlab::VisibilityLevel::PUBLIC] + get :index, visibility_level: [Gitlab::VisibilityLevel::PUBLIC] expect(response.body).to match(project.name) end it 'does not retrieve the project' do - get :index, visibility_levels: [Gitlab::VisibilityLevel::INTERNAL] + get :index, visibility_level: [Gitlab::VisibilityLevel::INTERNAL] expect(response.body).not_to match(project.name) end end diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb index b51b303a714..520a4f6f9c5 100644 --- a/spec/controllers/admin/spam_logs_controller_spec.rb +++ b/spec/controllers/admin/spam_logs_controller_spec.rb @@ -14,7 +14,7 @@ describe Admin::SpamLogsController do it 'lists all spam logs' do get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -22,14 +22,14 @@ describe Admin::SpamLogsController do it 'removes only the spam log when removing log' do expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1) expect(User.find(user.id)).to be_truthy - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'removes user and his spam logs when removing the user' do delete :destroy, id: first_spam.id, remove_user: true expect(flash[:notice]).to eq "User #{user.username} was successfully removed." - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(SpamLog.count).to eq(0) expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index 6caf37ddc2c..ab9aa65f7b9 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -17,7 +17,7 @@ describe Admin::UsersController do it 'deletes user' do delete :destroy, id: user.username, format: :json - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound) end end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index ff5b3916273..8bd210cbc3d 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -44,17 +44,16 @@ describe ApplicationController do context "when the 'private_token' param is populated with the private token" do it "logs the user in" do get :index, private_token: user.private_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq("authenticated") end end - context "when the 'PRIVATE-TOKEN' header is populated with the private token" do it "logs the user in" do @request.headers['PRIVATE-TOKEN'] = user.private_token get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq("authenticated") end end @@ -80,7 +79,7 @@ describe ApplicationController do context "when the 'personal_access_token' param is populated with the personal access token" do it "logs the user in" do get :index, private_token: personal_access_token.token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq('authenticated') end end @@ -89,7 +88,7 @@ describe ApplicationController do it "logs the user in" do @request.headers["PRIVATE-TOKEN"] = personal_access_token.token get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq('authenticated') end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 28cf804c1b2..60c654f622d 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -29,7 +29,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end end @@ -58,7 +58,7 @@ describe AutocompleteController do get(:users, group_id: 'unknown') end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end end @@ -114,7 +114,7 @@ describe AutocompleteController do get(:users, project_id: project.id) end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end describe 'GET #users with unknown project' do @@ -122,7 +122,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end describe 'GET #users with inaccessible group' do @@ -131,7 +131,7 @@ describe AutocompleteController do get(:users, group_id: user.namespace.id) end - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end describe 'GET #users with no project' do diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index cf5c606c723..a3a3309e15e 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -155,7 +155,7 @@ describe Projects::CommitController do id: commit.id) expect(response).not_to be_success - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -204,7 +204,7 @@ describe Projects::CommitController do id: master_pickable_commit.id) expect(response).not_to be_success - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index c8601341d54..c34475976c6 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -13,7 +13,7 @@ describe Groups::GroupMembersController do it 'renders index with group members' do get :index, group_id: group - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template(:index) end end @@ -26,7 +26,7 @@ describe Groups::GroupMembersController do delete :destroy, group_id: group, id: 42 - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -48,7 +48,7 @@ describe Groups::GroupMembersController do delete :destroy, group_id: group, id: member - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(group.users).to include group_user end end @@ -89,7 +89,7 @@ describe Groups::GroupMembersController do it 'returns 403' do delete :leave, group_id: group - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -118,7 +118,7 @@ describe Groups::GroupMembersController do it 'cannot removes himself from the group' do delete :leave, group_id: group - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -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 @@ -166,7 +166,7 @@ describe Groups::GroupMembersController do post :approve_access_request, group_id: group, id: 42 - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -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 @@ -188,7 +188,7 @@ describe Groups::GroupMembersController do post :approve_access_request, group_id: group, id: member - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(group.users).not_to include group_requester end end diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 0d8a68bb51a..56ecf2bb644 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -65,21 +65,21 @@ describe HealthCheckController do it 'supports passing the token in the header' do request.headers['TOKEN'] = token get :index - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'text/plain' expect(response.body).to include('The server is on fire') end it 'supports failure plaintest response' do get :index, token: token - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'text/plain' expect(response.body).to include('The server is on fire') end it 'supports failure json response' do get :index, token: token, format: :json - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false expect(json_response['message']).to include('The server is on fire') @@ -87,7 +87,7 @@ describe HealthCheckController do it 'supports failure xml response' do get :index, token: token, format: :xml - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/xml' expect(xml_response['healthy']).to be false expect(xml_response['message']).to include('The server is on fire') @@ -95,7 +95,7 @@ describe HealthCheckController do it 'supports failure responses for specific checks' do get :index, token: token, checks: 'email', format: :json - expect(response.status).to eq(500) + expect(response).to have_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false expect(json_response['message']).to include('Email is on fire') diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index c55a3c28208..51d59526854 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -16,6 +16,24 @@ describe Import::GithubController do allow(controller).to receive(:github_import_enabled?).and_return(true) end + describe "GET new" do + it "redirects to GitHub for an access token if logged in with GitHub" do + allow(controller).to receive(:logged_in_with_github?).and_return(true) + expect(controller).to receive(:go_to_github_for_permissions) + + get :new + end + + it "redirects to status if we already have a token" do + assign_session_token + allow(controller).to receive(:logged_in_with_github?).and_return(false) + + get :new + + expect(controller).to redirect_to(status_import_github_url) + end + end + describe "GET callback" do it "updates access token" do token = "asdasd12345" @@ -32,6 +50,20 @@ describe Import::GithubController do end end + describe "POST personal_access_token" do + it "updates access token" do + token = "asdfasdf9876" + + allow_any_instance_of(Gitlab::GithubImport::Client). + to receive(:user).and_return(true) + + post :personal_access_token, personal_access_token: token + + expect(session[:github_access_token]).to eq(token) + expect(controller).to redirect_to(status_import_github_url) + end + end + describe "GET status" do before do @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim') @@ -59,6 +91,17 @@ describe Import::GithubController do expect(assigns(:already_added_projects)).to eq([@project]) expect(assigns(:repos)).to eq([]) end + + it "handles an invalid access token" do + allow_any_instance_of(Gitlab::GithubImport::Client). + to receive(:repos).and_raise(Octokit::Unauthorized) + + get :status + + expect(session[:github_access_token]).to eq(nil) + expect(controller).to redirect_to(new_import_github_url) + expect(flash[:alert]).to eq('Access denied to your GitHub account.') + end end describe "POST create" do diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index 3c6e54839b5..e478a253b3f 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -15,7 +15,7 @@ describe InvitesController do get :accept, id: token member.reload - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(member.user).to eq(user) expect(flash[:notice]).to include 'You have been granted' end @@ -26,7 +26,7 @@ describe InvitesController do get :decline, id: token expect{member.reload}.to raise_error ActiveRecord::RecordNotFound - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(flash[:notice]).to include 'You have declined the invitation to join' end end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb index 27e9afe582e..2b334ed1172 100644 --- a/spec/controllers/namespaces_controller_spec.rb +++ b/spec/controllers/namespaces_controller_spec.rb @@ -86,7 +86,7 @@ describe NamespacesController do it "responds with status 404" do get :show, id: group.path - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -102,7 +102,7 @@ describe NamespacesController do it "responds with status 404" do get :show, id: "doesntexist" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index 15d155833b4..79b819a1377 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe NotificationSettingsController do let(:project) { create(:empty_project) } + let(:group) { create(:group, :internal) } let(:user) { create(:user) } before do @@ -12,7 +13,7 @@ describe NotificationSettingsController do context 'when not authorized' do it 'redirects to sign in page' do post :create, - project: { id: project.id }, + project_id: project.id, notification_setting: { level: :participating } expect(response).to redirect_to(new_user_session_path) @@ -20,33 +21,73 @@ describe NotificationSettingsController do end context 'when authorized' do + let(:custom_events) do + events = {} + + NotificationSetting::EMAIL_EVENTS.each do |event| + events[event.to_s] = true + end + + events + end + before do sign_in(user) end - it 'returns success' do - post :create, - project: { id: project.id }, - notification_setting: { level: :participating } + context 'for projects' do + let(:notification_setting) { user.notification_settings_for(project) } - expect(response.status).to eq 200 - end + it 'creates notification setting' do + post :create, + project_id: project.id, + notification_setting: { level: :participating } - context 'and setting custom notification setting' do - let(:custom_events) do - events = {} + expect(response.status).to eq 200 + expect(notification_setting.level).to eq("participating") + expect(notification_setting.user_id).to eq(user.id) + expect(notification_setting.source_id).to eq(project.id) + expect(notification_setting.source_type).to eq("Project") + end - NotificationSetting::EMAIL_EVENTS.each do |event| - events[event] = "true" + context 'with custom settings' do + it 'creates notification setting' do + post :create, + project_id: project.id, + notification_setting: { level: :custom }.merge(custom_events) + + expect(response.status).to eq 200 + expect(notification_setting.level).to eq("custom") + expect(notification_setting.events).to eq(custom_events) end end + end - it 'returns success' do + context 'for groups' do + let(:notification_setting) { user.notification_settings_for(group) } + + it 'creates notification setting' do post :create, - project: { id: project.id }, - notification_setting: { level: :participating, events: custom_events } + namespace_id: group.id, + notification_setting: { level: :watch } expect(response.status).to eq 200 + expect(notification_setting.level).to eq("watch") + expect(notification_setting.user_id).to eq(user.id) + expect(notification_setting.source_id).to eq(group.id) + expect(notification_setting.source_type).to eq("Namespace") + end + + context 'with custom settings' do + it 'creates notification setting' do + post :create, + namespace_id: group.id, + notification_setting: { level: :custom }.merge(custom_events) + + expect(response.status).to eq 200 + expect(notification_setting.level).to eq("custom") + expect(notification_setting.events).to eq(custom_events) + end end end end @@ -57,10 +98,10 @@ describe NotificationSettingsController do it 'returns 404' do post :create, - project: { id: private_project.id }, + project_id: private_project.id, notification_setting: { level: :participating } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -118,7 +159,7 @@ describe NotificationSettingsController do id: notification_setting, notification_setting: { level: :participating } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb index af378304893..552899eb36c 100644 --- a/spec/controllers/oauth/applications_controller_spec.rb +++ b/spec/controllers/oauth/applications_controller_spec.rb @@ -12,7 +12,7 @@ describe Oauth::ApplicationsController do it 'shows list of applications' do get :index - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'redirects back to profile page if OAuth applications are disabled' do @@ -21,7 +21,7 @@ describe Oauth::ApplicationsController do get :index - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(response).to redirect_to(profile_path) end end diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index 4eafc11abaa..18148acde3e 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Profiles::AccountsController do - let(:user) { create(:omniauth_user, provider: 'saml') } before do @@ -13,7 +12,7 @@ describe Profiles::AccountsController do delete :unlink, provider: 'saml' updated_user = User.find(user.id) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(updated_user.identities.size).to eq(1) expect(updated_user.identities).to include(identity) end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index c4b4a888b4e..644de308c64 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -68,7 +68,6 @@ describe Projects::BranchesController do let(:branch) { "1-feature-branch" } let!(:issue) { create(:issue, project: project) } - it 'redirects' do post :create, namespace_id: project.namespace.to_param, @@ -89,7 +88,6 @@ describe Projects::BranchesController do branch_name: branch, issue_iid: issue.iid end - end end @@ -103,7 +101,7 @@ describe Projects::BranchesController do namespace_id: project.namespace.to_param, project_id: project.to_param - expect(response.status).to eq(303) + expect(response).to have_http_status(303) end end @@ -121,24 +119,24 @@ describe Projects::BranchesController do context "valid branch name, valid source" do let(:branch) { "feature" } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end context "valid branch name with unencoded slashes" do let(:branch) { "improve/awesome" } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end context "valid branch name with encoded slashes" do let(:branch) { "improve%2Fawesome" } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end context "invalid branch name, valid ref" do let(:branch) { "no-branch" } - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end end end diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 70ed8f3a62e..f66bcb8099c 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -64,9 +64,7 @@ describe Projects::ForksController do expect(assigns[:forks]).to be_present end end - end end end - end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index cbaa3e0b7b2..7cf09fa4a4a 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -14,7 +14,7 @@ describe Projects::IssuesController do it "returns index" do get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "return 301 if request path doesn't match project path" do @@ -28,7 +28,7 @@ describe Projects::IssuesController do project.save get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns 404 when external issue tracker is enabled" do @@ -36,7 +36,7 @@ describe Projects::IssuesController do allow(project).to receive(:default_issues_tracker?).and_return(false) get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -248,7 +248,7 @@ describe Projects::IssuesController do before { sign_in(user) } it "rejects a developer to destroy an issue" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -262,7 +262,7 @@ describe Projects::IssuesController do it "deletes the issue" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now end end @@ -280,7 +280,7 @@ describe Projects::IssuesController do project_id: project.path, id: issue.iid, name: "thumbsup") end.to change { issue.award_emoji.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index ab1dd34ed57..3492b6ffbbb 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -18,7 +18,6 @@ describe Projects::LabelsController do 15.times { |i| create_label(priority: (i % 3) + 1, title: "label #{15 - i}") } 5.times { |i| create_label(title: "label #{100 - i}") } - get :index, namespace_id: project.namespace.to_param, project_id: project.to_param end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 5e66106122c..c4b57e77804 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -96,26 +96,14 @@ describe Projects::MergeRequestsController do end describe "as patch" do - include_examples "export merge as", :patch - let(:format) { :patch } - - it "should really be a git email patch with commit" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: merge_request.iid, format: format) - - expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}") - end - - it "should contain git diffs" do + it 'triggers workhorse to serve the request' do get(:show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: merge_request.iid, - format: format) + format: :patch) - expect(response.body).to match(/^diff --git/) + expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-format-patch:") end end end @@ -129,7 +117,6 @@ describe Projects::MergeRequestsController do end context 'when filtering by opened state' do - context 'with opened merge requests' do it 'should list those merge requests' do get_merge_requests @@ -150,7 +137,6 @@ describe Projects::MergeRequestsController do expect(assigns(:merge_requests)).to include(merge_request) end end - end end @@ -226,7 +212,7 @@ describe Projects::MergeRequestsController do context 'when the sha parameter matches the source SHA' do def merge_with_sha - post :merge, base_params.merge(sha: merge_request.source_sha) + post :merge, base_params.merge(sha: merge_request.diff_head_sha) end it 'returns :success' do @@ -243,11 +229,11 @@ describe Projects::MergeRequestsController do context 'when merge_when_build_succeeds is passed' do def merge_when_build_succeeds - post :merge, base_params.merge(sha: merge_request.source_sha, merge_when_build_succeeds: '1') + post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_build_succeeds: '1') end before do - create(:ci_empty_pipeline, project: project, sha: merge_request.source_sha, ref: merge_request.source_branch) + create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) end it 'returns :merge_when_build_succeeds' do @@ -284,7 +270,7 @@ describe Projects::MergeRequestsController do it "denies access to users unless they're admin or project owner" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "when the user is owner" do @@ -297,7 +283,7 @@ describe Projects::MergeRequestsController do it "deletes the merge request" do delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now end end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 00bc38b6071..75590c1ed4f 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -18,7 +18,7 @@ describe Projects::NotesController do project_id: project.path, id: note.id, name: "thumbsup") end.to change { note.award_emoji.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "removes the already awarded emoji" do @@ -30,7 +30,7 @@ describe Projects::NotesController do project_id: project.path, id: note.id, name: "thumbsup") end.to change { AwardEmoji.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index e5e750c855f..5e2a8cf3849 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -58,7 +58,7 @@ describe Projects::ProjectMembersController do get :index, namespace_id: project.namespace, project_id: project end - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } end end @@ -71,7 +71,7 @@ describe Projects::ProjectMembersController do project_id: project, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -94,7 +94,7 @@ describe Projects::ProjectMembersController do project_id: project, id: member - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(project.users).to include team_user end end @@ -139,7 +139,7 @@ describe Projects::ProjectMembersController do delete :leave, namespace_id: project.namespace, project_id: project - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -171,7 +171,7 @@ describe Projects::ProjectMembersController do delete :leave, namespace_id: project.namespace, project_id: project - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -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 @@ -224,7 +224,7 @@ describe Projects::ProjectMembersController do project_id: project, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -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 @@ -247,7 +247,7 @@ describe Projects::ProjectMembersController do project_id: project, id: member - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(project.users).not_to include team_requester end end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 33c35161da3..48f799d8ca1 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') expect(response.header['Content-Disposition']). to eq("inline") @@ -30,7 +30,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.header['Content-Type']).to eq('image/jpeg') expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:") end @@ -54,7 +54,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -65,7 +65,7 @@ describe Projects::RawController do project_id: public_project.to_param, id: id) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index aad62cf20e3..2fe3c263524 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -28,7 +28,6 @@ describe Projects::RepositoriesController do end context "when the service raises an error" do - before do allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed") end @@ -36,7 +35,7 @@ describe Projects::RepositoriesController do it "renders Not Found" do get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 0f32a30f18b..b8a28f43707 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace.path, project_id: project.path expect(assigns(:snippets)).not_to include(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -30,7 +30,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace.path, project_id: project.path expect(assigns(:snippets)).to include(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -41,7 +41,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace.path, project_id: project.path expect(assigns(:snippets)).to include(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -56,7 +56,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -67,7 +67,7 @@ describe Projects::SnippetsController do get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param expect(assigns(:snippet)).to eq(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -78,7 +78,7 @@ describe Projects::SnippetsController do get action, namespace_id: project.namespace.path, project_id: project.path, id: project_snippet.to_param expect(assigns(:snippet)).to eq(project_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -88,7 +88,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace.path, project_id: project.path, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -98,7 +98,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace.path, project_id: project.path, id: 42 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects/todo_controller_spec.rb b/spec/controllers/projects/todo_controller_spec.rb index 40a3403b660..5a8bba28594 100644 --- a/spec/controllers/projects/todo_controller_spec.rb +++ b/spec/controllers/projects/todo_controller_spec.rb @@ -22,7 +22,7 @@ describe Projects::TodosController do issuable_type: 'issue') end.to change { user.todos.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -36,7 +36,7 @@ describe Projects::TodosController do issuable_type: 'issue') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not create todo for issue when user not logged in' do @@ -47,7 +47,7 @@ describe Projects::TodosController do issuable_type: 'issue') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) end end end @@ -69,7 +69,7 @@ describe Projects::TodosController do issuable_type: 'merge_request') end.to change { user.todos.count }.by(1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -83,7 +83,7 @@ describe Projects::TodosController do issuable_type: 'merge_request') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not create todo for merge request user has no access to' do @@ -94,7 +94,7 @@ describe Projects::TodosController do issuable_type: 'merge_request') end.to change { user.todos.count }.by(0) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) end end end diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index e74731c9ed8..1cc050247c6 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -64,9 +64,8 @@ describe Projects::TreeController do context "valid SHA commit ID with path" do let(:id) { '6d39438/.gitignore' } - it { expect(response.status).to eq(302) } + it { expect(response).to have_http_status(302) } end - end describe 'GET show with blob path' do diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 93c4494c660..0893ee89f6a 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -18,7 +18,7 @@ describe Projects::UploadsController do namespace_id: project.namespace.to_param, project_id: project.to_param, format: :json - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -79,7 +79,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -87,7 +87,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -106,7 +106,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -114,7 +114,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -140,7 +140,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -192,7 +192,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -224,7 +224,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -232,7 +232,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -253,7 +253,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -261,7 +261,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -270,7 +270,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 146b2c2e131..1b1b1bdf52d 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -77,7 +77,7 @@ describe ProjectsController do get :show, namespace_id: public_project.namespace.path, id: public_project.path expect(assigns(:project)).to eq(public_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -89,19 +89,16 @@ describe ProjectsController do expect(response).to redirect_to("/#{public_project.path_with_namespace}") end - # MySQL queries are case insensitive by default, so this spec would fail. if Gitlab::Database.postgresql? context "when there is also a match with the same casing" do - let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) } it "loads the exactly matched project" do - get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase expect(assigns(:project)).to eq(other_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -146,7 +143,7 @@ describe ProjectsController do expect(project.repository.path).to include(new_path) expect(assigns(:repository).path).to eq(project.repository.path) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -161,7 +158,7 @@ describe ProjectsController do delete :destroy, namespace_id: project.namespace.path, id: project.path expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound) - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(response).to redirect_to(dashboard_projects_path) end end @@ -234,7 +231,7 @@ describe ProjectsController do delete(:remove_fork, namespace_id: project.namespace.to_param, id: project.to_param, format: :js) - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 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 diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index b3dcb52c500..2a89159c070 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -19,7 +19,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: other_personal_snippet.to_param - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -28,7 +28,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -54,7 +54,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -79,7 +79,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -88,7 +88,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -102,7 +102,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -110,7 +110,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -134,7 +134,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: other_personal_snippet.to_param - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -143,7 +143,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -169,7 +169,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -194,7 +194,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -203,7 +203,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -217,7 +217,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -225,7 +225,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: 'doesntexist' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 73858e6f063..69124ab06bf 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -26,7 +26,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -35,7 +35,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -52,7 +52,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -64,7 +64,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -109,7 +109,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -118,7 +118,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -133,7 +133,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -145,7 +145,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -181,7 +181,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -190,7 +190,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -210,7 +210,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -222,7 +222,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -267,7 +267,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -276,7 +276,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index c61ec174665..54a2d3d9460 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -33,7 +33,7 @@ describe UsersController do it 'renders the show template' do get :show, username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template('show') end end @@ -47,7 +47,7 @@ describe UsersController do context 'when logged out' do it 'renders 404' do get :show, username: user.username - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -56,7 +56,7 @@ describe UsersController do it 'renders show' do get :show, username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template('show') end end @@ -64,7 +64,6 @@ describe UsersController do end describe 'GET #calendar' do - it 'renders calendar' do sign_in(user) @@ -121,7 +120,7 @@ describe UsersController do context 'format html' do it 'renders snippets page' do get :snippets, username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response).to render_template('show') end end @@ -129,7 +128,7 @@ describe UsersController do context 'format json' do it 'response with snippets json data' do get :snippets, username: user.username, format: :json - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(JSON.parse(response.body)).to have_key('html') end end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 696cf276e57..83e38095feb 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -10,21 +10,46 @@ FactoryGirl.define do on_issue factory :note_on_commit, traits: [:on_commit] - factory :note_on_commit_diff, traits: [:on_commit, :on_diff], class: LegacyDiffNote factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] factory :note_on_merge_request, traits: [:on_merge_request] - factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff], class: LegacyDiffNote factory :note_on_project_snippet, traits: [:on_project_snippet] factory :system_note, traits: [:system] + factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote + factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote + + factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do + position do + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: noteable.diff_refs + ) + end + end + + factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do + position do + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: project.commit(commit_id).try(:diff_refs) + ) + end + end + trait :on_commit do noteable nil - noteable_id nil noteable_type 'Commit' + noteable_id nil commit_id RepoHelpers.sample_commit.id end - trait :on_diff do + trait :legacy_diff_note do line_code "0_184_184" end diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb new file mode 100644 index 00000000000..b5e96d18b8f --- /dev/null +++ b/spec/factories/notification_settings.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :notification_setting do + source factory: :empty_project + user + level 3 + events [] + end +end diff --git a/spec/factories/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/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb new file mode 100644 index 00000000000..16baf7e9516 --- /dev/null +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe "Admin::AbuseReports", feature: true, js: true do + let(:user) { create(:user) } + + context 'as an admin' do + describe 'if a user has been reported for abuse' do + before do + create(:abuse_report, user: user) + login_as :admin + end + + describe 'in the abuse report view' do + it "should present a link to the user's profile" do + visit admin_abuse_reports_path + + expect(page).to have_link user.name, href: user_path(user) + end + end + + describe 'in the profile page of the user' do + it 'should show a link to the admin view of the user' do + visit user_path(user) + + expect(page).to have_link '', href: admin_user_path(user) + end + end + end + end +end diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb new file mode 100644 index 00000000000..5b1c0460274 --- /dev/null +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +feature 'Admin disables Git access protocol', feature: true do + let(:project) { create(:empty_project, :empty_repo) } + let(:admin) { create(:admin) } + + background do + login_as(admin) + end + + context 'with HTTP disabled' do + background do + disable_http_protocol + end + + scenario 'shows only SSH url' do + visit_project + + expect(page).to have_content("git clone #{project.ssh_url_to_repo}") + expect(page).not_to have_selector('#clone-dropdown') + end + end + + context 'with SSH disabled' do + background do + disable_ssh_protocol + end + + scenario 'shows only HTTP url' do + visit_project + + expect(page).to have_content("git clone #{project.http_url_to_repo}") + expect(page).not_to have_selector('#clone-dropdown') + end + end + + context 'with nothing disabled' do + background do + create(:personal_key, user: admin) + end + + scenario 'shows default SSH url and protocol selection dropdown' do + visit_project + + expect(page).to have_content("git clone #{project.ssh_url_to_repo}") + expect(page).to have_selector('#clone-dropdown') + end + + end + + def visit_project + visit namespace_project_path(project.namespace, project) + end + + def disable_http_protocol + visit admin_application_settings_path + find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[2]').select_option + click_on 'Save' + end + + def disable_ssh_protocol + visit admin_application_settings_path + find('#application_setting_enabled_git_access_protocol').find(:xpath, 'option[3]').select_option + click_on 'Save' + end +end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 31633817d53..7964951ae99 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -6,7 +6,6 @@ describe "Admin::Hooks", feature: true do login_as :admin @system_hook = create(:system_hook) - end describe "GET /admin/hooks" do @@ -49,5 +48,4 @@ describe "Admin::Hooks", feature: true do it { expect(current_path).to eq(admin_hooks_path) } end - end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 2d297776cb0..2f82fafc13a 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -62,19 +62,45 @@ describe "Admin Runners" do end describe 'enable/create' do - before do - @project1.runners << runner - visit admin_runner_path(runner) + shared_examples 'assignable runner' do + it 'enables a runner for a project' do + within '.unassigned-projects' do + click_on 'Enable' + end + + assigned_project = page.find('.assigned-projects') + + expect(assigned_project).to have_content(@project2.path) + end end - it 'enables specific runner for project' do - within '.unassigned-projects' do - click_on 'Enable' + context 'with specific runner' do + before do + @project1.runners << runner + visit admin_runner_path(runner) end - assigned_project = page.find('.assigned-projects') + it_behaves_like 'assignable runner' + end + + context 'with locked runner' do + before do + runner.update(locked: true) + @project1.runners << runner + visit admin_runner_path(runner) + end + + it_behaves_like 'assignable runner' + end + + context 'with shared runner' do + before do + @project1.destroy + runner.update(is_shared: true) + visit admin_runner_path(runner) + end - expect(assigned_project).to have_content(@project2.path) + it_behaves_like 'assignable runner' end end diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb new file mode 100644 index 00000000000..f4e5c26b519 --- /dev/null +++ b/spec/features/admin/admin_system_info_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'Admin System Info' do + before do + login_as :admin + end + + describe 'GET /admin/system_info' do + it 'shows system info page' do + visit admin_system_info_path + + expect(page).to have_content 'CPU' + expect(page).to have_content 'Memory' + expect(page).to have_content 'Disks' + end + end +end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 1cb709c1de3..767504df251 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -144,9 +144,7 @@ describe "Admin::Users", feature: true do before { click_link 'Impersonate' } it 'logs in as the user when impersonate is clicked' do - page.within '.sidebar-wrapper' do - expect(page.find('.sidebar-user')['data-user']).to eql(another_user.username) - end + expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username) end it 'sees impersonation log out icon' do @@ -158,9 +156,7 @@ describe "Admin::Users", feature: true do it 'can log out of impersonated user back to original user' do find(:css, 'li.impersonation a').click - page.within '.sidebar-wrapper' do - expect(page.find('.sidebar-user')['data-user']).to eql(@user.username) - end + expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username) end it 'is redirected back to the impersonated users page in the admin after stopping' do diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index de6aed74fb4..91704377a07 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -61,7 +61,7 @@ describe "User Feed", feature: true do end it 'should have XHTML summaries in merge request descriptions' do - expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/ + expect(body).to match /Here is the fix: <\/p><div[^>]*><a[^>]*><img[^>]*\/><\/a><\/div>/ end end end diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index cf86e2c85e9..c2e0612aef8 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe "Dashboard > User filters projects", feature: true do - describe 'filtering personal projects' do before do user = create(:user) diff --git a/spec/features/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/member_cannot_request_access_to_his_project_spec.rb b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb new file mode 100644 index 00000000000..37c433cc09a --- /dev/null +++ b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +feature 'Groups > Members > Member cannot request access to his project', feature: true do + let(:member) { create(:user) } + let(:group) { create(:group) } + + background do + group.add_developer(member) + login_as(member) + visit group_path(group) + end + + scenario 'member does not see the request access button' do + expect(page).not_to have_content 'Request Access' + end +end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 321c9bad7d0..10d3713f19f 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -39,9 +39,8 @@ 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(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 1ea607cbca0..d1a6a98ab72 100644 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ b/spec/features/groups/members/user_requests_access_spec.rb @@ -4,6 +4,7 @@ feature 'Groups > Members > User requests access', feature: true do let(:user) { create(:user) } let(:owner) { create(:user) } let(:group) { create(:group, :public) } + let!(:project) { create(:project, :private, namespace: group) } background do group.add_owner(owner) @@ -17,17 +18,31 @@ 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' expect(page).not_to have_content 'Leave Group' end + scenario 'user does not see private projects' do + perform_enqueued_jobs { click_link 'Request Access' } + + expect(page).not_to have_content project.name + end + + scenario 'user does not see group in the Dashboard > Groups page' do + perform_enqueued_jobs { click_link 'Request Access' } + + visit dashboard_groups_path + + expect(page).not_to have_content group.name + end + scenario 'user is not listed in the group members page' do click_link 'Request Access' - expect(group.members.request.exists?(user_id: user)).to be_truthy + expect(group.requesters.exists?(user_id: user)).to be_truthy click_link 'Members' @@ -39,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/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 4bcb105b17d..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] @@ -14,7 +15,6 @@ describe 'Filter issues', feature: true do end describe 'Filter issues for assignee from issues#index' do - before do visit namespace_project_issues_path(project.namespace, project) @@ -36,7 +36,6 @@ describe 'Filter issues', feature: true do expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) end - it 'should not change when all link is clicked' do find('.issues-state-filters a', text: "All").click @@ -46,7 +45,6 @@ describe 'Filter issues', feature: true do end describe 'Filter issues for milestone from issues#index' do - before do visit namespace_project_issues_path(project.namespace, project) @@ -68,7 +66,6 @@ describe 'Filter issues', feature: true do expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) end - it 'should not change when all link is clicked' do find('.issues-state-filters a', text: "All").click @@ -113,7 +110,6 @@ describe 'Filter issues', feature: true do end describe 'Filter issues for assignee and label from issues#index' do - before do visit namespace_project_issues_path(project.namespace, project) @@ -144,7 +140,6 @@ describe 'Filter issues', feature: true do expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) end - it 'should not change when all link is clicked' do find('.issues-state-filters a', text: "All").click @@ -202,6 +197,7 @@ describe 'Filter issues', feature: true do page.within '.labels-filter' do click_link 'bug' end + find('.dropdown-menu-close-icon').click page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) @@ -293,7 +289,7 @@ describe 'Filter issues', feature: true do wait_for_ajax page.within '.issues-list' do - expect(first('.issue')).to have_content('Frontend') + expect(page).to have_content('Frontend') end end end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 5065dfb849c..d51c9abea19 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -92,7 +92,7 @@ describe 'Issues', feature: true do end context 'on edit form' do - let(:issue) { create(:issue, author: @user,project: project, due_date: Date.today.at_beginning_of_month.to_s) } + let(:issue) { create(:issue, author: @user, project: project, due_date: Date.today.at_beginning_of_month.to_s) } before do visit edit_namespace_project_issue_path(project.namespace, project, issue) @@ -361,7 +361,6 @@ describe 'Issues', feature: true do let(:issue) { create(:issue, project: project, author: @user, assignee: @user) } context 'by authorized user' do - it 'allows user to select unassigned', js: true do visit namespace_project_issue_path(project.namespace, project, issue) @@ -420,7 +419,6 @@ describe 'Issues', feature: true do end context 'by unauthorized user' do - let(:guest) { create(:user) } before do @@ -442,8 +440,6 @@ describe 'Issues', feature: true do let!(:milestone) { create(:milestone, project: project) } context 'by authorized user' do - - it 'allows user to select unassigned', js: true do visit namespace_project_issue_path(project.namespace, project, issue) diff --git a/spec/features/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/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/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb index 98703ef3ac4..e7ee0aaea3c 100644 --- a/spec/features/pipelines_spec.rb +++ b/spec/features/pipelines_spec.rb @@ -123,7 +123,7 @@ describe "Pipelines" do before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } it 'showing a list of builds' do - expect(page).to have_content('Tests') + expect(page).to have_content('Test') expect(page).to have_content(@success.id) expect(page).to have_content('Deploy') expect(page).to have_content(@failed.id) diff --git a/spec/features/projects/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/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb index b8c06c383fb..fca40f68b01 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -19,12 +19,12 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do find('.js-gitlab-ci-yml-selector').click wait_for_ajax within '.gitlab-ci-yml-selector' do - find('.dropdown-input-field').set('jekyll') - find('.dropdown-content li', text: 'jekyll').click + find('.dropdown-input-field').set('Jekyll') + find('.dropdown-content li', text: 'Jekyll').click end wait_for_ajax - expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'jekyll') + expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Jekyll') expect(page).to have_content('This file is a template, and might need editing before it works on your project') expect(page).to have_content('jekyll build -d test') end diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index c5fb0fc783b..bc3bf53fe9d 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -24,7 +24,7 @@ feature 'project import', feature: true, js: true do visit new_project_path select2('2', from: '#project_namespace_id') - fill_in :project_path, with:'test-project-path', visible: true + fill_in :project_path, with: 'test-project-path', visible: true click_link 'GitLab export' expect(page).to have_content('GitLab project export') @@ -42,6 +42,23 @@ feature 'project import', feature: true, js: true do expect(project.import_status).to eq('finished') end + scenario 'invalid project' do + project = create(:project, namespace_id: 2) + + visit new_project_path + + select2('2', from: '#project_namespace_id') + fill_in :project_path, with: project.name, visible: true + click_link 'GitLab export' + + attach_file('file', file) + click_on 'Import project' + + page.within('.flash-container') do + expect(page).to have_content('Project could not be imported') + end + end + def wiki_exists? wiki = ProjectWiki.new(project) File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty? diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz Binary files differindex 1fd04416d95..7bb0d26b21c 100644 --- a/spec/features/projects/import_export/test_project_export.tar.gz +++ b/spec/features/projects/import_export/test_project_export.tar.gz 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/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/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/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb new file mode 100644 index 00000000000..9564347e733 --- /dev/null +++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +feature 'Projects > Members > Member cannot request access to his project', feature: true do + let(:member) { create(:user) } + let(:project) { create(:project) } + + background do + project.team << [member, :developer] + login_as(member) + visit namespace_project_path(project.namespace, project) + end + + scenario 'member does not see the request access button' do + expect(page).not_to have_content 'Request Access' + end +end diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb new file mode 100644 index 00000000000..0e54c4fdf20 --- /dev/null +++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +feature 'Projects > Members > Owner cannot request access to his project', feature: true do + let(:owner) { create(:user) } + let(:project) { create(:project) } + + background do + project.team << [owner, :owner] + login_as(owner) + visit namespace_project_path(project.namespace, project) + end + + scenario 'owner does not see the request access button' do + expect(page).not_to have_content 'Request Access' + end +end diff --git a/spec/features/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/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb new file mode 100644 index 00000000000..d94dee0c797 --- /dev/null +++ b/spec/features/protected_branches_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +feature 'Projected Branches', feature: true, js: true do + let(:user) { create(:user, :admin) } + let(:project) { create(:project) } + + before { login_as(user) } + + def set_protected_branch_name(branch_name) + find(".js-protected-branch-select").click + find(".dropdown-input-field").set(branch_name) + click_on "Create Protected Branch: #{branch_name}" + end + + describe "explicit protected branches" do + it "allows creating explicit protected branches" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('some-branch') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content('some-branch') } + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.name).to eq('some-branch') + end + + it "displays the last commit on the matching branch if it exists" do + commit = create(:commit, project: project) + project.repository.add_branch(user, 'some-branch', commit.id) + + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('some-branch') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content(commit.id[0..7]) } + end + + it "displays an error message if the named branch does not exist" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('some-branch') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content('branch was removed') } + end + end + + describe "wildcard protected branches" do + it "allows creating protected branches with a wildcard" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('*-stable') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content('*-stable') } + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.name).to eq('*-stable') + end + + it "displays the number of matching branches" do + project.repository.add_branch(user, 'production-stable', 'master') + project.repository.add_branch(user, 'staging-stable', 'master') + + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('*-stable') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content("2 matching branches") } + end + + it "displays all the branches matching the wildcard" do + project.repository.add_branch(user, 'production-stable', 'master') + project.repository.add_branch(user, 'staging-stable', 'master') + project.repository.add_branch(user, 'development', 'master') + create(:protected_branch, project: project, name: "*-stable") + + visit namespace_project_protected_branches_path(project.namespace, project) + click_on "2 matching branches" + + within(".protected-branches-list") do + expect(page).to have_content("production-stable") + expect(page).to have_content("staging-stable") + expect(page).not_to have_content("development") + end + end + end +end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index b9e63a7152c..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) @@ -48,9 +50,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 +73,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 +123,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/fixtures/dk.png b/spec/fixtures/dk.png Binary files differindex 87ce25e877a..1247f2fecd7 100644 --- a/spec/fixtures/dk.png +++ b/spec/fixtures/dk.png 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 <span id="LC6" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 6 + :new_line: 6 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 6 :text: |2 <span id="LC6" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 6 + :new_line: 6 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 7 :text: |2 <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 7 + :new_line: 7 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 7 :text: |2 <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 7 + :new_line: 7 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 8 :text: |2 <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 8 + :new_line: 8 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 8 :text: |2 <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 8 + :new_line: 8 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: old :number: 9 :text: | -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 9 + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 9 :text: | +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 9 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 10 :text: |2 <span id="LC10" class="line"> <span class="k">end</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 10 + :new_line: 10 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 10 :text: |2 <span id="LC10" class="line"> <span class="k">end</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 10 + :new_line: 10 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 11 :text: |2 <span id="LC11" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 11 + :new_line: 11 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 11 :text: |2 <span id="LC11" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 11 + :new_line: 11 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 12 :text: |2 <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 12 + :new_line: 12 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 12 :text: |2 <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 12 + :new_line: 12 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: old :number: 13 :text: | -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span> <span class="p">}</span></span> :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: | -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 14 + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 13 :text: | +<span id="LC13" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 13 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14 + :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: | +<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 14 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 15 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 15 :text: | +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=></span> <span class="n">path</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 15 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 16 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 16 :text: | +<span id="LC16" class="line"> <span class="p">}</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 16 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 17 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 17 :text: | +<span id="LC17" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 17 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 18 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 18 :text: | +<span id="LC18" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 18 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 19 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 19 :text: | +<span id="LC19" class="line"> <span class="ss">chdir: </span><span class="n">path</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 19 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 20 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 20 :text: | +<span id="LC20" class="line"> <span class="p">}</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 20 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 15 :text: |2 <span id="LC21" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 15 + :new_line: 21 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 21 :text: |2 <span id="LC21" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 15 + :new_line: 21 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 16 :text: |2 <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 16 + :new_line: 22 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 22 :text: |2 <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 16 + :new_line: 22 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 17 :text: |2 <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 17 + :new_line: 23 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 23 :text: |2 <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 17 + :new_line: 23 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: match :number: 19 :text: "@@ -19,6 +25,7 @@ module Popen" - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :line_code: + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: match :number: 25 :text: "@@ -19,6 +25,7 @@ module Popen" - :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :line_code: + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 19 :text: |2 <span id="LC25" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 19 + :new_line: 25 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 25 :text: |2 <span id="LC25" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 19 + :new_line: 25 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 20 :text: |2 <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 20 + :new_line: 26 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 26 :text: |2 <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 20 + :new_line: 26 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 21 :text: |2 <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 21 + :new_line: 27 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 27 :text: |2 <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 21 + :new_line: 27 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: :text: '' :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 28 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: new :number: 28 :text: | +<span id="LC28" class="line"></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: + :new_line: 28 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 22 :text: |2 <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 22 + :new_line: 29 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 29 :text: |2 <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 22 + :new_line: 29 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 23 :text: |2 <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 23 + :new_line: 30 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 30 :text: |2 <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 23 + :new_line: 30 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d - :left: :type: :number: 24 :text: |2 <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 24 + :new_line: 31 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d :right: :type: :number: 31 :text: |2 <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o"><<</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span> :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31 + :position: !ruby/object:Gitlab::Diff::Position + attributes: + :old_path: files/ruby/popen.rb + :new_path: files/ruby/popen.rb + :old_line: 24 + :new_line: 31 + :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d diff --git a/spec/helpers/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/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb new file mode 100644 index 00000000000..08a93503258 --- /dev/null +++ b/spec/helpers/notes_helper_spec.rb @@ -0,0 +1,46 @@ +require "spec_helper" + +describe NotesHelper do + describe "#notes_max_access_for_users" do + let(:owner) { create(:owner) } + let(:group) { create(:group) } + let(:project) { create(:empty_project, namespace: group) } + let(:master) { create(:user) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + + let(:owner_note) { create(:note, author: owner, project: project) } + let(:master_note) { create(:note, author: master, project: project) } + let(:reporter_note) { create(:note, author: reporter, project: project) } + let!(:notes) { [owner_note, master_note, reporter_note] } + + before do + group.add_owner(owner) + project.team << [master, :master] + project.team << [reporter, :reporter] + project.team << [guest, :guest] + end + + it 'return human access levels' do + original_method = project.team.method(:human_max_access) + expect_any_instance_of(ProjectTeam).to receive(:human_max_access).exactly(3).times do |*args| + original_method.call(args[1]) + end + + expect(helper.note_max_access_for_user(owner_note)).to eq('Owner') + expect(helper.note_max_access_for_user(master_note)).to eq('Master') + expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter') + # Call it again to ensure value is cached + expect(helper.note_max_access_for_user(owner_note)).to eq('Owner') + end + + it 'handles access in different projects' do + second_project = create(:empty_project) + second_project.team << [master, :reporter] + other_note = create(:note, author: master, project: second_project) + + expect(helper.note_max_access_for_user(master_note)).to eq('Master') + expect(helper.note_max_access_for_user(other_note)).to eq('Reporter') + end + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 09e0bbfd00b..604204cca0a 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -123,11 +123,17 @@ describe ProjectsHelper do end describe '#sanitized_import_error' do + let(:project) { create(:project) } + + before do + allow(project).to receive(:repository_storage_path).and_return('/base/repo/path') + end + it 'removes the repo path' do - repo = File.join(Gitlab.config.gitlab_shell.repos_path, '/namespace/test.git') + repo = '/base/repo/path/namespace/test.git' import_error = "Could not clone #{repo}\n" - expect(sanitize_repo_path(import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') + expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') end end end diff --git a/spec/helpers/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/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb new file mode 100644 index 00000000000..5178bd130f4 --- /dev/null +++ b/spec/initializers/6_validations_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe '6_validations', lib: true do + context 'with correct settings' do + before do + mock_storages('foo' => '/a/b/c', 'bar' => 'a/b/d') + end + + it 'passes through' do + expect { load_validations }.not_to raise_error + end + end + + context 'with invalid storage names' do + before do + mock_storages('name with spaces' => '/a/b/c') + end + + it 'throws an error' do + expect { load_validations }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.') + end + end + + context 'with nested storage paths' do + before do + mock_storages('foo' => '/a/b/c', 'bar' => '/a/b/c/d') + end + + it 'throws an error' do + expect { load_validations }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.') + end + end + + def mock_storages(storages) + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end + + def load_validations + load File.join(__dir__, '../../config/initializers/6_validations.rb') + 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/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb index 4bb149f25ff..14c8df954a6 100644 --- a/spec/initializers/trusted_proxies_spec.rb +++ b/spec/initializers/trusted_proxies_spec.rb @@ -6,14 +6,16 @@ describe 'trusted_proxies', lib: true do set_trusted_proxies([]) end - it 'preserves private IPs as remote_ip' do + it 'preserves private IPs' do request = stub_request('HTTP_X_FORWARDED_FOR' => '10.1.5.89') expect(request.remote_ip).to eq('10.1.5.89') + expect(request.ip).to eq('10.1.5.89') end - it 'filters out localhost from remote_ip' do + it 'filters out localhost' do request = stub_request('HTTP_X_FORWARDED_FOR' => '1.1.1.1, 10.1.5.89, 127.0.0.1') expect(request.remote_ip).to eq('10.1.5.89') + expect(request.ip).to eq('10.1.5.89') end end @@ -22,9 +24,10 @@ describe 'trusted_proxies', lib: true do set_trusted_proxies([ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ]) end - it 'filters out private and local IPs from remote_ip' do + it 'filters out private and local IPs' do request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 10.1.5.89, 127.0.0.1') expect(request.remote_ip).to eq('1.1.1.1') + expect(request.ip).to eq('1.1.1.1') end end @@ -33,9 +36,10 @@ describe 'trusted_proxies', lib: true do set_trusted_proxies([ "60.98.25.47" ]) end - it 'filters out proxy IP from remote_ip' do + it 'filters out proxy IP' do request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 60.98.25.47, 127.0.0.1') expect(request.remote_ip).to eq('1.1.1.1') + expect(request.ip).to eq('1.1.1.1') end end diff --git a/spec/javascripts/awards_handler_spec.js.coffee b/spec/javascripts/awards_handler_spec.js.coffee index ba191199dc7..d7f9c6fc076 100644 --- a/spec/javascripts/awards_handler_spec.js.coffee +++ b/spec/javascripts/awards_handler_spec.js.coffee @@ -160,7 +160,6 @@ describe 'AwardsHandler', -> expect($('[data-emoji=angel]').is(':visible')).toBe no expect($('[data-emoji=anger]').is(':visible')).toBe no expect($('[data-emoji=alien]').is(':visible')).toBe yes - expect($('h5.emoji-search').is(':visible')).toBe yes describe 'emoji menu', -> diff --git a/spec/javascripts/fixtures/emoji_menu.coffee b/spec/javascripts/fixtures/emoji_menu.coffee index e529dd5f1cd..ce1a41390d2 100644 --- a/spec/javascripts/fixtures/emoji_menu.coffee +++ b/spec/javascripts/fixtures/emoji_menu.coffee @@ -1,7 +1,7 @@ window.emojiMenu = """ <div class='emoji-menu'> + <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" /> <div class='emoji-menu-content'> - <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" /> <h5 class='emoji-menu-title'> Emoticons </h5> diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb index dd5594750c8..a2a1ed58d1b 100644 --- a/spec/lib/banzai/filter/image_link_filter_spec.rb +++ b/spec/lib/banzai/filter/image_link_filter_spec.rb @@ -21,4 +21,9 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do doc = filter(image('https://i.imgur.com/DfssX9C.jpg')) expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] end + + it 'wraps the image with a link and a div' do + doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.to_html).to include('<div class="image-container">') + end end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 8d6ce114aa9..a005b4990e7 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -199,6 +199,19 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end end + context 'referencing external issues' do + let(:project) { create(:redmine_project) } + + it 'renders internal issue IDs as external issue links' do + doc = reference_filter('#1') + link = doc.css('a').first + + expect(link.attr('data-reference-type')).to eq('external_issue') + expect(link.attr('title')).to eq('Issue in Redmine') + expect(link.attr('data-external-issue')).to eq('1') + end + end + describe '#issues_per_Project' do context 'using an internal issue tracker' do it 'returns a Hash containing the issues per project' do 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(\(<a.+><span.+>\?gfm&</span></a>\.\))) + 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(\(<a.+><span.+>gfm & references\?</span></a>\.\))) + 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' 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 200ca6aeeea..bad439bc489 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"] }, @@ -551,8 +550,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 assume this is a valid + # configuration, see issue #18775 # expect(config_processor.job_variables(:rspec)) .to be_an_instance_of(Array).and be_empty @@ -591,7 +590,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 contains unknown keys: invalid' + ) + end + end + it "returns cache when defined globally" do config = YAML.dump({ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' }, @@ -951,7 +963,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 @@ -965,7 +977,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 @@ -979,7 +991,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 @@ -1007,14 +1019,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 @@ -1081,31 +1093,31 @@ 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 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 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 should be a map of key-value strings") + 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 @@ -1161,21 +1173,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 @@ -1206,5 +1218,17 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings") end end + + describe "Validate configuration templates" do + templates = Dir.glob("#{Rails.root.join('vendor/gitlab-ci-yml')}/**/*.gitlab-ci.yml") + + templates.each do |file| + it "does not return errors for #{file}" do + file = File.read(file) + + expect { GitlabCiYamlProcessor.new(file) }.not_to raise_error + end + end + end end end 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) { '<b>ascii</b>' } let(:context) { {} } let(:html) { 'H<sub>2</sub>O' } 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/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 diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index fd869f48b5c..6e5ba211382 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -13,9 +13,37 @@ describe Gitlab::Shell, lib: true do it { is_expected.to respond_to :add_repository } it { is_expected.to respond_to :remove_repository } it { is_expected.to respond_to :fork_repository } + it { is_expected.to respond_to :gc } + it { is_expected.to respond_to :add_namespace } + it { is_expected.to respond_to :rm_namespace } + it { is_expected.to respond_to :mv_namespace } + it { is_expected.to respond_to :exists? } it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") } + describe 'generate_and_link_secret_token' do + let(:secret_file) { 'tmp/tests/.secret_shell_test' } + let(:link_file) { 'tmp/tests/shell-secret-test/.gitlab_shell_secret' } + + before do + allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test') + allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file) + FileUtils.mkdir('tmp/tests/shell-secret-test') + gitlab_shell.generate_and_link_secret_token + end + + after do + FileUtils.rm_rf('tmp/tests/shell-secret-test') + FileUtils.rm_rf(secret_file) + end + + it 'creates and links the secret token file' do + expect(File.exist?(secret_file)).to be(true) + expect(File.symlink?(link_file)).to be(true) + expect(File.readlink(link_file)).to eq(secret_file) + end + end + describe Gitlab::Shell::KeyAdder, lib: true do describe '#add_key' do it 'normalizes space characters in the key' 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/ci/config/node/boolean_spec.rb b/spec/lib/gitlab/ci/config/node/boolean_spec.rb new file mode 100644 index 00000000000..deafa8bf8a7 --- /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 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..50f619ce26e --- /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 9bbda6e7396..c468ecf957b 100644 --- a/spec/lib/gitlab/ci/config/node/configurable_spec.rb +++ b/spec/lib/gitlab/ci/config/node/configurable_spec.rb @@ -7,10 +7,42 @@ 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 - allow_node :object, Object, description: 'test object' + node :object, Object, description: 'test object' 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 01a707a6bd4..91ddef7bfbf 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -5,13 +5,13 @@ 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']) .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 @@ -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 @@ -45,14 +57,13 @@ describe Gitlab::Ci::Config::Node::Factory do end end - context 'when creating a null 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) - .nullify! .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/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index fddd53a2b57..c87c9e97bc8 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -13,57 +13,163 @@ 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 - let(:hash) do - { before_script: ['ls', 'pwd'] } - 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'], + stages: ['build', 'pages'], + cache: { key: 'k', untracked: true, paths: ['public/'] } } + 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 + 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 8 + 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 - it 'creates node object for each entry' do - expect(global.nodes.count).to eq 1 + describe '#leaf?' do + it 'is not leaf' do + expect(global).not_to be_leaf + end end - it 'creates node object using valid class' do - expect(global.nodes.first) - .to be_an_instance_of Gitlab::Ci::Config::Node::Script + context 'when not processed' do + describe '#before_script' do + it 'returns nil' do + expect(global.before_script).to be nil + end + end end - it 'sets correct description for nodes' do - expect(global.nodes.first.description) - .to eq 'Script that will be executed before each job.' + 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 + + 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 + + describe '#cache' do + it 'returns cache configuration' do + expect(global.cache) + .to eq(key: 'k', untracked: true, paths: ['public/']) + end + end end end - describe '#leaf?' do - it 'is not leaf' do - expect(global).not_to be_leaf + context 'when most of entires not defined' do + let(:hash) { { cache: { key: 'a' }, rspec: {} } } + before { global.process! } + + describe '#nodes' do + it 'instantizes all nodes' do + expect(global.nodes.count).to eq 8 + end + + it 'contains undefined nodes' do + expect(global.nodes.first) + .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined + end end - end - describe '#before_script' do - context 'when processed' do - before { global.process! } + describe '#variables' do + it 'returns default value for variables' do + expect(global.variables).to eq({}) + end + end - it 'returns correct script' do - expect(global.before_script).to eq "ls\npwd" + describe '#stages' do + it 'returns an array of default stages' do + expect(global.stages).to eq %w[build test deploy] end end - context 'when not processed' do - it 'returns nil' do - expect(global.before_script).to be nil + describe '#cache' do + it 'returns correct cache definition' do + expect(global.cache).to eq(key: 'a') + end + 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 'undefined entry returns a default value' do + expect(global.variables).to eq({}) end end end @@ -85,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 @@ -106,5 +212,17 @@ 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 + + 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/image_spec.rb b/spec/lib/gitlab/ci/config/node/image_spec.rb new file mode 100644 index 00000000000..d11bb39f328 --- /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 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..8cda43173fe --- /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 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/paths_spec.rb b/spec/lib/gitlab/ci/config/node/paths_spec.rb new file mode 100644 index 00000000000..6fd744b3975 --- /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 diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb index 6af6aa15eef..ee7395362a9 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 @@ -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 new file mode 100644 index 00000000000..be0fe46befd --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/services_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Services do + let(:entry) { described_class.new(config) } + + describe 'validations' do + 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 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..1a3818d8997 --- /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 new file mode 100644 index 00000000000..0c6608d906d --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Undefined do + let(:undefined) { described_class.new(entry) } + let(:entry) { Class.new } + + describe '#leaf?' do + it 'is leaf node' do + expect(undefined).to be_leaf + end + end + + 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 + before do + allow(entry).to receive(:default).and_return('some value') + end + + it 'returns default value for entry' do + expect(undefined.value).to eq 'some value' + end + end + + describe '#undefined?' do + it 'is not a defined entry' do + expect(undefined.defined?).to be false + end + 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 ad875d55384..090fd63b844 100644 --- a/spec/lib/gitlab/ci/config/node/validator_spec.rb +++ b/spec/lib/gitlab/ci/config/node/validator_spec.rb @@ -5,7 +5,18 @@ describe Gitlab::Ci::Config::Node::Validator do let(:validator_instance) { validator.new(node) } let(:node) { spy('node') } - shared_examples 'delegated validator' do + before do + allow(node).to receive(:key).and_return('node') + allow(node).to receive(:ancestors).and_return([]) + end + + 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') @@ -19,7 +30,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,32 +47,9 @@ 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) + .to include "node test attribute can't be blank" 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 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..4b6d971ec71 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/variables_spec.rb @@ -0,0 +1,48 @@ +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 + 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 diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb new file mode 100644 index 00000000000..004341ffd02 --- /dev/null +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::CurrentSettings do + describe '#current_application_settings' do + it 'attempts to use cached values first' do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) + expect(ApplicationSetting).to receive(:current).and_return(::ApplicationSetting.create_from_defaults) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to be_a(ApplicationSetting) + end + + it 'does not attempt to connect to DB or Redis' do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to eq fake_application_settings + end + + it 'falls back to DB if Redis returns an empty value' do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) + expect(ApplicationSetting).to receive(:last).and_call_original + + expect(current_application_settings).to be_a(ApplicationSetting) + end + + it 'falls back to DB if Redis fails' do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) + expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) + expect(ApplicationSetting).to receive(:last).and_call_original + + expect(current_application_settings).to be_a(ApplicationSetting) + end + end +end 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/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) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">'def'</span>}.html_safe } diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb new file mode 100644 index 00000000000..4e50e03bb7e --- /dev/null +++ b/spec/lib/gitlab/diff/line_mapper_spec.rb @@ -0,0 +1,137 @@ +require 'spec_helper' + +describe Gitlab::Diff::LineMapper, lib: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:repository) { project.repository } + let(:commit) { project.commit(sample_commit.id) } + let(:diffs) { commit.diffs } + let(:diff) { diffs.first } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } + subject { described_class.new(diff_file) } + + describe '#old_to_new' do + context "with a diff file" do + let(:mapping) do + { + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + 9 => nil, + # nil => 9, + 10 => 10, + 11 => 11, + 12 => 12, + 13 => nil, + 14 => nil, + # nil => 15, + # nil => 16, + # nil => 17, + # nil => 18, + # nil => 19, + # nil => 20, + 15 => 21, + 16 => 22, + 17 => 23, + 18 => 24, + 19 => 25, + 20 => 26, + 21 => 27, + # nil => 28, + 22 => 29, + 23 => 30, + 24 => 31, + 25 => 32, + 26 => 33, + 27 => 34, + 28 => 35, + 29 => 36, + 30 => 37 + } + end + + it 'returns the new line number for the old line number' do + mapping.each do |old_line, new_line| + expect(subject.old_to_new(old_line)).to eq(new_line) + end + end + end + + context "without a diff file" do + let(:diff_file) { nil } + + it "returns the same line number" do + expect(subject.old_to_new(100)).to eq(100) + end + end + end + + describe '#new_to_old' do + context "with a diff file" do + let(:mapping) do + { + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + # nil => 9, + 9 => nil, + 10 => 10, + 11 => 11, + 12 => 12, + # nil => 13, + # nil => 14, + 13 => nil, + 14 => nil, + 15 => nil, + 16 => nil, + 17 => nil, + 18 => nil, + 19 => nil, + 20 => nil, + 21 => 15, + 22 => 16, + 23 => 17, + 24 => 18, + 25 => 19, + 26 => 20, + 27 => 21, + 28 => nil, + 29 => 22, + 30 => 23, + 31 => 24, + 32 => 25, + 33 => 26, + 34 => 27, + 35 => 28, + 36 => 29, + 37 => 30 + } + end + + it 'returns the old line number for the new line number' do + mapping.each do |new_line, old_line| + expect(subject.new_to_old(new_line)).to eq(old_line) + end + end + end + + context "without a diff file" do + let(:diff_file) { nil } + + it "returns the same line number" do + expect(subject.new_to_old(100)).to eq(100) + end + end + end +end diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index 1c5bbc47120..5f76b70c6f5 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -8,8 +8,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do let(:commit) { project.commit(sample_commit.id) } let(:diffs) { commit.diffs } let(:diff) { diffs.first } - let(:diff_refs) { [commit.parent, commit] } - let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } subject { described_class.new(diff_file) } let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") } diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb new file mode 100644 index 00000000000..cf28628cb96 --- /dev/null +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -0,0 +1,341 @@ +require 'spec_helper' + +describe Gitlab::Diff::Position, lib: true do + include RepoHelpers + + let(:project) { create(:project) } + + describe "position for an added file" do + let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } + + subject do + described_class.new( + old_path: "files/images/wm.svg", + new_path: "files/images/wm.svg", + old_line: nil, + new_line: 5, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.new_file).to be true + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq("+ <desc>Created with Sketch.</desc>") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for a changed file" do + let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } + + describe "position for an added line" do + subject do + described_class.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq("+ vars = {") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 15) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for an unchanged line" do + subject do + described_class.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: 16, + new_line: 22, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.unchanged?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq(" unless File.directory?(path)") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for a removed line" do + subject do + described_class.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: 14, + new_line: nil, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.removed?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.text).to eq("- options = { chdir: path }") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + end + + describe "position for a renamed file" do + let(:commit) { project.commit("6907208d755b60ebeacb2e9dfea74c92c3449a1f") } + + describe "position for an added line" do + subject do + described_class.new( + old_path: "files/js/commit.js.coffee", + new_path: "files/js/commit.coffee", + old_line: nil, + new_line: 4, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq("+ new CommitFile(@)") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 5) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for an unchanged line" do + subject do + described_class.new( + old_path: "files/js/commit.js.coffee", + new_path: "files/js/commit.coffee", + old_line: 3, + new_line: 3, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.unchanged?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.new_line).to eq(subject.new_line) + expect(diff_line.text).to eq(" $('.files .diff-file').each ->") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + + describe "position for a removed line" do + subject do + described_class.new( + old_path: "files/js/commit.js.coffee", + new_path: "files/js/commit.coffee", + old_line: 4, + new_line: nil, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.removed?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.text).to eq("- new CommitFile(this)") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end + end + + describe "position for a deleted file" do + let(:commit) { project.commit("8634272bfad4cf321067c3e94d64d5a253f8321d") } + + subject do + described_class.new( + old_path: "LICENSE", + new_path: "LICENSE", + old_line: 3, + new_line: nil, + diff_refs: commit.diff_refs + ) + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.deleted_file).to be true + expect(diff_file.old_path).to eq(subject.old_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line(project.repository) + + expect(diff_line.removed?).to be true + expect(diff_line.old_line).to eq(subject.old_line) + expect(diff_line.text).to eq("-Copyright (c) 2014 gitlabhq") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) + + expect(subject.line_code(project.repository)).to eq(line_code) + end + end + end +end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb new file mode 100644 index 00000000000..08312e60f4a --- /dev/null +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -0,0 +1,1758 @@ +require 'spec_helper' + +describe Gitlab::Diff::PositionTracer, lib: true do + # Douwe's diary New York City, 2016-06-28 + # -------------------------------------------------------------------------- + # + # Dear diary, + # + # Ideally, we would have a test for every single diff scenario that can + # occur and that the PositionTracer should correctly trace a position + # through, across the following variables: + # + # - Old diff file type: created, changed, renamed, deleted, unchanged (5) + # - Old diff line type: added, removed, unchanged (3) + # - New diff file type: created, changed, renamed, deleted, unchanged (5) + # - New diff line type: added, removed, unchanged (3) + # - Old-to-new diff line change: kept, moved, undone (3) + # + # This adds up to 5 * 3 * 5 * 3 * 3 = 675 different potential scenarios, + # and 675 different tests to cover them all. In reality, it would be fewer, + # since one cannot have a removed line in a created file diff, for example, + # but for the sake of this diary entry, let's be pessimistic. + # + # Writing these tests is a manual and time consuming process, as every test + # requires the manual construction or finding of a combination of diffs that + # create the exact diff scenario we are looking for, and can take between + # 1 and 10 minutes, depending on the farfetchedness of the scenario and + # complexity of creating it. + # + # This means that writing tests to cover all of these scenarios would end up + # taking between 11 and 112 hours in total, which I do not believe is the + # best use of my time. + # + # A better course of action would be to think of scenarios that are likely + # to occur, but also potentially tricky to trace correctly, and only cover + # those, with a few more obvious scenarios thrown in to cover our bases. + # + # Unfortunately, I only came to the above realization once I was about + # 1/5th of the way through the process of writing ALL THE SPECS, having + # already wasted about 3 hours trying to be thorough. + # + # I did find 2 bugs while writing those though, so that's good. + # + # In any case, all of this means that the tests below will be extremely + # (excessively, unjustifiably) thorough for scenarios where "the file was + # created in the old diff" and then drop off to comparitively lackluster + # testing of other scenarios. + # + # I did still try to cover most of the obvious and potentially tricky + # scenarios, though. + + include RepoHelpers + + let(:project) { create(:project) } + let(:current_user) { project.owner } + let(:repository) { project.repository } + let(:file_name) { "test-file" } + let(:new_file_name) { "#{file_name}-new" } + let(:second_file_name) { "#{file_name}-2" } + let(:branch_name) { "position-tracer-test" } + + let(:old_diff_refs) { raise NotImplementedError } + let(:new_diff_refs) { raise NotImplementedError } + let(:old_position) { raise NotImplementedError } + + let(:position_tracer) { described_class.new(repository: project.repository, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) } + subject { position_tracer.trace(old_position) } + + def diff_refs(base_commit, head_commit) + Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) + end + + def position(attrs = {}) + attrs.reverse_merge!( + diff_refs: old_diff_refs + ) + Gitlab::Diff::Position.new(attrs) + end + + def expect_new_position(attrs, new_position = subject) + if attrs.nil? + expect(new_position).to be_nil + else + expect(new_position).not_to be_nil + + expect(new_position.diff_refs).to eq(new_diff_refs) + + attrs.each do |attr, value| + expect(new_position.send(attr)).to eq(value) + end + end + end + + def create_branch(new_name, branch_name) + CreateBranchService.new(project, current_user).execute(new_name, branch_name) + end + + def create_file(branch_name, file_name, content) + Files::CreateService.new( + project, + current_user, + source_branch: branch_name, + target_branch: branch_name, + commit_message: "Create file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def update_file(branch_name, file_name, content) + Files::UpdateService.new( + project, + current_user, + source_branch: branch_name, + target_branch: branch_name, + commit_message: "Update file", + file_path: file_name, + file_content: content + ).execute + project.commit(branch_name) + end + + def delete_file(branch_name, file_name) + Files::DeleteService.new( + project, + current_user, + source_branch: branch_name, + target_branch: branch_name, + commit_message: "Delete file", + file_path: file_name + ).execute + project.commit(branch_name) + end + + let(:initial_commit) do + create_branch(branch_name, "master")[:branch].name + project.commit(branch_name) + end + + describe "#trace" do + describe "diff scenarios" do + let(:create_file_commit) do + initial_commit + + create_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + B + C + CONTENT + ) + end + + let(:create_second_file_commit) do + create_file_commit + + create_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + E + CONTENT + ) + end + + let(:update_line_commit) do + create_second_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + CONTENT + ) + end + + let(:update_second_file_line_commit) do + update_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + EE + CONTENT + ) + end + + let(:move_line_commit) do + update_second_file_line_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + BB + A + C + CONTENT + ) + end + + let(:add_second_file_line_commit) do + move_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + EE + F + CONTENT + ) + end + + let(:move_second_file_line_commit) do + add_second_file_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + F + EE + CONTENT + ) + end + + let(:delete_line_commit) do + move_second_file_line_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + BB + A + CONTENT + ) + end + + let(:delete_second_file_line_commit) do + delete_line_commit + + update_file( + branch_name, + second_file_name, + <<-CONTENT.strip_heredoc + D + F + CONTENT + ) + end + + let(:delete_file_commit) do + delete_second_file_line_commit + + delete_file(branch_name, file_name) + end + + let(:rename_file_commit) do + delete_file_commit + + create_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + BB + A + CONTENT + ) + end + + let(:update_line_again_commit) do + rename_file_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + BB + AA + CONTENT + ) + end + + let(:move_line_again_commit) do + update_line_again_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + AA + BB + CONTENT + ) + end + + let(:delete_line_again_commit) do + move_line_again_commit + + update_file( + branch_name, + new_file_name, + <<-CONTENT.strip_heredoc + AA + CONTENT + ) + end + + context "when the file was created in the old diff" do + context "when the file is created in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + B + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 + BB + # 2 + A + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: 1 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 + A + # 2 + BB + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is changed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + new_line: 1 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 - C + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is renamed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 2 A + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: old_position.new_line, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 - A + # 2 + AA + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: old_position.new_line, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, move_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 + AA + # 1 2 BB + # 2 - A + + it "returns the new position" do + expect_new_position( + old_path: file_name, + new_path: new_file_name, + old_line: 1, + new_line: 2 + ) + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 1 BB + # 2 - A + # 2 + AA + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_line_again_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # file_name -> new_file_name + # 1 - BB + # 2 - A + # 1 + AA + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is deleted in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # + # new diff: + # 1 - BB + # 2 - A + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + # 3 - C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 - A + # 2 - BB + # 3 - C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 - BB + # 2 - A + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is unchanged in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 B + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 1) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + BB + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was changed between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 2) } + + # old diff: + # 1 + A + # 2 + B + # 3 + C + # + # new diff: + # 1 1 A + # 2 2 BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when that line was deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) } + let(:old_position) { position(new_path: file_name, new_line: 3) } + + # old diff: + # 1 + BB + # 2 + A + # 3 + C + # + # new diff: + # 1 1 BB + # 2 2 A + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + end + + context "when the file was changed in the old diff" do + context "when the file is created in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + A + # 2 + B + # 3 + C + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + + context "when the position pointed at a deleted line in the old diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns nil" do + expect(subject).to be_nil + end + end + + context "when the position pointed at an unchanged line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 1) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 1, new_line: 2) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + BB + # 2 + A + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2, new_line: 2) } + + # old diff: + # 1 1 BB + # 2 2 A + # 3 - C + # + # new diff: + # 1 + A + # 2 + BB + # 3 + C + + it "returns the new position" do + expect_new_position( + new_path: old_position.new_path, + old_line: nil, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 + A + # 2 + B + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + end + + context "when the file is changed in the new diff" do + context "when the position pointed at an added line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: nil, + new_line: old_position.new_line + ) + end + end + + context "when the file's content was changed between the old and the new diff" do + context "when that line was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 1 BB + # 2 2 A + # 3 - C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: 1, + new_line: 1 + ) + end + end + + context "when that line was moved between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(update_line_commit, move_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 - A + # 2 1 BB + # 2 + A + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: 2, + new_line: 1 + ) + end + end + + context "when that line was changed or deleted between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) } + + # old diff: + # 1 + BB + # 1 2 A + # 2 - B + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns nil" do + expect(subject).to be_nil + end + end + end + end + + context "when the position pointed at a deleted line in the old diff" do + context "when the file's content was unchanged between the old and the new diff" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) } + let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + + it "returns the new position" do + expect_new_position( + old_path: old_position.old_path, + new_path: old_position.new_path, + old_line: old_position.old_line, + new_line: nil + ) + end + end + end + end + end + end + end + + describe "typical use scenarios" do + let(:second_branch_name) { "#{branch_name}-2" } + + def expect_positions(old_attrs, new_attrs) + old_positions = old_attrs.map do |old_attrs| + position(old_attrs) + end + + new_positions = old_positions.map do |old_position| + position_tracer.trace(old_position) + end + + new_positions.zip(new_attrs).each do |new_position, new_attrs| + expect_new_position(new_attrs, new_position) + end + end + + let(:create_file_commit) do + initial_commit + + create_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + B + C + D + E + F + CONTENT + ) + end + + let(:second_create_file_commit) do + create_file_commit + + create_branch(second_branch_name, branch_name) + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + B + C + D + E + F + CONTENT + ) + end + + let(:update_file_commit) do + second_create_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + C + DD + E + F + G + CONTENT + ) + end + + let(:update_file_again_commit) do + update_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + describe "simple push of new commit" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C + { old_path: file_name, old_line: 4 }, # - D + { new_path: file_name, new_line: 3 }, # + DD + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F + { new_path: file_name, new_line: 6 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, + { old_path: file_name, old_line: 4, new_line: 4 }, + nil, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, + { old_path: file_name, old_line: 6 }, + { new_path: file_name, new_line: 7 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "force push to overwrite last commit" do + let(:second_create_file_commit) do + create_file_commit + + create_branch(second_branch_name, branch_name) + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + # + # new diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, # C + { old_path: file_name, old_line: 4 }, # - D + { new_path: file_name, new_line: 3 }, # + DD + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, # E + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, # F + { new_path: file_name, new_line: 6 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, + { old_path: file_name, old_line: 4, new_line: 4 }, + nil, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, + { old_path: file_name, old_line: 6 }, + { new_path: file_name, new_line: 7 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "force push to delete last commit" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 1 A + # 2 - B + # 3 2 C + # 4 - D + # 3 + DD + # 5 4 E + # 6 5 F + # 6 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + { old_path: file_name, old_line: 2 }, + nil, + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 }, + { old_path: file_name, old_line: 4 }, + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, + { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, + nil, + { new_path: file_name, new_line: 6 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "rebase on top of target branch" do + let(:second_update_file_commit) do + update_file_commit + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + C + DD + E + F + G + CONTENT + ) + end + + let(:update_file_again_commit) do + second_update_file_commit + + update_file( + branch_name, + file_name, + <<-CONTENT.strip_heredoc + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:overwrite_update_file_again_commit) do + update_file_again_commit + + update_file( + second_branch_name, + file_name, + <<-CONTENT.strip_heredoc + Z + Z + Z + A + BB + C + D + E + FF + G + CONTENT + ) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 + Z + # 2 + Z + # 3 + Z + # 1 4 A + # 2 - B + # 5 + BB + # 3 6 C + # 4 7 D + # 5 8 E + # 6 - F + # 9 + FF + # 0 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 5 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 9 }, # + FF + { new_path: file_name, new_line: 10 }, # + G + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "merge of target branch" do + let(:merge_commit) do + update_file_again_commit + + committer = repository.user_to_committer(current_user) + + options = { + message: "Merge branches", + author: committer, + committer: committer + } + + repository.merge(current_user, second_create_file_commit.sha, branch_name, options) + project.commit(branch_name) + end + + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 + Z + # 2 + Z + # 3 + Z + # 1 4 A + # 2 - B + # 5 + BB + # 3 6 C + # 4 7 D + # 5 8 E + # 6 - F + # 9 + FF + # 0 + G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 4 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 5 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 6 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 7 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 8 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 9 }, # + FF + { new_path: file_name, new_line: 10 }, # + G + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + + describe "changing target branch" do + let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) } + let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) } + + # old diff: + # 1 1 A + # 2 - B + # 2 + BB + # 3 3 C + # 4 4 D + # 5 5 E + # 6 - F + # 6 + FF + # 7 + G + # + # new diff: + # 1 1 A + # 2 + BB + # 2 3 C + # 3 - DD + # 4 + D + # 4 5 E + # 5 - F + # 6 + FF + # 7 G + + it "returns the new positions" do + old_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, # A + { old_path: file_name, old_line: 2 }, # - B + { new_path: file_name, new_line: 2 }, # + BB + { old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 }, # C + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 4 }, # D + { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, # E + { old_path: file_name, old_line: 6 }, # - F + { new_path: file_name, new_line: 6 }, # + FF + { new_path: file_name, new_line: 7 }, # + G + ] + + new_position_attrs = [ + { old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 }, + nil, + { new_path: file_name, new_line: 2 }, + { old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 }, + { new_path: file_name, new_line: 4 }, + { old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 }, + { old_path: file_name, old_line: 5 }, + { new_path: file_name, new_line: 6 }, + { new_path: file_name, new_line: 7 }, + ] + + expect_positions(old_position_attrs, new_position_attrs) + end + end + end +end diff --git a/spec/lib/gitlab/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/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb new file mode 100644 index 00000000000..a15aa173fbd --- /dev/null +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' +require 'fileutils' + +describe Gitlab::Git::Hook, lib: true do + describe "#trigger" do + let(:project) { create(:project) } + let(:user) { create(:user) } + + def create_hook(name) + FileUtils.mkdir_p(File.join(project.repository.path, 'hooks')) + File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f| + f.write('exit 0') + end + end + + def create_failing_hook(name) + FileUtils.mkdir_p(File.join(project.repository.path, 'hooks')) + File.open(File.join(project.repository.path, 'hooks', name), 'w', 0755) do |f| + f.write(<<-HOOK) + echo 'regular message from the hook' + echo 'error message from the hook' 1>&2 + exit 1 + HOOK + end + end + + ['pre-receive', 'post-receive', 'update'].each do |hook_name| + + context "when triggering a #{hook_name} hook" do + context "when the hook is successful" do + it "returns success with no errors" do + create_hook(hook_name) + hook = Gitlab::Git::Hook.new(hook_name, project.repository.path) + blank = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' + + status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + expect(status).to be true + expect(errors).to be_blank + end + end + + context "when the hook is unsuccessful" do + it "returns failure with errors" do + create_failing_hook(hook_name) + hook = Gitlab::Git::Hook.new(hook_name, project.repository.path) + blank = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' + + status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + expect(status).to be false + expect(errors).to eq("error message from the hook\n") + end + end + end + end + + context "when the hook doesn't exist" do + it "returns success with no errors" do + hook = Gitlab::Git::Hook.new('unknown_hook', project.repository.path) + blank = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' + + status, errors = hook.trigger(Gitlab::GlId.gl_id(user), blank, blank, ref) + expect(status).to be true + expect(errors).to be_nil + end + end + end +end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 9b3a0e3a75f..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 } @@ -65,7 +65,43 @@ describe Gitlab::GitAccess, lib: true do expect(access.can_push_to_branch?(@branch.name)).to be_falsey end end + end + + describe '#check with single protocols allowed' do + def disable_protocol(protocol) + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, protocol) + end + + context 'ssh disabled' do + before do + disable_protocol('ssh') + @acc = Gitlab::GitAccess.new(actor, project, 'ssh') + end + + it 'blocks ssh git push' do + expect(@acc.check('git-receive-pack').allowed?).to be_falsey + end + + it 'blocks ssh git pull' do + expect(@acc.check('git-upload-pack').allowed?).to be_falsey + end + end + + context 'http disabled' do + before do + disable_protocol('http') + @acc = Gitlab::GitAccess.new(actor, project, 'http') + end + it 'blocks http push' do + expect(@acc.check('git-receive-pack').allowed?).to be_falsey + end + + it 'blocks http git pull' do + expect(@acc.check('git-upload-pack').allowed?).to be_falsey + end + end end describe 'download_access_check' do diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 77ecfce6f17..4244b807d41 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do - let(:access) { Gitlab::GitAccessWiki.new(user, project) } + let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') } let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb index 3cb634ba010..fc9d5204148 100644 --- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb @@ -2,17 +2,18 @@ require 'spec_helper' describe Gitlab::GithubImport::BranchFormatter, lib: true do let(:project) { create(:project) } + let(:commit) { create(:commit, project: project) } let(:repo) { double } let(:raw) do { ref: 'feature', repo: repo, - sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b' + sha: commit.id } end describe '#exists?' do - it 'returns true when branch exists' do + it 'returns true when both branch, and commit exists' do branch = described_class.new(project, double(raw)) expect(branch.exists?).to eq true @@ -23,6 +24,12 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do expect(branch.exists?).to eq false end + + it 'returns false when commit does not exist' do + branch = described_class.new(project, double(raw.merge(sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'))) + + expect(branch.exists?).to eq false + end end describe '#name' do @@ -33,7 +40,7 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do end it 'returns formatted ref when branch does not exist' do - branch = described_class.new(project, double(raw.merge(ref: 'removed-branch'))) + branch = described_class.new(project, double(raw.merge(ref: 'removed-branch', sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'))) expect(branch.name).to eq 'removed-branch-2e5d3239' end @@ -51,18 +58,18 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do it 'returns raw sha' do branch = described_class.new(project, double(raw)) - expect(branch.sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b' + expect(branch.sha).to eq commit.id end end describe '#valid?' do - it 'returns true when repository exists' do + it 'returns true when raw repo is present' do branch = described_class.new(project, double(raw)) expect(branch.valid?).to eq true end - it 'returns false when repository does not exist' do + it 'returns false when raw repo is blank' do branch = described_class.new(project, double(raw.merge(repo: nil))) expect(branch.valid?).to eq false diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index 7c21cbe96d9..3b023a35446 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -20,6 +20,20 @@ describe Gitlab::GithubImport::Client, lib: true do expect { client.api }.not_to raise_error end + context 'when config is missing' do + before do + allow(Gitlab.config.omniauth).to receive(:providers).and_return([]) + end + + it 'is still possible to get an Octokit client' do + expect { client.api }.not_to raise_error + end + + it 'is not be possible to get an OAuth2 client' do + expect { client.client }.to raise_error(Projects::ImportService::Error) + end + end + context 'allow SSL verification to be configurable on API' do before do github_provider['verify_ssl'] = false 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/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index 120f59e6e71..79931ecd134 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -2,11 +2,13 @@ require 'spec_helper' describe Gitlab::GithubImport::PullRequestFormatter, lib: true do let(:project) { create(:project) } + let(:source_sha) { create(:commit, project: project).id } + let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } let(:repository) { double(id: 1, fork: false) } let(:source_repo) { repository } - let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') } + let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: source_sha) } let(:target_repo) { repository } - let(:target_branch) { double(ref: 'master', repo: target_repo, sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7') } + let(:target_branch) { double(ref: 'master', repo: target_repo, sha: target_sha) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } @@ -41,10 +43,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, source_branch: 'feature', - head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + target_branch_sha: target_sha, state: 'opened', milestone: nil, author_id: project.creator_id, @@ -68,10 +70,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, source_branch: 'feature', - head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + target_branch_sha: target_sha, state: 'closed', milestone: nil, author_id: project.creator_id, @@ -95,10 +97,10 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do description: "*Created by: octocat*\n\nPlease pull these awesome changes", source_project: project, source_branch: 'feature', - head_source_sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', + source_branch_sha: source_sha, target_project: project, target_branch: 'master', - base_target_sha: '8ffb3c15a5475e59ae909384297fede4badcb4c7', + target_branch_sha: target_sha, state: 'merged', milestone: nil, author_id: project.creator_id, diff --git a/spec/lib/gitlab/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/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb new file mode 100644 index 00000000000..f5c064303ad --- /dev/null +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Gitlab::Graphs::Commits, lib: true do + let!(:project) { create(:project, :public, :empty_repo) } + + let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) } + let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)} + + let!(:commit2) { create(:commit, git_commit: RepoHelpers.another_sample_commit, project: project, committed_date: Time.now) } + + describe '#commit_per_day' do + context 'when range is only commits from today' do + subject { described_class.new([commit2, commit1]).commit_per_day } + it { is_expected.to eq 2 } + end + end + + context 'when range is only commits from today' do + subject { described_class.new([commit2, commit1]) } + describe '#commit_per_day' do + it { expect(subject.commit_per_day).to eq 2 } + end + + describe '#duration' do + it { expect(subject.duration).to eq 0 } + end + end + + context 'with commits from yesterday and today' do + subject { described_class.new([commit2, commit1_yesterday]) } + describe '#commit_per_day' do + it { expect(subject.commit_per_day).to eq 1 } + end + + describe '#duration' do + it { expect(subject.duration).to eq 1 } + end + end +end diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 1620eb6c60a..364532e94e3 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::Highlight, lib: true do include RepoHelpers let(:project) { create(:project) } + let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } describe '.highlight_lines' do @@ -18,4 +19,30 @@ describe Gitlab::Highlight, lib: true do end end + describe 'custom highlighting from .gitattributes' do + let(:branch) { 'gitattributes' } + let(:blob) { repository.blob_at_branch(branch, path) } + + let(:highlighter) do + Gitlab::Highlight.new(blob.path, blob.data, repository: repository) + end + + before { project.change_head('gitattributes') } + + describe 'basic language selection' do + let(:path) { 'custom-highlighting/test.gitlab-custom' } + it 'highlights as ruby' do + expect(highlighter.lexer.tag).to eq 'ruby' + end + end + + describe 'cgi options' do + let(:path) { 'custom-highlighting/test.gitlab-cgi' } + + it 'highlights as json with erb' do + expect(highlighter.lexer.tag).to eq 'erb' + expect(highlighter.lexer.parent.tag).to eq 'json' + end + end + end end 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.json b/spec/lib/gitlab/import_export/project.json index 403bd582ef3..0b30e8c9b04 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -1,62 +1,63 @@ { - "name": "Gitlab Test", - "path": "gitlab-test", - "description": "Aut saepe in eos dolorem aliquam hic.", + "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", "issues_enabled": true, "merge_requests_enabled": true, "wiki_enabled": true, "snippets_enabled": false, - "visibility_level": 20, + "visibility_level": 10, "archived": false, "issues": [ { "id": 40, - "title": "Voluptatem modi rerum ipsum vero voluptas repudiandae veniam quibusdam.", + "title": "Voluptatem amet doloribus deleniti eos maxime repudiandae molestias.", "assignee_id": 1, - "author_id": 4, + "author_id": 22, "project_id": 5, - "created_at": "2016-03-22T15:13:28.411Z", - "updated_at": "2016-04-12T13:08:26.029Z", + "created_at": "2016-06-14T15:02:08.340Z", + "updated_at": "2016-06-14T15:02:47.967Z", "position": 0, "branch_name": null, - "description": "Aut minima non sit qui nulla rerum laborum.", - "milestone_id": 10, + "description": "Aliquam enim illo et possimus.", + "milestone_id": 18, "state": "opened", "iid": 10, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 1357, - "note": "test", + "id": 351, + "note": "Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-04-12T13:08:26.006Z", - "updated_at": "2016-04-12T13:08:26.006Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:47.770Z", + "updated_at": "2016-06-14T15:02:47.770Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, - "commit_id": "", + "commit_id": null, "noteable_id": 40, "system": false, "st_diff": null, "updated_by_id": null, "author": { - "name": "Administrator" - } + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 338, - "note": "Fugit in aliquid voluptas dolor.", + "id": 352, + "note": "Est reprehenderit quas aut aspernatur autem recusandae voluptatem.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:19:59.213Z", - "updated_at": "2016-03-22T15:19:59.213Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:47.795Z", + "updated_at": "2016-06-14T15:02:47.795Z", "project_id": 5, "attachment": { "url": null @@ -68,16 +69,19 @@ "st_diff": null, "updated_by_id": null, "author": { - "name": "Administrator" - } + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 337, - "note": "Occaecati consequatur facilis doloribus omnis hic placeat nihil.", + "id": 353, + "note": "Perspiciatis suscipit voluptates in eius nihil.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:19:59.186Z", - "updated_at": "2016-03-22T15:19:59.186Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:47.823Z", + "updated_at": "2016-06-14T15:02:47.823Z", "project_id": 5, "attachment": { "url": null @@ -89,16 +93,19 @@ "st_diff": null, "updated_by_id": null, "author": { - "name": "Alexie Trantow" - } + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 336, - "note": "Nostrum et et est repudiandae non dolores voluptatem.", + "id": 354, + "note": "Aut vel voluptas corrupti nisi provident laboriosam magnam aut.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:19:59.156Z", - "updated_at": "2016-03-22T15:19:59.156Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:47.850Z", + "updated_at": "2016-06-14T15:02:47.850Z", "project_id": 5, "attachment": { "url": null @@ -110,37 +117,19 @@ "st_diff": null, "updated_by_id": null, "author": { - "name": "Julius Moore" - } - }, - { - "id": 335, - "note": "Nihil et aut dolorum aut sit maxime.", - "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:19:59.130Z", - "updated_at": "2016-03-22T15:19:59.130Z", - "project_id": 5, - "attachment": { - "url": null + "name": "Ottis Schuster II" }, - "line_code": null, - "commit_id": null, - "noteable_id": 40, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "events": [ + + ] }, { - "id": 334, - "note": "Non blanditiis voluptatem sit earum accusantium distinctio voluptas officiis.", + "id": 355, + "note": "Officia dolore consequatur in saepe cum magni.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:19:59.101Z", - "updated_at": "2016-03-22T15:19:59.101Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:47.876Z", + "updated_at": "2016-06-14T15:02:47.876Z", "project_id": 5, "attachment": { "url": null @@ -151,17 +140,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 333, - "note": "Nesciunt non dolorem similique nam ipsa et.", + "id": 356, + "note": "Cum ipsum rem voluptas eaque et ea.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:19:59.075Z", - "updated_at": "2016-03-22T15:19:59.075Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:47.908Z", + "updated_at": "2016-06-14T15:02:47.908Z", "project_id": 5, "attachment": { "url": null @@ -172,17 +164,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 332, - "note": "Sed aut fugit et officiis dolor.", + "id": 357, + "note": "Recusandae excepturi asperiores suscipit autem nostrum.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:19:59.047Z", - "updated_at": "2016-03-22T15:19:59.047Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:47.937Z", + "updated_at": "2016-06-14T15:02:47.937Z", "project_id": 5, "attachment": { "url": null @@ -193,17 +188,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 331, - "note": "Officiis iste eum recusandae suscipit consequatur consequatur.", + "id": 358, + "note": "Et hic est id similique et non nesciunt voluptate.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:19:59.015Z", - "updated_at": "2016-03-22T15:19:59.015Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:47.965Z", + "updated_at": "2016-06-14T15:02:47.965Z", "project_id": 5, "attachment": { "url": null @@ -214,39 +212,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 39, - "title": "Sit ut adipisci sint temporibus velit quis.", - "assignee_id": 1, - "author_id": 12, + "title": "Delectus veniam ratione in eos culpa et natus molestiae earum aut.", + "assignee_id": 20, + "author_id": 22, "project_id": 5, - "created_at": "2016-03-22T15:13:28.278Z", - "updated_at": "2016-03-22T15:19:59.473Z", + "created_at": "2016-06-14T15:02:08.233Z", + "updated_at": "2016-06-14T15:02:48.194Z", "position": 0, "branch_name": null, - "description": "Ab sint nostrum aliquam laudantium magni recusandae qui.", - "milestone_id": 10, - "state": "closed", + "description": "Voluptate vel reprehenderit facilis omnis voluptas magnam tenetur.", + "milestone_id": 16, + "state": "opened", "iid": 9, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 346, - "note": "Natus rerum qui dolorem dolorum voluptas.", + "id": 359, + "note": "Quo eius velit quia et id quam.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:19:59.469Z", - "updated_at": "2016-03-22T15:19:59.469Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:48.009Z", + "updated_at": "2016-06-14T15:02:48.009Z", "project_id": 5, "attachment": { "url": null @@ -257,17 +258,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 345, - "note": "Voluptatibus et qui quis id sed necessitatibus quos.", + "id": 360, + "note": "Nulla commodi ratione cumque id autem.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:19:59.438Z", - "updated_at": "2016-03-22T15:19:59.438Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:48.032Z", + "updated_at": "2016-06-14T15:02:48.032Z", "project_id": 5, "attachment": { "url": null @@ -278,17 +282,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 344, - "note": "Aperiam possimus ipsam quibusdam in.", + "id": 361, + "note": "Illum non ea sed dolores corrupti.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:19:59.410Z", - "updated_at": "2016-03-22T15:19:59.410Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:48.056Z", + "updated_at": "2016-06-14T15:02:48.056Z", "project_id": 5, "attachment": { "url": null @@ -299,17 +306,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 343, - "note": "Ad vel hic molestiae tempora.", + "id": 362, + "note": "Facere dolores ipsum dolorum maiores omnis occaecati ab.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:19:59.379Z", - "updated_at": "2016-03-22T15:19:59.379Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:48.082Z", + "updated_at": "2016-06-14T15:02:48.082Z", "project_id": 5, "attachment": { "url": null @@ -320,17 +330,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 342, - "note": "Vel magnam sed quidem aut molestiae facilis alias.", + "id": 363, + "note": "Quod laudantium similique sint aut est ducimus.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:19:59.348Z", - "updated_at": "2016-03-22T15:19:59.348Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:48.113Z", + "updated_at": "2016-06-14T15:02:48.113Z", "project_id": 5, "attachment": { "url": null @@ -341,17 +354,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 341, - "note": "Veritatis dolorum aut qui quod.", + "id": 364, + "note": "Aut omnis eos esse incidunt vero reiciendis.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:19:59.319Z", - "updated_at": "2016-03-22T15:19:59.319Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:48.139Z", + "updated_at": "2016-06-14T15:02:48.139Z", "project_id": 5, "attachment": { "url": null @@ -362,17 +378,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 340, - "note": "Illum at cumque dolorum et quia.", + "id": 365, + "note": "Beatae dolore et doloremque asperiores sunt.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:19:59.289Z", - "updated_at": "2016-03-22T15:19:59.289Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:48.162Z", + "updated_at": "2016-06-14T15:02:48.162Z", "project_id": 5, "attachment": { "url": null @@ -383,17 +402,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 339, - "note": "Fugiat et error molestiae cumque quos aperiam.", + "id": 366, + "note": "Doloribus ipsam ex delectus rerum libero recusandae modi repellendus.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:19:59.255Z", - "updated_at": "2016-03-22T15:19:59.255Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:48.192Z", + "updated_at": "2016-06-14T15:02:48.192Z", "project_id": 5, "attachment": { "url": null @@ -404,39 +426,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 38, - "title": "Quod quo est quis vel natus nulla eos reiciendis.", - "assignee_id": 12, - "author_id": 3, + "title": "Quasi adipisci non cupiditate dolorem quo qui earum sed.", + "assignee_id": 1, + "author_id": 6, "project_id": 5, - "created_at": "2016-03-22T15:13:28.137Z", - "updated_at": "2016-03-22T15:19:59.712Z", + "created_at": "2016-06-14T15:02:08.154Z", + "updated_at": "2016-06-14T15:02:48.614Z", "position": 0, "branch_name": null, - "description": "Fugit dolor accusantium suscipit facere voluptate.", - "milestone_id": 10, - "state": "opened", + "description": "Ea recusandae neque autem tempora.", + "milestone_id": 16, + "state": "closed", "iid": 8, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 354, - "note": "Id commodi natus vel corrupti ea placeat cum nihil.", + "id": 367, + "note": "Accusantium fugiat et eaque quisquam esse corporis.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:19:59.708Z", - "updated_at": "2016-03-22T15:19:59.708Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:48.235Z", + "updated_at": "2016-06-14T15:02:48.235Z", "project_id": 5, "attachment": { "url": null @@ -447,17 +472,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 353, - "note": "Quia hic sed ratione eos voluptate dolor occaecati dolorem.", + "id": 368, + "note": "Ea labore eum nam qui laboriosam.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:19:59.680Z", - "updated_at": "2016-03-22T15:19:59.680Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:48.261Z", + "updated_at": "2016-06-14T15:02:48.261Z", "project_id": 5, "attachment": { "url": null @@ -468,17 +496,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 352, - "note": "Commodi sint voluptatem est aut.", + "id": 369, + "note": "Accusantium quis sed molestiae et.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:19:59.650Z", - "updated_at": "2016-03-22T15:19:59.650Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:48.294Z", + "updated_at": "2016-06-14T15:02:48.294Z", "project_id": 5, "attachment": { "url": null @@ -489,17 +520,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 351, - "note": "Et quibusdam voluptatibus dolores aut quam architecto optio.", + "id": 370, + "note": "Corporis numquam a voluptatem pariatur asperiores dolorem delectus autem.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:19:59.622Z", - "updated_at": "2016-03-22T15:19:59.622Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:48.523Z", + "updated_at": "2016-06-14T15:02:48.523Z", "project_id": 5, "attachment": { "url": null @@ -510,17 +544,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 350, - "note": "Fugit natus explicabo sed pariatur et quasi autem.", + "id": 371, + "note": "Ea accusantium maxime voluptas rerum.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:19:59.590Z", - "updated_at": "2016-03-22T15:19:59.590Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:48.546Z", + "updated_at": "2016-06-14T15:02:48.546Z", "project_id": 5, "attachment": { "url": null @@ -531,17 +568,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 349, - "note": "Corporis commodi eos quia optio sunt corrupti.", + "id": 372, + "note": "Pariatur iusto et et excepturi similique ipsam eum.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:19:59.562Z", - "updated_at": "2016-03-22T15:19:59.562Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:48.569Z", + "updated_at": "2016-06-14T15:02:48.569Z", "project_id": 5, "attachment": { "url": null @@ -552,17 +592,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 348, - "note": "Occaecati nostrum hic dolor tenetur aliquid maxime animi.", + "id": 373, + "note": "Aliquam et culpa officia iste eius.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:19:59.536Z", - "updated_at": "2016-03-22T15:19:59.536Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:48.591Z", + "updated_at": "2016-06-14T15:02:48.591Z", "project_id": 5, "attachment": { "url": null @@ -573,17 +616,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 347, - "note": "Inventore ullam sed repellendus laudantium itaque et quia.", + "id": 374, + "note": "Ab id velit id unde laborum.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:19:59.506Z", - "updated_at": "2016-03-22T15:19:59.506Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:48.613Z", + "updated_at": "2016-06-14T15:02:48.613Z", "project_id": 5, "attachment": { "url": null @@ -594,39 +640,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 37, - "title": "Animi suscipit quia ut hic asperiores perferendis nisi ut.", - "assignee_id": 22, - "author_id": 10, + "title": "Cupiditate quo aut ducimus minima molestiae vero numquam possimus.", + "assignee_id": 15, + "author_id": 20, "project_id": 5, - "created_at": "2016-03-22T15:13:27.994Z", - "updated_at": "2016-03-22T15:19:59.972Z", + "created_at": "2016-06-14T15:02:08.051Z", + "updated_at": "2016-06-14T15:02:48.854Z", "position": 0, "branch_name": null, - "description": "Non quibusdam in maxime earum eveniet itaque culpa.", - "milestone_id": 11, - "state": "closed", + "description": "Maiores architecto quos in dolorem.", + "milestone_id": 17, + "state": "opened", "iid": 7, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 362, - "note": "Quia qui quis molestiae in praesentium.", + "id": 375, + "note": "Quasi fugit qui sed eligendi aut quia.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:19:59.966Z", - "updated_at": "2016-03-22T15:19:59.966Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:48.647Z", + "updated_at": "2016-06-14T15:02:48.647Z", "project_id": 5, "attachment": { "url": null @@ -637,17 +686,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 361, - "note": "Maxime sed eius qui consequatur beatae.", + "id": 376, + "note": "Esse nesciunt voluptatem ex vero est consequatur.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:19:59.924Z", - "updated_at": "2016-03-22T15:19:59.924Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:48.674Z", + "updated_at": "2016-06-14T15:02:48.674Z", "project_id": 5, "attachment": { "url": null @@ -658,17 +710,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 360, - "note": "Voluptatum quasi corrupti eveniet sed ut quis quibusdam.", + "id": 377, + "note": "Similique qui quas non aut et velit sequi in.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:19:59.897Z", - "updated_at": "2016-03-22T15:19:59.897Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:48.696Z", + "updated_at": "2016-06-14T15:02:48.696Z", "project_id": 5, "attachment": { "url": null @@ -679,17 +734,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 359, - "note": "Molestias quia eius ipsum non.", + "id": 378, + "note": "Eveniet ut cupiditate repellendus numquam in esse eius.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:19:59.866Z", - "updated_at": "2016-03-22T15:19:59.866Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:48.720Z", + "updated_at": "2016-06-14T15:02:48.720Z", "project_id": 5, "attachment": { "url": null @@ -700,17 +758,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 358, - "note": "Aut non est accusantium aliquam.", + "id": 379, + "note": "Velit est dolorem adipisci rerum sed iure.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:19:59.834Z", - "updated_at": "2016-03-22T15:19:59.834Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:48.755Z", + "updated_at": "2016-06-14T15:02:48.755Z", "project_id": 5, "attachment": { "url": null @@ -721,17 +782,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 357, - "note": "Aspernatur voluptas id voluptas vel cum ipsam.", + "id": 380, + "note": "Voluptatem ullam ab ut illo ut quo.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:19:59.805Z", - "updated_at": "2016-03-22T15:19:59.805Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:48.793Z", + "updated_at": "2016-06-14T15:02:48.793Z", "project_id": 5, "attachment": { "url": null @@ -742,17 +806,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 356, - "note": "Harum dignissimos provident tempora sit numquam est qui.", + "id": 381, + "note": "Voluptatem impedit beatae quasi ipsa earum consectetur.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:19:59.773Z", - "updated_at": "2016-03-22T15:19:59.773Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:48.823Z", + "updated_at": "2016-06-14T15:02:48.823Z", "project_id": 5, "attachment": { "url": null @@ -763,17 +830,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 355, - "note": "Sint dignissimos molestiae recusandae delectus.", + "id": 382, + "note": "Nihil officiis eaque incidunt sunt voluptatum excepturi.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:19:59.746Z", - "updated_at": "2016-03-22T15:19:59.746Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:48.852Z", + "updated_at": "2016-06-14T15:02:48.852Z", "project_id": 5, "attachment": { "url": null @@ -784,39 +854,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 36, - "title": "Quia dolores commodi eligendi ut nemo totam.", - "assignee_id": 3, - "author_id": 4, + "title": "Necessitatibus dolor est enim quia rem suscipit quidem voluptas ullam.", + "assignee_id": 20, + "author_id": 16, "project_id": 5, - "created_at": "2016-03-22T15:13:27.814Z", - "updated_at": "2016-03-22T15:20:00.371Z", + "created_at": "2016-06-14T15:02:07.958Z", + "updated_at": "2016-06-14T15:02:49.044Z", "position": 0, "branch_name": null, - "description": "Molestiae veniam laudantium autem et natus.", - "milestone_id": 11, + "description": "Ut aut ut et tenetur velit aut id modi.", + "milestone_id": 16, "state": "opened", "iid": 6, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 370, - "note": "Occaecati temporibus tempore harum vero incidunt veniam iste.", + "id": 383, + "note": "Excepturi deleniti sunt rerum nesciunt vero fugiat possimus.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:20:00.365Z", - "updated_at": "2016-03-22T15:20:00.365Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:48.885Z", + "updated_at": "2016-06-14T15:02:48.885Z", "project_id": 5, "attachment": { "url": null @@ -827,17 +900,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 369, - "note": "Modi architecto officiis quia iste voluptas libero nihil quo.", + "id": 384, + "note": "Et est nemo sed nam sed.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:20:00.331Z", - "updated_at": "2016-03-22T15:20:00.331Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:48.910Z", + "updated_at": "2016-06-14T15:02:48.910Z", "project_id": 5, "attachment": { "url": null @@ -848,17 +924,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 368, - "note": "Eaque est tenetur ex est molestiae nobis.", + "id": 385, + "note": "Animi mollitia nulla facere amet aut quaerat.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:20:00.296Z", - "updated_at": "2016-03-22T15:20:00.296Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:48.934Z", + "updated_at": "2016-06-14T15:02:48.934Z", "project_id": 5, "attachment": { "url": null @@ -869,17 +948,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 367, - "note": "Odit enim ut a quo qui.", + "id": 386, + "note": "Excepturi id voluptas ut odio officiis omnis.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:20:00.261Z", - "updated_at": "2016-03-22T15:20:00.261Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:48.955Z", + "updated_at": "2016-06-14T15:02:48.955Z", "project_id": 5, "attachment": { "url": null @@ -890,17 +972,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 366, - "note": "Omnis unde cum officiis est.", + "id": 387, + "note": "Molestiae labore officiis magni et eligendi quasi maxime.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:20:00.223Z", - "updated_at": "2016-03-22T15:20:00.223Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:48.978Z", + "updated_at": "2016-06-14T15:02:48.978Z", "project_id": 5, "attachment": { "url": null @@ -911,17 +996,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 365, - "note": "Ab consequuntur aliquam illo voluptatum.", + "id": 388, + "note": "Officia tenetur praesentium rem nam non.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:20:00.178Z", - "updated_at": "2016-03-22T15:20:00.178Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:49.001Z", + "updated_at": "2016-06-14T15:02:49.001Z", "project_id": 5, "attachment": { "url": null @@ -932,17 +1020,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 364, - "note": "Molestiae dolorem est eos dolores aut.", + "id": 389, + "note": "Et et et molestiae reprehenderit.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:20:00.127Z", - "updated_at": "2016-03-22T15:20:00.127Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:49.022Z", + "updated_at": "2016-06-14T15:02:49.022Z", "project_id": 5, "attachment": { "url": null @@ -953,17 +1044,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 363, - "note": "Nemo velit nam quod veniam.", + "id": 390, + "note": "Aperiam in consequatur est sunt cum quia.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:20:00.083Z", - "updated_at": "2016-03-22T15:20:00.083Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:49.043Z", + "updated_at": "2016-06-14T15:02:49.043Z", "project_id": 5, "attachment": { "url": null @@ -974,39 +1068,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 35, - "title": "Rerum tenetur harum molestiae quam aut praesentium quaerat doloremque.", - "assignee_id": 4, - "author_id": 1, + "title": "Repellat praesentium deserunt maxime incidunt harum porro qui.", + "assignee_id": 6, + "author_id": 20, "project_id": 5, - "created_at": "2016-03-22T15:13:27.660Z", - "updated_at": "2016-03-22T15:20:00.665Z", + "created_at": "2016-06-14T15:02:07.832Z", + "updated_at": "2016-06-14T15:02:49.226Z", "position": 0, "branch_name": null, - "description": "Omnis et voluptatibus expedita qui et explicabo rem ut.", - "milestone_id": 11, - "state": "opened", + "description": "Dicta nisi nihil non ipsa velit.", + "milestone_id": 20, + "state": "closed", "iid": 5, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 378, - "note": "Molestiae atque exercitationem culpa harum nemo.", + "id": 391, + "note": "Qui magnam et assumenda quod id dicta necessitatibus.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:20:00.660Z", - "updated_at": "2016-03-22T15:20:00.660Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:49.075Z", + "updated_at": "2016-06-14T15:02:49.075Z", "project_id": 5, "attachment": { "url": null @@ -1017,17 +1114,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 377, - "note": "Porro sed nobis neque amet velit velit.", + "id": 392, + "note": "Consectetur deserunt possimus dolor est odio.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:20:00.625Z", - "updated_at": "2016-03-22T15:20:00.625Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:49.095Z", + "updated_at": "2016-06-14T15:02:49.095Z", "project_id": 5, "attachment": { "url": null @@ -1038,17 +1138,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 376, - "note": "Dicta officiis doloremque voluptatum qui omnis.", + "id": 393, + "note": "Labore nisi quo cumque voluptas consequatur aut qui.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:20:00.589Z", - "updated_at": "2016-03-22T15:20:00.589Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:49.117Z", + "updated_at": "2016-06-14T15:02:49.117Z", "project_id": 5, "attachment": { "url": null @@ -1059,17 +1162,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 375, - "note": "Incidunt rerum omnis cum laudantium aut impedit.", + "id": 394, + "note": "Et totam facilis voluptas et enim.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:20:00.553Z", - "updated_at": "2016-03-22T15:20:00.553Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:49.138Z", + "updated_at": "2016-06-14T15:02:49.138Z", "project_id": 5, "attachment": { "url": null @@ -1080,17 +1186,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 374, - "note": "Et suscipit omnis dolorum officia vero.", + "id": 395, + "note": "Ratione sint pariatur sed omnis eligendi quo libero exercitationem.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:20:00.517Z", - "updated_at": "2016-03-22T15:20:00.517Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:49.160Z", + "updated_at": "2016-06-14T15:02:49.160Z", "project_id": 5, "attachment": { "url": null @@ -1101,17 +1210,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 373, - "note": "Doloremque adipisci et cumque inventore beatae consectetur.", + "id": 396, + "note": "Iure hic autem id voluptatem.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:20:00.485Z", - "updated_at": "2016-03-22T15:20:00.485Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:49.182Z", + "updated_at": "2016-06-14T15:02:49.182Z", "project_id": 5, "attachment": { "url": null @@ -1122,17 +1234,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 372, - "note": "Dolores sapiente ea dolorum et quae adipisci id.", + "id": 397, + "note": "Excepturi eum laboriosam delectus repellendus odio nisi et voluptatem.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:20:00.455Z", - "updated_at": "2016-03-22T15:20:00.455Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:49.205Z", + "updated_at": "2016-06-14T15:02:49.205Z", "project_id": 5, "attachment": { "url": null @@ -1143,17 +1258,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 371, - "note": "Accusantium repellat tenetur natus dicta ullam saepe facere.", + "id": 398, + "note": "Ut quis ex soluta consequatur et blanditiis.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:20:00.420Z", - "updated_at": "2016-03-22T15:20:00.420Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:49.225Z", + "updated_at": "2016-06-14T15:02:49.225Z", "project_id": 5, "attachment": { "url": null @@ -1164,39 +1282,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 34, - "title": "Enim occaecati aut sed quia mollitia eligendi atque dolores voluptatem.", - "assignee_id": 24, + "title": "Ullam expedita deserunt libero consequatur quia dolor harum perferendis facere quidem.", + "assignee_id": 20, "author_id": 1, "project_id": 5, - "created_at": "2016-03-22T15:13:27.506Z", - "updated_at": "2016-03-22T15:20:00.961Z", + "created_at": "2016-06-14T15:02:07.717Z", + "updated_at": "2016-06-14T15:02:49.416Z", "position": 0, "branch_name": null, - "description": "Voluptatem totam magnam fugit assumenda consequatur illo qui.", - "milestone_id": 10, - "state": "opened", + "description": "Ut et explicabo vel voluptatem consequuntur ut sed.", + "milestone_id": 19, + "state": "closed", "iid": 4, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 379, - "note": "Praesentium odio quia fugit consequuntur repudiandae ducimus.", + "id": 399, + "note": "Dolor iste tempora tenetur non vitae maiores voluptatibus.", "noteable_type": "Issue", "author_id": 26, - "created_at": "2016-03-22T15:20:00.717Z", - "updated_at": "2016-03-22T15:20:00.717Z", + "created_at": "2016-06-14T15:02:49.256Z", + "updated_at": "2016-06-14T15:02:49.256Z", "project_id": 5, "attachment": { "url": null @@ -1207,17 +1328,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { + "author": { "name": "User 4" - } + }, + "events": [ + + ] }, { - "id": 380, - "note": "Dolores aut dolorem quia soluta incidunt commodi quia.", + "id": 400, + "note": "Aut sit quidem qui adipisci maxime excepturi iusto.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:20:00.754Z", - "updated_at": "2016-03-22T15:20:00.754Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:49.284Z", + "updated_at": "2016-06-14T15:02:49.284Z", "project_id": 5, "attachment": { "url": null @@ -1228,17 +1352,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 381, - "note": "Enim et velit iure ad.", + "id": 401, + "note": "Et a necessitatibus autem quidem animi sunt voluptatum rerum.", "noteable_type": "Issue", "author_id": 22, - "created_at": "2016-03-22T15:20:00.787Z", - "updated_at": "2016-03-22T15:20:00.787Z", + "created_at": "2016-06-14T15:02:49.305Z", + "updated_at": "2016-06-14T15:02:49.305Z", "project_id": 5, "attachment": { "url": null @@ -1249,17 +1376,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { + "author": { "name": "User 0" - } + }, + "events": [ + + ] }, { - "id": 382, - "note": "Impedit nobis quis laudantium ad assumenda.", + "id": 402, + "note": "Esse laboriosam quo voluptatem quis molestiae.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:20:00.822Z", - "updated_at": "2016-03-22T15:20:00.822Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:49.328Z", + "updated_at": "2016-06-14T15:02:49.328Z", "project_id": 5, "attachment": { "url": null @@ -1270,17 +1400,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 383, - "note": "Facere sed numquam quos quas.", + "id": 403, + "note": "Nemo magnam distinctio est ut voluptate ea.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:20:00.855Z", - "updated_at": "2016-03-22T15:20:00.855Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:49.350Z", + "updated_at": "2016-06-14T15:02:49.350Z", "project_id": 5, "attachment": { "url": null @@ -1291,17 +1424,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 384, - "note": "Ex voluptatem sit provident error.", + "id": 404, + "note": "Omnis sed rerum neque rerum quae quam nulla officiis.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:20:00.889Z", - "updated_at": "2016-03-22T15:20:00.889Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:49.372Z", + "updated_at": "2016-06-14T15:02:49.372Z", "project_id": 5, "attachment": { "url": null @@ -1312,17 +1448,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 385, - "note": "Soluta laboriosam recusandae est cupiditate.", + "id": 405, + "note": "Quo soluta dolorem vitae ad consequatur qui aut dicta.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:20:00.925Z", - "updated_at": "2016-03-22T15:20:00.925Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:49.394Z", + "updated_at": "2016-06-14T15:02:49.394Z", "project_id": 5, "attachment": { "url": null @@ -1333,17 +1472,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 386, - "note": "Similique dolorem rerum iusto animi perferendis aut inventore.", + "id": 406, + "note": "Magni minus est aut aut totam ut.", "noteable_type": "Issue", "author_id": 1, - "created_at": "2016-03-22T15:20:00.957Z", - "updated_at": "2016-03-22T15:20:00.957Z", + "created_at": "2016-06-14T15:02:49.414Z", + "updated_at": "2016-06-14T15:02:49.414Z", "project_id": 5, "attachment": { "url": null @@ -1354,39 +1496,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { + "author": { "name": "Administrator" - } + }, + "events": [ + + ] } ] }, { "id": 33, - "title": "Rem fugiat fugit occaecati quibusdam enim consectetur numquam.", - "assignee_id": 22, - "author_id": 22, + "title": "Numquam accusamus eos iste exercitationem magni non inventore.", + "assignee_id": 15, + "author_id": 26, "project_id": 5, - "created_at": "2016-03-22T15:13:27.364Z", - "updated_at": "2016-03-22T15:20:01.227Z", + "created_at": "2016-06-14T15:02:07.611Z", + "updated_at": "2016-06-14T15:02:49.661Z", "position": 0, "branch_name": null, - "description": "Provident nulla architecto neque beatae fuga alias repudiandae.", - "milestone_id": 10, + "description": "Non asperiores velit accusantium voluptate.", + "milestone_id": 18, "state": "closed", "iid": 3, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 394, - "note": "Suscipit numquam voluptatibus ipsam libero dolorum dolore totam.", + "id": 407, + "note": "Quod ea et possimus architecto.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:20:01.223Z", - "updated_at": "2016-03-22T15:20:01.223Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:49.450Z", + "updated_at": "2016-06-14T15:02:49.450Z", "project_id": 5, "attachment": { "url": null @@ -1397,17 +1542,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 393, - "note": "Et et sed sit sint.", + "id": 408, + "note": "Reiciendis est et unde perferendis dicta ut praesentium quasi.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:20:01.194Z", - "updated_at": "2016-03-22T15:20:01.194Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:49.503Z", + "updated_at": "2016-06-14T15:02:49.503Z", "project_id": 5, "attachment": { "url": null @@ -1418,17 +1566,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 392, - "note": "Corrupti perferendis voluptas et iure omnis officia.", + "id": 409, + "note": "Magni quia odio blanditiis pariatur voluptas.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:20:01.160Z", - "updated_at": "2016-03-22T15:20:01.160Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:49.527Z", + "updated_at": "2016-06-14T15:02:49.527Z", "project_id": 5, "attachment": { "url": null @@ -1439,17 +1590,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 391, - "note": "Autem quo fugit in iste nesciunt tempora.", + "id": 410, + "note": "Enim quam ut et et et.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:20:01.131Z", - "updated_at": "2016-03-22T15:20:01.131Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:49.551Z", + "updated_at": "2016-06-14T15:02:49.551Z", "project_id": 5, "attachment": { "url": null @@ -1460,17 +1614,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 390, - "note": "Magni porro ut soluta quis et eveniet maiores.", + "id": 411, + "note": "Fugit voluptatem ratione maxime expedita.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:20:01.101Z", - "updated_at": "2016-03-22T15:20:01.101Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:49.578Z", + "updated_at": "2016-06-14T15:02:49.578Z", "project_id": 5, "attachment": { "url": null @@ -1481,17 +1638,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 389, - "note": "Sed consequuntur debitis nisi veniam exercitationem recusandae a quisquam.", + "id": 412, + "note": "Voluptatem enim aut ipsa et et ducimus.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:20:01.070Z", - "updated_at": "2016-03-22T15:20:01.070Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:49.604Z", + "updated_at": "2016-06-14T15:02:49.604Z", "project_id": 5, "attachment": { "url": null @@ -1502,17 +1662,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 388, - "note": "Aut impedit qui consectetur dicta temporibus.", + "id": 413, + "note": "Quia repellat fugiat consectetur quidem.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:20:01.042Z", - "updated_at": "2016-03-22T15:20:01.042Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:49.631Z", + "updated_at": "2016-06-14T15:02:49.631Z", "project_id": 5, "attachment": { "url": null @@ -1523,17 +1686,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 387, - "note": "Officia repudiandae ut culpa ipsa reiciendis.", + "id": 414, + "note": "Corporis ipsum et ea necessitatibus quod assumenda repudiandae quam.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:20:01.005Z", - "updated_at": "2016-03-22T15:20:01.005Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:49.659Z", + "updated_at": "2016-06-14T15:02:49.659Z", "project_id": 5, "attachment": { "url": null @@ -1544,39 +1710,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 32, - "title": "Velit nihil est alias blanditiis eius earum autem hic.", + "title": "Necessitatibus magnam qui at velit consequatur perspiciatis.", "assignee_id": 22, - "author_id": 26, + "author_id": 15, "project_id": 5, - "created_at": "2016-03-22T15:13:27.225Z", - "updated_at": "2016-03-22T15:20:01.495Z", + "created_at": "2016-06-14T15:02:07.431Z", + "updated_at": "2016-06-14T15:02:49.884Z", "position": 0, "branch_name": null, - "description": "Id voluptas ut sint aut laborum nobis commodi.", - "milestone_id": 11, - "state": "opened", + "description": "Molestiae corporis magnam et fugit aliquid nulla quia.", + "milestone_id": 17, + "state": "closed", "iid": 2, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 402, - "note": "Magni ut eligendi sit sint recusandae voluptas tempore necessitatibus.", + "id": 415, + "note": "Nemo consequatur sed blanditiis qui id iure dolores.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:20:01.489Z", - "updated_at": "2016-03-22T15:20:01.489Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:49.694Z", + "updated_at": "2016-06-14T15:02:49.694Z", "project_id": 5, "attachment": { "url": null @@ -1587,17 +1756,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 401, - "note": "Est repellat commodi incidunt tempore earum optio unde sint.", + "id": 416, + "note": "Voluptas ab accusantium dicta in.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:20:01.455Z", - "updated_at": "2016-03-22T15:20:01.455Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:49.718Z", + "updated_at": "2016-06-14T15:02:49.718Z", "project_id": 5, "attachment": { "url": null @@ -1608,17 +1780,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 400, - "note": "Vero unde debitis tempore est laboriosam ut esse.", + "id": 417, + "note": "Esse odit qui a et eum ducimus.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:20:01.421Z", - "updated_at": "2016-03-22T15:20:01.421Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:49.741Z", + "updated_at": "2016-06-14T15:02:49.741Z", "project_id": 5, "attachment": { "url": null @@ -1629,17 +1804,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 399, - "note": "Omnis qui asperiores expedita harum voluptatem eius.", + "id": 418, + "note": "Sequi dolor doloribus ratione placeat repellendus.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:20:01.391Z", - "updated_at": "2016-03-22T15:20:01.391Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:49.767Z", + "updated_at": "2016-06-14T15:02:49.767Z", "project_id": 5, "attachment": { "url": null @@ -1650,17 +1828,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 398, - "note": "Dolorem doloribus delectus quo ratione esse veritatis.", + "id": 419, + "note": "Quae aspernatur rem est similique.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:20:01.358Z", - "updated_at": "2016-03-22T15:20:01.358Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:49.796Z", + "updated_at": "2016-06-14T15:02:49.796Z", "project_id": 5, "attachment": { "url": null @@ -1671,17 +1852,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 397, - "note": "Quia esse et odit id est omnis dolorum quia.", + "id": 420, + "note": "Voluptate omnis et id rerum non nesciunt laudantium assumenda.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:20:01.329Z", - "updated_at": "2016-03-22T15:20:01.329Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:49.825Z", + "updated_at": "2016-06-14T15:02:49.825Z", "project_id": 5, "attachment": { "url": null @@ -1692,17 +1876,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 396, - "note": "Exercitationem suscipit non rerum tempore sit.", + "id": 421, + "note": "Quia enim ab et eligendi.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:20:01.297Z", - "updated_at": "2016-03-22T15:20:01.297Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:49.853Z", + "updated_at": "2016-06-14T15:02:49.853Z", "project_id": 5, "attachment": { "url": null @@ -1713,17 +1900,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 395, - "note": "Nihil veniam magni sit officiis.", + "id": 422, + "note": "In fugiat rerum voluptas quas officia.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:20:01.268Z", - "updated_at": "2016-03-22T15:20:01.268Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:49.881Z", + "updated_at": "2016-06-14T15:02:49.881Z", "project_id": 5, "attachment": { "url": null @@ -1734,39 +1924,42 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] }, { "id": 31, - "title": "Asperiores recusandae praesentium voluptas pariatur provident qui exercitationem quis.", - "assignee_id": 26, - "author_id": 24, + "title": "Libero nam magnam incidunt eaque placeat error et.", + "assignee_id": 1, + "author_id": 16, "project_id": 5, - "created_at": "2016-03-22T15:13:26.889Z", - "updated_at": "2016-03-22T15:20:01.834Z", + "created_at": "2016-06-14T15:02:07.280Z", + "updated_at": "2016-06-14T15:02:50.134Z", "position": 0, "branch_name": null, - "description": "Ex voluptates qui excepturi cupiditate.", - "milestone_id": 11, + "description": "Quod ad architecto qui est sed quia.", + "milestone_id": 20, "state": "closed", "iid": 1, "updated_by_id": null, "confidential": false, "deleted_at": null, - "moved_to_id": null, "due_date": null, + "moved_to_id": null, "notes": [ { - "id": 410, - "note": "Sit itaque non nihil nisi qui voluptatem dolorem error.", + "id": 423, + "note": "A mollitia qui iste consequatur eaque iure omnis sunt.", "noteable_type": "Issue", - "author_id": 1, - "created_at": "2016-03-22T15:20:01.828Z", - "updated_at": "2016-03-22T15:20:01.828Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:49.933Z", + "updated_at": "2016-06-14T15:02:49.933Z", "project_id": 5, "attachment": { "url": null @@ -1777,17 +1970,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 409, - "note": "Omnis rem nihil molestiae enim laudantium doloremque.", + "id": 424, + "note": "Eveniet est et blanditiis sequi alias.", "noteable_type": "Issue", - "author_id": 3, - "created_at": "2016-03-22T15:20:01.783Z", - "updated_at": "2016-03-22T15:20:01.783Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:49.965Z", + "updated_at": "2016-06-14T15:02:49.965Z", "project_id": 5, "attachment": { "url": null @@ -1798,17 +1994,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 408, - "note": "Ullam harum sit et optio incidunt.", + "id": 425, + "note": "Commodi tempore voluptas doloremque est.", "noteable_type": "Issue", - "author_id": 4, - "created_at": "2016-03-22T15:20:01.746Z", - "updated_at": "2016-03-22T15:20:01.746Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:49.996Z", + "updated_at": "2016-06-14T15:02:49.996Z", "project_id": 5, "attachment": { "url": null @@ -1819,17 +2018,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 407, - "note": "Fugit distinctio ab quo ipsam.", + "id": 426, + "note": "Quo libero impedit odio debitis rerum aspernatur.", "noteable_type": "Issue", - "author_id": 10, - "created_at": "2016-03-22T15:20:01.716Z", - "updated_at": "2016-03-22T15:20:01.716Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:50.024Z", + "updated_at": "2016-06-14T15:02:50.024Z", "project_id": 5, "attachment": { "url": null @@ -1840,17 +2042,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 406, - "note": "Impedit iste possimus ad ea.", + "id": 427, + "note": "Dolorem voluptatem qui labore deserunt.", "noteable_type": "Issue", - "author_id": 12, - "created_at": "2016-03-22T15:20:01.676Z", - "updated_at": "2016-03-22T15:20:01.676Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:50.049Z", + "updated_at": "2016-06-14T15:02:50.049Z", "project_id": 5, "attachment": { "url": null @@ -1861,17 +2066,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 405, - "note": "Nemo recusandae dolore distinctio quam consequuntur ut et aut.", + "id": 428, + "note": "Est blanditiis laboriosam enim ipsam.", "noteable_type": "Issue", - "author_id": 22, - "created_at": "2016-03-22T15:20:01.641Z", - "updated_at": "2016-03-22T15:20:01.641Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:50.077Z", + "updated_at": "2016-06-14T15:02:50.077Z", "project_id": 5, "attachment": { "url": null @@ -1882,17 +2090,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 404, - "note": "Nisi repudiandae repellat nulla culpa quasi expedita quod velit.", + "id": 429, + "note": "Et in voluptatem animi dolorem eos.", "noteable_type": "Issue", - "author_id": 24, - "created_at": "2016-03-22T15:20:01.601Z", - "updated_at": "2016-03-22T15:20:01.601Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:50.107Z", + "updated_at": "2016-06-14T15:02:50.107Z", "project_id": 5, "attachment": { "url": null @@ -1903,17 +2114,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 403, - "note": "Quibusdam odio temporibus nemo voluptatibus accusamus.", + "id": 430, + "note": "Unde culpa voluptate qui sint quos.", "noteable_type": "Issue", - "author_id": 26, - "created_at": "2016-03-22T15:20:01.552Z", - "updated_at": "2016-03-22T15:20:01.552Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:50.132Z", + "updated_at": "2016-06-14T15:02:50.132Z", "project_id": 5, "attachment": { "url": null @@ -1924,470 +2138,338 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ] } ], "labels": [ - { - "id": 12, - "title": "test", - "color": "#428bca", - "project_id": 5, - "created_at": "2016-05-10T10:53:14.214Z", - "updated_at": "2016-05-10T10:53:14.214Z", - "template": false, - "description": "test label" - } + ], "milestones": [ { - "id": 11, - "title": "v2.0", + "id": 20, + "title": "v4.0", "project_id": 5, - "description": "Sapiente facilis architecto reprehenderit aut sed enim.", + "description": "Totam quam laborum id magnam natus eaque aspernatur.", "due_date": null, - "created_at": "2016-03-22T15:13:21.631Z", - "updated_at": "2016-03-22T15:13:21.631Z", - "state": "closed", - "iid": 2 + "created_at": "2016-06-14T15:02:04.590Z", + "updated_at": "2016-06-14T15:02:04.590Z", + "state": "active", + "iid": 5, + "events": [ + { + "id": 240, + "target_type": "Milestone", + "target_id": 20, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:04.593Z", + "updated_at": "2016-06-14T15:02:04.593Z", + "action": 1, + "author_id": 1 + }, + { + "id": 60, + "target_type": "Milestone", + "target_id": 20, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:04.593Z", + "updated_at": "2016-06-14T15:02:04.593Z", + "action": 1, + "author_id": 20 + } + ] }, { - "id": 10, - "title": "v1.0", + "id": 19, + "title": "v3.0", "project_id": 5, - "description": "Est sed eos minima veniam culpa aut non.", + "description": "Rerum at autem exercitationem ea voluptates harum quam placeat.", "due_date": null, - "created_at": "2016-03-22T15:13:21.622Z", - "updated_at": "2016-03-22T15:13:21.622Z", - "state": "closed", - "iid": 1 - } - ], - "snippets": [ - - ], - "releases": [ - - ], - "events": [ - { - "id": 301, - "target_type": "Note", - "target_id": 1357, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-04-12T13:08:30.886Z", - "updated_at": "2016-04-12T13:08:30.886Z", - "action": 6, - "author_id": 1 - }, - { - "id": 227, - "target_type": "MergeRequest", - "target_id": 85, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:19:44.957Z", - "updated_at": "2016-03-22T15:19:44.957Z", - "action": 1, - "author_id": 1 - }, - { - "id": 226, - "target_type": "MergeRequest", - "target_id": 84, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:19:44.600Z", - "updated_at": "2016-03-22T15:19:44.600Z", - "action": 1, - "author_id": 1 - }, - { - "id": 157, - "target_type": "MergeRequest", - "target_id": 15, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:45.936Z", - "updated_at": "2016-03-22T15:13:45.936Z", - "action": 1, - "author_id": 3 - }, - { - "id": 156, - "target_type": "MergeRequest", - "target_id": 14, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:45.500Z", - "updated_at": "2016-03-22T15:13:45.500Z", - "action": 1, - "author_id": 10 - }, - { - "id": 155, - "target_type": "MergeRequest", - "target_id": 13, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:45.242Z", - "updated_at": "2016-03-22T15:13:45.242Z", - "action": 1, - "author_id": 1 - }, - { - "id": 154, - "target_type": "MergeRequest", - "target_id": 12, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:44.940Z", - "updated_at": "2016-03-22T15:13:44.940Z", - "action": 1, - "author_id": 24 - }, - { - "id": 153, - "target_type": "MergeRequest", - "target_id": 11, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:44.568Z", - "updated_at": "2016-03-22T15:13:44.568Z", - "action": 1, - "author_id": 26 - }, - { - "id": 152, - "target_type": "MergeRequest", - "target_id": 10, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:44.225Z", - "updated_at": "2016-03-22T15:13:44.225Z", - "action": 1, - "author_id": 22 - }, - { - "id": 151, - "target_type": "MergeRequest", - "target_id": 9, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:43.868Z", - "updated_at": "2016-03-22T15:13:43.868Z", - "action": 1, - "author_id": 24 - }, - { - "id": 102, - "target_type": "Issue", - "target_id": 40, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:28.474Z", - "updated_at": "2016-03-22T15:13:28.474Z", - "action": 1, - "author_id": 4 - }, - { - "id": 101, - "target_type": "Issue", - "target_id": 39, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:28.328Z", - "updated_at": "2016-03-22T15:13:28.328Z", - "action": 1, - "author_id": 12 - }, - { - "id": 100, - "target_type": "Issue", - "target_id": 38, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:28.204Z", - "updated_at": "2016-03-22T15:13:28.204Z", - "action": 1, - "author_id": 3 - }, - { - "id": 99, - "target_type": "Issue", - "target_id": 37, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:28.055Z", - "updated_at": "2016-03-22T15:13:28.055Z", - "action": 1, - "author_id": 10 - }, - { - "id": 98, - "target_type": "Issue", - "target_id": 36, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:27.913Z", - "updated_at": "2016-03-22T15:13:27.913Z", - "action": 1, - "author_id": 4 - }, - { - "id": 97, - "target_type": "Issue", - "target_id": 35, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:27.731Z", - "updated_at": "2016-03-22T15:13:27.731Z", - "action": 1, - "author_id": 1 - }, - { - "id": 96, - "target_type": "Issue", - "target_id": 34, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:27.564Z", - "updated_at": "2016-03-22T15:13:27.564Z", - "action": 1, - "author_id": 1 - }, - { - "id": 95, - "target_type": "Issue", - "target_id": 33, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:27.429Z", - "updated_at": "2016-03-22T15:13:27.429Z", - "action": 1, - "author_id": 22 - }, - { - "id": 94, - "target_type": "Issue", - "target_id": 32, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:27.287Z", - "updated_at": "2016-03-22T15:13:27.287Z", - "action": 1, - "author_id": 26 - }, - { - "id": 93, - "target_type": "Issue", - "target_id": 31, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:26.997Z", - "updated_at": "2016-03-22T15:13:26.997Z", - "action": 1, - "author_id": 24 - }, - { - "id": 51, - "target_type": "Milestone", - "target_id": 11, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:21.634Z", - "updated_at": "2016-03-22T15:13:21.634Z", - "action": 1, - "author_id": 26 - }, - { - "id": 50, - "target_type": "Milestone", - "target_id": 10, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:21.625Z", - "updated_at": "2016-03-22T15:13:21.625Z", - "action": 1, - "author_id": 22 - }, - { - "id": 24, - "target_type": null, - "target_id": null, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:20.750Z", - "updated_at": "2016-03-22T15:13:20.750Z", - "action": 8, - "author_id": 12 - }, - { - "id": 23, - "target_type": null, - "target_id": null, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-03-22T15:13:20.711Z", - "updated_at": "2016-03-22T15:13:20.711Z", - "action": 8, - "author_id": 22 + "created_at": "2016-06-14T15:02:04.583Z", + "updated_at": "2016-06-14T15:02:04.583Z", + "state": "active", + "iid": 4, + "events": [ + { + "id": 241, + "target_type": "Milestone", + "target_id": 19, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:04.585Z", + "updated_at": "2016-06-14T15:02:04.585Z", + "action": 1, + "author_id": 1 + }, + { + "id": 59, + "target_type": "Milestone", + "target_id": 19, + "title": null, + "data": { + "object_kind": "push", + "before": "0000000000000000000000000000000000000000", + "after": "de990aa15829d0ab182ad5a55b4c527846c0d39c", + "ref": "refs/heads/removable-group-owner", + "checkout_sha": "de990aa15829d0ab182ad5a55b4c527846c0d39c", + "message": null, + "user_id": 273486, + "user_name": "James Lopez", + "user_email": "james@jameslopez.es", + "project_id": 562317, + "repository": { + "name": "GitLab Community Edition", + "url": "git@gitlab.com:james11/gitlab-ce.git", + "description": "Version Control on your Server. See http://gitlab.org/gitlab-ce/ and the README for more information", + "homepage": "https://gitlab.com/james11/gitlab-ce", + "git_http_url": "https://gitlab.com/james11/gitlab-ce.git", + "git_ssh_url": "git@gitlab.com:james11/gitlab-ce.git", + "visibility_level": 20 + }, + "commits": [ + { + "id": "de990aa15829d0ab182ad5a55b4c527846c0d39c", + "message": "fixed last group owner issue and added test\\n", + "timestamp": "2015-10-29T16:10:27+00:00", + "url": "https://gitlab.com/james11/gitlab-ce/commit/de990aa15829d0ab182ad5a55b4c527846c0d39c", + "author": { + "name": "James Lopez", + "email": "james.lopez@vodafone.com" + } + } + ], + "total_commits_count": 1 + }, + "project_id": 5, + "created_at": "2016-06-14T15:02:04.585Z", + "updated_at": "2016-06-14T15:02:04.585Z", + "action": 1, + "author_id": 25 + } + ] }, { - "id": 22, - "target_type": null, - "target_id": null, - "title": null, - "data": null, + "id": 18, + "title": "v2.0", "project_id": 5, - "created_at": "2016-03-22T15:13:20.667Z", - "updated_at": "2016-03-22T15:13:20.667Z", - "action": 8, - "author_id": 26 + "description": "Error dolorem rerum aut nulla.", + "due_date": null, + "created_at": "2016-06-14T15:02:04.576Z", + "updated_at": "2016-06-14T15:02:04.576Z", + "state": "active", + "iid": 3, + "events": [ + { + "id": 242, + "target_type": "Milestone", + "target_id": 18, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:04.579Z", + "updated_at": "2016-06-14T15:02:04.579Z", + "action": 1, + "author_id": 1 + }, + { + "id": 58, + "target_type": "Milestone", + "target_id": 18, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:04.579Z", + "updated_at": "2016-06-14T15:02:04.579Z", + "action": 1, + "author_id": 22 + } + ] }, { - "id": 21, - "target_type": null, - "target_id": null, - "title": null, - "data": null, + "id": 17, + "title": "v1.0", "project_id": 5, - "created_at": "2016-03-22T15:13:20.646Z", - "updated_at": "2016-03-22T15:13:20.646Z", - "action": 8, - "author_id": 1 + "description": "Molestiae perspiciatis voluptates doloremque commodi veniam consequatur.", + "due_date": null, + "created_at": "2016-06-14T15:02:04.569Z", + "updated_at": "2016-06-14T15:02:04.569Z", + "state": "active", + "iid": 2, + "events": [ + { + "id": 243, + "target_type": "Milestone", + "target_id": 17, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:04.570Z", + "updated_at": "2016-06-14T15:02:04.570Z", + "action": 1, + "author_id": 1 + }, + { + "id": 57, + "target_type": "Milestone", + "target_id": 17, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:04.570Z", + "updated_at": "2016-06-14T15:02:04.570Z", + "action": 1, + "author_id": 20 + } + ] }, { - "id": 5, - "target_type": null, - "target_id": null, - "title": null, - "data": null, + "id": 16, + "title": "v0.0", "project_id": 5, - "created_at": "2016-03-22T15:13:10.369Z", - "updated_at": "2016-03-22T15:13:10.369Z", - "action": 1, - "author_id": 1 + "description": "Velit numquam et sed sit.", + "due_date": null, + "created_at": "2016-06-14T15:02:04.561Z", + "updated_at": "2016-06-14T15:02:04.561Z", + "state": "closed", + "iid": 1, + "events": [ + { + "id": 244, + "target_type": "Milestone", + "target_id": 16, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:04.563Z", + "updated_at": "2016-06-14T15:02:04.563Z", + "action": 1, + "author_id": 26 + }, + { + "id": 56, + "target_type": "Milestone", + "target_id": 16, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:04.563Z", + "updated_at": "2016-06-14T15:02:04.563Z", + "action": 1, + "author_id": 26 + } + ] } ], + "snippets": [ + + ], + "releases": [ + + ], "project_members": [ { - "id": 35, + "id": 36, "access_level": 40, "source_id": 5, "source_type": "Project", - "user_id": 12, + "user_id": 16, "notification_level": 3, - "created_at": "2016-03-22T15:13:20.743Z", - "updated_at": "2016-03-22T15:13:20.743Z", + "created_at": "2016-06-14T15:02:03.834Z", + "updated_at": "2016-06-14T15:02:03.834Z", "created_by_id": null, "invite_email": null, "invite_token": null, "invite_accepted_at": null, + "requested_at": null, "user": { - "id": 12, - "email": "maureen.bogisich@russelkessler.com", - "username": "evans" + "id": 16, + "email": "maritza_schoen@block.ca", + "username": "bernard_willms" } }, { - "id": 34, - "access_level": 40, + "id": 35, + "access_level": 10, "source_id": 5, "source_type": "Project", - "user_id": 22, + "user_id": 6, "notification_level": 3, - "created_at": "2016-03-22T15:13:20.708Z", - "updated_at": "2016-03-22T15:13:20.708Z", + "created_at": "2016-06-14T15:02:03.811Z", + "updated_at": "2016-06-14T15:02:03.811Z", "created_by_id": null, "invite_email": null, "invite_token": null, "invite_accepted_at": null, + "requested_at": null, "user": { - "id": 22, - "email": "user0@example.com", - "username": "user0" + "id": 6, + "email": "shaina@koelpindenesik.com", + "username": "saul_will" } }, { - "id": 33, - "access_level": 40, + "id": 34, + "access_level": 20, "source_id": 5, "source_type": "Project", - "user_id": 26, + "user_id": 15, "notification_level": 3, - "created_at": "2016-03-22T15:13:20.664Z", - "updated_at": "2016-03-22T15:13:20.664Z", + "created_at": "2016-06-14T15:02:03.776Z", + "updated_at": "2016-06-14T15:02:03.776Z", "created_by_id": null, "invite_email": null, "invite_token": null, "invite_accepted_at": null, + "requested_at": null, "user": { - "id": 26, - "email": "user4@example.com", - "username": "user4" + "id": 15, + "email": "breanna_sanford@wolf.com", + "username": "emmet.schamberger" } }, { - "id": 32, + "id": 33, "access_level": 20, "source_id": 5, "source_type": "Project", - "user_id": 1, + "user_id": 26, "notification_level": 3, - "created_at": "2016-03-22T15:13:20.643Z", - "updated_at": "2016-03-22T15:13:20.643Z", + "created_at": "2016-06-14T15:02:03.742Z", + "updated_at": "2016-06-14T15:02:03.742Z", "created_by_id": null, "invite_email": null, "invite_token": null, "invite_accepted_at": null, + "requested_at": null, "user": { - "id": 1, - "email": "nospam@bluegod.net", - "username": "root" + "id": 26, + "email": "user4@example.com", + "username": "user4" } } ], "merge_requests": [ { - "id": 85, + "id": 27, "target_branch": "feature", "source_branch": "feature_conflict", "source_project_id": 5, "author_id": 1, "assignee_id": null, "title": "Cannot be automatically merged", - "created_at": "2016-03-22T15:19:44.807Z", - "updated_at": "2016-03-22T15:20:09.557Z", + "created_at": "2016-06-14T15:02:36.568Z", + "updated_at": "2016-06-14T15:02:56.815Z", "milestone_id": null, "state": "opened", "merge_status": "unchecked", @@ -2399,7 +2481,7 @@ "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -2407,176 +2489,200 @@ "deleted_at": null, "notes": [ { - "id": 638, - "note": "Ab velit ducimus totam sunt ut.", + "id": 671, + "note": "Sit voluptatibus eveniet architecto quidem.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:09.553Z", - "updated_at": "2016-03-22T15:20:09.553Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:56.632Z", + "updated_at": "2016-06-14T15:02:56.632Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 637, - "note": "Ipsum aliquam est in unde similique nihil illo ea.", + "id": 672, + "note": "Odio maxime ratione voluptatibus sed.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:09.528Z", - "updated_at": "2016-03-22T15:20:09.528Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:56.656Z", + "updated_at": "2016-06-14T15:02:56.656Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 636, - "note": "Soluta inventore adipisci et consequatur expedita aliquid earum modi.", + "id": 673, + "note": "Et deserunt et omnis nihil excepturi accusantium.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:09.496Z", - "updated_at": "2016-03-22T15:20:09.496Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:56.679Z", + "updated_at": "2016-06-14T15:02:56.679Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 635, - "note": "Corporis incidunt tempore est deleniti.", + "id": 674, + "note": "Saepe asperiores exercitationem non dignissimos laborum reiciendis et ipsum.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:09.469Z", - "updated_at": "2016-03-22T15:20:09.469Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:56.700Z", + "updated_at": "2016-06-14T15:02:56.700Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 634, - "note": "Hic dolores voluptatibus qui necessitatibus.", + "id": 675, + "note": "Numquam est at dolor quo et sed eligendi similique.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:09.440Z", - "updated_at": "2016-03-22T15:20:09.440Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:56.720Z", + "updated_at": "2016-06-14T15:02:56.720Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 633, - "note": "Rerum architecto placeat doloribus voluptates consequuntur quo.", + "id": 676, + "note": "Et perferendis aliquam sunt nisi labore delectus.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:09.412Z", - "updated_at": "2016-03-22T15:20:09.412Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:56.742Z", + "updated_at": "2016-06-14T15:02:56.742Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 632, - "note": "Vel earum aut ut occaecati aut ut rerum qui.", + "id": 677, + "note": "Aut ex rerum et in.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:09.389Z", - "updated_at": "2016-03-22T15:20:09.389Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:56.791Z", + "updated_at": "2016-06-14T15:02:56.791Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 631, - "note": "Est voluptatibus dolores animi numquam.", + "id": 678, + "note": "Dolor laborum earum ut exercitationem.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:09.361Z", - "updated_at": "2016-03-22T15:20:09.361Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:56.814Z", + "updated_at": "2016-06-14T15:02:56.814Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 85, + "noteable_id": 27, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { - "id": 85, + "id": 27, "state": "collected", "st_commits": [ { @@ -2759,23 +2865,49 @@ "too_large": false } ], - "merge_request_id": 85, - "created_at": "2016-03-22T15:19:44.810Z", - "updated_at": "2016-03-22T15:19:44.901Z", + "merge_request_id": 27, + "created_at": "2016-06-14T15:02:36.572Z", + "updated_at": "2016-06-14T15:02:36.658Z", "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f", "real_size": "9" - } + }, + "events": [ + { + "id": 221, + "target_type": "MergeRequest", + "target_id": 27, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:36.703Z", + "updated_at": "2016-06-14T15:02:36.703Z", + "action": 1, + "author_id": 1 + }, + { + "id": 187, + "target_type": "MergeRequest", + "target_id": 27, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:36.703Z", + "updated_at": "2016-06-14T15:02:36.703Z", + "action": 1, + "author_id": 1 + } + ] }, { - "id": 84, + "id": 26, "target_branch": "master", "source_branch": "feature", "source_project_id": 5, "author_id": 1, "assignee_id": null, "title": "Can be automatically merged", - "created_at": "2016-03-22T15:19:44.482Z", - "updated_at": "2016-03-22T15:20:09.773Z", + "created_at": "2016-06-14T15:02:36.418Z", + "updated_at": "2016-06-14T15:02:57.013Z", "milestone_id": null, "state": "opened", "merge_status": "unchecked", @@ -2787,7 +2919,7 @@ "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -2795,176 +2927,200 @@ "deleted_at": null, "notes": [ { - "id": 646, - "note": "Temporibus debitis veniam est ut sit nihil.", + "id": 679, + "note": "Qui rerum totam nisi est.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:09.770Z", - "updated_at": "2016-03-22T15:20:09.770Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:56.848Z", + "updated_at": "2016-06-14T15:02:56.848Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 645, - "note": "Ut assumenda dignissimos quibusdam veritatis sequi dolores.", + "id": 680, + "note": "Pariatur magni corrupti consequatur debitis minima error beatae voluptatem.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:09.740Z", - "updated_at": "2016-03-22T15:20:09.740Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:56.871Z", + "updated_at": "2016-06-14T15:02:56.871Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 644, - "note": "Velit quae quidem cupiditate laudantium nihil ut eveniet.", + "id": 681, + "note": "Qui quis ut modi eos rerum ratione.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:09.717Z", - "updated_at": "2016-03-22T15:20:09.717Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:56.895Z", + "updated_at": "2016-06-14T15:02:56.895Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 643, - "note": "Repellat quas porro sed mollitia laborum ut fugiat.", + "id": 682, + "note": "Illum quidem expedita mollitia fugit.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:09.690Z", - "updated_at": "2016-03-22T15:20:09.690Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:56.918Z", + "updated_at": "2016-06-14T15:02:56.918Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 642, - "note": "Qui aut debitis perspiciatis et voluptatem.", + "id": 683, + "note": "Consectetur voluptate sit sint possimus veritatis quod.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:09.665Z", - "updated_at": "2016-03-22T15:20:09.665Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:56.942Z", + "updated_at": "2016-06-14T15:02:56.942Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 641, - "note": "Quia id quia velit et.", + "id": 684, + "note": "Natus libero quibusdam rem assumenda deleniti accusamus sed earum.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:09.639Z", - "updated_at": "2016-03-22T15:20:09.639Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:56.966Z", + "updated_at": "2016-06-14T15:02:56.966Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 640, - "note": "Corporis commodi doloremque itaque non animi.", + "id": 685, + "note": "Tenetur autem nihil rerum odit.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:09.617Z", - "updated_at": "2016-03-22T15:20:09.617Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:56.989Z", + "updated_at": "2016-06-14T15:02:56.989Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 639, - "note": "Possimus dignissimos voluptatum in tenetur.", + "id": 686, + "note": "Quia maiores et odio sed.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:09.589Z", - "updated_at": "2016-03-22T15:20:09.589Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:57.012Z", + "updated_at": "2016-06-14T15:02:57.012Z", "project_id": 5, "attachment": { "url": null }, "line_code": null, "commit_id": null, - "noteable_id": 84, + "noteable_id": 26, "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { - "id": 84, + "id": 26, "state": "collected", "st_commits": [ { @@ -2994,35 +3150,61 @@ "too_large": false } ], - "merge_request_id": 84, - "created_at": "2016-03-22T15:19:44.485Z", - "updated_at": "2016-03-22T15:19:44.577Z", + "merge_request_id": 26, + "created_at": "2016-06-14T15:02:36.421Z", + "updated_at": "2016-06-14T15:02:36.474Z", "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f", "real_size": "1" - } + }, + "events": [ + { + "id": 222, + "target_type": "MergeRequest", + "target_id": 26, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:36.496Z", + "updated_at": "2016-06-14T15:02:36.496Z", + "action": 1, + "author_id": 1 + }, + { + "id": 186, + "target_type": "MergeRequest", + "target_id": 26, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:36.496Z", + "updated_at": "2016-06-14T15:02:36.496Z", + "action": 1, + "author_id": 1 + } + ] }, { "id": 15, - "target_branch": "markdown", - "source_branch": "master", + "target_branch": "test-7", + "source_branch": "test-1", "source_project_id": 5, - "author_id": 3, - "assignee_id": 3, - "title": "Nulla explicabo iure voluptas perferendis autem autem unde nemo totam optio.", - "created_at": "2016-03-22T15:13:45.689Z", - "updated_at": "2016-03-22T15:20:30.476Z", - "milestone_id": 10, + "author_id": 22, + "assignee_id": 16, + "title": "Qui accusantium et inventore facilis doloribus occaecati officiis.", + "created_at": "2016-06-14T15:02:25.168Z", + "updated_at": "2016-06-14T15:02:59.521Z", + "milestone_id": 17, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, "iid": 7, - "description": "Doloribus dignissimos impedit qui et provident exercitationem. Veniam quis magni qui fugiat. Et quia voluptate et vel consequatur pariatur ea est.", + "description": "Et commodi deserunt aspernatur vero rerum. Ut non dolorum alias in odit est libero. Voluptatibus eos in et vitae repudiandae facilis ex mollitia.", "position": 0, "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -3030,12 +3212,12 @@ "deleted_at": null, "notes": [ { - "id": 1231, - "note": "Rerum optio quibusdam provident possimus quis cum.", + "id": 777, + "note": "Pariatur voluptas placeat aspernatur culpa suscipit soluta.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:30.472Z", - "updated_at": "2016-03-22T15:20:30.472Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:59.348Z", + "updated_at": "2016-06-14T15:02:59.348Z", "project_id": 5, "attachment": { "url": null @@ -3046,17 +3228,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 1230, - "note": "Quasi odit repudiandae ut officiis ut nihil illo.", + "id": 778, + "note": "Alias et iure mollitia suscipit molestiae voluptatum nostrum asperiores.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:30.444Z", - "updated_at": "2016-03-22T15:20:30.444Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:59.372Z", + "updated_at": "2016-06-14T15:02:59.372Z", "project_id": 5, "attachment": { "url": null @@ -3067,17 +3252,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 1229, - "note": "Aut vero dolores facere sed.", + "id": 779, + "note": "Laudantium qui eum qui sunt.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:30.412Z", - "updated_at": "2016-03-22T15:20:30.412Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:59.395Z", + "updated_at": "2016-06-14T15:02:59.395Z", "project_id": 5, "attachment": { "url": null @@ -3088,17 +3276,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 1228, - "note": "Autem voluptatem et blanditiis accusantium deserunt et et.", + "id": 780, + "note": "Quas rem est iusto ut delectus fugiat recusandae mollitia.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:30.383Z", - "updated_at": "2016-03-22T15:20:30.383Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:59.418Z", + "updated_at": "2016-06-14T15:02:59.418Z", "project_id": 5, "attachment": { "url": null @@ -3109,17 +3300,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 1227, - "note": "Voluptatem aliquam voluptatem molestiae est.", + "id": 781, + "note": "Repellendus ab et qui nesciunt.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:30.352Z", - "updated_at": "2016-03-22T15:20:30.352Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:59.444Z", + "updated_at": "2016-06-14T15:02:59.444Z", "project_id": 5, "attachment": { "url": null @@ -3130,17 +3324,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 1226, - "note": "Ea aut cupiditate est consequatur animi error qui et.", + "id": 782, + "note": "Non possimus voluptatum odio qui ut.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:30.319Z", - "updated_at": "2016-03-22T15:20:30.319Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:59.469Z", + "updated_at": "2016-06-14T15:02:59.469Z", "project_id": 5, "attachment": { "url": null @@ -3151,17 +3348,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 1225, - "note": "Voluptates est voluptas et nostrum modi beatae inventore et.", + "id": 783, + "note": "Dolores repellendus eum ducimus quam ab dolorem quia.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:30.289Z", - "updated_at": "2016-03-22T15:20:30.289Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:59.494Z", + "updated_at": "2016-06-14T15:02:59.494Z", "project_id": 5, "attachment": { "url": null @@ -3172,17 +3372,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 1224, - "note": "Quia est rerum adipisci cupiditate.", + "id": 784, + "note": "Facilis dolorem aut corrupti id ratione occaecati.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:30.260Z", - "updated_at": "2016-03-22T15:20:30.260Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:59.520Z", + "updated_at": "2016-06-14T15:02:59.520Z", "project_id": 5, "attachment": { "url": null @@ -3193,9 +3396,12 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { @@ -3203,6 +3409,304 @@ "state": "collected", "st_commits": [ { + "id": "94b8d581c48d894b86661718582fecbc5e3ed2eb", + "message": "fixes #10\n", + "parent_ids": [ + "be93687618e4b132087f430a4d8fc3a609c9b77c" + ], + "authored_date": "2016-01-19T13:22:56.000+01:00", + "author_name": "James Lopez", + "author_email": "james@jameslopez.es", + "committed_date": "2016-01-19T13:22:56.000+01:00", + "committer_name": "James Lopez", + "committer_email": "james@jameslopez.es" + } + ], + "st_diffs": [ + { + "diff": "--- /dev/null\n+++ b/test\n", + "new_path": "test", + "old_path": "test", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + } + ], + "merge_request_id": 15, + "created_at": "2016-06-14T15:02:25.171Z", + "updated_at": "2016-06-14T15:02:25.230Z", + "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c", + "real_size": "1" + }, + "events": [ + { + "id": 223, + "target_type": "MergeRequest", + "target_id": 15, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:25.262Z", + "updated_at": "2016-06-14T15:02:25.262Z", + "action": 1, + "author_id": 1 + }, + { + "id": 175, + "target_type": "MergeRequest", + "target_id": 15, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:25.262Z", + "updated_at": "2016-06-14T15:02:25.262Z", + "action": 1, + "author_id": 22 + } + ] + }, + { + "id": 14, + "target_branch": "fix", + "source_branch": "test-3", + "source_project_id": 5, + "author_id": 20, + "assignee_id": 20, + "title": "In voluptas aut sequi voluptatem ullam vel corporis illum consequatur.", + "created_at": "2016-06-14T15:02:24.760Z", + "updated_at": "2016-06-14T15:02:59.749Z", + "milestone_id": 20, + "state": "opened", + "merge_status": "unchecked", + "target_project_id": 5, + "iid": 6, + "description": "Dicta magnam non voluptates nam dignissimos nostrum deserunt. Dolorum et suscipit iure quae doloremque. Necessitatibus saepe aut labore sed.", + "position": 0, + "locked_at": null, + "updated_by_id": null, + "merge_error": null, + "merge_params": { + "force_remove_source_branch": null + }, + "merge_when_build_succeeds": false, + "merge_user_id": null, + "merge_commit_sha": null, + "deleted_at": null, + "notes": [ + { + "id": 785, + "note": "Atque cupiditate necessitatibus deserunt minus natus odit.", + "noteable_type": "MergeRequest", + "author_id": 26, + "created_at": "2016-06-14T15:02:59.559Z", + "updated_at": "2016-06-14T15:02:59.559Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "User 4" + }, + "events": [ + + ] + }, + { + "id": 786, + "note": "Non dolorem provident mollitia nesciunt optio ex eveniet.", + "noteable_type": "MergeRequest", + "author_id": 25, + "created_at": "2016-06-14T15:02:59.587Z", + "updated_at": "2016-06-14T15:02:59.587Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "User 3" + }, + "events": [ + + ] + }, + { + "id": 787, + "note": "Similique officia nemo quasi commodi accusantium quae qui.", + "noteable_type": "MergeRequest", + "author_id": 22, + "created_at": "2016-06-14T15:02:59.621Z", + "updated_at": "2016-06-14T15:02:59.621Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "User 0" + }, + "events": [ + + ] + }, + { + "id": 788, + "note": "Et est et alias ad dolor qui.", + "noteable_type": "MergeRequest", + "author_id": 20, + "created_at": "2016-06-14T15:02:59.650Z", + "updated_at": "2016-06-14T15:02:59.650Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] + }, + { + "id": 789, + "note": "Numquam temporibus ratione voluptatibus aliquid.", + "noteable_type": "MergeRequest", + "author_id": 16, + "created_at": "2016-06-14T15:02:59.675Z", + "updated_at": "2016-06-14T15:02:59.675Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] + }, + { + "id": 790, + "note": "Ut ex aliquam consectetur perferendis est hic aut quia.", + "noteable_type": "MergeRequest", + "author_id": 15, + "created_at": "2016-06-14T15:02:59.703Z", + "updated_at": "2016-06-14T15:02:59.703Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] + }, + { + "id": 791, + "note": "Esse eos quam quaerat aut ut asperiores officiis.", + "noteable_type": "MergeRequest", + "author_id": 6, + "created_at": "2016-06-14T15:02:59.726Z", + "updated_at": "2016-06-14T15:02:59.726Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] + }, + { + "id": 792, + "note": "Sint facilis accusantium iure blanditiis.", + "noteable_type": "MergeRequest", + "author_id": 1, + "created_at": "2016-06-14T15:02:59.748Z", + "updated_at": "2016-06-14T15:02:59.748Z", + "project_id": 5, + "attachment": { + "url": null + }, + "line_code": null, + "commit_id": null, + "noteable_id": 14, + "system": false, + "st_diff": null, + "updated_by_id": null, + "author": { + "name": "Administrator" + }, + "events": [ + + ] + } + ], + "merge_request_diff": { + "id": 14, + "state": "collected", + "st_commits": [ + { + "id": "ddd4ff416a931589c695eb4f5b23f844426f6928", + "message": "fixes #10\n", + "parent_ids": [ + "be93687618e4b132087f430a4d8fc3a609c9b77c" + ], + "authored_date": "2016-01-19T14:14:43.000+01:00", + "author_name": "James Lopez", + "author_email": "james@jameslopez.es", + "committed_date": "2016-01-19T14:14:43.000+01:00", + "committer_name": "James Lopez", + "committer_email": "james@jameslopez.es" + }, + { "id": "be93687618e4b132087f430a4d8fc3a609c9b77c", "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6", "parent_ids": [ @@ -3388,10 +3892,108 @@ "committed_date": "2015-01-10T21:28:18.000+01:00", "committer_name": "marmis85", "committer_email": "marmis85@gmail.com" + }, + { + "id": "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "570e7b2abdd848b95f2f578043fc23bd6f6fd24d" + ], + "authored_date": "2014-02-27T10:01:38.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T10:01:38.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", + "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" + ], + "authored_date": "2014-02-27T09:57:31.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:57:31.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "d14d6c0abdd253381df51a723d58691b2ee1ab08" + ], + "authored_date": "2014-02-27T09:54:21.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:54:21.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "d14d6c0abdd253381df51a723d58691b2ee1ab08", + "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "c1acaa58bbcbc3eafe538cb8274ba387047b69f8" + ], + "authored_date": "2014-02-27T09:49:50.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:49:50.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8", + "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "ae73cb07c9eeaf35924a10f713b364d32b2dd34f" + ], + "authored_date": "2014-02-27T09:48:32.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:48:32.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" } ], "st_diffs": [ { + "diff": "Binary files a/.DS_Store and /dev/null differ\n", + "new_path": ".DS_Store", + "old_path": ".DS_Store", + "a_mode": "100644", + "b_mode": "0", + "new_file": false, + "renamed_file": false, + "deleted_file": true, + "too_large": false + }, + { + "diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n", + "new_path": ".gitignore", + "old_path": ".gitignore", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n", + "new_path": ".gitmodules", + "old_path": ".gitmodules", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n", "new_path": "CHANGELOG", "old_path": "CHANGELOG", @@ -3414,6 +4016,17 @@ "too_large": false }, { + "diff": "Binary files a/files/.DS_Store and /dev/null differ\n", + "new_path": "files/.DS_Store", + "old_path": "files/.DS_Store", + "a_mode": "100644", + "b_mode": "0", + "new_file": false, + "renamed_file": false, + "deleted_file": true, + "too_large": false + }, + { "diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", "new_path": "files/images/wm.svg", "old_path": "files/images/wm.svg", @@ -3436,6 +4049,28 @@ "too_large": false }, { + "diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n", + "new_path": "files/ruby/popen.rb", + "old_path": "files/ruby/popen.rb", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n end\n \n def archive_formats_regex\n- #|zip|tar| tar.gz | tar.bz2 |\n- /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+ /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n end\n \n def git_reference_regex\n # Valid git ref regex, see:\n # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n %r{\n (?!\n (?# doesn't begins with)\n", + "new_path": "files/ruby/regex.rb", + "old_path": "files/ruby/regex.rb", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { "diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n", "new_path": "files/whitespace", "old_path": "files/whitespace", @@ -3456,231 +4091,29 @@ "renamed_file": false, "deleted_file": false, "too_large": false - } - ], - "merge_request_id": 15, - "created_at": "2016-03-22T15:13:45.692Z", - "updated_at": "2016-03-22T15:13:45.808Z", - "base_commit_sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e", - "real_size": "6" - } - }, - { - "id": 14, - "target_branch": "test-1", - "source_branch": "test-10", - "source_project_id": 5, - "author_id": 10, - "assignee_id": 1, - "title": "Tempore aliquid sit amet odit qui cum iusto voluptatibus asperiores.", - "created_at": "2016-03-22T15:13:45.442Z", - "updated_at": "2016-03-22T15:20:30.735Z", - "milestone_id": 10, - "state": "opened", - "merge_status": "unchecked", - "target_project_id": 5, - "iid": 6, - "description": "Quis et et autem saepe ut. Eum corporis tempore cum dolore. Molestiae pariatur voluptatem officia perferendis aut veniam.", - "position": 0, - "locked_at": null, - "updated_by_id": null, - "merge_error": null, - "merge_params": { - - }, - "merge_when_build_succeeds": false, - "merge_user_id": null, - "merge_commit_sha": null, - "deleted_at": null, - "notes": [ - { - "id": 1239, - "note": "Aspernatur suscipit veritatis aliquid rerum.", - "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:30.731Z", - "updated_at": "2016-03-22T15:20:30.731Z", - "project_id": 5, - "attachment": { - "url": null - }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "Administrator" - } - }, - { - "id": 1238, - "note": "Rerum deleniti omnis porro commodi.", - "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:30.701Z", - "updated_at": "2016-03-22T15:20:30.701Z", - "project_id": 5, - "attachment": { - "url": null - }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } - }, - { - "id": 1237, - "note": "Eaque ut magnam rerum non dolores esse.", - "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:30.667Z", - "updated_at": "2016-03-22T15:20:30.667Z", - "project_id": 5, - "attachment": { - "url": null - }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "Julius Moore" - } - }, - { - "id": 1236, - "note": "Fugit et aut similique illum ut natus maiores et.", - "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:30.637Z", - "updated_at": "2016-03-22T15:20:30.637Z", - "project_id": 5, - "attachment": { - "url": null - }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } - }, - { - "id": 1235, - "note": "Qui qui temporibus eos aliquam.", - "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:30.608Z", - "updated_at": "2016-03-22T15:20:30.608Z", - "project_id": 5, - "attachment": { - "url": null - }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } - }, - { - "id": 1234, - "note": "Voluptates hic dolorum aut inventore.", - "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:30.575Z", - "updated_at": "2016-03-22T15:20:30.575Z", - "project_id": 5, - "attachment": { - "url": null }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "User 0" - } - }, - { - "id": 1233, - "note": "Dolorum iure at dolor dolores numquam iusto.", - "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:30.548Z", - "updated_at": "2016-03-22T15:20:30.548Z", - "project_id": 5, - "attachment": { - "url": null - }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "User 2" - } - }, - { - "id": 1232, - "note": "Nihil est eum aspernatur amet minus et corporis consectetur.", - "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:30.517Z", - "updated_at": "2016-03-22T15:20:30.517Z", - "project_id": 5, - "attachment": { - "url": null + { + "diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n", + "new_path": "gitlab-grack", + "old_path": "gitlab-grack", + "a_mode": "0", + "b_mode": "160000", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false }, - "line_code": null, - "commit_id": null, - "noteable_id": 14, - "system": false, - "st_diff": null, - "updated_by_id": null, - "author": { - "name": "User 4" - } - } - ], - "merge_request_diff": { - "id": 14, - "state": "collected", - "st_commits": [ { - "id": "bce96ecee98f51fa5d91021e6c42859a35a701ad", - "message": "fixes #10\n", - "parent_ids": [ - "be93687618e4b132087f430a4d8fc3a609c9b77c" - ], - "authored_date": "2016-01-19T15:40:05.000+01:00", - "author_name": "Test Lopez", - "author_email": "Test@Testlopez.es", - "committed_date": "2016-01-19T15:40:05.000+01:00", - "committer_name": "Test Lopez", - "committer_email": "Test@Testlopez.es" - } - ], - "st_diffs": [ + "diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n", + "new_path": "gitlab-shell", + "old_path": "gitlab-shell", + "a_mode": "0", + "b_mode": "160000", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, { "diff": "--- /dev/null\n+++ b/test\n", "new_path": "test", @@ -3694,34 +4127,60 @@ } ], "merge_request_id": 14, - "created_at": "2016-03-22T15:13:45.444Z", - "updated_at": "2016-03-22T15:13:45.486Z", - "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c", - "real_size": "1" - } + "created_at": "2016-06-14T15:02:24.770Z", + "updated_at": "2016-06-14T15:02:25.007Z", + "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f", + "real_size": "15" + }, + "events": [ + { + "id": 224, + "target_type": "MergeRequest", + "target_id": 14, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:25.113Z", + "updated_at": "2016-06-14T15:02:25.113Z", + "action": 1, + "author_id": 1 + }, + { + "id": 174, + "target_type": "MergeRequest", + "target_id": 14, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:25.113Z", + "updated_at": "2016-06-14T15:02:25.113Z", + "action": 1, + "author_id": 20 + } + ] }, { "id": 13, - "target_branch": "test-11", - "source_branch": "test-12", + "target_branch": "improve/awesome", + "source_branch": "test-8", "source_project_id": 5, - "author_id": 1, - "assignee_id": 26, - "title": "Voluptas minus sunt voluptatum quis quia ut velit distinctio itaque.", - "created_at": "2016-03-22T15:13:45.164Z", - "updated_at": "2016-03-22T15:20:30.994Z", - "milestone_id": 11, + "author_id": 16, + "assignee_id": 25, + "title": "Voluptates consequatur eius nemo amet libero animi illum delectus tempore.", + "created_at": "2016-06-14T15:02:24.415Z", + "updated_at": "2016-06-14T15:02:59.958Z", + "milestone_id": 17, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, "iid": 5, - "description": "Ea ut modi consectetur et minus beatae. Et sunt ducimus praesentium libero officia maiores voluptas cumque. Rerum in aut corporis et ullam omnis.", + "description": "Est eaque quasi qui qui. Similique voluptatem impedit iusto ratione reprehenderit. Itaque est illum ut nulla aut.", "position": 0, "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -3729,12 +4188,12 @@ "deleted_at": null, "notes": [ { - "id": 1247, - "note": "Non error magnam placeat cupiditate eum.", + "id": 793, + "note": "In illum maxime aperiam nulla est aspernatur.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:30.989Z", - "updated_at": "2016-03-22T15:20:30.989Z", + "author_id": 26, + "created_at": "2016-06-14T15:02:59.782Z", + "updated_at": "2016-06-14T15:02:59.782Z", "project_id": 5, "attachment": { "url": null @@ -3745,17 +4204,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 1246, - "note": "Eos optio et architecto eligendi ea est nihil.", + "id": 794, + "note": "Enim quia perferendis cum distinctio tenetur optio voluptas veniam.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:30.957Z", - "updated_at": "2016-03-22T15:20:30.957Z", + "author_id": 25, + "created_at": "2016-06-14T15:02:59.807Z", + "updated_at": "2016-06-14T15:02:59.807Z", "project_id": 5, "attachment": { "url": null @@ -3766,17 +4228,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 1245, - "note": "Reprehenderit in atque dolor et repudiandae a est.", + "id": 795, + "note": "Dolor ad quia quis pariatur ducimus.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:30.928Z", - "updated_at": "2016-03-22T15:20:30.928Z", + "author_id": 22, + "created_at": "2016-06-14T15:02:59.831Z", + "updated_at": "2016-06-14T15:02:59.831Z", "project_id": 5, "attachment": { "url": null @@ -3787,17 +4252,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 1244, - "note": "Numquam fugit doloremque iure odio et.", + "id": 796, + "note": "Et a odio voluptate aut.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:30.902Z", - "updated_at": "2016-03-22T15:20:30.902Z", + "author_id": 20, + "created_at": "2016-06-14T15:02:59.854Z", + "updated_at": "2016-06-14T15:02:59.854Z", "project_id": 5, "attachment": { "url": null @@ -3808,17 +4276,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 1243, - "note": "Doloribus laboriosam id harum voluptatum vitae ut quam.", + "id": 797, + "note": "Quis nihil temporibus voluptatum modi minima a ut.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:30.863Z", - "updated_at": "2016-03-22T15:20:30.863Z", + "author_id": 16, + "created_at": "2016-06-14T15:02:59.879Z", + "updated_at": "2016-06-14T15:02:59.879Z", "project_id": 5, "attachment": { "url": null @@ -3829,17 +4300,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 1242, - "note": "Harum et ut ipsum dolore ea.", + "id": 798, + "note": "Ut alias consequatur in nostrum.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:30.832Z", - "updated_at": "2016-03-22T15:20:30.832Z", + "author_id": 15, + "created_at": "2016-06-14T15:02:59.904Z", + "updated_at": "2016-06-14T15:02:59.904Z", "project_id": 5, "attachment": { "url": null @@ -3850,17 +4324,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 1241, - "note": "Corporis sed soluta ut est modi natus ab.", + "id": 799, + "note": "Voluptatibus aperiam assumenda et neque sint libero.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:30.802Z", - "updated_at": "2016-03-22T15:20:30.802Z", + "author_id": 6, + "created_at": "2016-06-14T15:02:59.926Z", + "updated_at": "2016-06-14T15:02:59.926Z", "project_id": 5, "attachment": { "url": null @@ -3871,17 +4348,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 1240, - "note": "Corrupti totam tenetur officiis ratione dolores est qui vel.", + "id": 800, + "note": "Veritatis voluptatem dolor dolores magni quo ut ipsa fuga.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:30.771Z", - "updated_at": "2016-03-22T15:20:30.771Z", + "author_id": 1, + "created_at": "2016-06-14T15:02:59.956Z", + "updated_at": "2016-06-14T15:02:59.956Z", "project_id": 5, "attachment": { "url": null @@ -3892,9 +4372,12 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { @@ -3902,21 +4385,274 @@ "state": "collected", "st_commits": [ { - "id": "a4e5dfebf42e34596526acb8611bc7ed80e4eb3f", + "id": "0bfedc29d30280c7e8564e19f654584b459e5868", "message": "fixes #10\n", "parent_ids": [ "be93687618e4b132087f430a4d8fc3a609c9b77c" ], - "authored_date": "2016-01-19T15:44:02.000+01:00", - "author_name": "Test Lopez", - "author_email": "Test@Testlopez.es", - "committed_date": "2016-01-19T15:44:02.000+01:00", - "committer_name": "Test Lopez", - "committer_email": "Test@Testlopez.es" + "authored_date": "2016-01-19T15:25:23.000+01:00", + "author_name": "James Lopez", + "author_email": "james@jameslopez.es", + "committed_date": "2016-01-19T15:25:23.000+01:00", + "committer_name": "James Lopez", + "committer_email": "james@jameslopez.es" + }, + { + "id": "be93687618e4b132087f430a4d8fc3a609c9b77c", + "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6", + "parent_ids": [ + "5f923865dde3436854e9ceb9cdb7815618d4e849", + "048721d90c449b244b7b4c53a9186b04330174ec" + ], + "authored_date": "2015-12-07T12:52:12.000+01:00", + "author_name": "Marin Jankovski", + "author_email": "marin@gitlab.com", + "committed_date": "2015-12-07T12:52:12.000+01:00", + "committer_name": "Marin Jankovski", + "committer_email": "marin@gitlab.com" + }, + { + "id": "048721d90c449b244b7b4c53a9186b04330174ec", + "message": "LFS object pointer.\n", + "parent_ids": [ + "5f923865dde3436854e9ceb9cdb7815618d4e849" + ], + "authored_date": "2015-12-07T11:54:28.000+01:00", + "author_name": "Marin Jankovski", + "author_email": "maxlazio@gmail.com", + "committed_date": "2015-12-07T11:54:28.000+01:00", + "committer_name": "Marin Jankovski", + "committer_email": "maxlazio@gmail.com" + }, + { + "id": "5f923865dde3436854e9ceb9cdb7815618d4e849", + "message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n", + "parent_ids": [ + "d2d430676773caa88cdaf7c55944073b2fd5561a" + ], + "authored_date": "2015-11-13T16:27:12.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T16:27:12.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "d2d430676773caa88cdaf7c55944073b2fd5561a", + "message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5", + "parent_ids": [ + "59e29889be61e6e0e5e223bfa9ac2721d31605b8", + "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73" + ], + "authored_date": "2015-11-13T08:50:17.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T08:50:17.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73", + "message": "Add GitLab SVG\n", + "parent_ids": [ + "59e29889be61e6e0e5e223bfa9ac2721d31605b8" + ], + "authored_date": "2015-11-13T08:39:43.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T08:39:43.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "59e29889be61e6e0e5e223bfa9ac2721d31605b8", + "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4", + "parent_ids": [ + "19e2e9b4ef76b422ce1154af39a91323ccc57434", + "66eceea0db202bb39c4e445e8ca28689645366c5" + ], + "authored_date": "2015-11-13T07:21:40.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T07:21:40.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "66eceea0db202bb39c4e445e8ca28689645366c5", + "message": "add spaces in whitespace file\n", + "parent_ids": [ + "08f22f255f082689c0d7d39d19205085311542bc" + ], + "authored_date": "2015-11-13T06:01:27.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T06:01:27.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "08f22f255f082689c0d7d39d19205085311542bc", + "message": "remove emtpy file.(beacase git ignore empty file)\nadd whitespace test file.\n", + "parent_ids": [ + "c642fe9b8b9f28f9225d7ea953fe14e74748d53b" + ], + "authored_date": "2015-11-13T06:00:16.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T06:00:16.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "19e2e9b4ef76b422ce1154af39a91323ccc57434", + "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3", + "parent_ids": [ + "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd", + "c642fe9b8b9f28f9225d7ea953fe14e74748d53b" + ], + "authored_date": "2015-11-13T05:23:14.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T05:23:14.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b", + "message": "add whitespace in empty\n", + "parent_ids": [ + "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0" + ], + "authored_date": "2015-11-13T05:08:45.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T05:08:45.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0", + "message": "add empty file\n", + "parent_ids": [ + "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd" + ], + "authored_date": "2015-11-13T05:08:04.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T05:08:04.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd", + "message": "Add ISO-8859 test file\n", + "parent_ids": [ + "e56497bb5f03a90a51293fc6d516788730953899" + ], + "authored_date": "2015-08-25T17:53:12.000+02:00", + "author_name": "Stan Hu", + "author_email": "stanhu@packetzoom.com", + "committed_date": "2015-08-25T17:53:12.000+02:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@packetzoom.com" + }, + { + "id": "e56497bb5f03a90a51293fc6d516788730953899", + "message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/275#note_732774)\n\nSee merge request !2\n", + "parent_ids": [ + "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + "4cd80ccab63c82b4bad16faa5193fbd2aa06df40" + ], + "authored_date": "2015-01-10T22:23:29.000+01:00", + "author_name": "Sytse Sijbrandij", + "author_email": "sytse@gitlab.com", + "committed_date": "2015-01-10T22:23:29.000+01:00", + "committer_name": "Sytse Sijbrandij", + "committer_email": "sytse@gitlab.com" + }, + { + "id": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40", + "message": "add directory structure for tree_helper spec\n", + "parent_ids": [ + "5937ac0a7beb003549fc5fd26fc247adbce4a52e" + ], + "authored_date": "2015-01-10T21:28:18.000+01:00", + "author_name": "marmis85", + "author_email": "marmis85@gmail.com", + "committed_date": "2015-01-10T21:28:18.000+01:00", + "committer_name": "marmis85", + "committer_email": "marmis85@gmail.com" } ], "st_diffs": [ { + "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n", + "new_path": "CHANGELOG", + "old_path": "CHANGELOG", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/encoding/iso8859.txt\n@@ -0,0 +1 @@\n+Äü\n", + "new_path": "encoding/iso8859.txt", + "old_path": "encoding/iso8859.txt", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", + "new_path": "files/images/wm.svg", + "old_path": "files/images/wm.svg", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/lfs/lfs_object.iso\n@@ -0,0 +1,4 @@\n+version https://git-lfs.github.com/spec/v1\n+oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897\n+size 1575078\n+\n", + "new_path": "files/lfs/lfs_object.iso", + "old_path": "files/lfs/lfs_object.iso", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n", + "new_path": "files/whitespace", + "old_path": "files/whitespace", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/foo/bar/.gitkeep\n", + "new_path": "foo/bar/.gitkeep", + "old_path": "foo/bar/.gitkeep", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { "diff": "--- /dev/null\n+++ b/test\n", "new_path": "test", "old_path": "test", @@ -3929,34 +4665,60 @@ } ], "merge_request_id": 13, - "created_at": "2016-03-22T15:13:45.167Z", - "updated_at": "2016-03-22T15:13:45.216Z", - "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c", - "real_size": "1" - } + "created_at": "2016-06-14T15:02:24.420Z", + "updated_at": "2016-06-14T15:02:24.561Z", + "base_commit_sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + "real_size": "7" + }, + "events": [ + { + "id": 225, + "target_type": "MergeRequest", + "target_id": 13, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:24.636Z", + "updated_at": "2016-06-14T15:02:24.636Z", + "action": 1, + "author_id": 16 + }, + { + "id": 173, + "target_type": "MergeRequest", + "target_id": 13, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:24.636Z", + "updated_at": "2016-06-14T15:02:24.636Z", + "action": 1, + "author_id": 16 + } + ] }, { "id": 12, - "target_branch": "test-15", + "target_branch": "flatten-dirs", "source_branch": "test-2", "source_project_id": 5, - "author_id": 24, - "assignee_id": 12, - "title": "In assumenda nam quaerat qui eos sit facilis enim quia quis.", - "created_at": "2016-03-22T15:13:44.837Z", - "updated_at": "2016-03-22T15:20:31.258Z", - "milestone_id": 10, + "author_id": 1, + "assignee_id": 22, + "title": "In a rerum harum nihil accusamus aut quia nobis non.", + "created_at": "2016-06-14T15:02:24.000Z", + "updated_at": "2016-06-14T15:03:00.225Z", + "milestone_id": 19, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, "iid": 4, - "description": "Soluta excepturi quis iste vero delectus rerum. Consequatur possimus aliquam necessitatibus deleniti rerum est impedit. Eius rem et consequatur assumenda est commodi.", + "description": "Nam magnam odit velit rerum. Sapiente dolore sunt saepe debitis. Culpa maiores ut ad dolores dolorem et.", "position": 0, "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -3964,12 +4726,12 @@ "deleted_at": null, "notes": [ { - "id": 1255, - "note": "Quibusdam rem aut similique ipsum recusandae ut accusamus.", + "id": 801, + "note": "Nihil dicta molestias expedita atque.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:31.253Z", - "updated_at": "2016-03-22T15:20:31.253Z", + "author_id": 26, + "created_at": "2016-06-14T15:03:00.001Z", + "updated_at": "2016-06-14T15:03:00.001Z", "project_id": 5, "attachment": { "url": null @@ -3980,17 +4742,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 1254, - "note": "Cumque sed omnis ipsa et magnam dolorem et.", + "id": 802, + "note": "Illum culpa voluptas enim accusantium deserunt.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:31.224Z", - "updated_at": "2016-03-22T15:20:31.224Z", + "author_id": 25, + "created_at": "2016-06-14T15:03:00.034Z", + "updated_at": "2016-06-14T15:03:00.034Z", "project_id": 5, "attachment": { "url": null @@ -4001,17 +4766,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 1253, - "note": "Molestiae beatae id consequatur nam minus quia.", + "id": 803, + "note": "Dicta esse aliquam laboriosam unde alias.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:31.195Z", - "updated_at": "2016-03-22T15:20:31.195Z", + "author_id": 22, + "created_at": "2016-06-14T15:03:00.065Z", + "updated_at": "2016-06-14T15:03:00.065Z", "project_id": 5, "attachment": { "url": null @@ -4022,17 +4790,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 1252, - "note": "Voluptatem dolorem dignissimos itaque tempora quas ut.", + "id": 804, + "note": "Dicta autem et sed molestiae ut quae.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:31.166Z", - "updated_at": "2016-03-22T15:20:31.166Z", + "author_id": 20, + "created_at": "2016-06-14T15:03:00.097Z", + "updated_at": "2016-06-14T15:03:00.097Z", "project_id": 5, "attachment": { "url": null @@ -4043,17 +4814,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 1251, - "note": "Debitis qui quibusdam voluptas repellat veritatis dicta rerum id.", + "id": 805, + "note": "Ut ut temporibus voluptas dolore quia velit.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:31.137Z", - "updated_at": "2016-03-22T15:20:31.137Z", + "author_id": 16, + "created_at": "2016-06-14T15:03:00.129Z", + "updated_at": "2016-06-14T15:03:00.129Z", "project_id": 5, "attachment": { "url": null @@ -4064,17 +4838,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 1250, - "note": "Suscipit optio ad voluptatem dignissimos temporibus amet molestias ut.", + "id": 806, + "note": "Dolores similique sint pariatur error id quia fugit aut.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:31.107Z", - "updated_at": "2016-03-22T15:20:31.107Z", + "author_id": 15, + "created_at": "2016-06-14T15:03:00.162Z", + "updated_at": "2016-06-14T15:03:00.162Z", "project_id": 5, "attachment": { "url": null @@ -4085,17 +4862,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 1249, - "note": "Nemo aut vitae et ducimus autem ex dolores.", + "id": 807, + "note": "Quisquam provident nihil aperiam voluptatem.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:31.073Z", - "updated_at": "2016-03-22T15:20:31.073Z", + "author_id": 6, + "created_at": "2016-06-14T15:03:00.193Z", + "updated_at": "2016-06-14T15:03:00.193Z", "project_id": 5, "attachment": { "url": null @@ -4106,17 +4886,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 1248, - "note": "Repellendus eaque ex molestiae laudantium placeat quidem vitae recusandae.", + "id": 808, + "note": "Similique quo vero expedita deserunt ipsam earum.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:31.038Z", - "updated_at": "2016-03-22T15:20:31.038Z", + "author_id": 1, + "created_at": "2016-06-14T15:03:00.224Z", + "updated_at": "2016-06-14T15:03:00.224Z", "project_id": 5, "attachment": { "url": null @@ -4127,9 +4910,12 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { @@ -4143,15 +4929,230 @@ "be93687618e4b132087f430a4d8fc3a609c9b77c" ], "authored_date": "2016-01-19T14:08:21.000+01:00", - "author_name": "Test Lopez", - "author_email": "Test@Testlopez.es", + "author_name": "James Lopez", + "author_email": "james@jameslopez.es", "committed_date": "2016-01-19T14:08:21.000+01:00", - "committer_name": "Test Lopez", - "committer_email": "Test@Testlopez.es" + "committer_name": "James Lopez", + "committer_email": "james@jameslopez.es" + }, + { + "id": "be93687618e4b132087f430a4d8fc3a609c9b77c", + "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6", + "parent_ids": [ + "5f923865dde3436854e9ceb9cdb7815618d4e849", + "048721d90c449b244b7b4c53a9186b04330174ec" + ], + "authored_date": "2015-12-07T12:52:12.000+01:00", + "author_name": "Marin Jankovski", + "author_email": "marin@gitlab.com", + "committed_date": "2015-12-07T12:52:12.000+01:00", + "committer_name": "Marin Jankovski", + "committer_email": "marin@gitlab.com" + }, + { + "id": "048721d90c449b244b7b4c53a9186b04330174ec", + "message": "LFS object pointer.\n", + "parent_ids": [ + "5f923865dde3436854e9ceb9cdb7815618d4e849" + ], + "authored_date": "2015-12-07T11:54:28.000+01:00", + "author_name": "Marin Jankovski", + "author_email": "maxlazio@gmail.com", + "committed_date": "2015-12-07T11:54:28.000+01:00", + "committer_name": "Marin Jankovski", + "committer_email": "maxlazio@gmail.com" + }, + { + "id": "5f923865dde3436854e9ceb9cdb7815618d4e849", + "message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n", + "parent_ids": [ + "d2d430676773caa88cdaf7c55944073b2fd5561a" + ], + "authored_date": "2015-11-13T16:27:12.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T16:27:12.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "d2d430676773caa88cdaf7c55944073b2fd5561a", + "message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5", + "parent_ids": [ + "59e29889be61e6e0e5e223bfa9ac2721d31605b8", + "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73" + ], + "authored_date": "2015-11-13T08:50:17.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T08:50:17.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73", + "message": "Add GitLab SVG\n", + "parent_ids": [ + "59e29889be61e6e0e5e223bfa9ac2721d31605b8" + ], + "authored_date": "2015-11-13T08:39:43.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T08:39:43.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "59e29889be61e6e0e5e223bfa9ac2721d31605b8", + "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4", + "parent_ids": [ + "19e2e9b4ef76b422ce1154af39a91323ccc57434", + "66eceea0db202bb39c4e445e8ca28689645366c5" + ], + "authored_date": "2015-11-13T07:21:40.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T07:21:40.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "66eceea0db202bb39c4e445e8ca28689645366c5", + "message": "add spaces in whitespace file\n", + "parent_ids": [ + "08f22f255f082689c0d7d39d19205085311542bc" + ], + "authored_date": "2015-11-13T06:01:27.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T06:01:27.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "08f22f255f082689c0d7d39d19205085311542bc", + "message": "remove emtpy file.(beacase git ignore empty file)\nadd whitespace test file.\n", + "parent_ids": [ + "c642fe9b8b9f28f9225d7ea953fe14e74748d53b" + ], + "authored_date": "2015-11-13T06:00:16.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T06:00:16.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "19e2e9b4ef76b422ce1154af39a91323ccc57434", + "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3", + "parent_ids": [ + "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd", + "c642fe9b8b9f28f9225d7ea953fe14e74748d53b" + ], + "authored_date": "2015-11-13T05:23:14.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T05:23:14.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b", + "message": "add whitespace in empty\n", + "parent_ids": [ + "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0" + ], + "authored_date": "2015-11-13T05:08:45.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T05:08:45.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0", + "message": "add empty file\n", + "parent_ids": [ + "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd" + ], + "authored_date": "2015-11-13T05:08:04.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T05:08:04.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd", + "message": "Add ISO-8859 test file\n", + "parent_ids": [ + "e56497bb5f03a90a51293fc6d516788730953899" + ], + "authored_date": "2015-08-25T17:53:12.000+02:00", + "author_name": "Stan Hu", + "author_email": "stanhu@packetzoom.com", + "committed_date": "2015-08-25T17:53:12.000+02:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@packetzoom.com" } ], "st_diffs": [ { + "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n", + "new_path": "CHANGELOG", + "old_path": "CHANGELOG", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/encoding/iso8859.txt\n@@ -0,0 +1 @@\n+Äü\n", + "new_path": "encoding/iso8859.txt", + "old_path": "encoding/iso8859.txt", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", + "new_path": "files/images/wm.svg", + "old_path": "files/images/wm.svg", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/lfs/lfs_object.iso\n@@ -0,0 +1,4 @@\n+version https://git-lfs.github.com/spec/v1\n+oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897\n+size 1575078\n+\n", + "new_path": "files/lfs/lfs_object.iso", + "old_path": "files/lfs/lfs_object.iso", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n", + "new_path": "files/whitespace", + "old_path": "files/whitespace", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { "diff": "--- /dev/null\n+++ b/test\n", "new_path": "test", "old_path": "test", @@ -4164,34 +5165,60 @@ } ], "merge_request_id": 12, - "created_at": "2016-03-22T15:13:44.840Z", - "updated_at": "2016-03-22T15:13:44.908Z", - "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c", - "real_size": "1" - } + "created_at": "2016-06-14T15:02:24.006Z", + "updated_at": "2016-06-14T15:02:24.169Z", + "base_commit_sha": "e56497bb5f03a90a51293fc6d516788730953899", + "real_size": "6" + }, + "events": [ + { + "id": 226, + "target_type": "MergeRequest", + "target_id": 12, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:24.253Z", + "updated_at": "2016-06-14T15:02:24.253Z", + "action": 1, + "author_id": 1 + }, + { + "id": 172, + "target_type": "MergeRequest", + "target_id": 12, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:24.253Z", + "updated_at": "2016-06-14T15:02:24.253Z", + "action": 1, + "author_id": 1 + } + ] }, { "id": 11, - "target_branch": "test-3", - "source_branch": "test-5", + "target_branch": "test-15", + "source_branch": "'test'", "source_project_id": 5, - "author_id": 26, - "assignee_id": 12, - "title": "Magni aut reprehenderit ut accusantium est eum.", - "created_at": "2016-03-22T15:13:44.494Z", - "updated_at": "2016-03-22T15:20:31.886Z", - "milestone_id": 10, + "author_id": 16, + "assignee_id": 16, + "title": "Corporis provident similique perspiciatis dolores eos animi.", + "created_at": "2016-06-14T15:02:23.767Z", + "updated_at": "2016-06-14T15:03:00.475Z", + "milestone_id": 18, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, "iid": 3, - "description": "Et hic maxime harum ullam. Nulla velit pariatur libero recusandae. Dolor est earum laboriosam harum quo.", + "description": "Libero nesciunt mollitia quis odit eos vero quasi. Iure voluptatem ut sint pariatur voluptates ut aut. Laborum possimus unde illum ipsum eum.", "position": 0, "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -4199,12 +5226,12 @@ "deleted_at": null, "notes": [ { - "id": 1263, - "note": "Beatae incidunt exercitationem voluptates recusandae fuga quia enim.", + "id": 809, + "note": "Omnis ratione laboriosam dolores qui.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:31.883Z", - "updated_at": "2016-03-22T15:20:31.883Z", + "author_id": 26, + "created_at": "2016-06-14T15:03:00.260Z", + "updated_at": "2016-06-14T15:03:00.260Z", "project_id": 5, "attachment": { "url": null @@ -4215,17 +5242,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 1262, - "note": "Illum sunt id consequuntur fugit et quo ullam eum.", + "id": 810, + "note": "Voluptas voluptates pariatur dolores maxime est voluptas.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:31.860Z", - "updated_at": "2016-03-22T15:20:31.860Z", + "author_id": 25, + "created_at": "2016-06-14T15:03:00.290Z", + "updated_at": "2016-06-14T15:03:00.290Z", "project_id": 5, "attachment": { "url": null @@ -4236,17 +5266,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 1261, - "note": "Alias reiciendis autem ipsa sequi autem nemo odio.", + "id": 811, + "note": "Sit perspiciatis facilis ipsum consequatur.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:31.456Z", - "updated_at": "2016-03-22T15:20:31.456Z", + "author_id": 22, + "created_at": "2016-06-14T15:03:00.323Z", + "updated_at": "2016-06-14T15:03:00.323Z", "project_id": 5, "attachment": { "url": null @@ -4257,17 +5290,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 1260, - "note": "Maxime nisi odit eos nulla vel ex accusamus velit.", + "id": 812, + "note": "Ut neque aliquam nam et est.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:31.426Z", - "updated_at": "2016-03-22T15:20:31.426Z", + "author_id": 20, + "created_at": "2016-06-14T15:03:00.349Z", + "updated_at": "2016-06-14T15:03:00.349Z", "project_id": 5, "attachment": { "url": null @@ -4278,17 +5314,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 1259, - "note": "Excepturi et qui sapiente ut ducimus sunt nesciunt.", + "id": 813, + "note": "Et debitis rerum minima sit aut dolorem.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:31.397Z", - "updated_at": "2016-03-22T15:20:31.397Z", + "author_id": 16, + "created_at": "2016-06-14T15:03:00.374Z", + "updated_at": "2016-06-14T15:03:00.374Z", "project_id": 5, "attachment": { "url": null @@ -4299,17 +5338,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 1258, - "note": "Quis rerum dolores et dolorem modi neque ullam doloribus.", + "id": 814, + "note": "Ea nisi earum fugit iste aperiam consequatur.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:31.364Z", - "updated_at": "2016-03-22T15:20:31.364Z", + "author_id": 15, + "created_at": "2016-06-14T15:03:00.397Z", + "updated_at": "2016-06-14T15:03:00.397Z", "project_id": 5, "attachment": { "url": null @@ -4320,17 +5362,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 1257, - "note": "Voluptatum et mollitia neque aut.", + "id": 815, + "note": "Amet ratione consequatur laudantium rerum voluptas est nobis.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:31.328Z", - "updated_at": "2016-03-22T15:20:31.328Z", + "author_id": 6, + "created_at": "2016-06-14T15:03:00.450Z", + "updated_at": "2016-06-14T15:03:00.450Z", "project_id": 5, "attachment": { "url": null @@ -4341,17 +5386,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 1256, - "note": "Rerum laudantium dolor natus doloribus voluptas aliquid a.", + "id": 816, + "note": "Ab ducimus cumque quia dolorem vitae sint beatae rerum.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:31.298Z", - "updated_at": "2016-03-22T15:20:31.298Z", + "author_id": 1, + "created_at": "2016-06-14T15:03:00.474Z", + "updated_at": "2016-06-14T15:03:00.474Z", "project_id": 5, "attachment": { "url": null @@ -4362,71 +5410,76 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { "id": 11, - "state": "collected", - "st_commits": [ - { - "id": "f998ac87ac9244f15e9c15109a6f4e62a54b779d", - "message": "fixes #10\n", - "parent_ids": [ - "be93687618e4b132087f430a4d8fc3a609c9b77c" - ], - "authored_date": "2016-01-19T14:43:23.000+01:00", - "author_name": "Test Lopez", - "author_email": "Test@Testlopez.es", - "committed_date": "2016-01-19T14:43:23.000+01:00", - "committer_name": "Test Lopez", - "committer_email": "Test@Testlopez.es" - } - ], + "state": "empty", + "st_commits": null, "st_diffs": [ - { - "diff": "--- /dev/null\n+++ b/test\n", - "new_path": "test", - "old_path": "test", - "a_mode": "0", - "b_mode": "100644", - "new_file": true, - "renamed_file": false, - "deleted_file": false, - "too_large": false - } + ], "merge_request_id": 11, - "created_at": "2016-03-22T15:13:44.497Z", - "updated_at": "2016-03-22T15:13:44.547Z", - "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c", - "real_size": "1" - } + "created_at": "2016-06-14T15:02:23.772Z", + "updated_at": "2016-06-14T15:02:23.833Z", + "base_commit_sha": "e56497bb5f03a90a51293fc6d516788730953899", + "real_size": null + }, + "events": [ + { + "id": 227, + "target_type": "MergeRequest", + "target_id": 11, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:23.865Z", + "updated_at": "2016-06-14T15:02:23.865Z", + "action": 1, + "author_id": 16 + }, + { + "id": 171, + "target_type": "MergeRequest", + "target_id": 11, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:23.865Z", + "updated_at": "2016-06-14T15:02:23.865Z", + "action": 1, + "author_id": 16 + } + ] }, { "id": 10, - "target_branch": "test-6", - "source_branch": "test-7", + "target_branch": "feature", + "source_branch": "test-5", "source_project_id": 5, - "author_id": 22, - "assignee_id": 4, - "title": "Rerum commodi corporis quis qui fugit sed ut.", - "created_at": "2016-03-22T15:13:44.103Z", - "updated_at": "2016-03-22T15:20:32.096Z", - "milestone_id": 11, + "author_id": 20, + "assignee_id": 25, + "title": "Eligendi reprehenderit doloribus quia et sit id.", + "created_at": "2016-06-14T15:02:23.014Z", + "updated_at": "2016-06-14T15:03:00.685Z", + "milestone_id": 20, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, "iid": 2, - "description": "Laudantium vel dignissimos aspernatur quis aut. Dolores et doloremque ipsa quia voluptate modi labore. Ipsa provident repellat error et nihil.", + "description": "Ut dolor quia aliquid dolore et nisi. Est minus suscipit enim quaerat sapiente consequatur rerum. Eveniet provident consequatur dolor accusantium reiciendis.", "position": 0, "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -4434,12 +5487,12 @@ "deleted_at": null, "notes": [ { - "id": 1271, - "note": "Quod ut ut quisquam et ut dolorem dolor.", + "id": 817, + "note": "Recusandae et voluptas enim qui et.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:32.093Z", - "updated_at": "2016-03-22T15:20:32.093Z", + "author_id": 26, + "created_at": "2016-06-14T15:03:00.510Z", + "updated_at": "2016-06-14T15:03:00.510Z", "project_id": 5, "attachment": { "url": null @@ -4450,17 +5503,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 1270, - "note": "Sed deserunt et explicabo rem repellat voluptatem.", + "id": 818, + "note": "Asperiores dolorem rerum ipsum totam.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:32.070Z", - "updated_at": "2016-03-22T15:20:32.070Z", + "author_id": 25, + "created_at": "2016-06-14T15:03:00.538Z", + "updated_at": "2016-06-14T15:03:00.538Z", "project_id": 5, "attachment": { "url": null @@ -4471,17 +5527,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 1269, - "note": "Veritatis architecto omnis consequatur et optio.", + "id": 819, + "note": "Qui quam et iure quasi provident cumque itaque sequi.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:32.046Z", - "updated_at": "2016-03-22T15:20:32.046Z", + "author_id": 22, + "created_at": "2016-06-14T15:03:00.562Z", + "updated_at": "2016-06-14T15:03:00.562Z", "project_id": 5, "attachment": { "url": null @@ -4492,17 +5551,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 1268, - "note": "Omnis suscipit odio molestiae debitis quia autem magni.", + "id": 820, + "note": "Sint accusantium aliquid iste qui iusto minus vel.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:32.019Z", - "updated_at": "2016-03-22T15:20:32.019Z", + "author_id": 20, + "created_at": "2016-06-14T15:03:00.585Z", + "updated_at": "2016-06-14T15:03:00.585Z", "project_id": 5, "attachment": { "url": null @@ -4513,17 +5575,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 1267, - "note": "Molestias est sunt est tempora consequatur cupiditate magnam.", + "id": 821, + "note": "Dolor corrupti dolorem blanditiis voluptas.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:31.993Z", - "updated_at": "2016-03-22T15:20:31.993Z", + "author_id": 16, + "created_at": "2016-06-14T15:03:00.610Z", + "updated_at": "2016-06-14T15:03:00.610Z", "project_id": 5, "attachment": { "url": null @@ -4534,17 +5599,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 1266, - "note": "Ratione blanditiis eveniet voluptatem nostrum rerum excepturi in molestiae.", + "id": 822, + "note": "Est perferendis assumenda aliquam aliquid sit ipsum ullam aut.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:31.969Z", - "updated_at": "2016-03-22T15:20:31.969Z", + "author_id": 15, + "created_at": "2016-06-14T15:03:00.635Z", + "updated_at": "2016-06-14T15:03:00.635Z", "project_id": 5, "attachment": { "url": null @@ -4555,17 +5623,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 1265, - "note": "Illo voluptatibus vel odio ea.", + "id": 823, + "note": "Hic neque reiciendis quaerat maiores.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:31.944Z", - "updated_at": "2016-03-22T15:20:31.944Z", + "author_id": 6, + "created_at": "2016-06-14T15:03:00.659Z", + "updated_at": "2016-06-14T15:03:00.659Z", "project_id": 5, "attachment": { "url": null @@ -4576,17 +5647,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 1264, - "note": "Earum veritatis quis facere itaque iure.", + "id": 824, + "note": "Sequi architecto doloribus ut vel autem.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:31.919Z", - "updated_at": "2016-03-22T15:20:31.919Z", + "author_id": 1, + "created_at": "2016-06-14T15:03:00.683Z", + "updated_at": "2016-06-14T15:03:00.683Z", "project_id": 5, "attachment": { "url": null @@ -4597,9 +5671,12 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { @@ -4607,21 +5684,427 @@ "state": "collected", "st_commits": [ { - "id": "b42bb86cea49bdcef943e521584b7f417d8ddd3d", + "id": "f998ac87ac9244f15e9c15109a6f4e62a54b779d", "message": "fixes #10\n", "parent_ids": [ "be93687618e4b132087f430a4d8fc3a609c9b77c" ], - "authored_date": "2016-01-19T15:03:09.000+01:00", - "author_name": "Test Lopez", - "author_email": "Test@Testlopez.es", - "committed_date": "2016-01-19T15:03:09.000+01:00", - "committer_name": "Test Lopez", - "committer_email": "Test@Testlopez.es" + "authored_date": "2016-01-19T14:43:23.000+01:00", + "author_name": "James Lopez", + "author_email": "james@jameslopez.es", + "committed_date": "2016-01-19T14:43:23.000+01:00", + "committer_name": "James Lopez", + "committer_email": "james@jameslopez.es" + }, + { + "id": "be93687618e4b132087f430a4d8fc3a609c9b77c", + "message": "Merge branch 'master' into 'master'\r\n\r\nLFS object pointer.\r\n\r\n\r\n\r\nSee merge request !6", + "parent_ids": [ + "5f923865dde3436854e9ceb9cdb7815618d4e849", + "048721d90c449b244b7b4c53a9186b04330174ec" + ], + "authored_date": "2015-12-07T12:52:12.000+01:00", + "author_name": "Marin Jankovski", + "author_email": "marin@gitlab.com", + "committed_date": "2015-12-07T12:52:12.000+01:00", + "committer_name": "Marin Jankovski", + "committer_email": "marin@gitlab.com" + }, + { + "id": "048721d90c449b244b7b4c53a9186b04330174ec", + "message": "LFS object pointer.\n", + "parent_ids": [ + "5f923865dde3436854e9ceb9cdb7815618d4e849" + ], + "authored_date": "2015-12-07T11:54:28.000+01:00", + "author_name": "Marin Jankovski", + "author_email": "maxlazio@gmail.com", + "committed_date": "2015-12-07T11:54:28.000+01:00", + "committer_name": "Marin Jankovski", + "committer_email": "maxlazio@gmail.com" + }, + { + "id": "5f923865dde3436854e9ceb9cdb7815618d4e849", + "message": "GitLab currently doesn't support patches that involve a merge commit: add a commit here\n", + "parent_ids": [ + "d2d430676773caa88cdaf7c55944073b2fd5561a" + ], + "authored_date": "2015-11-13T16:27:12.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T16:27:12.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "d2d430676773caa88cdaf7c55944073b2fd5561a", + "message": "Merge branch 'add-svg' into 'master'\r\n\r\nAdd GitLab SVG\r\n\r\nAdded to test preview of sanitized SVG images\r\n\r\nSee merge request !5", + "parent_ids": [ + "59e29889be61e6e0e5e223bfa9ac2721d31605b8", + "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73" + ], + "authored_date": "2015-11-13T08:50:17.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T08:50:17.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "2ea1f3dec713d940208fb5ce4a38765ecb5d3f73", + "message": "Add GitLab SVG\n", + "parent_ids": [ + "59e29889be61e6e0e5e223bfa9ac2721d31605b8" + ], + "authored_date": "2015-11-13T08:39:43.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T08:39:43.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "59e29889be61e6e0e5e223bfa9ac2721d31605b8", + "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd whitespace test file\r\n\r\nSorry, I did a mistake.\r\nGit ignore empty files.\r\nSo I add a new whitespace test file.\r\n\r\nSee merge request !4", + "parent_ids": [ + "19e2e9b4ef76b422ce1154af39a91323ccc57434", + "66eceea0db202bb39c4e445e8ca28689645366c5" + ], + "authored_date": "2015-11-13T07:21:40.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T07:21:40.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "66eceea0db202bb39c4e445e8ca28689645366c5", + "message": "add spaces in whitespace file\n", + "parent_ids": [ + "08f22f255f082689c0d7d39d19205085311542bc" + ], + "authored_date": "2015-11-13T06:01:27.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T06:01:27.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "08f22f255f082689c0d7d39d19205085311542bc", + "message": "remove emtpy file.(beacase git ignore empty file)\nadd whitespace test file.\n", + "parent_ids": [ + "c642fe9b8b9f28f9225d7ea953fe14e74748d53b" + ], + "authored_date": "2015-11-13T06:00:16.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T06:00:16.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "19e2e9b4ef76b422ce1154af39a91323ccc57434", + "message": "Merge branch 'whitespace' into 'master'\r\n\r\nadd spaces\r\n\r\nTo test this pull request.(https://github.com/gitlabhq/gitlabhq/pull/9757)\r\nJust add whitespaces.\r\n\r\nSee merge request !3", + "parent_ids": [ + "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd", + "c642fe9b8b9f28f9225d7ea953fe14e74748d53b" + ], + "authored_date": "2015-11-13T05:23:14.000+01:00", + "author_name": "Stan Hu", + "author_email": "stanhu@gmail.com", + "committed_date": "2015-11-13T05:23:14.000+01:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@gmail.com" + }, + { + "id": "c642fe9b8b9f28f9225d7ea953fe14e74748d53b", + "message": "add whitespace in empty\n", + "parent_ids": [ + "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0" + ], + "authored_date": "2015-11-13T05:08:45.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T05:08:45.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "9a944d90955aaf45f6d0c88f30e27f8d2c41cec0", + "message": "add empty file\n", + "parent_ids": [ + "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd" + ], + "authored_date": "2015-11-13T05:08:04.000+01:00", + "author_name": "윤민식", + "author_email": "minsik.yoon@samsung.com", + "committed_date": "2015-11-13T05:08:04.000+01:00", + "committer_name": "윤민식", + "committer_email": "minsik.yoon@samsung.com" + }, + { + "id": "c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd", + "message": "Add ISO-8859 test file\n", + "parent_ids": [ + "e56497bb5f03a90a51293fc6d516788730953899" + ], + "authored_date": "2015-08-25T17:53:12.000+02:00", + "author_name": "Stan Hu", + "author_email": "stanhu@packetzoom.com", + "committed_date": "2015-08-25T17:53:12.000+02:00", + "committer_name": "Stan Hu", + "committer_email": "stanhu@packetzoom.com" + }, + { + "id": "e56497bb5f03a90a51293fc6d516788730953899", + "message": "Merge branch 'tree_helper_spec' into 'master'\n\nAdd directory structure for tree_helper spec\n\nThis directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module\n\nSee [merge request #275](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/275#note_732774)\n\nSee merge request !2\n", + "parent_ids": [ + "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + "4cd80ccab63c82b4bad16faa5193fbd2aa06df40" + ], + "authored_date": "2015-01-10T22:23:29.000+01:00", + "author_name": "Sytse Sijbrandij", + "author_email": "sytse@gitlab.com", + "committed_date": "2015-01-10T22:23:29.000+01:00", + "committer_name": "Sytse Sijbrandij", + "committer_email": "sytse@gitlab.com" + }, + { + "id": "4cd80ccab63c82b4bad16faa5193fbd2aa06df40", + "message": "add directory structure for tree_helper spec\n", + "parent_ids": [ + "5937ac0a7beb003549fc5fd26fc247adbce4a52e" + ], + "authored_date": "2015-01-10T21:28:18.000+01:00", + "author_name": "marmis85", + "author_email": "marmis85@gmail.com", + "committed_date": "2015-01-10T21:28:18.000+01:00", + "committer_name": "marmis85", + "committer_email": "marmis85@gmail.com" + }, + { + "id": "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "570e7b2abdd848b95f2f578043fc23bd6f6fd24d" + ], + "authored_date": "2014-02-27T10:01:38.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T10:01:38.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", + "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9" + ], + "authored_date": "2014-02-27T09:57:31.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:57:31.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "d14d6c0abdd253381df51a723d58691b2ee1ab08" + ], + "authored_date": "2014-02-27T09:54:21.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:54:21.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "d14d6c0abdd253381df51a723d58691b2ee1ab08", + "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "c1acaa58bbcbc3eafe538cb8274ba387047b69f8" + ], + "authored_date": "2014-02-27T09:49:50.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:49:50.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + }, + { + "id": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8", + "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "parent_ids": [ + "ae73cb07c9eeaf35924a10f713b364d32b2dd34f" + ], + "authored_date": "2014-02-27T09:48:32.000+01:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:48:32.000+01:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" } ], "st_diffs": [ { + "diff": "Binary files a/.DS_Store and /dev/null differ\n", + "new_path": ".DS_Store", + "old_path": ".DS_Store", + "a_mode": "100644", + "b_mode": "0", + "new_file": false, + "renamed_file": false, + "deleted_file": true, + "too_large": false + }, + { + "diff": "--- a/.gitignore\n+++ b/.gitignore\n@@ -17,3 +17,4 @@ rerun.txt\n pickle-email-*.html\n .project\n config/initializers/secret_token.rb\n+.DS_Store\n", + "new_path": ".gitignore", + "old_path": ".gitignore", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- a/.gitmodules\n+++ b/.gitmodules\n@@ -1,3 +1,9 @@\n [submodule \"six\"]\n \tpath = six\n \turl = git://github.com/randx/six.git\n+[submodule \"gitlab-shell\"]\n+\tpath = gitlab-shell\n+\turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n", + "new_path": ".gitmodules", + "old_path": ".gitmodules", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n", + "new_path": "CHANGELOG", + "old_path": "CHANGELOG", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/encoding/iso8859.txt\n@@ -0,0 +1 @@\n+Äü\n", + "new_path": "encoding/iso8859.txt", + "old_path": "encoding/iso8859.txt", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "Binary files a/files/.DS_Store and /dev/null differ\n", + "new_path": "files/.DS_Store", + "old_path": "files/.DS_Store", + "a_mode": "100644", + "b_mode": "0", + "new_file": false, + "renamed_file": false, + "deleted_file": true, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", + "new_path": "files/images/wm.svg", + "old_path": "files/images/wm.svg", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/lfs/lfs_object.iso\n@@ -0,0 +1,4 @@\n+version https://git-lfs.github.com/spec/v1\n+oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897\n+size 1575078\n+\n", + "new_path": "files/lfs/lfs_object.iso", + "old_path": "files/lfs/lfs_object.iso", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n", + "new_path": "files/ruby/popen.rb", + "old_path": "files/ruby/popen.rb", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- a/files/ruby/regex.rb\n+++ b/files/ruby/regex.rb\n@@ -19,14 +19,12 @@ module Gitlab\n end\n \n def archive_formats_regex\n- #|zip|tar| tar.gz | tar.bz2 |\n- /(zip|tar|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n+ /(zip|tar|7z|tar\\.gz|tgz|gz|tar\\.bz2|tbz|tbz2|tb2|bz2)/\n end\n \n def git_reference_regex\n # Valid git ref regex, see:\n # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html\n-\n %r{\n (?!\n (?# doesn't begins with)\n", + "new_path": "files/ruby/regex.rb", + "old_path": "files/ruby/regex.rb", + "a_mode": "100644", + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/files/whitespace\n@@ -0,0 +1 @@\n+test \n", + "new_path": "files/whitespace", + "old_path": "files/whitespace", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/foo/bar/.gitkeep\n", + "new_path": "foo/bar/.gitkeep", + "old_path": "foo/bar/.gitkeep", + "a_mode": "0", + "b_mode": "100644", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/gitlab-grack\n@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n", + "new_path": "gitlab-grack", + "old_path": "gitlab-grack", + "a_mode": "0", + "b_mode": "160000", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { + "diff": "--- /dev/null\n+++ b/gitlab-shell\n@@ -0,0 +1 @@\n+Subproject commit 79bceae69cb5750d6567b223597999bfa91cb3b9\n", + "new_path": "gitlab-shell", + "old_path": "gitlab-shell", + "a_mode": "0", + "b_mode": "160000", + "new_file": true, + "renamed_file": false, + "deleted_file": false, + "too_large": false + }, + { "diff": "--- /dev/null\n+++ b/test\n", "new_path": "test", "old_path": "test", @@ -4634,34 +6117,60 @@ } ], "merge_request_id": 10, - "created_at": "2016-03-22T15:13:44.107Z", - "updated_at": "2016-03-22T15:13:44.190Z", - "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c", - "real_size": "1" - } + "created_at": "2016-06-14T15:02:23.019Z", + "updated_at": "2016-06-14T15:02:23.493Z", + "base_commit_sha": "ae73cb07c9eeaf35924a10f713b364d32b2dd34f", + "real_size": "15" + }, + "events": [ + { + "id": 228, + "target_type": "MergeRequest", + "target_id": 10, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:23.660Z", + "updated_at": "2016-06-14T15:02:23.660Z", + "action": 1, + "author_id": 1 + }, + { + "id": 170, + "target_type": "MergeRequest", + "target_id": 10, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:23.660Z", + "updated_at": "2016-06-14T15:02:23.660Z", + "action": 1, + "author_id": 20 + } + ] }, { "id": 9, - "target_branch": "test-8", - "source_branch": "test-9", + "target_branch": "test-6", + "source_branch": "test-12", "source_project_id": 5, - "author_id": 24, - "assignee_id": 3, - "title": "Saepe et neque ut vero nobis et voluptatum facere qui minima.", - "created_at": "2016-03-22T15:13:43.792Z", - "updated_at": "2016-03-22T15:20:32.309Z", - "milestone_id": 10, + "author_id": 16, + "assignee_id": 6, + "title": "Et ipsam voluptas velit sequi illum ut.", + "created_at": "2016-06-14T15:02:22.825Z", + "updated_at": "2016-06-14T15:03:00.904Z", + "milestone_id": 16, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, "iid": 1, - "description": "Autem enim aliquam labore qui voluptas ut voluptatem. Et corrupti sit fuga dolores alias iusto voluptatem. Excepturi ut saepe accusamus neque distinctio.", + "description": "Eveniet nihil ratione veniam similique qui aut sapiente tempora. Sed praesentium iusto dignissimos possimus id repudiandae quo nihil. Qui doloremque autem et iure fugit.", "position": 0, "locked_at": null, "updated_by_id": null, "merge_error": null, "merge_params": { - + "force_remove_source_branch": null }, "merge_when_build_succeeds": false, "merge_user_id": null, @@ -4669,12 +6178,12 @@ "deleted_at": null, "notes": [ { - "id": 1279, - "note": "A corrupti nesciunt pariatur ea.", + "id": 825, + "note": "Aliquid voluptatem consequatur voluptas ex perspiciatis.", "noteable_type": "MergeRequest", - "author_id": 1, - "created_at": "2016-03-22T15:20:32.307Z", - "updated_at": "2016-03-22T15:20:32.307Z", + "author_id": 26, + "created_at": "2016-06-14T15:03:00.722Z", + "updated_at": "2016-06-14T15:03:00.722Z", "project_id": 5, "attachment": { "url": null @@ -4685,17 +6194,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Administrator" - } + "author": { + "name": "User 4" + }, + "events": [ + + ] }, { - "id": 1278, - "note": "Adipisci aut ut et voluptate numquam.", + "id": 826, + "note": "Itaque optio voluptatem praesentium voluptas.", "noteable_type": "MergeRequest", - "author_id": 3, - "created_at": "2016-03-22T15:20:32.281Z", - "updated_at": "2016-03-22T15:20:32.281Z", + "author_id": 25, + "created_at": "2016-06-14T15:03:00.745Z", + "updated_at": "2016-06-14T15:03:00.745Z", "project_id": 5, "attachment": { "url": null @@ -4706,17 +6218,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Alexie Trantow" - } + "author": { + "name": "User 3" + }, + "events": [ + + ] }, { - "id": 1277, - "note": "Adipisci voluptatem quod ut placeat repellendus deleniti.", + "id": 827, + "note": "Ut est corporis fuga asperiores delectus excepturi aperiam.", "noteable_type": "MergeRequest", - "author_id": 4, - "created_at": "2016-03-22T15:20:32.255Z", - "updated_at": "2016-03-22T15:20:32.255Z", + "author_id": 22, + "created_at": "2016-06-14T15:03:00.771Z", + "updated_at": "2016-06-14T15:03:00.771Z", "project_id": 5, "attachment": { "url": null @@ -4727,17 +6242,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Julius Moore" - } + "author": { + "name": "User 0" + }, + "events": [ + + ] }, { - "id": 1276, - "note": "Vitae et doloremque aut et aspernatur velit placeat sed.", + "id": 828, + "note": "Similique ea dolore officiis temporibus.", "noteable_type": "MergeRequest", - "author_id": 10, - "created_at": "2016-03-22T15:20:32.230Z", - "updated_at": "2016-03-22T15:20:32.230Z", + "author_id": 20, + "created_at": "2016-06-14T15:03:00.798Z", + "updated_at": "2016-06-14T15:03:00.798Z", "project_id": 5, "attachment": { "url": null @@ -4748,17 +6266,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Robyn McCullough Jr." - } + "author": { + "name": "Ottis Schuster II" + }, + "events": [ + + ] }, { - "id": 1275, - "note": "Quos cupiditate nesciunt expedita aspernatur.", + "id": 829, + "note": "Qui laudantium qui quae quis.", "noteable_type": "MergeRequest", - "author_id": 12, - "created_at": "2016-03-22T15:20:32.207Z", - "updated_at": "2016-03-22T15:20:32.207Z", + "author_id": 16, + "created_at": "2016-06-14T15:03:00.828Z", + "updated_at": "2016-06-14T15:03:00.828Z", "project_id": 5, "attachment": { "url": null @@ -4769,17 +6290,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "Vladimir McCullough" - } + "author": { + "name": "Rhett Emmerich IV" + }, + "events": [ + + ] }, { - "id": 1274, - "note": "Optio rem inventore dicta praesentium sit.", + "id": 830, + "note": "Et vel voluptas amet laborum qui soluta.", "noteable_type": "MergeRequest", - "author_id": 22, - "created_at": "2016-03-22T15:20:32.181Z", - "updated_at": "2016-03-22T15:20:32.181Z", + "author_id": 15, + "created_at": "2016-06-14T15:03:00.850Z", + "updated_at": "2016-06-14T15:03:00.850Z", "project_id": 5, "attachment": { "url": null @@ -4790,17 +6314,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 0" - } + "author": { + "name": "Burdette Bernier" + }, + "events": [ + + ] }, { - "id": 1273, - "note": "Sit incidunt molestiae maxime officiis rerum necessitatibus.", + "id": 831, + "note": "Enim ad consequuntur assumenda provident voluptatem similique deleniti.", "noteable_type": "MergeRequest", - "author_id": 24, - "created_at": "2016-03-22T15:20:32.159Z", - "updated_at": "2016-03-22T15:20:32.159Z", + "author_id": 6, + "created_at": "2016-06-14T15:03:00.876Z", + "updated_at": "2016-06-14T15:03:00.876Z", "project_id": 5, "attachment": { "url": null @@ -4811,17 +6338,20 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 2" - } + "author": { + "name": "Ari Wintheiser" + }, + "events": [ + + ] }, { - "id": 1272, - "note": "Autem ut non itaque molestiae nisi quia officiis doloribus.", + "id": 832, + "note": "Officiis sequi commodi pariatur totam fugiat voluptas corporis dignissimos.", "noteable_type": "MergeRequest", - "author_id": 26, - "created_at": "2016-03-22T15:20:32.129Z", - "updated_at": "2016-03-22T15:20:32.129Z", + "author_id": 1, + "created_at": "2016-06-14T15:03:00.902Z", + "updated_at": "2016-06-14T15:03:00.902Z", "project_id": 5, "attachment": { "url": null @@ -4832,9 +6362,12 @@ "system": false, "st_diff": null, "updated_by_id": null, - "author": { - "name": "User 4" - } + "author": { + "name": "Administrator" + }, + "events": [ + + ] } ], "merge_request_diff": { @@ -4842,17 +6375,17 @@ "state": "collected", "st_commits": [ { - "id": "e239ba8c97b80b2874579a4d625ea9628f4c8ff5", + "id": "a4e5dfebf42e34596526acb8611bc7ed80e4eb3f", "message": "fixes #10\n", "parent_ids": [ "be93687618e4b132087f430a4d8fc3a609c9b77c" ], - "authored_date": "2016-01-19T15:38:06.000+01:00", - "author_name": "Test Lopez", - "author_email": "Test@Testlopez.es", - "committed_date": "2016-01-19T15:38:06.000+01:00", - "committer_name": "Test Lopez", - "committer_email": "Test@Testlopez.es" + "authored_date": "2016-01-19T15:44:02.000+01:00", + "author_name": "James Lopez", + "author_email": "james@jameslopez.es", + "committed_date": "2016-01-19T15:44:02.000+01:00", + "committer_name": "James Lopez", + "committer_email": "james@jameslopez.es" } ], "st_diffs": [ @@ -4869,11 +6402,37 @@ } ], "merge_request_id": 9, - "created_at": "2016-03-22T15:13:43.794Z", - "updated_at": "2016-03-22T15:13:43.848Z", + "created_at": "2016-06-14T15:02:22.829Z", + "updated_at": "2016-06-14T15:02:22.900Z", "base_commit_sha": "be93687618e4b132087f430a4d8fc3a609c9b77c", "real_size": "1" - } + }, + "events": [ + { + "id": 229, + "target_type": "MergeRequest", + "target_id": 9, + "title": null, + "data": null, + "project_id": 36, + "created_at": "2016-06-14T15:02:22.927Z", + "updated_at": "2016-06-14T15:02:22.927Z", + "action": 1, + "author_id": 16 + }, + { + "id": 169, + "target_type": "MergeRequest", + "target_id": 9, + "title": null, + "data": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:22.927Z", + "updated_at": "2016-06-14T15:02:22.927Z", + "action": 1, + "author_id": 16 + } + ] } ], "pipelines": [ @@ -5360,5 +6919,464 @@ } ] } + ], + "variables": [ + + ], + "triggers": [ + + ], + "deploy_keys": [ + + ], + "services": [ + { + "id": 164, + "title": null, + "project_id": 5, + "created_at": "2016-06-14T15:02:07.372Z", + "updated_at": "2016-06-14T15:02:07.372Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "issue_tracker", + "default": true, + "wiki_page_events": true + }, + { + "id": 100, + "title": "JetBrains TeamCity CI", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.315Z", + "updated_at": "2016-06-14T15:01:51.315Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "ci", + "default": false, + "wiki_page_events": true + }, + { + "id": 99, + "title": "Slack", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.303Z", + "updated_at": "2016-06-14T15:01:51.303Z", + "active": false, + "properties": { + "notify_only_broken_builds": true + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 98, + "title": "Redmine", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.289Z", + "updated_at": "2016-06-14T15:01:51.289Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "issue_tracker", + "default": false, + "wiki_page_events": true + }, + { + "id": 97, + "title": "Pushover", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.277Z", + "updated_at": "2016-06-14T15:01:51.277Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 96, + "title": "PivotalTracker", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.267Z", + "updated_at": "2016-06-14T15:01:51.267Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 95, + "title": "JIRA", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.255Z", + "updated_at": "2016-06-14T15:01:51.255Z", + "active": false, + "properties": { + "api_url": "", + "jira_issue_transition_id": "2" + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "issue_tracker", + "default": false, + "wiki_page_events": true + }, + { + "id": 94, + "title": "Irker (IRC gateway)", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.232Z", + "updated_at": "2016-06-14T15:01:51.232Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 93, + "title": "HipChat", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.219Z", + "updated_at": "2016-06-14T15:01:51.219Z", + "active": false, + "properties": { + "notify_only_broken_builds": true + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 92, + "title": "Gemnasium", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.202Z", + "updated_at": "2016-06-14T15:01:51.202Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 91, + "title": "Flowdock", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.182Z", + "updated_at": "2016-06-14T15:01:51.182Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 90, + "title": "External Wiki", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.166Z", + "updated_at": "2016-06-14T15:01:51.166Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 89, + "title": "Emails on push", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.153Z", + "updated_at": "2016-06-14T15:01:51.153Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 88, + "title": "Drone CI", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.139Z", + "updated_at": "2016-06-14T15:01:51.139Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "ci", + "default": false, + "wiki_page_events": true + }, + { + "id": 87, + "title": "Custom Issue Tracker", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.125Z", + "updated_at": "2016-06-14T15:01:51.125Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "issue_tracker", + "default": false, + "wiki_page_events": true + }, + { + "id": 86, + "title": "Campfire", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.113Z", + "updated_at": "2016-06-14T15:01:51.113Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 85, + "title": "Builds emails", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.090Z", + "updated_at": "2016-06-14T15:01:51.090Z", + "active": false, + "properties": { + "notify_only_broken_builds": true + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 84, + "title": "Buildkite", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.080Z", + "updated_at": "2016-06-14T15:01:51.080Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "ci", + "default": false, + "wiki_page_events": true + }, + { + "id": 83, + "title": "Atlassian Bamboo CI", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.067Z", + "updated_at": "2016-06-14T15:01:51.067Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "ci", + "default": false, + "wiki_page_events": true + }, + { + "id": 82, + "title": "Assembla", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.047Z", + "updated_at": "2016-06-14T15:01:51.047Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + }, + { + "id": 81, + "title": "Asana", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.031Z", + "updated_at": "2016-06-14T15:01:51.031Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true + } + ], + "hooks": [ + + ], + "protected_branches": [ + ] }
\ No newline at end of file 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 23036ab8108..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') } @@ -24,6 +23,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Ci::Pipeline.first.notes).not_to be_empty end + + it 'restores the correct event' do + restored_project_json + + expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty + end end end end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 8d29b2f8fd1..1424de9e60b 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) @@ -34,7 +32,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do end it 'has events' do - expect(saved_project_json['events']).not_to be_empty + expect(saved_project_json['milestones'].first['events']).not_to be_empty end it 'has milestones' do @@ -127,12 +125,12 @@ 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]) create(:ci_build, pipeline: ci_pipeline, project: project) - create(:milestone, project: project) + milestone = create(:milestone, project: project) create(:note, noteable: issue, project: project) create(:note, noteable: merge_request, project: project) create(:note, noteable: snippet, project: project) @@ -140,6 +138,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do author: user, project: project, commit_id: ci_pipeline.sha) + + create(:event, target: milestone, project: project, action: Event::CREATED, author: user) + project end diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb index 109522fa626..b76e14deca1 100644 --- a/spec/lib/gitlab/import_export/reader_spec.rb +++ b/spec/lib/gitlab/import_export/reader_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::Reader, lib: true do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path:'') } + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') } let(:test_config) { 'spec/support/import_export/import_export.yml' } let(:project_tree_hash) do { @@ -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/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb index d824dc54438..d986c6fac43 100644 --- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb @@ -13,6 +13,61 @@ describe Gitlab::Metrics::Subscribers::RailsCache do subscriber.cache_read(event) end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + context 'with hit event' do + let(:event) { double(:event, duration: 15.2, payload: { hit: true }) } + + it 'increments the cache_read_hit count' do + expect(transaction).to receive(:increment). + with(:cache_read_hit_count, 1) + expect(transaction).to receive(:increment). + with(any_args).at_least(1) # Other calls + + subscriber.cache_read(event) + end + + context 'when super operation is fetch' do + let(:event) { double(:event, duration: 15.2, payload: { hit: true, super_operation: :fetch }) } + + it 'does not increment cache read miss' do + expect(transaction).not_to receive(:increment). + with(:cache_read_hit_count, 1) + + subscriber.cache_read(event) + end + end + end + + context 'with miss event' do + let(:event) { double(:event, duration: 15.2, payload: { hit: false }) } + + it 'increments the cache_read_miss count' do + expect(transaction).to receive(:increment). + with(:cache_read_miss_count, 1) + expect(transaction).to receive(:increment). + with(any_args).at_least(1) # Other calls + + subscriber.cache_read(event) + end + + context 'when super operation is fetch' do + let(:event) { double(:event, duration: 15.2, payload: { hit: false, super_operation: :fetch }) } + + it 'does not increment cache read miss' do + expect(transaction).not_to receive(:increment). + with(:cache_read_miss_count, 1) + + subscriber.cache_read(event) + end + end + end + end end describe '#cache_write' do @@ -42,6 +97,54 @@ describe Gitlab::Metrics::Subscribers::RailsCache do end end + describe '#cache_fetch_hit' do + context 'without a transaction' do + it 'returns' do + expect(transaction).not_to receive(:increment) + + subscriber.cache_fetch_hit(event) + end + end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + it 'increments the cache_read_hit count' do + expect(transaction).to receive(:increment). + with(:cache_read_hit_count, 1) + + subscriber.cache_fetch_hit(event) + end + end + end + + describe '#cache_generate' do + context 'without a transaction' do + it 'returns' do + expect(transaction).not_to receive(:increment) + + subscriber.cache_generate(event) + end + end + + context 'with a transaction' do + before do + allow(subscriber).to receive(:current_transaction). + and_return(transaction) + end + + it 'increments the cache_fetch_miss count' do + expect(transaction).to receive(:increment). + with(:cache_read_miss_count, 1) + + subscriber.cache_generate(event) + end + end + end + describe '#increment' do context 'without a transaction' do it 'returns' do diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index d6ae54e25e8..cf0e282c2fb 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -28,8 +28,20 @@ describe Gitlab::Metrics::System do end describe '.cpu_time' do - it 'returns a Fixnum' do - expect(described_class.cpu_time).to be_an_instance_of(Fixnum) + it 'returns a Float' do + expect(described_class.cpu_time).to be_an_instance_of(Float) + end + end + + describe '.real_time' do + it 'returns a Float' do + expect(described_class.real_time).to be_an_instance_of(Float) + end + end + + describe '.monotonic_time' do + it 'returns a Float' do + expect(described_class.monotonic_time).to be_an_instance_of(Float) end end 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/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 6727a83e58a..1fca8a13037 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 @@ -122,13 +135,12 @@ describe Gitlab::OAuth::User, lib: true do before do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] } + allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) 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 +181,6 @@ describe Gitlab::OAuth::User, lib: true do end end end - end describe 'blocking' do @@ -203,7 +214,7 @@ describe Gitlab::OAuth::User, lib: true do stub_omniauth_config(auto_link_ldap_user: true) allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } - allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] } + allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } allow(oauth_user).to receive(:ldap_person).and_return(ldap_user) end @@ -255,7 +266,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/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 795cf241278..e8b236426e9 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -10,7 +10,7 @@ describe 'Gitlab::Popen', lib: true, no_db: true do context 'zero status' do before do - @output, @status = @klass.new.popen(%W(ls), path) + @output, @status = @klass.new.popen(%w(ls), path) end it { expect(@status).to be_zero } @@ -19,7 +19,7 @@ describe 'Gitlab::Popen', lib: true, no_db: true do context 'non-zero status' do before do - @output, @status = @klass.new.popen(%W(cat NOTHING), path) + @output, @status = @klass.new.popen(%w(cat NOTHING), path) end it { expect(@status).to eq(1) } @@ -34,7 +34,7 @@ describe 'Gitlab::Popen', lib: true, no_db: true do context 'without a directory argument' do before do - @output, @status = @klass.new.popen(%W(ls)) + @output, @status = @klass.new.popen(%w(ls)) end it { expect(@status).to be_zero } 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_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index bf11472407a..a826b24419a 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -43,9 +43,9 @@ describe Gitlab::UrlBuilder, lib: true do end end - context 'on a CommitDiff' do + context 'on a Commit Diff' do it 'returns a proper URL' do - note = build_stubbed(:note_on_commit_diff) + note = build_stubbed(:diff_note_on_commit) url = described_class.build(note) @@ -75,10 +75,10 @@ describe Gitlab::UrlBuilder, lib: true do end end - context 'on a MergeRequestDiff' do + context 'on a MergeRequest Diff' do it 'returns a proper URL' do merge_request = create(:merge_request, iid: 42) - note = build_stubbed(:note_on_merge_request_diff, noteable: merge_request) + note = build_stubbed(:diff_note_on_merge_request, noteable: merge_request) url = described_class.build(note) diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 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/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index c59dfea5c55..c4c107c9eea 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -8,6 +8,12 @@ describe Gitlab, lib: true do expect(described_class.com?).to eq true end + it 'is true when on staging' do + stub_config_setting(url: 'https://staging.gitlab.com') + + expect(described_class.com?).to eq true + end + it 'is false when not on GitLab.com' do stub_config_setting(url: 'http://example.com') diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index ae55a01ebea..0a9b10bebea 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) } @@ -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) } @@ -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 @@ -1053,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) } @@ -1084,5 +1080,4 @@ describe Notify do is_expected.to have_body_text /#{diff_path}/ end end - end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index d84f3e998f5..2ea1320267c 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -40,6 +40,16 @@ describe ApplicationSetting, models: true do it_behaves_like 'an object with email-formated attributes', :admin_notification_email do subject { setting } end + + context 'repository storages inclussion' do + before do + storages = { 'custom' => 'tmp/tests/custom_repositories' } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end + + it { is_expected.to allow_value('custom').for(:repository_storage) } + it { is_expected.not_to allow_value('alternative').for(:repository_storage) } + end end context 'restricted signup domains' do diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 8154001cf46..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 @@ -669,4 +668,22 @@ describe Ci::Build, models: true do expect(build.commit).to eq project.commit end end + + describe '#retryable?' do + context 'when build is running' do + before { build.run! } + + it 'should return false' do + expect(build.retryable?).to be false + end + end + + context 'when build is finished' do + before { build.success! } + + it 'should return true' do + expect(build.retryable?).to be true + end + end + end end 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/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/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') } 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/diff_note_spec.rb b/spec/models/diff_note_spec.rb new file mode 100644 index 00000000000..af8e890ca95 --- /dev/null +++ b/spec/models/diff_note_spec.rb @@ -0,0 +1,191 @@ +require 'spec_helper' + +describe DiffNote, models: true do + include RepoHelpers + + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, source_project: project) } + let(:commit) { project.commit(sample_commit.id) } + + let(:path) { "files/ruby/popen.rb" } + + let!(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + old_line: nil, + new_line: 14, + diff_refs: merge_request.diff_refs + ) + end + + let!(:new_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + old_line: 16, + new_line: 22, + diff_refs: merge_request.diff_refs + ) + end + + subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + + describe "#position=" do + context "when provided a string" do + it "sets the position" do + subject.position = new_position.to_json + + expect(subject.position).to eq(new_position) + end + end + + context "when provided a hash" do + it "sets the position" do + subject.position = new_position.to_h + + expect(subject.position).to eq(new_position) + end + end + + context "when provided a position object" do + it "sets the position" do + subject.position = new_position + + expect(subject.position).to eq(new_position) + end + end + end + + describe "#diff_file" do + it "returns the correct diff file" do + diff_file = subject.diff_file + + expect(diff_file.old_path).to eq(position.old_path) + expect(diff_file.new_path).to eq(position.new_path) + expect(diff_file.diff_refs).to eq(position.diff_refs) + end + end + + describe "#diff_line" do + it "returns the correct diff line" do + diff_line = subject.diff_line + + expect(diff_line.added?).to be true + expect(diff_line.new_line).to eq(position.new_line) + expect(diff_line.text).to eq("+ vars = {") + end + end + + describe "#line_code" do + it "returns the correct line code" do + line_code = Gitlab::Diff::LineCode.generate(position.file_path, position.new_line, 15) + + expect(subject.line_code).to eq(line_code) + end + end + + describe "#for_line?" do + context "when provided the correct diff line" do + it "returns true" do + expect(subject.for_line?(subject.diff_line)).to be true + end + end + + context "when provided a different diff line" do + it "returns false" do + some_line = subject.diff_file.diff_lines.first + + expect(subject.for_line?(some_line)).to be false + end + end + end + + describe "#active?" do + context "when noteable is a commit" do + subject { create(:diff_note_on_commit, project: project, position: position) } + + it "returns true" do + expect(subject.active?).to be true + end + end + + context "when noteable is a merge request" do + context "when the merge request's diff refs match that of the diff note" do + it "returns true" do + expect(subject.active?).to be true + end + end + + context "when the merge request's diff refs don't match that of the diff note" do + before do + allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs) + end + + it "returns false" do + expect(subject.active?).to be false + end + end + end + end + + describe "creation" do + describe "updating of position" do + context "when noteable is a commit" do + let(:diff_note) { create(:diff_note_on_commit, project: project, position: position) } + + it "doesn't use the DiffPositionUpdateService" do + expect(Notes::DiffPositionUpdateService).not_to receive(:new) + + diff_note + end + + it "doesn't update the position" do + diff_note + + expect(diff_note.original_position).to eq(position) + expect(diff_note.position).to eq(position) + end + end + + context "when noteable is a merge request" do + let(:diff_note) { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + + context "when the note is active" do + it "doesn't use the DiffPositionUpdateService" do + expect(Notes::DiffPositionUpdateService).not_to receive(:new) + + diff_note + end + + it "doesn't update the position" do + diff_note + + expect(diff_note.original_position).to eq(position) + expect(diff_note.position).to eq(position) + end + end + + context "when the note is outdated" do + before do + allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs) + end + + it "uses the DiffPositionUpdateService" do + service = instance_double("Notes::DiffPositionUpdateService") + expect(Notes::DiffPositionUpdateService).to receive(:new).with( + project, + nil, + old_diff_refs: position.diff_refs, + new_diff_refs: commit.diff_refs, + paths: [path] + ).and_return(service) + expect(service).to receive(:execute) + + diff_note + end + end + end + end + end +end diff --git a/spec/models/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/event_spec.rb b/spec/models/event_spec.rb index 166a1dc4ddb..b5d0d79e14e 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -46,6 +46,22 @@ describe Event, models: true do it { expect(@event.author).to eq(@user) } end + describe '#note?' do + subject { Event.new(project: target.project, target: target) } + + context 'issue note event' do + let(:target) { create(:note_on_issue) } + + it { is_expected.to be_note } + end + + context 'merge request diff note event' do + let(:target) { create(:legacy_diff_note_on_merge_request) } + + it { is_expected.to be_note } + end + end + describe '#visible_to_user?' do let(:project) { create(:empty_project, :public) } let(:non_member) { create(:user) } @@ -89,7 +105,7 @@ describe Event, models: true do end end - context 'note event' do + context 'issue note event' do context 'on non confidential issues' do let(:target) { note_on_issue } @@ -112,6 +128,20 @@ describe Event, models: true do it { expect(event.visible_to_user?(admin)).to eq true } end end + + context 'merge request diff note event' do + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) } + let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } + let(:target) { note_on_merge_request } + + it { expect(event.visible_to_user?(non_member)).to eq true } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(guest)).to eq true } + it { expect(event.visible_to_user?(admin)).to eq true } + end end describe '.limit_recent' do diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 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/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/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/label_spec.rb b/spec/models/label_spec.rb index dad2628651b..f37f44a608e 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -32,21 +32,20 @@ describe Label, models: true do it 'should validate title' do expect(label).not_to allow_value('G,ITLAB').for(:title) - expect(label).not_to allow_value('G?ITLAB').for(:title) - expect(label).not_to allow_value('G&ITLAB').for(:title) expect(label).not_to allow_value('').for(:title) expect(label).to allow_value('GITLAB').for(:title) expect(label).to allow_value('gitlab').for(:title) + expect(label).to allow_value('G?ITLAB').for(:title) + expect(label).to allow_value('G&ITLAB').for(:title) expect(label).to allow_value("customer's request").for(:title) end end - describe "#title" do - let(:label) { create(:label, title: "<b>test</b>") } - - it "sanitizes title" do - expect(label.title).to eq("test") + describe '#title' do + it 'sanitizes title' do + label = described_class.new(title: '<b>foo & bar?</b>') + expect(label.title).to eq('foo & bar?') end end diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb index b2d06853886..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 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/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/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 3b199f4d98d..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 @@ -62,7 +64,7 @@ describe MergeRequest, models: true do end end - describe '#target_sha' do + describe '#target_branch_sha' do context 'when the target branch does not exist anymore' do let(:project) { create(:project) } @@ -73,32 +75,32 @@ describe MergeRequest, models: true do end it 'returns nil' do - expect(subject.target_sha).to be_nil + expect(subject.target_branch_sha).to be_nil end end end - describe '#source_sha' do + describe '#source_branch_sha' do let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) } context 'with diffs' do subject { create(:merge_request, :with_diffs) } it 'returns the sha of the source branch last commit' do - expect(subject.source_sha).to eq(last_branch_commit.sha) + expect(subject.source_branch_sha).to eq(last_branch_commit.sha) end end context 'without diffs' do subject { create(:merge_request, :without_diffs) } it 'returns the sha of the source branch last commit' do - expect(subject.source_sha).to eq(last_branch_commit.sha) + expect(subject.source_branch_sha).to eq(last_branch_commit.sha) end end context 'when the merge request is being created' do subject { build(:merge_request, source_branch: nil, compare_commits: []) } it 'returns nil' do - expect(subject.source_sha).to be_nil + expect(subject.source_branch_sha).to be_nil end end end @@ -252,12 +254,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 +367,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 +377,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 +396,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 +467,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?).and_return(true) expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') end @@ -481,7 +484,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?).and_return(false) end it 'becomes unmergeable' do @@ -608,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 diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 4e68ac5e63a..5f68cd2b066 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -57,6 +57,7 @@ describe Namespace, models: true do describe :move_dir do before do @namespace = create :namespace + @project = create :project, namespace: @namespace allow(@namespace).to receive(:path_changed?).and_return(true) end @@ -87,8 +88,13 @@ describe Namespace, models: true do end describe :rm_dir do - it "should remove dir" do - expect(namespace.rm_dir).to be_truthy + let!(:project) { create(:project, namespace: namespace) } + let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) } + + before { namespace.destroy } + + it "should remove its dirs when deleted" do + expect(File.exist?(path)).to be(false) end end @@ -103,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/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/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index df336a6effe..d58673413c8 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -38,4 +38,21 @@ RSpec.describe NotificationSetting, type: :model do end end end + + describe '#for_projects' do + let(:user) { create(:user) } + + before do + 1.upto(4) do |i| + setting = create(:notification_setting, user: user) + + setting.project.update_attributes(pending_delete: true) if i.even? + end + end + + it 'excludes projects pending delete' do + expect(user.notification_settings.for_projects).to all(have_attributes(project: an_instance_of(Project))) + expect(user.notification_settings.for_projects.map(&:project)).to all(have_attributes(pending_delete: false)) + end + end end diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb new file mode 100644 index 00000000000..a6d9717ccb5 --- /dev/null +++ b/spec/models/project_services/bugzilla_service_spec.rb @@ -0,0 +1,49 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require 'spec_helper' + +describe BugzillaService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it { is_expected.to validate_presence_of(:new_issue_url) } + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + end + + context 'when service is inactive' do + before { subject.active = false } + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + it { is_expected.not_to validate_presence_of(:new_issue_url) } + end + end +end 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/project_spec.rb b/spec/models/project_spec.rb index 53c8408633c..5a27ccbab0a 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 @@ -56,6 +86,7 @@ describe Project, models: true do it { is_expected.to validate_length_of(:description).is_within(0..2000) } it { is_expected.to validate_presence_of(:creator) } it { is_expected.to validate_presence_of(:namespace) } + it { is_expected.to validate_presence_of(:repository_storage) } it 'should not allow new projects beyond user limits' do project2 = build(:project) @@ -63,6 +94,53 @@ 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 + + describe 'wiki path conflict' do + context "when the new path has been used by the wiki of other Project" do + it 'should have an error on the name attribute' do + new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") + + expect(new_project).not_to be_valid + expect(new_project.errors[:name].first).to eq('has already been taken') + end + end + + context "when the new wiki path has been used by the path of other Project" do + it 'should have an error on the name attribute' do + project_with_wiki_suffix = create(:project, path: 'foo.wiki') + new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') + + expect(new_project).not_to be_valid + expect(new_project.errors[:name].first).to eq('has already been taken') + end + end + end + + context 'repository storages inclussion' do + let(:project2) { build(:project, repository_storage: 'missing') } + + before do + storages = { 'custom' => 'tmp/tests/custom_repositories' } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end + + it "should not allow repository storages that don't match a label in the configuration" do + expect(project2).not_to be_valid + expect(project2.errors[:repository_storage].first).to match(/is not included in the list/) + end + end + + it 'should not allow an invalid URI as import_url' do + 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 describe 'default_scope' do @@ -110,6 +188,24 @@ describe Project, models: true do end end + describe '#repository_storage_path' do + let(:project) { create(:project, repository_storage: 'custom') } + + before do + FileUtils.mkdir('tmp/tests/custom_repositories') + storages = { 'custom' => 'tmp/tests/custom_repositories' } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end + + after do + FileUtils.rm_rf('tmp/tests/custom_repositories') + end + + it 'returns the repository storage path' do + expect(project.repository_storage_path).to eq('tmp/tests/custom_repositories') + end + end + it 'should return valid url to repo' do project = Project.new(path: 'somewhere') expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') @@ -216,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 @@ -353,6 +449,14 @@ describe Project, models: true do it { expect(project.open_branches.map(&:name)).to include('feature') } it { expect(project.open_branches.map(&:name)).not_to include('master') } + + it "includes branches matching a protected branch wildcard" do + expect(project.open_branches.map(&:name)).to include('feature') + + create(:protected_branch, name: 'feat*', project: project) + + expect(Project.find(project.id).open_branches.map(&:name)).to include('feature') + end end describe '#star_count' do @@ -553,6 +657,21 @@ describe Project, models: true do end end + context 'repository storage by default' do + let(:project) { create(:empty_project) } + + subject { project.repository_storage } + + before do + storages = { 'alternative_storage' => '/some/path' } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + stub_application_setting(repository_storage: 'alternative_storage') + allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(true) + end + + it { is_expected.to eq('alternative_storage') } + end + context 'shared runners by default' do let(:project) { create(:empty_project) } @@ -708,12 +827,12 @@ describe Project, models: true do expect(gitlab_shell).to receive(:mv_repository). ordered. - with("#{ns}/foo", "#{ns}/#{project.path}"). + with(project.repository_storage_path, "#{ns}/foo", "#{ns}/#{project.path}"). and_return(true) expect(gitlab_shell).to receive(:mv_repository). ordered. - with("#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki"). + with(project.repository_storage_path, "#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki"). and_return(true) expect_any_instance_of(SystemHooksService). @@ -805,7 +924,7 @@ describe Project, models: true do context 'using a regular repository' do it 'creates the repository' do expect(shell).to receive(:add_repository). - with(project.path_with_namespace). + with(project.repository_storage_path, project.path_with_namespace). and_return(true) expect(project.repository).to receive(:after_create) @@ -815,7 +934,7 @@ describe Project, models: true do it 'adds an error if the repository could not be created' do expect(shell).to receive(:add_repository). - with(project.path_with_namespace). + with(project.repository_storage_path, project.path_with_namespace). and_return(false) expect(project.repository).not_to receive(:after_create) @@ -838,15 +957,67 @@ describe Project, models: true do describe '#protected_branch?' do let(:project) { create(:empty_project) } - it 'returns true when a branch is a protected branch' do + it 'returns true when the branch matches a protected branch via direct match' do project.protected_branches.create!(name: 'foo') expect(project.protected_branch?('foo')).to eq(true) end - it 'returns false when a branch is not a protected branch' do + it 'returns true when the branch matches a protected branch via wildcard match' do + project.protected_branches.create!(name: 'production/*') + + expect(project.protected_branch?('production/some-branch')).to eq(true) + end + + it 'returns false when the branch does not match a protected branch via direct match' do expect(project.protected_branch?('foo')).to eq(false) end + + it 'returns false when the branch does not match a protected branch via wildcard match' do + project.protected_branches.create!(name: 'production/*') + + expect(project.protected_branch?('staging/some-branch')).to eq(false) + end + end + + describe "#developers_can_push_to_protected_branch?" do + let(:project) { create(:empty_project) } + + context "when the branch matches a protected branch via direct match" do + it "returns true if 'Developers can Push' is turned on" do + create(:protected_branch, name: "production", project: project, developers_can_push: true) + + expect(project.developers_can_push_to_protected_branch?('production')).to be true + end + + it "returns false if 'Developers can Push' is turned off" do + create(:protected_branch, name: "production", project: project, developers_can_push: false) + + expect(project.developers_can_push_to_protected_branch?('production')).to be false + end + end + + context "when the branch matches a protected branch via wilcard match" do + it "returns true if 'Developers can Push' is turned on" do + create(:protected_branch, name: "production/*", project: project, developers_can_push: true) + + expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true + end + + it "returns false if 'Developers can Push' is turned off" do + create(:protected_branch, name: "production/*", project: project, developers_can_push: false) + + expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false + end + end + + context "when the branch does not match a protected branch" do + it "returns false" do + create(:protected_branch, name: "production/*", project: project, developers_can_push: true) + + expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false + end + end end describe '#container_registry_path_with_namespace' do diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index b523834c6e9..8bf0d24a128 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe ProtectedBranch, models: true do + subject { build_stubbed(:protected_branch) } + describe 'Associations' do it { is_expected.to belong_to(:project) } end @@ -12,4 +14,127 @@ describe ProtectedBranch, models: true do it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:name) } end + + describe "#matches?" do + context "when the protected branch setting is not a wildcard" do + let(:protected_branch) { build(:protected_branch, name: "production/some-branch") } + + it "returns true for branch names that are an exact match" do + expect(protected_branch.matches?("production/some-branch")).to be true + end + + it "returns false for branch names that are not an exact match" do + expect(protected_branch.matches?("staging/some-branch")).to be false + end + end + + context "when the protected branch name contains wildcard(s)" do + context "when there is a single '*'" do + let(:protected_branch) { build(:protected_branch, name: "production/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("production/some-branch")).to be true + expect(protected_branch.matches?("production/")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("staging/some-branch")).to be false + expect(protected_branch.matches?("production")).to be false + end + end + + context "when the wildcard contains regex symbols other than a '*'" do + let(:protected_branch) { build(:protected_branch, name: "pro.duc.tion/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("pro.duc.tion/some-branch")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("production/some-branch")).to be false + expect(protected_branch.matches?("proXducYtion/some-branch")).to be false + end + end + + context "when there are '*'s at either end" do + let(:protected_branch) { build(:protected_branch, name: "*/production/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("gitlab/production/some-branch")).to be true + expect(protected_branch.matches?("/production/some-branch")).to be true + expect(protected_branch.matches?("gitlab/production/")).to be true + expect(protected_branch.matches?("/production/")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("gitlabproductionsome-branch")).to be false + expect(protected_branch.matches?("production/some-branch")).to be false + expect(protected_branch.matches?("gitlab/production")).to be false + expect(protected_branch.matches?("production")).to be false + end + end + + context "when there are arbitrarily placed '*'s" do + let(:protected_branch) { build(:protected_branch, name: "pro*duction/*/gitlab/*") } + + it "returns true for branch names matching the wildcard" do + expect(protected_branch.matches?("production/some-branch/gitlab/second-branch")).to be true + expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/second-branch")).to be true + expect(protected_branch.matches?("proXYZduction/gitlab/gitlab/gitlab")).to be true + expect(protected_branch.matches?("proXYZduction//gitlab/")).to be true + expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/")).to be true + expect(protected_branch.matches?("proXYZduction//gitlab/some-branch")).to be true + end + + it "returns false for branch names not matching the wildcard" do + expect(protected_branch.matches?("production/some-branch/not-gitlab/second-branch")).to be false + expect(protected_branch.matches?("prodXYZuction/some-branch/gitlab/second-branch")).to be false + expect(protected_branch.matches?("proXYZduction/gitlab/some-branch/gitlab")).to be false + expect(protected_branch.matches?("proXYZduction/gitlab//")).to be false + expect(protected_branch.matches?("proXYZduction/gitlab/")).to be false + expect(protected_branch.matches?("proXYZduction//some-branch/gitlab")).to be false + end + end + end + end + + describe "#matching" do + context "for direct matches" do + it "returns a list of protected branches matching the given branch name" do + production = create(:protected_branch, name: "production") + staging = create(:protected_branch, name: "staging") + + expect(ProtectedBranch.matching("production")).to include(production) + expect(ProtectedBranch.matching("production")).not_to include(staging) + end + + it "accepts a list of protected branches to search from, so as to avoid a DB call" do + production = build(:protected_branch, name: "production") + staging = build(:protected_branch, name: "staging") + + expect(ProtectedBranch.matching("production")).to be_empty + expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).to include(production) + expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).not_to include(staging) + end + end + + context "for wildcard matches" do + it "returns a list of protected branches matching the given branch name" do + production = create(:protected_branch, name: "production/*") + staging = create(:protected_branch, name: "staging/*") + + expect(ProtectedBranch.matching("production/some-branch")).to include(production) + expect(ProtectedBranch.matching("production/some-branch")).not_to include(staging) + end + + it "accepts a list of protected branches to search from, so as to avoid a DB call" do + production = build(:protected_branch, name: "production/*") + staging = build(:protected_branch, name: "staging/*") + + expect(ProtectedBranch.matching("production/some-branch")).to be_empty + expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).to include(production) + expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).not_to include(staging) + end + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index d8350000bf6..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 @@ -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 } @@ -531,8 +531,6 @@ describe Repository, models: true do describe '#expire_cache' do it 'expires all caches' do expect(repository).to receive(:expire_branch_cache) - expect(repository).to receive(:expire_branch_count_cache) - expect(repository).to receive(:expire_tag_count_cache) repository.expire_cache end @@ -857,7 +855,6 @@ describe Repository, models: true do repository.after_create end - end describe "#copy_gitattributes" do @@ -1055,12 +1052,14 @@ describe Repository, models: true do let(:cache) { repository.send(:cache) } it 'builds the caches if they do not already exist' do + cache_keys = repository.cache_keys + repository.cache_keys_for_branches_and_tags + expect(cache).to receive(:exist?). - exactly(repository.cache_keys.length). + exactly(cache_keys.length). times. and_return(false) - repository.cache_keys.each do |key| + cache_keys.each do |key| expect(repository).to receive(key) end @@ -1068,12 +1067,14 @@ describe Repository, models: true do end it 'does not build any caches that already exist' do + cache_keys = repository.cache_keys + repository.cache_keys_for_branches_and_tags + expect(cache).to receive(:exist?). - exactly(repository.cache_keys.length). + exactly(cache_keys.length). times. and_return(true) - repository.cache_keys.each do |key| + cache_keys.each do |key| expect(repository).not_to receive(key) end @@ -1116,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) 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/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 789816bf2c7..0621c6a06ce 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -72,7 +72,7 @@ describe Snippet, models: true do end end - describe '#search_code' do + describe '.search_code' do let(:snippet) { create(:snippet, content: 'class Foo; end') } it 'returns snippets with matching content' do @@ -88,6 +88,46 @@ describe Snippet, models: true do end end + describe '.accessible_to' do + let(:author) { create(:author) } + let(:project) { create(:empty_project) } + + let!(:public_snippet) { create(:snippet, :public) } + let!(:internal_snippet) { create(:snippet, :internal) } + let!(:private_snippet) { create(:snippet, :private, author: author) } + + let!(:project_public_snippet) { create(:snippet, :public, project: project) } + let!(:project_internal_snippet) { create(:snippet, :internal, project: project) } + let!(:project_private_snippet) { create(:snippet, :private, project: project) } + + it 'returns only public snippets when user is blank' do + expect(described_class.accessible_to(nil)).to match_array [public_snippet, project_public_snippet] + end + + it 'returns only public, and internal snippets for regular users' do + user = create(:user) + + expect(described_class.accessible_to(user)).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns public, internal snippets and project private snippets for project members' do + member = create(:user) + project.team << [member, :developer] + + expect(described_class.accessible_to(member)).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + + it 'returns private snippets where the user is the author' do + expect(described_class.accessible_to(author)).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns all snippets when for admins' do + admin = create(:admin) + + expect(described_class.accessible_to(admin)).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + end + describe '#participants' do let(:project) { create(:project, :public) } let(:snippet) { create(:snippet, content: 'foo', project: project) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 73bee535fe3..3984b30ddf8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -31,6 +31,26 @@ describe User, models: true do it { is_expected.to have_many(:spam_logs).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) } + + describe '#group_members' do + it 'does not include group memberships for which user is a requester' do + user = create(:user) + group = create(:group, :public) + group.request_access(user) + + expect(user.group_members).to be_empty + end + end + + describe '#project_members' do + it 'does not include project memberships for which user is a requester' do + user = create(:user) + project = create(:project, :public) + project.request_access(user) + + expect(user.project_members).to be_empty + end + end end describe 'validations' do @@ -426,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 @@ -448,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 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 2e65e7f1920..72a6d45f47d 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -17,7 +17,7 @@ describe API::API, api: true do it "returns an array of award_emoji" do get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(award_emoji.name) end @@ -25,7 +25,7 @@ describe API::API, api: true do it "should return a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/12345/award_emoji", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -33,7 +33,7 @@ describe API::API, api: true do it "returns an array of award_emoji" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(downvote.name) end @@ -45,7 +45,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -56,19 +56,18 @@ describe API::API, api: true do it 'returns an array of award emoji' do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(rocket.name) 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 get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(award_emoji.name) expect(json_response['awardable_id']).to eq(issue.id) expect(json_response['awardable_type']).to eq("Issue") @@ -77,7 +76,7 @@ describe API::API, api: true do it "returns a 404 error if the award is not found" do get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -85,7 +84,7 @@ describe API::API, api: true do it 'returns the award emoji' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(downvote.name) expect(json_response['awardable_id']).to eq(merge_request.id) expect(json_response['awardable_type']).to eq("MergeRequest") @@ -98,7 +97,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -109,7 +108,7 @@ describe API::API, api: true do it 'returns an award emoji' do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).not_to be_an Array expect(json_response['name']).to eq(rocket.name) end @@ -120,7 +119,7 @@ describe API::API, api: true do it "creates a new award emoji" do post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('blowfish') expect(json_response['user']['username']).to eq(user.username) end @@ -128,13 +127,13 @@ describe API::API, api: true do it "should return a 400 bad request error if the name is not given" do post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 401 unauthorized error if the user is not authenticated" do post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -145,7 +144,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' end.to change { note.award_emoji.count }.from(0).to(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['user']['username']).to eq(user.username) end end @@ -157,13 +156,13 @@ describe API::API, api: true do delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) end.to change { issue.award_emoji.count }.from(1).to(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'returns a 404 error when the award emoji can not be found' do delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -173,13 +172,13 @@ describe API::API, api: true do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) end.to change { merge_request.award_emoji.count }.from(1).to(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -192,7 +191,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) end.to change { note.award_emoji.count }.from(1).to(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 55582aa53d2..b11ca26ee68 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -17,7 +17,7 @@ describe API::API, api: true do project.repository.expire_cache get api("/projects/#{project.id}/repository/branches", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array branch_names = json_response.map { |x| x['name'] } expect(branch_names).to match_array(project.repository.branch_names) @@ -27,7 +27,7 @@ describe API::API, api: true do describe "GET /projects/:id/repository/branches/:branch" do it "should return the branch information for a single branch" do get api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) @@ -36,19 +36,19 @@ describe API::API, api: true do it "should return a 403 error if guest" do get api("/projects/#{project.id}/repository/branches", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return a 404 error if branch is not available" do get api("/projects/#{project.id}/repository/branches/unknown", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe "PUT /projects/:id/repository/branches/:branch/protect" do it "should protect a single branch" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) @@ -57,25 +57,25 @@ describe API::API, api: true do it "should return a 404 error if branch not found" do put api("/projects/#{project.id}/repository/branches/unknown/protect", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 403 error if guest" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return success when protect branch again" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end describe "PUT /projects/:id/repository/branches/:branch/unprotect" do it "should unprotect a single branch" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) @@ -84,13 +84,13 @@ describe API::API, api: true do it "should return success when unprotect branch" do put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return success when unprotect branch again" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -100,7 +100,7 @@ describe API::API, api: true do branch_name: 'feature1', ref: branch_sha - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('feature1') expect(json_response['commit']['id']).to eq(branch_sha) @@ -110,14 +110,14 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/branches", user2), branch_name: branch_name, ref: branch_sha - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should return 400 if branch name is invalid' do post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new design', ref: branch_sha - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Branch name is invalid') end @@ -125,12 +125,12 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design1', ref: branch_sha - expect(response.status).to eq(201) + expect(response).to have_http_status(201) post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design1', ref: branch_sha - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Branch already exists') end @@ -138,7 +138,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design3', ref: 'foo' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Invalid reference name') end end @@ -150,25 +150,25 @@ describe API::API, api: true do it "should remove branch" do delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['branch_name']).to eq(branch_name) end it 'should return 404 if branch not exists' do delete api("/projects/#{project.id}/repository/branches/foobar", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should remove protected branch" do project.protected_branches.create(name: branch_name) delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('Protected branch cant be removed') end it "should not remove HEAD branch" do delete api("/projects/#{project.id}/repository/branches/master", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('Cannot remove HEAD branch') end end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 47e9253a10c..f5b39c3d698 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -19,7 +19,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return project builds' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array end @@ -32,7 +32,7 @@ describe API::API, api: true do let(:query) { 'scope=pending' } it do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array end end @@ -41,7 +41,7 @@ describe API::API, api: true do let(:query) { 'scope[0]=pending&scope[1]=running' } it do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array end end @@ -49,7 +49,7 @@ describe API::API, api: true do context 'respond 400 when scope contains invalid state' do let(:query) { 'scope[0]=pending&scope[1]=unknown_status' } - it { expect(response.status).to eq(400) } + it { expect(response).to have_http_status(400) } end end @@ -57,29 +57,66 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return project builds' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end describe 'GET /projects/:id/repository/commits/:sha/builds' do - before do - project.ensure_pipeline(pipeline.sha, 'master') - get api("/projects/#{project.id}/repository/commits/#{pipeline.sha}/builds", api_user) - end + context 'when commit does not exist in repository' do + before do + get api("/projects/#{project.id}/repository/commits/1a271fd1/builds", api_user) + end - context 'authorized user' do - it 'should return project builds for specific commit' do - expect(response.status).to eq(200) - expect(json_response).to be_an Array + it 'responds with 404' do + expect(response).to have_http_status(404) end end - context 'unauthorized user' do - let(:api_user) { nil } + context 'when commit exists in repository' do + context 'when user is authorized' do + context 'when pipeline has builds' do + before do + create(:ci_pipeline, project: project, sha: project.commit.id) + create(:ci_build, pipeline: pipeline) + create(:ci_build) + + get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user) + end + + it 'should return project builds for specific commit' do + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq 2 + end + end - it 'should not return project builds' do - expect(response.status).to eq(401) + context 'when pipeline has no builds' do + before do + branch_head = project.commit('feature').id + get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user) + end + + it 'returns an empty array' do + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response).to be_empty + end + end + end + + context 'when user is not authorized' do + before do + create(:ci_pipeline, project: project, sha: project.commit.id) + create(:ci_build, pipeline: pipeline) + + get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil) + end + + it 'should not return project builds' do + expect(response).to have_http_status(401) + expect(json_response.except('message')).to be_empty + end end end end @@ -89,7 +126,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return specific build data' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq('test') end end @@ -98,7 +135,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return specific build data' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -116,7 +153,7 @@ describe API::API, api: true do end it 'should return specific build artifacts' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.headers).to include(download_headers) end end @@ -125,13 +162,13 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return specific build artifacts' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end it 'should not return build artifacts if not uploaded' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -142,7 +179,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return specific build trace' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.body).to eq(build.trace) end end @@ -151,7 +188,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not return specific build trace' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -162,7 +199,7 @@ describe API::API, api: true do context 'authorized user' do context 'user with :update_build persmission' do it 'should cancel running or pending build' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(project.builds.first.status).to eq('canceled') end end @@ -171,7 +208,7 @@ describe API::API, api: true do let(:api_user) { user2 } it 'should not cancel build' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -180,7 +217,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not cancel build' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -193,7 +230,7 @@ describe API::API, api: true do context 'authorized user' do context 'user with :update_build permission' do it 'should retry non-running build' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(project.builds.first.status).to eq('canceled') expect(json_response['status']).to eq('pending') end @@ -203,7 +240,7 @@ describe API::API, api: true do let(:api_user) { user2 } it 'should not retry build' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -212,7 +249,7 @@ describe API::API, api: true do let(:api_user) { nil } it 'should not retry build' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 298cdbad329..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" } @@ -41,7 +40,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter) } it 'returns latest commit statuses' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id) @@ -54,7 +53,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter), all: 1 } it 'returns all commit statuses' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status1.id, status2.id, @@ -67,7 +66,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter), ref: 'develop' } it 'returns latest commit statuses for specific ref' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status3.id, status5.id) @@ -78,7 +77,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, reporter), name: 'coverage' } it 'return latest commit statuses for specific name' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status4.id, status5.id) @@ -101,7 +100,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url, guest) } it "should not return project commits" do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -109,7 +108,7 @@ describe API::CommitStatuses, api: true do before { get api(get_url) } it "should not return project commits" do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -122,7 +121,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer), state: 'success' } it 'creates commit status' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('default') @@ -141,7 +140,7 @@ describe API::CommitStatuses, api: true do end it 'creates commit status' do - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') @@ -155,7 +154,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer), state: 'invalid' } it 'does not create commit status' do - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -163,7 +162,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer) } it 'does not create commit status' do - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -172,7 +171,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, developer), state: 'running' } it 'returns not found error' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -181,7 +180,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, reporter) } it 'should not create commit status' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -189,7 +188,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url, guest) } it 'should not create commit status' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -197,7 +196,7 @@ describe API::CommitStatuses, api: true do before { post api(post_url) } it 'should not create commit status' do - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 6fc38f537d3..5219c808791 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -19,7 +19,7 @@ describe API::API, api: true do it "should return project commits" do get api("/projects/#{project.id}/repository/commits", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['id']).to eq(project.repository.commit.id) @@ -29,7 +29,7 @@ describe API::API, api: true do context "unauthorized user" do it "should not return project commits" do get api("/projects/#{project.id}/repository/commits") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -63,7 +63,7 @@ describe API::API, api: true do it "should return an invalid parameter error message" do get api("/projects/#{project.id}/repository/commits?since=invalid-date", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format" end end @@ -73,26 +73,26 @@ describe API::API, api: true do context "authorized user" do it "should return a commit by sha" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(project.repository.commit.id) expect(json_response['title']).to eq(project.repository.commit.title) end it "should return a 404 error if not found" do get api("/projects/#{project.id}/repository/commits/invalid_sha", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return nil for commit without CI" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['status']).to be_nil end it "should return status for CI" do pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master') get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['status']).to eq(pipeline.status) end end @@ -100,7 +100,7 @@ describe API::API, api: true do context "unauthorized user" do it "should not return the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -111,7 +111,7 @@ describe API::API, api: true do it "should return the diff of the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to be >= 1 @@ -120,14 +120,14 @@ describe API::API, api: true do it "should return a 404 error if invalid commit" do get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "unauthorized user" do it "should not return the diff of the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -136,7 +136,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return merge_request comments' do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq('a comment on a commit') @@ -145,14 +145,14 @@ describe API::API, api: true do it 'should return a 404 error if merge_request_id not found' do get api("/projects/#{project.id}/repository/commits/1234ab/comments", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'unauthorized user' do it 'should not return the diff of the selected commit' do get api("/projects/#{project.id}/repository/commits/1234ab/comments") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -161,7 +161,7 @@ describe API::API, api: true do context 'authorized user' do it 'should return comment' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to be_nil expect(json_response['line']).to be_nil @@ -170,7 +170,7 @@ describe API::API, api: true do it 'should return the inline comment' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to eq(project.repository.commit.diffs.first.new_path) expect(json_response['line']).to eq(7) @@ -179,19 +179,19 @@ describe API::API, api: true do it 'should return 400 if note is missing' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 404 if note is attached to non existent commit' do post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'unauthorized user' do it 'should not return the diff of the selected commit' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 0afc3e79339..5262a623761 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -7,25 +7,24 @@ 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 - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end describe "when token invalid" do it "returns authentication error" do get api("/user"), access_token: "123a" - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end describe "authorization by private token" do it "returns authentication success" do get api("/user", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 8efa09f75fd..2e5448143d5 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -16,7 +16,7 @@ describe API::API, api: true do } get api("/projects/#{project.id}/repository/files", user), params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) expect(json_response['file_name']).to eq('popen.rb') expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') @@ -25,7 +25,7 @@ describe API::API, api: true do it "should return a 400 bad request if no params given" do get api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 404 if such file does not exist" do @@ -35,7 +35,7 @@ describe API::API, api: true do } get api("/projects/#{project.id}/repository/files", user), params - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -51,13 +51,13 @@ describe API::API, api: true do it "should create a new file in project repo" do post api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['file_path']).to eq('newfile.rb') end it "should return a 400 bad request if no params given" do post api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 if editor fails to create file" do @@ -65,7 +65,7 @@ describe API::API, api: true do and_return(false) post api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -81,13 +81,13 @@ describe API::API, api: true do it "should update existing file in project repo" do put api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) end it "should return a 400 bad request if no params given" do put api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -102,20 +102,20 @@ describe API::API, api: true do it "should delete existing file in project repo" do delete api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) end it "should return a 400 bad request if no params given" do delete api("/projects/#{project.id}/repository/files", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 if fails to create file" do allow_any_instance_of(Repository).to receive(:remove_file).and_return(false) delete api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -143,7 +143,7 @@ describe API::API, api: true do it "remains unchanged" do get api("/projects/#{project.id}/repository/files", user), get_params - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['file_path']).to eq(file_path) expect(json_response['file_name']).to eq(file_path) expect(json_response['content']).to eq(put_params[:content]) diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index fa94e03ec32..a9f5aa924b7 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -22,7 +22,7 @@ describe API::API, api: true do context 'when authenticated' do it 'should fork if user has sufficient access to project' do post api("/projects/fork/#{project.id}", user2) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq(project.name) expect(json_response['path']).to eq(project.path) expect(json_response['owner']['id']).to eq(user2.id) @@ -32,7 +32,7 @@ describe API::API, api: true do it 'should fork if user is admin' do post api("/projects/fork/#{project.id}", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq(project.name) expect(json_response['path']).to eq(project.path) expect(json_response['owner']['id']).to eq(admin.id) @@ -42,20 +42,20 @@ describe API::API, api: true do it 'should fail on missing project access for the project to fork' do post api("/projects/fork/#{project.id}", user3) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'should fail if forked project exists in the user namespace' do post api("/projects/fork/#{project.id}", user) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']['name']).to eq(['has already been taken']) expect(json_response['message']['path']).to eq(['has already been taken']) end it 'should fail if project to fork from does not exist' do post api('/projects/fork/424242', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end end @@ -63,7 +63,7 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do post api("/projects/fork/#{project.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') end end diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index 02553d0f8e2..52f9e7d4681 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -31,7 +31,7 @@ describe API::API, api: true do it "each user: should return an array of members groups of group3" do [owner, master, developer, reporter, guest].each do |user| get api("/groups/#{group_with_members.id}/members", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(5) expect(json_response.find { |e| e['id'] == owner.id }['access_level']).to eq(GroupMember::OWNER) @@ -45,7 +45,7 @@ describe API::API, api: true do it 'users not part of the group should get access error' do get api("/groups/#{group_with_members.id}/members", stranger) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -54,7 +54,7 @@ describe API::API, api: true do context "when not a member of the group" do it "should not add guest as member of group_no_members when adding being done by person outside the group" do post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -66,7 +66,7 @@ describe API::API, api: true do post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER end.to change { group_no_members.members.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq(new_user.name) expect(json_response['access_level']).to eq(GroupMember::MASTER) end @@ -78,27 +78,27 @@ describe API::API, api: true do post api("/groups/#{group_with_members.id}/members", guest), user_id: new_user.id, access_level: GroupMember::MASTER end.not_to change { group_with_members.members.count } - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return error if member already exists" do post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER - expect(response.status).to eq(409) + expect(response).to have_http_status(409) end it "should return a 400 error when user id is not given" do post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 error when access level is not given" do post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error when access level is not known" do post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end end @@ -110,7 +110,7 @@ describe API::API, api: true do api("/groups/#{group_no_members.id}/members/#{developer.id}", owner), access_level: GroupMember::MASTER ) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -122,7 +122,7 @@ describe API::API, api: true do access_level: GroupMember::MASTER ) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) get api("/groups/#{group_with_members.id}/members", owner) json_reporter = json_response.find do |e| @@ -139,7 +139,7 @@ describe API::API, api: true do access_level: GroupMember::MASTER ) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) get api("/groups/#{group_with_members.id}/members", owner) json_developer = json_response.find do |e| @@ -153,7 +153,7 @@ describe API::API, api: true do put( api("/groups/#{group_with_members.id}/members/#{master.id}", owner) ) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return a 422 error when access level is not known' do @@ -161,7 +161,7 @@ describe API::API, api: true do api("/groups/#{group_with_members.id}/members/#{master.id}", owner), access_level: 1234 ) - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end end @@ -172,7 +172,7 @@ describe API::API, api: true do random_user = create(:user) delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -182,17 +182,17 @@ describe API::API, api: true do delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) end.to change { group_with_members.members.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return a 404 error when user id is not known" do delete api("/groups/#{group_with_members.id}/members/1328", owner) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not allow guest to modify group members" do delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 7ecefce80d6..c2c94040ece 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -23,14 +23,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/groups") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated as user" do it "normal user: should return an array of groups of user1" do get api("/groups", user1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(group1.name) @@ -40,7 +40,7 @@ describe API::API, api: true do context "when authenticated as admin" do it "admin: should return an array of all groups" do get api("/groups", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -49,53 +49,68 @@ 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.status).to eq(200) - json_response['name'] == group1.name + + expect(response).to have_http_status(200) + 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 get api("/groups/1328", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not return a group not attached to user1" do get api("/groups/#{group2.id}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "when authenticated as admin" do it "should return any existing group" do get api("/groups/#{group2.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(group2.name) end it "should not return a non existing group" do get api("/groups/1328", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'when using group path in URL' do it 'should return any existing group' do get api("/groups/#{group1.path}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(group1.name) end it 'should not return a non existing group' do get api('/groups/unknown', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not return a group not attached to user1' do get api("/groups/#{group2.path}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -107,14 +122,14 @@ describe API::API, api: true do it 'updates the group' do put api("/groups/#{group1.id}", user1), name: new_group_name - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(new_group_name) end it 'returns 404 for a non existing group' do put api('/groups/1328', user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -122,7 +137,7 @@ describe API::API, api: true do it 'updates the group' do put api("/groups/#{group1.id}", admin), name: new_group_name - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(new_group_name) end end @@ -131,7 +146,7 @@ describe API::API, api: true do it 'does not updates the group' do put api("/groups/#{group1.id}", user2), name: new_group_name - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -139,7 +154,7 @@ describe API::API, api: true do it 'returns 404 when trying to update the group' do put api("/groups/#{group2.id}", user1), name: new_group_name - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -149,7 +164,7 @@ describe API::API, api: true do it "should return the group's projects" do get api("/groups/#{group1.id}/projects", user1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) @@ -157,13 +172,13 @@ describe API::API, api: true do it "should not return a non existing group" do get api("/groups/1328/projects", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not return a group not attached to user1" do get api("/groups/#{group2.id}/projects", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should only return projects to which user has access" do @@ -171,7 +186,7 @@ describe API::API, api: true do get api("/groups/#{group1.id}/projects", user3) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project3.name) end @@ -180,14 +195,14 @@ describe API::API, api: true do context "when authenticated as admin" do it "should return any existing group" do get api("/groups/#{group2.id}/projects", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project2.name) end it "should not return a non existing group" do get api("/groups/1328/projects", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -195,20 +210,20 @@ describe API::API, api: true do it 'should return any existing group' do get api("/groups/#{group1.path}/projects", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) end it 'should not return a non existing group' do get api('/groups/unknown/projects', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not return a group not attached to user1' do get api("/groups/#{group2.path}/projects", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -217,30 +232,30 @@ describe API::API, api: true do context "when authenticated as user without group permissions" do it "should not create group" do post api("/groups", user1), attributes_for(:group) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context "when authenticated as user with group permissions" do it "should create group" do post api("/groups", user3), attributes_for(:group) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it "should not create group, duplicate" do post api("/groups", user3), { name: 'Duplicate Test', path: group2.path } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(response.message).to eq("Bad Request") end it "should return 400 bad request error if name not given" do post api("/groups", user3), { path: group2.path } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 bad request error if path not given" do post api("/groups", user3), { name: 'test' } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end end @@ -249,37 +264,37 @@ describe API::API, api: true do context "when authenticated as user" do it "should remove group" do delete api("/groups/#{group1.id}", user1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should not remove a group if not an owner" do user4 = create(:user) group1.add_master(user4) delete api("/groups/#{group1.id}", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should not remove a non existing group" do delete api("/groups/1328", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should not remove a group not attached to user1" do delete api("/groups/#{group2.id}", user1) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "when authenticated as admin" do it "should remove any existing group" do delete api("/groups/#{group2.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should not remove a non existing group" do delete api("/groups/1328", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -295,14 +310,14 @@ describe API::API, api: true do context "when authenticated as user" do it "should not transfer project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context "when authenticated as admin" do it "should transfer project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 22802dd0e05..e567d36afa8 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -11,7 +11,7 @@ describe API::API, api: true do it do get api("/internal/check"), secret_token: secret_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['api_version']).to eq(API::API.version) end end @@ -23,7 +23,7 @@ describe API::API, api: true do it do get api("/internal/broadcast_message"), secret_token: secret_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["message"]).to eq(broadcast_message.message) end end @@ -32,7 +32,7 @@ describe API::API, api: true do it do get api("/internal/broadcast_message"), secret_token: secret_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_empty end end @@ -42,7 +42,7 @@ describe API::API, api: true do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(user.name) end @@ -61,7 +61,7 @@ describe API::API, api: true do push(key, project_wiki) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -70,8 +70,9 @@ describe API::API, api: true do it do pull(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) end end @@ -79,8 +80,9 @@ describe API::API, api: true do it do push(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy + expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) end end end @@ -94,7 +96,7 @@ describe API::API, api: true do it do pull(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -103,7 +105,7 @@ describe API::API, api: true do it do push(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -120,7 +122,7 @@ describe API::API, api: true do it do pull(key, personal_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -129,7 +131,7 @@ describe API::API, api: true do it do push(key, personal_project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -147,7 +149,7 @@ describe API::API, api: true do it do pull(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -156,7 +158,7 @@ describe API::API, api: true do it do push(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -173,7 +175,7 @@ describe API::API, api: true do it do archive(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -182,7 +184,7 @@ describe API::API, api: true do it do archive(key, project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -192,7 +194,7 @@ describe API::API, api: true do it do pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists')) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -201,30 +203,90 @@ describe API::API, api: true do it do pull(OpenStruct.new(id: 0), project) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["status"]).to be_falsey end end + + context 'ssh access has been disabled' do + before do + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, 'http') + end + + it 'rejects the SSH push' do + push(key, project) + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over SSH is not allowed' + end + + it 'rejects the SSH pull' do + pull(key, project) + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over SSH is not allowed' + end + end + + context 'http access has been disabled' do + before do + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, 'ssh') + end + + it 'rejects the HTTP push' do + push(key, project, 'http') + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over HTTP is not allowed' + end + + it 'rejects the HTTP pull' do + pull(key, project, 'http') + + expect(response.status).to eq(200) + expect(json_response['status']).to be_falsey + expect(json_response['message']).to eq 'Git access over HTTP is not allowed' + end + end + + context 'web actions are always allowed' do + it 'allows WEB push' do + settings = ::ApplicationSetting.create_from_defaults + settings.update_attribute(:enabled_git_access_protocol, 'ssh') + project.team << [user, :developer] + push(key, project, 'web') + + expect(response.status).to eq(200) + expect(json_response['status']).to be_truthy + end + end end - def pull(key, project) + def pull(key, project, protocol = 'ssh') post( api("/internal/allowed"), key_id: key.id, project: project.path_with_namespace, action: 'git-upload-pack', - secret_token: secret_token + secret_token: secret_token, + protocol: protocol ) end - def push(key, project) + def push(key, project, protocol = 'ssh') post( api("/internal/allowed"), changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, project: project.path_with_namespace, action: 'git-receive-pack', - secret_token: secret_token + secret_token: secret_token, + protocol: protocol ) end @@ -235,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 diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index edec53dad89..6adccb4ebae 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -51,14 +51,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/issues") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated" do it "should return an array of issues" do get api("/issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(issue.title) end @@ -72,7 +72,7 @@ describe API::API, api: true do it 'should return an array of closed issues' do get api('/issues?state=closed', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -80,7 +80,7 @@ describe API::API, api: true do it 'should return an array of opened issues' do get api('/issues?state=opened', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(issue.id) @@ -88,7 +88,7 @@ describe API::API, api: true do it 'should return an array of all issues' do get api('/issues?state=all', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['id']).to eq(issue.id) @@ -97,7 +97,7 @@ describe API::API, api: true do it 'should return an array of labeled issues' do get api("/issues?labels=#{label.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -105,7 +105,7 @@ describe API::API, api: true do it 'should return an array of labeled issues when at least one label matches' do get api("/issues?labels=#{label.title},foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -113,14 +113,14 @@ describe API::API, api: true do it 'should return an empty array if no issue matches labels' do get api('/issues?labels=foo,bar', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an array of labeled issues matching given state' do get api("/issues?labels=#{label.title}&state=opened", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -129,7 +129,7 @@ describe API::API, api: true do it 'should return an empty array if no issue matches labels and state filters' do get api("/issues?labels=#{label.title}&state=closed", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -179,7 +179,7 @@ describe API::API, api: true do it 'returns group issues without confidential issues for non project members' do get api(base_url, non_member) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(group_issue.title) @@ -188,7 +188,7 @@ describe API::API, api: true do it 'returns group confidential issues for author' do get api(base_url, author) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -196,7 +196,7 @@ describe API::API, api: true do it 'returns group confidential issues for assignee' do get api(base_url, assignee) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -204,7 +204,7 @@ describe API::API, api: true do it 'returns group issues with confidential issues for project members' do get api(base_url, user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -212,7 +212,7 @@ describe API::API, api: true do it 'returns group confidential issues for admin' do get api(base_url, admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -220,7 +220,7 @@ describe API::API, api: true do it 'returns an array of labeled group issues' do get api("#{base_url}?labels=#{group_label.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([group_label.title]) @@ -229,7 +229,7 @@ describe API::API, api: true do it 'returns an array of labeled group issues where all labels match' do get api("#{base_url}?labels=#{group_label.title},foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -237,7 +237,7 @@ describe API::API, api: true do it 'returns an empty array if no group issue matches labels' do get api("#{base_url}?labels=foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -245,7 +245,7 @@ describe API::API, api: true do it 'returns an empty array if no issue matches milestone' do get api("#{base_url}?milestone=#{group_empty_milestone.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -253,7 +253,7 @@ describe API::API, api: true do it 'returns an empty array if milestone does not exist' do get api("#{base_url}?milestone=foo", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -261,7 +261,7 @@ describe API::API, api: true do it 'returns an array of issues in given milestone' do get api("#{base_url}?milestone=#{group_milestone.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_issue.id) @@ -271,7 +271,7 @@ describe API::API, api: true do get api("#{base_url}?milestone=#{group_milestone.title}"\ '&state=closed', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_closed_issue.id) @@ -284,7 +284,7 @@ describe API::API, api: true do it 'should return project issues without confidential issues for non project members' do get api("#{base_url}/issues", non_member) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['title']).to eq(issue.title) @@ -292,7 +292,7 @@ describe API::API, api: true do it 'should return project issues without confidential issues for project members with guest role' do get api("#{base_url}/issues", guest) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['title']).to eq(issue.title) @@ -300,7 +300,7 @@ describe API::API, api: true do it 'should return project confidential issues for author' do get api("#{base_url}/issues", author) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -308,7 +308,7 @@ describe API::API, api: true do it 'should return project confidential issues for assignee' do get api("#{base_url}/issues", assignee) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -316,7 +316,7 @@ describe API::API, api: true do it 'should return project issues with confidential issues for project members' do get api("#{base_url}/issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -324,7 +324,7 @@ describe API::API, api: true do it 'should return project confidential issues for admin' do get api("#{base_url}/issues", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -332,7 +332,7 @@ describe API::API, api: true do it 'should return an array of labeled project issues' do get api("#{base_url}/issues?labels=#{label.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -340,7 +340,7 @@ describe API::API, api: true do it 'should return an array of labeled project issues when at least one label matches' do get api("#{base_url}/issues?labels=#{label.title},foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -348,28 +348,28 @@ describe API::API, api: true do it 'should return an empty array if no project issue matches labels' do get api("#{base_url}/issues?labels=foo,bar", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an empty array if no issue matches milestone' do get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an empty array if milestone does not exist' do get api("#{base_url}/issues?milestone=foo", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end it 'should return an array of issues in given milestone' do get api("#{base_url}/issues?milestone=#{title}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['id']).to eq(issue.id) @@ -379,7 +379,7 @@ describe API::API, api: true do it 'should return an array of issues matching state in milestone' do get api("#{base_url}/issues?milestone=#{milestone.title}"\ '&state=closed', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -390,7 +390,7 @@ describe API::API, api: true do it 'exposes known attributes' do get api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(issue.id) expect(json_response['iid']).to eq(issue.iid) expect(json_response['project_id']).to eq(issue.project.id) @@ -408,7 +408,7 @@ describe API::API, api: true do it "should return a project issue by id" do get api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(issue.title) expect(json_response['iid']).to eq(issue.iid) end @@ -423,44 +423,44 @@ describe API::API, api: true do it "should return 404 if issue id not found" do get api("/projects/#{project.id}/issues/54321", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context 'confidential issues' do it "should return 404 for non project members" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 for project members with guest role" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return confidential issue for project members" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end it "should return confidential issue for author" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end it "should return confidential issue for assignee" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end it "should return confidential issue for admin" do get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -471,7 +471,7 @@ describe API::API, api: true do it "should create a new project issue" do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['description']).to be_nil expect(json_response['labels']).to eq(['label', 'label2']) @@ -479,21 +479,25 @@ describe API::API, api: true do it "should return a 400 bad request if title not given" do post api("/projects/#{project.id}/issues", user), labels: 'label, label2' - expect(response.status).to eq(400) + 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.status).to eq(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 post api("/projects/#{project.id}/issues", user), title: 'g' * 256 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -505,7 +509,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2', created_at: creation_time - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) end end @@ -529,7 +533,7 @@ describe API::API, api: true do it "should not create a new project issue" do expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) spam_logs = SpamLog.all @@ -546,7 +550,7 @@ describe API::API, api: true do it "should update a project issue" do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -554,48 +558,53 @@ describe API::API, api: true do it "should return 404 error if issue id not found" do put api("/projects/#{project.id}/issues/44444", user), title: 'updated title' - expect(response.status).to eq(404) + 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.status).to eq(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 it "should return 403 for non project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member), title: 'updated title' - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return 403 for project members with guest role" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest), title: 'updated title' - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should update a confidential issue for project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end it "should update a confidential issue for author" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end it "should update a confidential issue for admin" do put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end end @@ -608,46 +617,43 @@ describe API::API, api: true do it 'should not update labels if not present' do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to eq([label.title]) end it 'should remove all labels' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to eq([]) end it 'should update labels' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'foo,bar' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'foo' 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.status).to eq(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' + 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 put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'g' * 256 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -658,7 +664,7 @@ describe API::API, api: true do it "should update a project issue" do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label2', state_event: "close" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label2' expect(json_response['state']).to eq "closed" @@ -669,7 +675,7 @@ describe API::API, api: true do update_time = 2.weeks.ago put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label3', state_event: 'close', updated_at: update_time - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label3' expect(Time.parse(json_response['updated_at'])).to be_within(1.second).of(update_time) @@ -680,12 +686,12 @@ describe API::API, api: true do describe "DELETE /projects/:id/issues/:issue_id" do it "rejects a non member from deleting an issue" do delete api("/projects/#{project.id}/issues/#{issue.id}", non_member) - expect(response.status).to be(403) + expect(response).to have_http_status(403) end it "rejects a developer from deleting an issue" do delete api("/projects/#{project.id}/issues/#{issue.id}", author) - expect(response.status).to be(403) + expect(response).to have_http_status(403) end context "when the user is project owner" do @@ -694,7 +700,7 @@ describe API::API, api: true do it "deletes the issue if an admin requests it" do delete api("/projects/#{project.id}/issues/#{issue.id}", owner) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['state']).to eq 'opened' end end @@ -708,7 +714,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: target_project.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['project_id']).to eq(target_project.id) end @@ -717,7 +723,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Cannot move issue to project it originates from!') end end @@ -727,7 +733,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: target_project2.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') end end @@ -736,7 +742,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", admin), to_project_id: target_project2.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['project_id']).to eq(target_project2.id) end @@ -745,7 +751,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/123/move", user), to_project_id: target_project.id - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -754,7 +760,7 @@ describe API::API, api: true do post api("/projects/123/issues/#{issue.id}/move", user), to_project_id: target_project.id - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -763,7 +769,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: 123 - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -772,26 +778,26 @@ describe API::API, api: true do it 'subscribes to an issue' do post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the issue is not found' do post api("/projects/#{project.id}/issues/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns 404 if the issue is confidential' do post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -799,26 +805,26 @@ describe API::API, api: true do it 'unsubscribes from an issue' do delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the issue is not found' do delete api("/projects/#{project.id}/issues/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns 404 if the issue is confidential' do delete api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index d2b87f88712..1861882d59e 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -14,14 +14,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/keys/#{key.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return 404 for non-existing key' do get api('/keys/999999', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -29,7 +29,7 @@ describe API::API, api: true do user.keys << key user.save get api("/keys/#{key.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(key.title) expect(json_response['user']['id']).to eq(user.id) expect(json_response['user']['username']).to eq(user.username) diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index b2c7f8d9acb..63636b4a1b6 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -11,11 +11,10 @@ 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) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.first['name']).to eq(label1.name) @@ -28,7 +27,7 @@ describe API::API, api: true do name: 'Foo', color: '#FFAABB', description: 'test' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('Foo') expect(json_response['color']).to eq('#FFAABB') expect(json_response['description']).to eq('test') @@ -36,29 +35,29 @@ 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.status).to eq(201) - expect(json_response['name']).to eq('Foo') + expect(json_response['name']).to eq('Foo & Bar') expect(json_response['color']).to eq('#FFAABB') expect(json_response['description']).to be_nil end it 'should return a 400 bad request if name not given' do post api("/projects/#{project.id}/labels", user), color: '#FFAABB' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return a 400 bad request if color not given' do post api("/projects/#{project.id}/labels", user), name: 'Foobar' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 for invalid color' do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAA' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -66,15 +65,15 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end it 'should return 400 for invalid name' do post api("/projects/#{project.id}/labels", user), - name: '?', + name: ',', color: '#FFAABB' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end @@ -82,7 +81,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFAABB' - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Label already exists') end end @@ -90,18 +89,18 @@ describe API::API, api: true do describe 'DELETE /projects/:id/labels' do it 'should return 200 for existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label1' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 for non existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label2' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Label Not Found') end it 'should return 400 for wrong parameters' do delete api("/projects/#{project.id}/labels", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -112,7 +111,7 @@ describe API::API, api: true do new_name: 'New Label', color: '#FFFFFF', description: 'test' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq('#FFFFFF') expect(json_response['description']).to eq('test') @@ -122,7 +121,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', new_name: 'New Label' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq(label1.color) end @@ -131,7 +130,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFFFFF' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(label1.name) expect(json_response['color']).to eq('#FFFFFF') end @@ -140,7 +139,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', description: 'test' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(label1.name) expect(json_response['description']).to eq('test') end @@ -149,18 +148,18 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label2', new_name: 'label3' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return 400 if no label name given' do put api("/projects/#{project.id}/labels", user), new_name: 'label2' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "name" not given') end it 'should return 400 if no new parameters given' do put api("/projects/#{project.id}/labels", user), name: 'label1' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Required parameters '\ '"new_name" or "color" missing') end @@ -168,9 +167,9 @@ 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.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end @@ -178,7 +177,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FF' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -186,7 +185,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end end @@ -196,7 +195,7 @@ describe API::API, api: true do it "should subscribe to the label" do post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -206,7 +205,7 @@ describe API::API, api: true do it "should subscribe to the label" do post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -218,7 +217,7 @@ describe API::API, api: true do it "should return 304" do post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end @@ -226,7 +225,7 @@ describe API::API, api: true do it "should a return 404 error" do post api("/projects/#{project.id}/labels/1234/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -238,7 +237,7 @@ describe API::API, api: true do it "should unsubscribe from the label" do delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -248,7 +247,7 @@ describe API::API, api: true do it "should unsubscribe from the label" do delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -260,7 +259,7 @@ describe API::API, api: true do it "should return 304" do delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end @@ -268,7 +267,7 @@ describe API::API, api: true do it "should a return 404 error" do delete api("/projects/#{project.id}/labels/1234/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/license_templates_spec.rb index 3726b2f5688..9a1894d63a2 100644 --- a/spec/requests/api/licenses_spec.rb +++ b/spec/requests/api/license_templates_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::Licenses, api: true do +describe API::API, api: true do include ApiHelpers describe 'Entity' do @@ -23,7 +23,7 @@ describe API::Licenses, api: true do it 'returns a list of available license templates' do get api('/licenses') - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(15) expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') @@ -34,7 +34,7 @@ describe API::Licenses, api: true do it 'returns a list of available popular license templates' do get api('/licenses?popular=1') - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(3) expect(json_response.map { |l| l['key'] }).to include('apache-2.0') @@ -116,7 +116,7 @@ describe API::Licenses, api: true do let(:license_type) { 'muth-over9000' } it 'returns a 404' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5896b93603f..4a1b5600bdf 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -22,14 +22,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/projects/#{project.id}/merge_requests") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated" do it "should return an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) @@ -37,7 +37,7 @@ describe API::API, api: true do it "should return an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) @@ -45,7 +45,7 @@ describe API::API, api: true do it "should return an array of open merge_requests" do get api("/projects/#{project.id}/merge_requests?state=opened", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.last['title']).to eq(merge_request.title) @@ -53,7 +53,7 @@ describe API::API, api: true do it "should return an array of closed merge_requests" do get api("/projects/#{project.id}/merge_requests?state=closed", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(merge_request_closed.title) @@ -61,7 +61,7 @@ describe API::API, api: true do it "should return an array of merged merge_requests" do get api("/projects/#{project.id}/merge_requests?state=merged", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(merge_request_merged.title) @@ -75,7 +75,7 @@ describe API::API, api: true do it "should return an array of merge_requests in ascending order" do get api("/projects/#{project.id}/merge_requests?sort=asc", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['created_at'] } @@ -84,7 +84,7 @@ describe API::API, api: true do it "should return an array of merge_requests in descending order" do get api("/projects/#{project.id}/merge_requests?sort=desc", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['created_at'] } @@ -93,7 +93,7 @@ describe API::API, api: true do it "should return an array of merge_requests ordered by updated_at" do get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['updated_at'] } @@ -102,7 +102,7 @@ describe API::API, api: true do it "should return an array of merge_requests ordered by created_at" do get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map{ |merge_request| merge_request['created_at'] } @@ -116,7 +116,7 @@ describe API::API, api: true do it 'exposes known attributes' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(merge_request.id) expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['project_id']).to eq(merge_request.project.id) @@ -142,7 +142,7 @@ describe API::API, api: true do it "should return merge_request" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(merge_request.title) expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['work_in_progress']).to eq(false) @@ -159,7 +159,7 @@ describe API::API, api: true do it "should return a 404 error if merge_request_id not found" do get api("/projects/#{project.id}/merge_requests/999", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context 'Work in Progress' do @@ -167,7 +167,7 @@ describe API::API, api: true do it "should return merge_request" do get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['work_in_progress']).to eq(true) end end @@ -186,7 +186,7 @@ describe API::API, api: true do it 'returns a 404 when merge_request_id not found' do get api("/projects/#{project.id}/merge_requests/999/commits", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -199,7 +199,7 @@ describe API::API, api: true do it 'returns a 404 when merge_request_id not found' do get api("/projects/#{project.id}/merge_requests/999/changes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -213,7 +213,7 @@ describe API::API, api: true do author: user, labels: 'label, label2', milestone_id: milestone.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['labels']).to eq(['label', 'label2']) expect(json_response['milestone']['id']).to eq(milestone.id) @@ -222,38 +222,40 @@ describe API::API, api: true do it "should return 422 when source_branch equals target_branch" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "master", target_branch: "master", author: user - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end it "should return 400 when source_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", target_branch: "master", author: user - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when target_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "markdown", author: user - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when title is missing" do post api("/projects/#{project.id}/merge_requests", user), target_branch: 'master', source_branch: 'markdown' - expect(response.status).to eq(400) + 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.status).to eq(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 @@ -274,7 +276,7 @@ describe API::API, api: true do target_branch: 'master', author: user end.to change { MergeRequest.count }.by(0) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) end end end @@ -292,7 +294,7 @@ describe API::API, api: true do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['description']).to eq('Test description for Test merge_request') end @@ -303,26 +305,26 @@ describe API::API, api: true do expect(fork_project.forked_from_project).to eq(project) post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('Test merge_request') end it "should return 400 when source_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when target_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 400 when title is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end context 'when target_branch is specified' do @@ -333,7 +335,7 @@ describe API::API, api: true do source_branch: 'markdown', author: user, target_project_id: fork_project.id - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end it 'should return 422 if targeting a different fork' do @@ -343,14 +345,14 @@ describe API::API, api: true do source_branch: 'markdown', author: user2, target_project_id: unrelated_project.id - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end it "should return 201 when target_branch is specified and for the same project" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end end end @@ -365,7 +367,7 @@ describe API::API, api: true do it "denies the deletion of the merge request" do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer) - expect(response.status).to be(403) + expect(response).to have_http_status(403) end end @@ -373,7 +375,7 @@ describe API::API, api: true do it "destroys the merge request owners can destroy" do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -381,7 +383,7 @@ describe API::API, api: true do describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do it "should return merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['state']).to eq('closed') end end @@ -392,7 +394,7 @@ describe API::API, api: true do it "should return merge_request in case of success" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 406 if branch can't be merged" do @@ -401,21 +403,21 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(406) + expect(response).to have_http_status(406) expect(json_response['message']).to eq('Branch cannot be merged') end it "should return 405 if merge_request is not open" do merge_request.close put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end it "should return 405 if merge_request is a work in progress" do merge_request.update_attribute(:title, "WIP: #{merge_request.title}") put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end @@ -424,7 +426,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end @@ -432,21 +434,21 @@ describe API::API, api: true do user2 = create(:user) project.team << [user2, :reporter] put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2) - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') 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.status).to eq(409) + 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.status).to eq(200) + expect(response).to have_http_status(200) end it "enables merge when build succeeds if the ci is active" do @@ -455,7 +457,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('Test') expect(json_response['merge_when_build_succeeds']).to eq(true) end @@ -464,41 +466,45 @@ describe API::API, api: true do describe "PUT /projects/:id/merge_requests/:merge_request_id" do it "updates title and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('New title') end it "updates description and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq('New description') end it "updates milestone_id and returns merge_request" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['milestone']['id']).to eq(milestone.id) end it "should return 400 when source_branch is specified" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), source_branch: "master", target_branch: "master" - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return merge_request with renamed target_branch" do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" - expect(response.status).to eq(200) + expect(response).to have_http_status(200) 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.status).to eq(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 @@ -507,7 +513,7 @@ describe API::API, api: true do original_count = merge_request.notes.size post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['author']['name']).to eq(user.name) expect(json_response['author']['username']).to eq(user.username) @@ -516,20 +522,20 @@ describe API::API, api: true do it "should return 400 if note is missing" do post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return 404 if note is attached to non existent merge request" do post api("/projects/#{project.id}/merge_requests/404/comments", user), note: 'My comment' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe "GET :id/merge_requests/:merge_request_id/comments" do it "should return merge_request comments ordered by created_at" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq("a comment on a MR") @@ -539,7 +545,7 @@ describe API::API, api: true do it "should return a 404 error if merge_request_id not found" do get api("/projects/#{project.id}/merge_requests/999/comments", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -551,7 +557,7 @@ describe API::API, api: true do end get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(issue.id) @@ -559,7 +565,7 @@ describe API::API, api: true do it 'returns an empty array when there are no issues to be closed' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -572,7 +578,7 @@ describe API::API, api: true do get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(issue.title) @@ -584,20 +590,20 @@ describe API::API, api: true do it 'subscribes to a merge request' do post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the merge request is not found' do post api("/projects/#{project.id}/merge_requests/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -605,20 +611,20 @@ describe API::API, api: true do it 'unsubscribes from a merge request' do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end it 'returns 404 if the merge request is not found' do post api("/projects/#{project.id}/merge_requests/123/subscription", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 0154d1c62cc..0f4e38b2475 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -12,20 +12,20 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones' do it 'should return project milestones' do get api("/projects/#{project.id}/milestones", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(milestone.title) end it 'should return a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it 'returns an array of active milestones' do get api("/projects/#{project.id}/milestones?state=active", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(milestone.id) @@ -34,7 +34,7 @@ describe API::API, api: true do it 'returns an array of closed milestones' do get api("/projects/#{project.id}/milestones?state=closed", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_milestone.id) @@ -44,7 +44,7 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones/:milestone_id' do it 'should return a project milestone by id' do get api("/projects/#{project.id}/milestones/#{milestone.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) end @@ -60,19 +60,19 @@ describe API::API, api: true do it 'should return 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it 'should return a 404 error if milestone id not found' do get api("/projects/#{project.id}/milestones/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'POST /projects/:id/milestones' do it 'should create a new project milestone' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('new milestone') expect(json_response['description']).to be_nil end @@ -80,14 +80,14 @@ describe API::API, api: true do it 'should create a new project milestone with description and due date' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone', description: 'release', due_date: '2013-03-02' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['description']).to eq('release') expect(json_response['due_date']).to eq('2013-03-02') end it 'should return a 400 error if title is missing' do post api("/projects/#{project.id}/milestones", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -95,14 +95,14 @@ describe API::API, api: true do it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), title: 'updated title' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end it 'should return a 404 error if milestone id not found' do put api("/projects/#{project.id}/milestones/1234", user), title: 'updated title' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -110,7 +110,7 @@ describe API::API, api: true do it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['state']).to eq('closed') end @@ -131,14 +131,14 @@ describe API::API, api: true do end it 'should return project issues for a particular milestone' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['milestone']['title']).to eq(milestone.title) end it 'should return a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end describe 'confidential issues' do @@ -155,7 +155,7 @@ describe API::API, api: true do it 'returns confidential issues to team members' do get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(2) expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) @@ -167,7 +167,7 @@ describe API::API, api: true do get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.map { |issue| issue['id'] }).to include(issue.id) @@ -176,7 +176,7 @@ describe API::API, api: true do it 'does not return confidential issues to regular users' do get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.map { |issue| issue['id'] }).to include(issue.id) diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 21787fdd895..237b4b17eb5 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -11,14 +11,14 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/namespaces") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when authenticated as admin" do it "admin: should return an array of all namespaces" do get api("/namespaces", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(Namespace.count) @@ -26,7 +26,7 @@ describe API::API, api: true do it "admin: should return an array of matched namespaces" do get api("/namespaces?search=#{group1.name}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -36,7 +36,7 @@ describe API::API, api: true do context "when authenticated as a regular user" do it "user: should return an array of namespaces" do get api("/namespaces", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -44,7 +44,7 @@ describe API::API, api: true do it "admin: should return an array of matched namespaces" do get api("/namespaces?search=#{user.username}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index beb29a68692..65c53211dd3 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -40,7 +40,7 @@ describe API::API, api: true do it "should return an array of issue notes" do get api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(issue_note.note) end @@ -48,14 +48,14 @@ describe API::API, api: true do it "should return a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/12345/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "and current user cannot view the notes" do it "should return an empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response).to be_empty end @@ -66,7 +66,7 @@ describe API::API, api: true do it "returns 404" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -74,7 +74,7 @@ describe API::API, api: true do it "should return an empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(cross_reference_note.note) end @@ -86,7 +86,7 @@ describe API::API, api: true do it "should return an array of snippet notes" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(snippet_note.note) end @@ -94,13 +94,13 @@ describe API::API, api: true do it "should return a 404 error when snippet id not found" do get api("/projects/#{project.id}/snippets/42/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns 404 when not authorized" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -108,7 +108,7 @@ describe API::API, api: true do it "should return an array of merge_requests notes" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['body']).to eq(merge_request_note.note) end @@ -116,13 +116,13 @@ describe API::API, api: true do it "should return a 404 error if merge request id not found" do get api("/projects/#{project.id}/merge_requests/4444/notes", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns 404 when not authorized" do get api("/projects/#{project.id}/merge_requests/4444/notes", private_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -132,21 +132,21 @@ describe API::API, api: true do it "should return an issue note by id" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq(issue_note.note) end it "should return a 404 error if issue note not found" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "and current user cannot view the note" do it "should return a 404 error" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end context "when issue is confidential" do @@ -155,16 +155,15 @@ describe API::API, api: true do it "returns 404" do get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", private_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) 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) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq(cross_reference_note.note) end end @@ -175,14 +174,14 @@ describe API::API, api: true do it "should return a snippet note by id" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq(snippet_note.note) end it "should return a 404 error if snippet note not found" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -192,7 +191,7 @@ describe API::API, api: true do it "should create a new issue note" do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -200,13 +199,13 @@ describe API::API, api: true do it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end context 'when an admin or owner makes the request' do @@ -215,20 +214,19 @@ describe API::API, api: true do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!', created_at: creation_time - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) 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 it "should create a new snippet note" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -236,13 +234,13 @@ describe API::API, api: true do it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -282,7 +280,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user), body: 'Hello!' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -290,14 +288,14 @@ describe API::API, api: true do put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user), body: 'Hello!' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return a 400 bad request error if body not given' do put api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -306,7 +304,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user), body: 'Hello!' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -314,7 +312,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user), body: "Hello!" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -323,7 +321,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/#{merge_request_note.id}", user), body: 'Hello!' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -331,7 +329,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/12345", user), body: "Hello!" - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -342,17 +340,17 @@ describe API::API, api: true do delete api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) # Check if note is really deleted delete api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -361,18 +359,18 @@ describe API::API, api: true do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) # Check if note is really deleted delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -381,20 +379,19 @@ describe API::API, api: true do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) # Check if note is really deleted delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/12345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end - end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index ffb93bbb120..fd1fffa6223 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -22,7 +22,7 @@ describe API::API, 'ProjectHooks', api: true do context "authorized user" do it "should return project hooks" do get api("/projects/#{project.id}/hooks", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.count).to eq(1) @@ -40,7 +40,7 @@ describe API::API, 'ProjectHooks', api: true do context "unauthorized user" do it "should not access project hooks" do get api("/projects/#{project.id}/hooks", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -49,7 +49,7 @@ describe API::API, 'ProjectHooks', api: true do context "authorized user" do it "should return a project hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['url']).to eq(hook.url) expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(hook.push_events) @@ -61,20 +61,20 @@ describe API::API, 'ProjectHooks', api: true do it "should return a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context "unauthorized user" do it "should not access an existing hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it "should return a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -83,7 +83,7 @@ describe API::API, 'ProjectHooks', api: true do expect do post api("/projects/#{project.id}/hooks", user), url: "http://example.com", issues_events: true end.to change {project.hooks.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['url']).to eq('http://example.com') expect(json_response['issues_events']).to eq(true) expect(json_response['push_events']).to eq(true) @@ -96,12 +96,12 @@ describe API::API, 'ProjectHooks', api: true do it "should return a 400 error if url not given" do post api("/projects/#{project.id}/hooks", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error if url not valid" do post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -109,7 +109,7 @@ describe API::API, 'ProjectHooks', api: true do it "should update an existing project hook" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'http://example.org', push_events: false - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['url']).to eq('http://example.org') expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(false) @@ -121,17 +121,17 @@ describe API::API, 'ProjectHooks', api: true do it "should return 404 error if hook id not found" do put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 400 error if url is not given" do put api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error if url is not valid" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -140,22 +140,22 @@ describe API::API, 'ProjectHooks', api: true do expect do delete api("/projects/#{project.id}/hooks/#{hook.id}", user) end.to change {project.hooks.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return success when deleting hook" do delete api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return a 404 error when deleting non existent hook" do delete api("/projects/#{project.id}/hooks/42", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 405 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end it "shold return a 404 if a user attempts to delete project hooks he/she does not own" do @@ -164,7 +164,7 @@ describe API::API, 'ProjectHooks', api: true do other_project.team << [test_user, :master] delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(WebHook.exists?(hook.id)).to be_truthy end end diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index 44b532b10e1..9a7c1da4401 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do it "should return project team members" do get api("/projects/#{project.id}/members", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.count).to eq(2) expect(json_response.map { |u| u['username'] }).to include user.username @@ -23,7 +23,7 @@ describe API::API, api: true do it "finds team members with query string" do get api("/projects/#{project.id}/members", user), query: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.count).to eq(1) expect(json_response.first['username']).to eq(user.username) @@ -31,7 +31,7 @@ describe API::API, api: true do it "should return a 404 error if id not found" do get api("/projects/9999/members", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -40,14 +40,14 @@ describe API::API, api: true do it "should return project team member" do get api("/projects/#{project.id}/members/#{user.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) expect(json_response['access_level']).to eq(ProjectMember::MASTER) end it "should return a 404 error if user id not found" do get api("/projects/#{project.id}/members/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -57,7 +57,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER end.to change { ProjectMember.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['username']).to eq(user2.username) expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER) end @@ -70,24 +70,24 @@ describe API::API, api: true do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER end.not_to change { ProjectMember.count } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['username']).to eq(user2.username) expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER) end it "should return a 400 error when user id is not given" do post api("/projects/#{project.id}/members", user), access_level: ProjectMember::MASTER - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 400 error when access level is not given" do post api("/projects/#{project.id}/members", user), user_id: user2.id - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error when access level is not known" do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234 - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -96,24 +96,24 @@ describe API::API, api: true do it "should update project team member" do put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: ProjectMember::MASTER - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user3.username) expect(json_response['access_level']).to eq(ProjectMember::MASTER) end it "should return a 404 error if user_id is not found" do put api("/projects/#{project.id}/members/1234", user), access_level: ProjectMember::MASTER - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 400 error when access level is not given" do put api("/projects/#{project.id}/members/#{user3.id}", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should return a 422 error when access level is not known" do put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123 - expect(response.status).to eq(422) + expect(response).to have_http_status(422) end end @@ -134,20 +134,20 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/members/#{user3.id}", user) end.not_to change { ProjectMember.count } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 200 if team member already removed" do delete api("/projects/#{project.id}/members/#{user3.id}", user) delete api("/projects/#{project.id}/members/#{user3.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 200 OK when the user was not member" do expect do delete api("/projects/#{project.id}/members/1000000", user) end.to change { ProjectMember.count }.by(0) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(1000000) expect(json_response['message']).to eq('Access revoked') end @@ -158,7 +158,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/members/#{user3.id}", user3) end.to change { ProjectMember.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(project_member2.id) end end diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 9706d060cfa..4ebde201941 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -27,7 +27,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/snippets/", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.size).to eq(3) expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) end @@ -38,7 +38,7 @@ describe API::API, api: true do create(:project_snippet, :private, project: project) get api("/projects/#{project.id}/snippets/", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.size).to eq(0) end end @@ -56,7 +56,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/snippets/", admin), params - expect(response.status).to eq(201) + expect(response).to have_http_status(201) snippet = ProjectSnippet.find(json_response['id']) expect(snippet.content).to eq(params[:code]) expect(snippet.title).to eq(params[:title]) @@ -73,7 +73,7 @@ describe API::API, api: true do put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content - expect(response.status).to eq(200) + expect(response).to have_http_status(200) snippet.reload expect(snippet.content).to eq(new_content) end @@ -86,7 +86,7 @@ describe API::API, api: true do delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -97,7 +97,7 @@ describe API::API, api: true do get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'text/plain' expect(response.body).to eq(snippet.content) end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 01eb4b44b83..8a52725a893 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -45,14 +45,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api('/projects') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return an array of projects' do get api('/projects', user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(project.name) expect(json_response.first['owner']['username']).to eq(user.username) @@ -84,7 +84,7 @@ describe API::API, api: true do context 'and using search' do it 'should return searched project' do get api('/projects', user), { search: project.name } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) end @@ -93,21 +93,21 @@ describe API::API, api: true do context 'and using the visibility filter' do it 'should filter based on private visibility param' do get api('/projects', user), { visibility: 'private' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count) end it 'should filter based on internal visibility param' do get api('/projects', user), { visibility: 'internal' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count) end it 'should filter based on public visibility param' do get api('/projects', user), { visibility: 'public' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count) end @@ -121,7 +121,7 @@ describe API::API, api: true do it 'should return the correct order when sorted by id' do get api('/projects', user), { order_by: 'id', sort: 'desc' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['id']).to eq(project3.id) end @@ -135,21 +135,21 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api('/projects/all') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated as regular user' do it 'should return authentication error' do get api('/projects/all', user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context 'when authenticated as admin' do it 'should return an array of all projects' do get api('/projects/all', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response).to satisfy do |response| @@ -173,7 +173,7 @@ describe API::API, api: true do it 'should return the starred projects viewable by the user' do get api('/projects/starred', user3) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id) end @@ -185,25 +185,25 @@ describe API::API, api: true do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) expect { post api('/projects', user2), name: 'foo' }. to change {Project.count}.by(0) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it 'should create new project without path and return 201' do expect { post api('/projects', user), name: 'foo' }. to change { Project.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should create last project before reaching project limit' do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) post api('/projects', user2), name: 'foo' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should not create new project without name and return 400' do expect { post api('/projects', user) }.not_to change { Project.count } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should assign attributes to project" do @@ -217,7 +217,7 @@ describe API::API, api: true do post api('/projects', user), project - project.each_pair do |k,v| + project.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end end @@ -273,7 +273,7 @@ describe API::API, api: true do it 'should not allow a non-admin to use a restricted visibility level' do post api('/projects', user), @project - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['visibility_level'].first).to( match('restricted by your GitLab administrator') ) @@ -295,14 +295,14 @@ describe API::API, api: true do it 'should create new project without path and return 201' do expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should respond with 400 on failure and not project' do expect { post api("/projects/user/#{user.id}", admin) }. not_to change { Project.count } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['name']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)', @@ -325,7 +325,7 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project - project.each_pair do |k,v| + project.each_pair do |k, v| next if k == :path expect(json_response[k.to_s]).to eq(v) end @@ -380,7 +380,7 @@ describe API::API, api: true do it "uploads the file and returns its info" do post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") - expect(response.status).to be(201) + expect(response).to have_http_status(201) expect(json_response['alt']).to eq("dk") expect(json_response['url']).to start_with("/uploads/") expect(json_response['url']).to end_with("/dk.png") @@ -392,29 +392,65 @@ 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.status).to eq(200) + + 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 get api("/projects/#{project.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(project.name) end it 'should return a 404 error if not found' do get api('/projects/42', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}", other_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should handle users with dots' do @@ -422,7 +458,7 @@ describe API::API, api: true do project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(project.name) end @@ -433,7 +469,7 @@ describe API::API, api: true do it 'contains permission information' do get api("/projects", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response.first['permissions']['project_access']['access_level']). to eq(Gitlab::Access::MASTER) expect(json_response.first['permissions']['group_access']).to be_nil @@ -445,7 +481,7 @@ describe API::API, api: true do project.team << [user, :master] get api("/projects/#{project.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['permissions']['project_access']['access_level']). to eq(Gitlab::Access::MASTER) expect(json_response['permissions']['group_access']).to be_nil @@ -460,7 +496,7 @@ describe API::API, api: true do it 'should set the owner and return 200' do get api("/projects/#{project2.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['permissions']['project_access']).to be_nil expect(json_response['permissions']['group_access']['access_level']). to eq(Gitlab::Access::OWNER) @@ -479,7 +515,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/events", user) end - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } context 'joined event' do let(:json_event) { json_response[1] } @@ -500,14 +536,14 @@ describe API::API, api: true do it 'should return a 404 error if not found' do get api('/projects/42/events', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}/events", other_user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -516,7 +552,7 @@ describe API::API, api: true do it 'should return an array of project snippets' do get api("/projects/#{project.id}/snippets", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(snippet.title) end @@ -525,13 +561,13 @@ describe API::API, api: true do describe 'GET /projects/:id/snippets/:snippet_id' do it 'should return a project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(snippet.title) end it 'should return a 404 error if snippet id not found' do get api("/projects/#{project.id}/snippets/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -540,7 +576,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/snippets", user), title: 'api test', file_name: 'sample.rb', code: 'test', visibility_level: '0' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['title']).to eq('api test') end @@ -554,7 +590,7 @@ describe API::API, api: true do it 'should update an existing project snippet' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), code: 'updated code' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('example') expect(snippet.reload.content).to eq('updated code') end @@ -562,7 +598,7 @@ describe API::API, api: true do it 'should update an existing project snippet with new title' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), title: 'other api test' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq('other api test') end end @@ -574,24 +610,24 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) end.to change { Snippet.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 when deleting unknown snippet id' do delete api("/projects/#{project.id}/snippets/1234", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'GET /projects/:id/snippets/:snippet_id/raw' do it 'should get a raw project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return a 404 error if raw project snippet not found' do get api("/projects/#{project.id}/snippets/5555/raw", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -604,7 +640,7 @@ describe API::API, api: true do it 'should return array of ssh keys' do get api("/projects/#{project.id}/keys", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(deploy_key.title) end @@ -613,20 +649,20 @@ describe API::API, api: true do describe 'GET /projects/:id/keys/:key_id' do it 'should return a single key' do get api("/projects/#{project.id}/keys/#{deploy_key.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(deploy_key.title) end it 'should return 404 Not Found with invalid ID' do get api("/projects/#{project.id}/keys/404", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'POST /projects/:id/keys' do it 'should not create an invalid ssh key' do post api("/projects/#{project.id}/keys", user), { title: 'invalid key' } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['key']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)', @@ -636,7 +672,7 @@ describe API::API, api: true do it 'should not create a key without title' do post api("/projects/#{project.id}/keys", user), key: 'some key' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['title']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)' @@ -662,7 +698,7 @@ describe API::API, api: true do it 'should return 404 Not Found with invalid ID' do delete api("/projects/#{project.id}/keys/404", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -676,13 +712,13 @@ describe API::API, api: true do it "shouldn't available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should allow project to be forked from an existing project' do expect(project_fork_target.forked?).not_to be_truthy post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked_project_link).not_to be_nil @@ -691,7 +727,7 @@ describe API::API, api: true do it 'should fail if forked_from project which does not exist' do post api("/projects/#{project_fork_target.id}/fork/9999", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should fail with 409 if already forked' do @@ -699,7 +735,7 @@ describe API::API, api: true do project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked?).to be_truthy @@ -707,10 +743,9 @@ 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.status).to eq(404) + expect(response).to have_http_status(404) end context 'when users belong to project group' do @@ -723,7 +758,7 @@ describe API::API, api: true do it 'should be forbidden to non-owner users' do delete api("/projects/#{project_fork_target.id}/fork", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should make forked project unforked' do @@ -732,7 +767,7 @@ describe API::API, api: true do expect(project_fork_target.forked_from_project).not_to be_nil expect(project_fork_target.forked?).to be_truthy delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_fork_target.reload expect(project_fork_target.forked_from_project).to be_nil expect(project_fork_target.forked?).not_to be_truthy @@ -741,7 +776,7 @@ describe API::API, api: true do it 'should be idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -799,14 +834,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/projects/search/#{query}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return an array of projects' do - get api("/projects/search/#{query}",user) - expect(response.status).to eq(200) + get api("/projects/search/#{query}", user) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(6) json_response.each {|project| expect(project['name']).to match(/.*query.*/)} @@ -816,7 +851,7 @@ describe API::API, api: true do context 'when authenticated as a different user' do it 'should return matching public projects' do get api("/projects/search/#{query}", user2) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(2) json_response.each {|project| expect(project['name']).to match(/(internal|public) query/)} @@ -838,7 +873,7 @@ describe API::API, api: true do it 'should return authentication error' do project_param = { name: 'bar' } put api("/projects/#{project.id}"), project_param - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -846,7 +881,7 @@ describe API::API, api: true do it 'should update name' do project_param = { name: 'bar' } put api("/projects/#{project.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -855,7 +890,7 @@ describe API::API, api: true do it 'should update visibility_level' do project_param = { visibility_level: 20 } put api("/projects/#{project3.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -866,7 +901,7 @@ describe API::API, api: true do project_param = { public: false } put api("/projects/#{project3.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -876,14 +911,14 @@ describe API::API, api: true do it 'should not update name to existing name' do project_param = { name: project3.name } put api("/projects/#{project.id}", user), project_param - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['name']).to eq(['has already been taken']) end it 'should update path & name to existing path & name in different namespace' do project_param = { path: project4.path, name: project4.name } put api("/projects/#{project3.id}", user), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -894,7 +929,7 @@ describe API::API, api: true do it 'should update path' do project_param = { path: 'bar' } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -908,7 +943,7 @@ describe API::API, api: true do description: 'new description' } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -917,20 +952,20 @@ describe API::API, api: true do it 'should not update path to existing path' do project_param = { path: project.path } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['path']).to eq(['has already been taken']) end it 'should not update name' do project_param = { name: 'bar' } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should not update visibility_level' do project_param = { visibility_level: 20 } put api("/projects/#{project3.id}", user4), project_param - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -943,7 +978,7 @@ describe API::API, api: true do merge_requests_enabled: true, description: 'new description' } put api("/projects/#{project.id}", user3), project_param - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -953,7 +988,7 @@ describe API::API, api: true do it 'archives the project' do post api("/projects/#{project.id}/archive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -966,7 +1001,7 @@ describe API::API, api: true do it 'remains archived' do post api("/projects/#{project.id}/archive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -979,7 +1014,7 @@ describe API::API, api: true do it 'rejects the action' do post api("/projects/#{project.id}/archive", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -989,7 +1024,7 @@ describe API::API, api: true do it 'remains unarchived' do post api("/projects/#{project.id}/unarchive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1002,7 +1037,7 @@ describe API::API, api: true do it 'unarchives the project' do post api("/projects/#{project.id}/unarchive", user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1015,7 +1050,7 @@ describe API::API, api: true do it 'rejects the action' do post api("/projects/#{project.id}/unarchive", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -1025,7 +1060,7 @@ describe API::API, api: true do it 'stars the project' do expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['star_count']).to eq(1) end end @@ -1039,7 +1074,7 @@ describe API::API, api: true do it 'does not modify the star count' do expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end end @@ -1054,7 +1089,7 @@ describe API::API, api: true do it 'unstars the project' do expect { delete api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['star_count']).to eq(0) end end @@ -1063,7 +1098,7 @@ describe API::API, api: true do it 'does not modify the star count' do expect { delete api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - expect(response.status).to eq(304) + expect(response).to have_http_status(304) end end end @@ -1072,36 +1107,36 @@ describe API::API, api: true do context 'when authenticated as user' do it 'should remove project' do delete api("/projects/#{project.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should not remove a project if not an owner' do user3 = create(:user) project.team << [user3, :developer] delete api("/projects/#{project.id}", user3) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should not remove a non existing project' do delete api('/projects/1328', user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should not remove a project not attached to user' do delete api("/projects/#{project.id}", user2) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end context 'when authenticated as admin' do it 'should remove any existing project' do delete api("/projects/#{project.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should not remove a non existing project' do delete api('/projects/1328', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 7cf4a01d76b..5890e9c9d3d 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -18,7 +18,7 @@ describe API::API, api: true do it "should return project commits" do get api("/projects/#{project.id}/repository/tree", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq('encoding') @@ -28,7 +28,7 @@ describe API::API, api: true do it 'should return a 404 for unknown ref' do get api("/projects/#{project.id}/repository/tree?ref_name=foo", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response).to be_an Object json_response['message'] == '404 Tree Not Found' @@ -38,7 +38,7 @@ describe API::API, api: true do context "unauthorized user" do it "should not return project commits" do get api("/projects/#{project.id}/repository/tree") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -46,41 +46,41 @@ describe API::API, api: true do describe "GET /projects/:id/repository/blobs/:sha" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 404 for invalid branch_name" do get api("/projects/#{project.id}/repository/blobs/invalid_branch_name?filepath=README.md", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 for invalid file" do get api("/projects/#{project.id}/repository/blobs/master?filepath=README.invalid", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return a 400 error if filepath is missing" do get api("/projects/#{project.id}/repository/blobs/master", user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end describe "GET /projects/:id/repository/commits/:sha/blob" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end describe "GET /projects/:id/repository/raw_blobs/:sha" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return a 404 for unknown blob' do get api("/projects/#{project.id}/repository/raw_blobs/123456", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response).to be_an Object json_response['message'] == '404 Blob Not Found' @@ -91,7 +91,7 @@ describe API::API, api: true do it "should get the archive" do get api("/projects/#{project.id}/repository/archive", user) repo_name = project.repository.name.gsub("\.git", "") - expect(response.status).to eq(200) + expect(response).to have_http_status(200) type, params = workhorse_send_data expect(type).to eq('git-archive') expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/) @@ -100,7 +100,7 @@ describe API::API, api: true do it "should get the archive.zip" do get api("/projects/#{project.id}/repository/archive.zip", user) repo_name = project.repository.name.gsub("\.git", "") - expect(response.status).to eq(200) + expect(response).to have_http_status(200) type, params = workhorse_send_data expect(type).to eq('git-archive') expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/) @@ -109,7 +109,7 @@ describe API::API, api: true do it "should get the archive.tar.bz2" do get api("/projects/#{project.id}/repository/archive.tar.bz2", user) repo_name = project.repository.name.gsub("\.git", "") - expect(response.status).to eq(200) + expect(response).to have_http_status(200) type, params = workhorse_send_data expect(type).to eq('git-archive') expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/) @@ -117,28 +117,28 @@ describe API::API, api: true do it "should return 404 for invalid sha" do get api("/projects/#{project.id}/repository/archive/?sha=xxx", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end describe 'GET /projects/:id/repository/compare' do it "should compare branches" do get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'feature' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "should compare tags" do get api("/projects/#{project.id}/repository/compare", user), from: 'v1.0.0', to: 'v1.1.0' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "should compare commits" do get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.id, to: sample_commit.parent_id - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_falsey @@ -146,14 +146,14 @@ describe API::API, api: true do it "should compare commits in reverse order" do get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.parent_id, to: sample_commit.id - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "should compare same refs" do get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'master' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_truthy @@ -163,7 +163,7 @@ describe API::API, api: true do describe 'GET /projects/:id/repository/contributors' do it 'should return valid data' do get api("/projects/#{project.id}/repository/contributors", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array contributor = json_response.first expect(contributor['email']).to eq('dmitriy.zaporozhets@gmail.com') diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index b4c826522a5..00a3c917b6a 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -39,7 +39,7 @@ describe API::Runners, api: true do get api('/runners', user) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_falsey end @@ -48,14 +48,14 @@ describe API::Runners, api: true do get api('/runners?scope=active', user) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_falsey end it 'should avoid filtering if scope is invalid' do get api('/runners?scope=unknown', user) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -63,7 +63,7 @@ describe API::Runners, api: true do it 'should not return runners' do get api('/runners') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -75,7 +75,7 @@ describe API::Runners, api: true do get api('/runners/all', admin) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_truthy end @@ -85,7 +85,7 @@ describe API::Runners, api: true do it 'should not return runners list' do get api('/runners/all', user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -93,14 +93,14 @@ describe API::Runners, api: true do get api('/runners/all?scope=specific', admin) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_falsey end it 'should avoid filtering if scope is invalid' do get api('/runners?scope=unknown', admin) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -108,7 +108,7 @@ describe API::Runners, api: true do it 'should not return runners' do get api('/runners') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -119,7 +119,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{shared_runner.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(shared_runner.description) end end @@ -128,7 +128,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{specific_runner.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(specific_runner.description) end end @@ -136,7 +136,7 @@ describe API::Runners, api: true do it 'should return 404 if runner does not exists' do get api('/runners/9999', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -145,7 +145,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{specific_runner.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(specific_runner.description) end end @@ -154,7 +154,7 @@ describe API::Runners, api: true do it "should return runner's details" do get api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['description']).to eq(shared_runner.description) end end @@ -164,7 +164,7 @@ describe API::Runners, api: true do it "should not return runner's details" do get api("/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -172,7 +172,7 @@ describe API::Runners, api: true do it "should not return runner's details" do get api("/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -191,7 +191,7 @@ describe API::Runners, api: true do locked: 'true') shared_runner.reload - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(shared_runner.description).to eq("#{description}_updated") expect(shared_runner.active).to eq(!active) expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql') @@ -206,7 +206,7 @@ describe API::Runners, api: true do update_runner(specific_runner.id, admin, description: 'test') specific_runner.reload - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(specific_runner.description).to eq('test') expect(specific_runner.description).not_to eq(description) end @@ -215,7 +215,7 @@ describe API::Runners, api: true do it 'should return 404 if runner does not exists' do update_runner(9999, admin, description: 'test') - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end def update_runner(id, user, args) @@ -228,7 +228,7 @@ describe API::Runners, api: true do it 'should not update runner' do put api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -236,7 +236,7 @@ describe API::Runners, api: true do it 'should not update runner without access to it' do put api("/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should update runner with access to it' do @@ -244,7 +244,7 @@ describe API::Runners, api: true do put api("/runners/#{specific_runner.id}", admin), description: 'test' specific_runner.reload - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(specific_runner.description).to eq('test') expect(specific_runner.description).not_to eq(description) end @@ -255,7 +255,7 @@ describe API::Runners, api: true do it 'should not delete runner' do put api("/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -267,7 +267,7 @@ describe API::Runners, api: true do expect do delete api("/runners/#{shared_runner.id}", admin) end.to change{ Ci::Runner.shared.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -276,21 +276,21 @@ describe API::Runners, api: true do expect do delete api("/runners/#{unused_specific_runner.id}", admin) end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should delete used runner' do expect do delete api("/runners/#{specific_runner.id}", admin) end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end it 'should return 404 if runner does not exists' do delete api('/runners/9999', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -298,26 +298,26 @@ describe API::Runners, api: true do context 'when runner is shared' do it 'should not delete runner' do delete api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context 'when runner is not shared' do it 'should not delete runner without access to it' do delete api("/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should not delete runner with more than one associated project' do delete api("/runners/#{two_projects_runner.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should delete runner for one owned project' do expect do delete api("/runners/#{specific_runner.id}", user) end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -326,7 +326,7 @@ describe API::Runners, api: true do it 'should not delete runner' do delete api("/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -337,7 +337,7 @@ describe API::Runners, api: true do get api("/projects/#{project.id}/runners", user) shared = json_response.any?{ |r| r['is_shared'] } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(shared).to be_truthy end @@ -347,7 +347,7 @@ describe API::Runners, api: true do it "should not return project's runners" do get api("/projects/#{project.id}/runners", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -355,7 +355,7 @@ describe API::Runners, api: true do it "should not return project's runners" do get api("/projects/#{project.id}/runners") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -372,14 +372,14 @@ describe API::Runners, api: true do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change{ project.runners.count }.by(+1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'should avoid changes when enabling already enabled runner' do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id end.to change{ project.runners.count }.by(0) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) end it 'should not enable locked runner' do @@ -389,13 +389,13 @@ describe API::Runners, api: true do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change{ project.runners.count }.by(0) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should not enable shared runner' do post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end context 'user is admin' do @@ -403,7 +403,7 @@ describe API::Runners, api: true do expect do post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id end.to change{ project.runners.count }.by(+1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end end @@ -411,14 +411,14 @@ describe API::Runners, api: true do it 'should not enable runner without access to' do post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it 'should raise an error when no runner_id param is provided' do post api("/projects/#{project.id}/runners", admin) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -426,7 +426,7 @@ describe API::Runners, api: true do it 'should not enable runner' do post api("/projects/#{project.id}/runners", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -434,7 +434,7 @@ describe API::Runners, api: true do it 'should not enable runner' do post api("/projects/#{project.id}/runners") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -446,7 +446,7 @@ describe API::Runners, api: true do expect do delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) end.to change{ project.runners.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -455,14 +455,14 @@ describe API::Runners, api: true do expect do delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user) end.to change{ project.runners.count }.by(0) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end it 'should return 404 is runner is not found' do delete api("/projects/#{project.id}/runners/9999", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -470,7 +470,7 @@ describe API::Runners, api: true do it "should not disable project's runner" do delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -478,7 +478,7 @@ describe API::Runners, api: true do it "should not disable project's runner" do delete api("/projects/#{project.id}/runners/#{specific_runner.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index fed9ae1949b..a2446e12804 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -14,7 +14,7 @@ describe API::API, api: true do it "should update #{service} settings" do put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return if required fields missing" do @@ -45,7 +45,7 @@ describe API::API, api: true do it "should delete #{service}" do delete api("/projects/#{project.id}/services/#{dashed_service}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end @@ -64,20 +64,20 @@ describe API::API, api: true do it 'should return authentication error when unauthenticated' do get api("/projects/#{project.id}/services/#{dashed_service}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should return all properties of service #{service} when authenticated as admin" do get api("/projects/#{project.id}/services/#{dashed_service}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map) end it "should return properties of service #{service} other than passwords when authenticated as project owner" do get api("/projects/#{project.id}/services/#{dashed_service}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords) end @@ -85,9 +85,8 @@ describe API::API, api: true do project.team << [user2, :developer] get api("/projects/#{project.id}/services/#{dashed_service}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end - end end end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index fbd57b34a58..c15b7ff9792 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -9,7 +9,7 @@ describe API::API, api: true do context "when valid password" do it "should return private token" do post api("/session"), email: user.email, password: '12345678' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['email']).to eq(user.email) expect(json_response['private_token']).to eq(user.private_token) @@ -48,7 +48,7 @@ describe API::API, api: true do context "when invalid password" do it "should return authentication error" do post api("/session"), email: user.email, password: '123' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['email']).to be_nil expect(json_response['private_token']).to be_nil @@ -58,7 +58,7 @@ describe API::API, api: true do context "when empty password" do it "should return authentication error" do post api("/session"), email: user.email - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['email']).to be_nil expect(json_response['private_token']).to be_nil @@ -68,7 +68,7 @@ describe API::API, api: true do context "when empty name" do it "should return authentication error" do post api("/session"), password: user.password - expect(response.status).to eq(401) + expect(response).to have_http_status(401) expect(json_response['email']).to be_nil expect(json_response['private_token']).to be_nil diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index c815a8e1d73..684c2cd8e24 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -6,24 +6,30 @@ 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) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) expect(json_response['signin_enabled']).to be_truthy + expect(json_response['repository_storage']).to eq('default') end end describe "PUT /application/settings" do + before do + storages = { 'custom' => 'tmp/tests/custom_repositories' } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end + it "should update application settings" do put api("/application/settings", admin), - default_projects_limit: 3, signin_enabled: false - expect(response.status).to eq(200) + default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom' + expect(response).to have_http_status(200) expect(json_response['default_projects_limit']).to eq(3) expect(json_response['signin_enabled']).to be_falsey + expect(json_response['repository_storage']).to eq('custom') end end end diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb index 41cbf0c6669..28067f8ca88 100644 --- a/spec/requests/api/sidekiq_metrics_spec.rb +++ b/spec/requests/api/sidekiq_metrics_spec.rb @@ -9,28 +9,28 @@ describe API::SidekiqMetrics, api: true do it 'defines the `queue_metrics` endpoint' do get api('/sidekiq/queue_metrics', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a Hash end it 'defines the `process_metrics` endpoint' do get api('/sidekiq/process_metrics', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['processes']).to be_an Array end it 'defines the `job_stats` endpoint' do get api('/sidekiq/job_stats', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a Hash end it 'defines the `compound_metrics` endpoint' do get api('/sidekiq/compound_metrics', admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a Hash expect(json_response['queues']).to be_a Hash expect(json_response['processes']).to be_an Array diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 94eebc48ec8..cf66f261ade 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -13,21 +13,21 @@ describe API::API, api: true do context "when no user" do it "should return authentication error" do get api("/hooks") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context "when not an admin" do it "should return forbidden error" do get api("/hooks", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end context "when authenticated as admin" do it "should return an array of hooks" do get api("/hooks", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['url']).to eq(hook.url) end @@ -43,7 +43,7 @@ describe API::API, api: true do it "should respond with 400 if url not given" do post api("/hooks", admin) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it "should not create new hook without url" do @@ -56,13 +56,13 @@ describe API::API, api: true do describe "GET /hooks/:id" do it "should return hook by id" do get api("/hooks/#{hook.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['event_name']).to eq('project_create') end it "should return 404 on failure" do get api("/hooks/404", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -75,7 +75,7 @@ describe API::API, api: true do it "should return success if hook id not found" do delete api("/hooks/12345", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 12e170b232f..fa700ab7343 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -18,7 +18,7 @@ describe API::API, api: true do context 'without releases' do it "should return an array of project tags" do get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) end @@ -33,7 +33,7 @@ describe API::API, api: true do it "should return an array of project tags with release info" do get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) expect(json_response.first['message']).to eq('Version 1.1.0') @@ -48,14 +48,14 @@ describe API::API, api: true do it 'returns a specific tag' do get api("/projects/#{project.id}/repository/tags/#{tag_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['name']).to eq(tag_name) end it 'returns 404 for an invalid tag name' do get api("/projects/#{project.id}/repository/tags/foobar", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -66,7 +66,7 @@ describe API::API, api: true do tag_name: 'v7.0.1', ref: 'master' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('v7.0.1') end end @@ -78,7 +78,7 @@ describe API::API, api: true do ref: 'master', release_description: 'Wow' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('v7.0.1') expect(json_response['release']['description']).to eq('Wow') end @@ -94,13 +94,13 @@ describe API::API, api: true do context 'delete tag' do it 'should delete an existing tag' do delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['tag_name']).to eq(tag_name) end it 'should raise 404 if the tag does not exist' do delete api("/projects/#{project.id}/repository/tags/foobar", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -117,7 +117,7 @@ describe API::API, api: true do ref: 'master', message: 'Release 7.1.0' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['name']).to eq('v7.1.0') expect(json_response['message']).to eq('Release 7.1.0') end @@ -127,14 +127,14 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags", user2), tag_name: 'v1.9.0', ref: '621491c677087aa243f165eab467bfdfbee00be1' - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it 'should return 400 if tag name is invalid' do post api("/projects/#{project.id}/repository/tags", user), tag_name: 'v 1.0.0', ref: 'master' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Tag name invalid') end @@ -142,11 +142,11 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags", user), tag_name: 'v8.0.0', ref: 'master' - expect(response.status).to eq(201) + expect(response).to have_http_status(201) post api("/projects/#{project.id}/repository/tags", user), tag_name: 'v8.0.0', ref: 'master' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Tag v8.0.0 already exists') end @@ -154,7 +154,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags", user), tag_name: 'mytag', ref: 'foo' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('Target foo is invalid') end end @@ -167,7 +167,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: description - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(description) end @@ -176,7 +176,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags/foobar/release", user), description: description - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('Tag does not exist') end @@ -190,7 +190,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: description - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Release already exists') end end @@ -211,7 +211,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: new_description - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(new_description) end @@ -221,7 +221,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/repository/tags/foobar/release", user), description: new_description - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('Tag does not exist') end @@ -229,7 +229,7 @@ describe API::API, api: true do put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: new_description - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('Release does not exist') end end diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index a6d5ade3013..68d0f41b489 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -22,7 +22,7 @@ describe API::Templates, api: true do it 'returns a list of available gitignore templates' do get api('/gitignores') - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to be > 15 end @@ -34,7 +34,7 @@ describe API::Templates, api: true do it 'returns a list of available gitlab_ci_ymls' do get api('/gitlab_ci_ymls') - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).not_to be_nil end @@ -45,7 +45,7 @@ describe API::Templates, api: true do it 'adds a disclaimer on the top' do get api('/gitlab_ci_ymls/Ruby') - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['content']).to start_with("# This file is a template,") end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb new file mode 100644 index 00000000000..92a4fa216cd --- /dev/null +++ b/spec/requests/api/todos_spec.rb @@ -0,0 +1,190 @@ +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, :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) } + 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 + get api('/todos') + + expect(response.status).to eq(401) + end + end + + context 'when authenticated' 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) + 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_type']).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') + expect(json_response[0]['action_name']).to eq('assigned') + expect(json_response[0]['created_at']).to be_present + end + + context 'and using the author filter' 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) + end + end + + 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) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end + + context 'and using the state filter' 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) + end + end + + context 'and using the project filter' do + it 'filters based on project_id param' do + 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 + + 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 + + describe 'DELETE /todos/:id' do + context 'when unauthenticated' 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 '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 + end + end + + describe 'DELETE /todos' do + context 'when unauthenticated' do + it 'returns authentication error' do + delete api('/todos') + + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'marks all todos as done' 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 + 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 diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index fdd4ec6d761..8992996c30a 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -29,17 +29,17 @@ describe API::API do context 'Handles errors' do it 'should return bad request if token is missing' do post api("/projects/#{project.id}/trigger/builds"), ref: 'master' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return not found if project is not found' do post api('/projects/0/trigger/builds'), options.merge(ref: 'master') - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return unauthorized if token is for different project' do post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -48,14 +48,14 @@ describe API::API do it 'should create builds' do post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master') - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.size).to eq(2) end it 'should return bad request with no builds created if there\'s no commit for that ref' do post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('No builds created') end @@ -66,19 +66,19 @@ describe API::API do it 'should validate variables to be a hash' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a hash') end it 'should validate variables needs to be a map of key-valued strings' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') end it 'create trigger request with variables' do post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master') - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.first.trigger_request.variables).to eq(variables) end @@ -91,7 +91,7 @@ describe API::API do it 'should return list of triggers' do get api("/projects/#{project.id}/triggers", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a(Array) expect(json_response[0]).to have_key('token') end @@ -101,7 +101,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -109,7 +109,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -119,14 +119,14 @@ describe API::API do it 'should return trigger details' do get api("/projects/#{project.id}/triggers/#{trigger.token}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a(Hash) end it 'should respond with 404 Not Found if requesting non-existing trigger' do get api("/projects/#{project.id}/triggers/abcdef012345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -134,7 +134,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -142,7 +142,7 @@ describe API::API do it 'should not return triggers list' do get api("/projects/#{project.id}/triggers/#{trigger.token}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -154,7 +154,7 @@ describe API::API do post api("/projects/#{project.id}/triggers", user) end.to change{project.triggers.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response).to be_a(Hash) end end @@ -163,7 +163,7 @@ describe API::API do it 'should not create trigger' do post api("/projects/#{project.id}/triggers", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -171,7 +171,7 @@ describe API::API do it 'should not create trigger' do post api("/projects/#{project.id}/triggers") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -182,13 +182,13 @@ describe API::API do expect do delete api("/projects/#{project.id}/triggers/#{trigger.token}", user) end.to change{project.triggers.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should respond with 404 Not Found if requesting non-existing trigger' do delete api("/projects/#{project.id}/triggers/abcdef012345", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -196,7 +196,7 @@ describe API::API do it 'should not delete trigger' do delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -204,7 +204,7 @@ describe API::API do it 'should not delete trigger' do delete api("/projects/#{project.id}/triggers/#{trigger.token}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a7690f430c4..e43e3e269bf 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/users") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -29,18 +29,18 @@ describe API::API, api: true do it "renders 403" do get api("/users") - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "renders 404" do get api("/users/#{user.id}") - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end it "should return an array of users" do get api("/users", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array username = user.username expect(json_response.detect do |user| @@ -50,7 +50,7 @@ describe API::API, api: true do it "should return one user" do get api("/users?username=#{omniauth_user.username}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['username']).to eq(omniauth_user.username) end @@ -59,7 +59,7 @@ describe API::API, api: true do context "when admin" do it "should return an array of users" do get api("/users", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first.keys).to include 'email' expect(json_response.first.keys).to include 'identities' @@ -74,24 +74,24 @@ describe API::API, api: true do describe "GET /users/:id" do it "should return a user by id" do get api("/users/#{user.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) end it "should return a 401 if unauthenticated" do get api("/users/9998") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should return a 404 error if user id not found" do get api("/users/9999", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end it "should return a 404 if invalid ID" do get api("/users/1ASDF", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -106,7 +106,7 @@ describe API::API, api: true do it "should create user with correct attributes" do post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -116,7 +116,7 @@ describe API::API, api: true do it "should create non-admin user" do post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -126,7 +126,7 @@ describe API::API, api: true do it "should create non-admin users by default" do post api('/users', admin), attributes_for(:user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -135,12 +135,12 @@ describe API::API, api: true do it "should return 201 Created on success" do post api("/users", admin), attributes_for(:user, projects_limit: 3) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it 'creates non-external users by default' do post api("/users", admin), attributes_for(:user) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -150,7 +150,7 @@ describe API::API, api: true do it 'should allow an external user to be created' do post api("/users", admin), attributes_for(:user, external: true) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -163,27 +163,27 @@ describe API::API, api: true do email: 'invalid email', password: 'password', name: 'test' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if name not given' do post api('/users', admin), attributes_for(:user).except(:name) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if password not given' do post api('/users', admin), attributes_for(:user).except(:password) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if email not given' do post api('/users', admin), attributes_for(:user).except(:email) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if username not given' do post api('/users', admin), attributes_for(:user).except(:username) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return 400 error if user does not validate' do @@ -194,7 +194,7 @@ describe API::API, api: true do name: 'test', bio: 'g' * 256, projects_limit: -1 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['password']). to eq(['is too short (minimum is 8 characters)']) expect(json_response['message']['bio']). @@ -207,7 +207,7 @@ describe API::API, api: true do it "shouldn't available for non admin users" do post api("/users", user), attributes_for(:user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end context 'with existing user' do @@ -227,7 +227,7 @@ describe API::API, api: true do password: 'password', username: 'foo' end.to change { User.count }.by(0) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Email has already been taken') end @@ -239,17 +239,16 @@ describe API::API, api: true do password: 'password', username: 'test' end.to change { User.count }.by(0) - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(json_response['message']).to eq('Username has already been taken') end end end describe "GET /users/sign_up" do - it "should redirect to sign in page" do get "/users/sign_up" - expect(response.status).to eq(302) + expect(response).to have_http_status(302) expect(response).to redirect_to(new_user_session_path) end end @@ -261,41 +260,41 @@ describe API::API, api: true do it "should update user with new bio" do put api("/users/#{user.id}", admin), { bio: 'new test bio' } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['bio']).to eq('new test bio') expect(user.reload.bio).to eq('new test bio') end it 'should update user with his own email' do put api("/users/#{user.id}", admin), email: user.email - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['email']).to eq(user.email) expect(user.reload.email).to eq(user.email) end it 'should update user with his own username' do put api("/users/#{user.id}", admin), username: user.username - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) expect(user.reload.username).to eq(user.username) end it "should update user's existing identity" do put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321') end it 'should update user with new identity' do put api("/users/#{user.id}", admin), provider: 'github', extern_uid: '67890' - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(user.reload.identities.first.extern_uid).to eq('67890') expect(user.reload.identities.first.provider).to eq('github') end it "should update admin status" do put api("/users/#{user.id}", admin), { admin: true } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['is_admin']).to eq(true) expect(user.reload.admin).to eq(true) end @@ -309,7 +308,7 @@ describe API::API, api: true do it "should not update admin status" do put api("/users/#{admin_user.id}", admin), { can_create_group: false } - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['is_admin']).to eq(true) expect(admin_user.reload.admin).to eq(true) expect(admin_user.can_create_group).to eq(false) @@ -317,18 +316,18 @@ describe API::API, api: true do it "should not allow invalid update" do put api("/users/#{user.id}", admin), { email: 'invalid email' } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(user.reload.email).not_to eq('invalid email') end it "shouldn't available for non admin users" do put api("/users/#{user.id}", user), attributes_for(:user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return 404 for non-existing user" do put api("/users/999999", admin), { bio: 'update should fail' } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -344,7 +343,7 @@ describe API::API, api: true do name: 'test', bio: 'g' * 256, projects_limit: -1 - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']['password']). to eq(['is too short (minimum is 8 characters)']) expect(json_response['message']['bio']). @@ -364,14 +363,14 @@ describe API::API, api: true do it 'should return 409 conflict error if email address exists' do put api("/users/#{@user.id}", admin), email: 'test@example.com' - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(@user.reload.email).to eq(@user.email) end it 'should return 409 conflict error if username taken' do @user_id = User.all.last.id put api("/users/#{@user.id}", admin), username: 'test' - expect(response.status).to eq(409) + expect(response).to have_http_status(409) expect(@user.reload.username).to eq(@user.username) end end @@ -382,13 +381,13 @@ describe API::API, api: true do it "should not create invalid ssh key" do post api("/users/#{user.id}/keys", admin), { title: "invalid key" } - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "key" not given') end it 'should not create key without title' do post api("/users/#{user.id}/keys", admin), key: 'some key' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "title" not given') end @@ -401,7 +400,7 @@ describe API::API, api: true do it "should return 405 for invalid ID" do post api("/users/ASDF/keys", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end @@ -411,14 +410,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/users/#{user.id}/keys") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return 404 for non-existing user' do get api('/users/999999/keys', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -426,14 +425,14 @@ describe API::API, api: true do user.keys << key user.save get api("/users/#{user.id}/keys", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(key.title) end it "should return 405 for invalid ID" do get api("/users/ASDF/keys", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end end @@ -444,7 +443,7 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do delete api("/users/#{user.id}/keys/42") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -455,20 +454,20 @@ describe API::API, api: true do expect do delete api("/users/#{user.id}/keys/#{key.id}", admin) end.to change { user.keys.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 error if user not found' do user.keys << key user.save delete api("/users/999999/keys/#{key.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'should return 404 error if key not foud' do delete api("/users/#{user.id}/keys/42", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Key Not Found') end end @@ -479,7 +478,7 @@ describe API::API, api: true do it "should not create invalid email" do post api("/users/#{user.id}/emails", admin), {} - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "email" not given') end @@ -492,7 +491,7 @@ describe API::API, api: true do it "should raise error for invalid ID" do post api("/users/ASDF/emails", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end @@ -502,14 +501,14 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/users/#{user.id}/emails") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'should return 404 for non-existing user' do get api('/users/999999/emails', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -517,14 +516,14 @@ describe API::API, api: true do user.emails << email user.save get api("/users/#{user.id}/emails", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['email']).to eq(email.email) end it "should raise error for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response.status).to eq(405) + expect(response).to have_http_status(405) end end end @@ -535,7 +534,7 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do delete api("/users/#{user.id}/emails/42") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -546,20 +545,20 @@ describe API::API, api: true do expect do delete api("/users/#{user.id}/emails/#{email.id}", admin) end.to change { user.emails.count }.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should return 404 error if user not found' do user.emails << email user.save delete api("/users/999999/emails/#{email.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'should return 404 error if email not foud' do delete api("/users/#{user.id}/emails/42", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Email Not Found') end @@ -574,24 +573,24 @@ describe API::API, api: true do it "should delete user" do delete api("/users/#{user.id}", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound expect(json_response['email']).to eq(user.email) end it "should not delete for unauthenticated user" do delete api("/users/#{user.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "shouldn't available for non admin users" do delete api("/users/#{user.id}", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end it "should return 404 for non-existing user" do delete api("/users/999999", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -603,7 +602,7 @@ describe API::API, api: true do describe "GET /user" do it "should return current user" do get api("/user", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['email']).to eq(user.email) expect(json_response['is_admin']).to eq(user.is_admin?) expect(json_response['can_create_project']).to eq(user.can_create_project?) @@ -613,7 +612,7 @@ describe API::API, api: true do it "should return 401 error if user is unauthenticated" do get api("/user") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -621,7 +620,7 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/user/keys") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -630,7 +629,7 @@ describe API::API, api: true do user.keys << key user.save get api("/user/keys", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first["title"]).to eq(key.title) end @@ -642,13 +641,13 @@ describe API::API, api: true do user.keys << key user.save get api("/user/keys/#{key.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["title"]).to eq(key.title) end it "should return 404 Not Found within invalid ID" do get api("/user/keys/42", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -657,13 +656,13 @@ describe API::API, api: true do user.save admin get api("/user/keys/#{key.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end it "should return 404 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -673,29 +672,29 @@ describe API::API, api: true do expect do post api("/user/keys", user), key_attrs end.to change{ user.keys.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it "should return a 401 error if unauthorized" do post api("/user/keys"), title: 'some title', key: 'some key' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should not create ssh key without key" do post api("/user/keys", user), title: 'title' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "key" not given') end it 'should not create ssh key without title' do post api('/user/keys', user), key: 'some key' - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "title" not given') end it "should not create ssh key without title" do post api("/user/keys", user), key: "somekey" - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -706,19 +705,19 @@ describe API::API, api: true do expect do delete api("/user/keys/#{key.id}", user) end.to change{user.keys.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return success if key ID not found" do delete api("/user/keys/42", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 401 error if unauthorized" do user.keys << key user.save delete api("/user/keys/#{key.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should raise error for invalid ID" do @@ -730,7 +729,7 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/user/emails") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -739,7 +738,7 @@ describe API::API, api: true do user.emails << email user.save get api("/user/emails", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first["email"]).to eq(email.email) end @@ -751,13 +750,13 @@ describe API::API, api: true do user.emails << email user.save get api("/user/emails/#{email.id}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["email"]).to eq(email.email) end it "should return 404 Not Found within invalid ID" do get api("/user/emails/42", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -766,13 +765,13 @@ describe API::API, api: true do user.save admin get api("/user/emails/#{email.id}", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end it "should return 404 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -782,17 +781,17 @@ describe API::API, api: true do expect do post api("/user/emails", user), email_attrs end.to change{ user.emails.count }.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) end it "should return a 401 error if unauthorized" do post api("/user/emails"), email: 'some email' - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should not create email with invalid email" do post api("/user/emails", user), {} - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('400 (Bad request) "email" not given') end end @@ -804,19 +803,19 @@ describe API::API, api: true do expect do delete api("/user/emails/#{email.id}", user) end.to change{user.emails.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return success if email ID not found" do delete api("/user/emails/42", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "should return 401 error if unauthorized" do user.emails << email user.save delete api("/user/emails/#{email.id}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end it "should raise error for invalid ID" do @@ -828,25 +827,25 @@ describe API::API, api: true do before { admin } it 'should block existing user' do put api("/users/#{user.id}/block", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(user.reload.state).to eq('blocked') end it 'should not re-block ldap blocked users' do put api("/users/#{ldap_blocked_user.id}/block", admin) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'should not be available for non admin users' do put api("/users/#{user.id}/block", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(user.reload.state).to eq('active') end it 'should return a 404 error if user id not found' do put api('/users/9999/block', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end end @@ -857,31 +856,31 @@ describe API::API, api: true do it 'should unblock existing user' do put api("/users/#{user.id}/unblock", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(user.reload.state).to eq('active') end it 'should unblock a blocked user' do put api("/users/#{blocked_user.id}/unblock", admin) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(blocked_user.reload.state).to eq('active') end it 'should not unblock ldap blocked users' do put api("/users/#{ldap_blocked_user.id}/unblock", admin) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'should not be available for non admin users' do put api("/users/#{user.id}/unblock", user) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) expect(user.reload.state).to eq('active') end it 'should return a 404 error if user id not found' do put api('/users/9999/block', admin) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index b1e1053d037..ddba18245f8 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do it 'should return project variables' do get api("/projects/#{project.id}/variables", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response).to be_a(Array) end end @@ -24,7 +24,7 @@ describe API::API, api: true do it 'should not return project variables' do get api("/projects/#{project.id}/variables", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -32,7 +32,7 @@ describe API::API, api: true do it 'should not return project variables' do get api("/projects/#{project.id}/variables") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -42,14 +42,14 @@ describe API::API, api: true do it 'should return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}", user) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response['value']).to eq(variable.value) end it 'should respond with 404 Not Found if requesting non-existing variable' do get api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -57,7 +57,7 @@ describe API::API, api: true do it 'should not return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -65,7 +65,7 @@ describe API::API, api: true do it 'should not return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -77,7 +77,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' end.to change{project.variables.count}.by(1) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') end @@ -87,7 +87,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2' end.to change{project.variables.count}.by(0) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -95,7 +95,7 @@ describe API::API, api: true do it 'should not create variable' do post api("/projects/#{project.id}/variables", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -103,7 +103,7 @@ describe API::API, api: true do it 'should not create variable' do post api("/projects/#{project.id}/variables") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -118,7 +118,7 @@ describe API::API, api: true do updated_variable = project.variables.first - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(value_before).to eq(variable.value) expect(updated_variable.value).to eq('VALUE_1_UP') end @@ -126,7 +126,7 @@ describe API::API, api: true do it 'should responde with 404 Not Found if requesting non-existing variable' do put api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -134,7 +134,7 @@ describe API::API, api: true do it 'should not update variable' do put api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -142,7 +142,7 @@ describe API::API, api: true do it 'should not update variable' do put api("/projects/#{project.id}/variables/#{variable.key}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -153,13 +153,13 @@ describe API::API, api: true do expect do delete api("/projects/#{project.id}/variables/#{variable.key}", user) end.to change{project.variables.count}.by(-1) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should responde with 404 Not Found if requesting non-existing variable' do delete api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end @@ -167,7 +167,7 @@ describe API::API, api: true do it 'should not delete variable' do delete api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end @@ -175,7 +175,7 @@ describe API::API, api: true do it 'should not delete variable' do delete api("/projects/#{project.id}/variables/#{variable.key}") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 7e50bea90d1..e7cbc3dd3a7 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -26,7 +26,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['sha']).to eq(build.sha) expect(runner.reload.platform).to eq("darwin") end @@ -34,7 +34,7 @@ describe Ci::API::API do it "should return 404 error if no pending build found" do post ci_api("/builds/register"), token: runner.token - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 error if no builds for specific runner" do @@ -43,7 +43,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "should return 404 error if no builds for shared runner" do @@ -52,7 +52,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: shared_runner.token - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it "returns options" do @@ -61,7 +61,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) end @@ -72,7 +72,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["variables"]).to eq([ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, @@ -91,7 +91,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["variables"]).to eq([ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, @@ -109,7 +109,7 @@ describe Ci::API::API do post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response["depends_on_builds"].count).to eq(2) expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec") end @@ -122,7 +122,7 @@ describe Ci::API::API do it do post ci_api("/builds/register"), token: runner.token, info: { param => value } - expect(response.status).to eq(404) + expect(response).to have_http_status(404) runner.reload is_expected.to eq(value) end @@ -172,7 +172,7 @@ describe Ci::API::API do end it "should update a running build" do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it 'should not override trace information when no trace is given' do @@ -252,13 +252,13 @@ describe Ci::API::API do context "should authorize posting artifact to running build" do it "using token as parameter" do post authorize_url, { token: build.token }, headers - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["TempPath"]).not_to be_nil end it "using token as header" do post authorize_url, {}, headers_with_token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_response["TempPath"]).not_to be_nil end end @@ -267,13 +267,13 @@ describe Ci::API::API do it "using token as parameter" do stub_application_setting(max_artifacts_size: 0) post authorize_url, { token: build.token, filesize: 100 }, headers - expect(response.status).to eq(413) + expect(response).to have_http_status(413) end it "using token as header" do stub_application_setting(max_artifacts_size: 0) post authorize_url, { filesize: 100 }, headers_with_token - expect(response.status).to eq(413) + expect(response).to have_http_status(413) end end @@ -281,7 +281,7 @@ describe Ci::API::API do before { post authorize_url, { token: 'invalid', filesize: 100 } } it 'should respond with forbidden' do - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -293,33 +293,52 @@ 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 { 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.status).to eq(201) - expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + describe 'uploading artifacts for a running build' do + shared_examples 'successful artifacts upload' 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 - it "uses accelerated file post" do - upload_artifacts(file_upload, headers_with_token, true) - expect(response.status).to eq(201) - expect(json_response["artifacts_file"]["filename"]).to eq(file_upload.original_filename) + context 'uses regular file post' do + before do + upload_artifacts(file_upload, headers_with_token, false) + end + + it_behaves_like 'successful artifacts upload' end - it "updates artifact" do - upload_artifacts(file_upload, headers_with_token) - upload_artifacts(file_upload2, headers_with_token) - expect(response.status).to eq(201) - expect(json_response["artifacts_file"]["filename"]).to eq(file_upload2.original_filename) + context 'uses accelerated file post' do + before do + upload_artifacts(file_upload, headers_with_token, true) + end + + it_behaves_like 'successful artifacts upload' + end + + context 'updates artifact' do + before do + upload_artifacts(file_upload2, headers_with_token) + upload_artifacts(file_upload, headers_with_token) + end + + it_behaves_like 'successful artifacts upload' end end @@ -329,6 +348,7 @@ describe Ci::API::API do let(:stored_artifacts_file) { build.reload.artifacts_file.file } let(:stored_metadata_file) { build.reload.artifacts_metadata.file } + let(:stored_artifacts_size) { build.reload.artifacts_size } before do post(post_url, post_data, headers_with_token) @@ -343,9 +363,10 @@ describe Ci::API::API do end it 'stores artifacts and artifacts metadata' do - expect(response.status).to eq(201) + 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_size).to eq(71759) end end @@ -355,7 +376,7 @@ describe Ci::API::API do end it 'is expected to respond with bad request' do - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'does not store metadata' do @@ -382,7 +403,7 @@ describe Ci::API::API do it 'updates when specified' do build.reload - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['artifacts_expire_at']).not_to be_empty expect(build.artifacts_expire_at).to be_within(5.minutes).of(Time.now + 7.days) end @@ -393,7 +414,7 @@ describe Ci::API::API do it 'ignores if not specified' do build.reload - expect(response.status).to eq(201) + expect(response).to have_http_status(201) expect(json_response['artifacts_expire_at']).to be_nil expect(build.artifacts_expire_at).to be_nil end @@ -404,21 +425,21 @@ describe Ci::API::API do it "should fail to post too large artifact" do stub_application_setting(max_artifacts_size: 0) upload_artifacts(file_upload, headers_with_token) - expect(response.status).to eq(413) + expect(response).to have_http_status(413) end end context "artifacts post request does not contain file" do it "should fail to post artifacts without file" do post post_url, {}, headers_with_token - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end context 'GitLab Workhorse is not configured' do it "should fail to post artifacts without GitLab-Workhorse" do post post_url, { token: build.token }, {} - expect(response.status).to eq(403) + expect(response).to have_http_status(403) end end end @@ -437,7 +458,7 @@ describe Ci::API::API do it "should fail to post artifacts for outside of tmp path" do upload_artifacts(file_upload, headers_with_token) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end end @@ -455,12 +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.status).to eq(200) + 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_nil end end @@ -475,14 +501,14 @@ describe Ci::API::API do end it 'should download artifact' do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(response.headers).to include download_headers end end context 'build does not has artifacts' do it 'should respond with not found' do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 72f6a3c981d..f12678e5a8e 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -21,17 +21,17 @@ describe Ci::API::API do context 'Handles errors' do it 'should return bad request if token is missing' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger") - expect(response.status).to eq(400) + expect(response).to have_http_status(400) end it 'should return not found if project is not found' do post ci_api('/projects/0/refs/master/trigger'), options - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end it 'should return unauthorized if token is for different project' do post ci_api("/projects/#{project2.ci_id}/refs/master/trigger"), options - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -40,14 +40,14 @@ describe Ci::API::API do it 'should create builds' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.size).to eq(2) end it 'should return bad request with no builds created if there\'s no commit for that ref' do post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('No builds created') end @@ -58,19 +58,19 @@ describe Ci::API::API do it 'should validate variables to be a hash' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value') - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a hash') end it 'should validate variables needs to be a map of key-valued strings' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) }) - expect(response.status).to eq(400) + expect(response).to have_http_status(400) expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') end it 'create trigger request with variables' do post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables) - expect(response.status).to eq(201) + expect(response).to have_http_status(201) pipeline.builds.reload expect(pipeline.builds.first.trigger_request.variables).to eq(variables) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index fd26ca97818..82ab582beac 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -14,7 +14,7 @@ describe 'Git HTTP requests', lib: true do context "when no authentication is provided" do it "responds with status 401 (no project existence information leak)" do download('doesnt/exist.git') do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -23,7 +23,7 @@ describe 'Git HTTP requests', lib: true do context "when authentication fails" do it "responds with status 401" do download('doesnt/exist.git', user: user.username, password: "nope") do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -31,7 +31,7 @@ describe 'Git HTTP requests', lib: true do context "when authentication succeeds" do it "responds with status 404" do download('/doesnt/exist.git', user: user.username, password: user.password) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -46,7 +46,7 @@ describe 'Git HTTP requests', lib: true do download("/#{wiki.repository.path_with_namespace}.git") do |response| json_body = ActiveSupport::JSON.decode(response.body) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) end end @@ -62,13 +62,13 @@ describe 'Git HTTP requests', lib: true do it "downloads get status 200" do download(path, {}) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end it "uploads get status 401" do upload(path, {}) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -77,7 +77,7 @@ describe 'Git HTTP requests', lib: true do it "uploads get status 200 (because Git hooks do the real check)" do upload(path, env) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -86,7 +86,7 @@ describe 'Git HTTP requests', lib: true do allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false) upload(path, env) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -97,7 +97,7 @@ describe 'Git HTTP requests', lib: true do allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) download(path, {}) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -111,13 +111,13 @@ describe 'Git HTTP requests', lib: true do context "when no authentication is provided" do it "responds with status 401 to downloads" do download(path, {}) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end it "responds with status 401 to uploads" do upload(path, {}) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -128,7 +128,7 @@ describe 'Git HTTP requests', lib: true do context "when authentication fails" do it "responds with status 401" do download(path, env) do |response| - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -139,7 +139,7 @@ describe 'Git HTTP requests', lib: true do clone_get(path, env) - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -158,7 +158,7 @@ describe 'Git HTTP requests', lib: true do project.team << [user, :master] download(path, env) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end end @@ -169,12 +169,12 @@ describe 'Git HTTP requests', lib: true do clone_get(path, env) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "uploads get status 200" do upload(path, env) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -188,13 +188,13 @@ describe 'Git HTTP requests', lib: true do it "downloads get status 200" do clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "uploads get status 401 (no project existence information leak)" do push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end @@ -232,13 +232,13 @@ describe 'Git HTTP requests', lib: true do context "when the user doesn't have access to the project" do it "downloads get status 404" do download(path, user: user.username, password: user.password) do |response| - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end it "uploads get status 200 (because Git hooks do the real check)" do upload(path, user: user.username, password: user.password) do |response| - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end end @@ -256,13 +256,13 @@ describe 'Git HTTP requests', lib: true do it "downloads get status 200" do clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end it "uploads get status 401 (no project existence information leak)" do push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end end @@ -336,7 +336,7 @@ describe 'Git HTTP requests', lib: true do end it "returns the file" do - expect(response.status).to eq(200) + expect(response).to have_http_status(200) end end @@ -344,29 +344,29 @@ describe 'Git HTTP requests', lib: true do before { get "/#{project.path_with_namespace}/blob/master/info/refs" } it "returns not found" do - expect(response.status).to eq(404) + expect(response).to have_http_status(404) end end 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 diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index d2d4a9eca18..c6172b9cc7d 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -11,12 +11,12 @@ describe JwtController do context 'existing service' do subject! { get '/jwt/auth', parameters } - it { expect(response.status).to eq(200) } + it { expect(response).to have_http_status(200) } context 'returning custom http code' do let(:service) { double(execute: { http_status: 505 }) } - it { expect(response.status).to eq(505) } + it { expect(response).to have_http_status(505) } end end @@ -36,7 +36,7 @@ describe JwtController do context 'project with disabled CI' do let(:builds_enabled) { false } - it { expect(response.status).to eq(403) } + it { expect(response).to have_http_status(403) } end end @@ -56,14 +56,14 @@ describe JwtController do subject! { get '/jwt/auth', parameters, headers } - it { expect(response.status).to eq(403) } + it { expect(response).to have_http_status(403) } end end context 'unknown service' do subject! { get '/jwt/auth', service: 'unknown' } - it { expect(response.status).to eq(404) } + it { expect(response).to have_http_status(404) } end def credentials(login, password) 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/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb index deab242f45a..309213bd44c 100644 --- a/spec/services/create_commit_builds_service_spec.rb +++ b/spec/services/create_commit_builds_service_spec.rb @@ -83,6 +83,9 @@ describe CreateCommitBuildsService, services: true do context 'when commit contains a [ci skip] directive' do let(:message) { "some message[ci skip]" } + let(:messageFlip) { "some message[skip ci]" } + let(:capMessage) { "some message[CI SKIP]" } + let(:capMessageFlip) { "some message[SKIP CI]" } before do allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message } @@ -96,12 +99,55 @@ describe CreateCommitBuildsService, services: true do after: '31das312', commits: commits ) + + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + + it "skips builds creation if there is [skip ci] tag in commit message" do + commits = [{ message: messageFlip }] + pipeline = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + + it "skips builds creation if there is [CI SKIP] tag in commit message" do + commits = [{ message: capMessage }] + pipeline = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + + it "skips builds creation if there is [SKIP CI] tag in commit message" do + commits = [{ message: capMessageFlip }] + pipeline = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + expect(pipeline).to be_persisted expect(pipeline.builds.any?).to be false expect(pipeline.status).to eq("skipped") end - it "does not skips builds creation if there is no [ci skip] tag in commit message" do + it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" } commits = [{ message: "some message" }] 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/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb index afa89b84175..eca8ddd8ea4 100644 --- a/spec/services/destroy_group_service_spec.rb +++ b/spec/services/destroy_group_service_spec.rb @@ -23,8 +23,8 @@ describe DestroyGroupService, services: true do Sidekiq::Testing.inline! { destroy_group(group, user) } end - it { expect(gitlab_shell.exists?(group.path)).to be_falsey } - it { expect(gitlab_shell.exists?(remove_path)).to be_falsey } + it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey } + it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey } end context 'Sidekiq fake' do @@ -33,8 +33,8 @@ describe DestroyGroupService, services: true do Sidekiq::Testing.fake! { destroy_group(group, user) } end - it { expect(gitlab_shell.exists?(group.path)).to be_falsey } - it { expect(gitlab_shell.exists?(remove_path)).to be_truthy } + it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey } + it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy } end end diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index 2bb9c3b3db3..3fc37a315c0 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -16,19 +16,18 @@ 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) + 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 @@ -39,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 @@ -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 f99ad046f0d..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 } @@ -40,10 +38,21 @@ describe GitPushService, services: true do subject end + + it 'flushes the branches cache' do + expect(project.repository).to receive(:expire_branches_cache) + + subject + end + + it 'flushes the branch count cache' do + expect(project.repository).to receive(:expire_branch_count_cache) + + subject + end end context 'existing branch' do - it { is_expected.to be_truthy } it 'flushes general cached data' do @@ -52,10 +61,21 @@ describe GitPushService, services: true do subject end + + it 'does not flush the branches cache' do + expect(project.repository).not_to receive(:expire_branches_cache) + + subject + end + + it 'does not flush the branch count cache' do + expect(project.repository).not_to receive(:expire_branch_count_cache) + + subject + end end context 'rm branch' do - let(:newrev) { @blankrev } it { is_expected.to be_truthy } @@ -66,6 +86,18 @@ describe GitPushService, services: true do subject end + it 'flushes the branches cache' do + expect(project.repository).to receive(:expire_branches_cache) + + subject + end + + it 'flushes the branch count cache' do + expect(project.repository).to receive(:expire_branch_count_cache) + + subject + end + it 'flushes general cached data' do expect(project.repository).to receive(:expire_cache). with('master', newrev) @@ -187,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 @@ -314,6 +345,8 @@ describe GitPushService, services: true do it "doesn't close issues when external issue tracker is in use" do allow_any_instance_of(Project).to receive(:default_issues_tracker?). and_return(false) + external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid) + allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(external_issue_tracker) # The push still shouldn't create cross-reference notes. expect do @@ -453,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/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index a63656e6268..a4fcd44882d 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -11,6 +11,31 @@ describe GitTagPushService, services: true do let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0 let(:ref) { 'refs/tags/v1.1.0' } + describe "Push tags" do + subject do + service.execute + service + end + + it 'flushes general cached data' do + expect(project.repository).to receive(:expire_cache) + + subject + end + + it 'flushes the tags cache' do + expect(project.repository).to receive(:expire_tags_cache) + + subject + end + + it 'flushes the tag count cache' do + expect(project.repository).to receive(:expire_tag_count_cache) + + subject + end + end + describe "Git Tag Push Data" do before do service.execute 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 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/notes/diff_position_update_service_spec.rb b/spec/services/notes/diff_position_update_service_spec.rb new file mode 100644 index 00000000000..110efb54fa0 --- /dev/null +++ b/spec/services/notes/diff_position_update_service_spec.rb @@ -0,0 +1,175 @@ +require 'spec_helper' + +describe Notes::DiffPositionUpdateService, services: true do + let(:project) { create(:project) } + let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") } + let(:modify_commit) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e") } + let(:edit_commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } + + let(:path) { "files/ruby/popen.rb" } + + let(:old_diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: create_commit.parent_id, + head_sha: modify_commit.sha + ) + end + + let(:new_diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: create_commit.parent_id, + head_sha: edit_commit.sha + ) + end + + subject do + described_class.new( + project, + nil, + old_diff_refs: old_diff_refs, + new_diff_refs: new_diff_refs, + paths: [path] + ) + end + + # old diff: + # 1 + require 'fileutils' + # 2 + require 'open3' + # 3 + + # 4 + module Popen + # 5 + extend self + # 6 + + # 7 + def popen(cmd, path=nil) + # 8 + unless cmd.is_a?(Array) + # 9 + raise "System commands must be given as an array of strings" + # 10 + end + # 11 + + # 12 + path ||= Dir.pwd + # 13 + vars = { "PWD" => path } + # 14 + options = { chdir: path } + # 15 + + # 16 + unless File.directory?(path) + # 17 + FileUtils.mkdir_p(path) + # 18 + end + # 19 + + # 20 + @cmd_output = "" + # 21 + @cmd_status = 0 + # 22 + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # 23 + @cmd_output << stdout.read + # 24 + @cmd_output << stderr.read + # 25 + @cmd_status = wait_thr.value.exitstatus + # 26 + end + # 27 + + # 28 + return @cmd_output, @cmd_status + # 29 + end + # 30 + end + # + # new diff: + # 1 + require 'fileutils' + # 2 + require 'open3' + # 3 + + # 4 + module Popen + # 5 + extend self + # 6 + + # 7 + def popen(cmd, path=nil) + # 8 + unless cmd.is_a?(Array) + # 9 + raise RuntimeError, "System commands must be given as an array of strings" + # 10 + end + # 11 + + # 12 + path ||= Dir.pwd + # 13 + + # 14 + vars = { + # 15 + "PWD" => path + # 16 + } + # 17 + + # 18 + options = { + # 19 + chdir: path + # 20 + } + # 21 + + # 22 + unless File.directory?(path) + # 23 + FileUtils.mkdir_p(path) + # 24 + end + # 25 + + # 26 + @cmd_output = "" + # 27 + @cmd_status = 0 + # 28 + + # 29 + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # 30 + @cmd_output << stdout.read + # 31 + @cmd_output << stderr.read + # 32 + @cmd_status = wait_thr.value.exitstatus + # 33 + end + # 34 + + # 35 + return @cmd_output, @cmd_status + # 36 + end + # 37 + end + # + # old->new diff: + # .. .. @@ -6,12 +6,18 @@ module Popen + # 6 6 + # 7 7 def popen(cmd, path=nil) + # 8 8 unless cmd.is_a?(Array) + # 9 - raise "System commands must be given as an array of strings" + # 9 + raise RuntimeError, "System commands must be given as an array of strings" + # 10 10 end + # 11 11 + # 12 12 path ||= Dir.pwd + # 13 - vars = { "PWD" => path } + # 14 - options = { chdir: path } + # 13 + + # 14 + vars = { + # 15 + "PWD" => path + # 16 + } + # 17 + + # 18 + options = { + # 19 + chdir: path + # 20 + } + # 15 21 + # 16 22 unless File.directory?(path) + # 17 23 FileUtils.mkdir_p(path) + # 18 24 end + # 19 25 + # 20 26 @cmd_output = "" + # 21 27 @cmd_status = 0 + # 28 + + # 22 29 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # 23 30 @cmd_output << stdout.read + # 24 31 @cmd_output << stderr.read + # .. .. + + describe "#execute" do + let(:note) { create(:diff_note_on_merge_request, project: project, position: old_position) } + + let(:old_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + old_line: nil, + new_line: line, + diff_refs: old_diff_refs + ) + end + + context "when the diff line is the same" do + let(:line) { 16 } + + it "updates the position" do + subject.execute(note) + + expect(note.original_position).to eq(old_position) + expect(note.position).not_to eq(old_position) + expect(note.position.new_line).to eq(22) + end + end + + context "when the diff line has changed" do + let(:line) { 9 } + + it "doesn't update the position" do + subject.execute(note) + + expect(note.original_position).to eq(old_position) + expect(note.position).to eq(old_position) + end + end + end +end diff --git a/spec/services/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/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 4c5ced7e746..bd4dc6a0f79 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -12,7 +12,7 @@ describe Projects::HousekeepingService do it 'enqueues a sidekiq job' do expect(subject).to receive(:try_obtain_lease).and_return(true) - expect(GitlabShellOneShotWorker).to receive(:perform_async).with(:gc, project.path_with_namespace) + expect(GitlabShellOneShotWorker).to receive(:perform_async).with(:gc, project.repository_storage_path, project.path_with_namespace) subject.execute expect(project.pushes_since_gc).to eq(0) diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index 068c9a1219c..d5d4d7c56ef 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -36,7 +36,7 @@ describe Projects::ImportService, services: true do end it 'succeeds if repository import is successfully' do - expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true) + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true) result = subject.execute @@ -44,7 +44,7 @@ describe Projects::ImportService, services: true do end it 'fails if repository import fails' do - expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository')) + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository')) result = subject.execute @@ -64,7 +64,7 @@ describe Projects::ImportService, services: true do end it 'succeeds if importer succeeds' do - expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true) + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true) expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true) result = subject.execute @@ -74,7 +74,7 @@ describe Projects::ImportService, services: true do it 'flushes various caches' do expect_any_instance_of(Gitlab::Shell).to receive(:import_repository). - with(project.path_with_namespace, project.import_url). + with(project.repository_storage_path, project.path_with_namespace, project.import_url). and_return(true) expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute). @@ -90,7 +90,7 @@ describe Projects::ImportService, services: true do end it 'fails if importer fails' do - expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true) + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true) expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false) result = subject.execute @@ -100,7 +100,7 @@ describe Projects::ImportService, services: true do end it 'fails if importer raise an error' do - expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true) + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_return(true) expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API')) result = subject.execute 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/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb new file mode 100644 index 00000000000..14f3301d9f4 --- /dev/null +++ b/spec/services/search/snippet_service_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Search::SnippetService, services: true do + let(:author) { create(:author) } + let(:project) { create(:empty_project) } + + let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') } + let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') } + let!(:private_snippet) { create(:snippet, :private, content: 'password: XXX', author: author) } + + let!(:project_public_snippet) { create(:snippet, :public, project: project, content: 'password: XXX') } + let!(:project_internal_snippet) { create(:snippet, :internal, project: project, content: 'password: XXX') } + let!(:project_private_snippet) { create(:snippet, :private, project: project, content: 'password: XXX') } + + describe '#execute' do + context 'unauthenticated' do + it 'returns public snippets only' do + search = described_class.new(nil, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, project_public_snippet] + end + end + + context 'authenticated' do + it 'returns only public & internal snippets for regular users' do + user = create(:user) + search = described_class.new(user, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns public, internal snippets and project private snippets for project members' do + member = create(:user) + project.team << [member, :developer] + search = described_class.new(member, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + + it 'returns public, internal and private snippets where user is the author' do + search = described_class.new(author, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet] + end + + it 'returns all snippets when user is admin' do + admin = create(:admin) + search = described_class.new(admin, search: 'password') + results = search.execute + + expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] + end + end + end +end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 85dd30bf48c..43693441450 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -213,7 +213,7 @@ describe SystemNoteService, services: true do create(:merge_request, source_project: project, target_project: project) end - subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.last_commit) } + subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.diff_head_commit) } it_behaves_like 'a system note' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b43f38ef202..606da1b7605 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,11 +3,6 @@ if ENV['SIMPLECOV'] SimpleCov.start :rails end -if ENV['COVERALLS'] - require 'coveralls' - Coveralls.wear_merged! -end - ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) 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/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 diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 498bd4bf800..6b99b0f24cb 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -17,6 +17,7 @@ module TestEnv "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', 'binary-encoding' => '7b1cf43', + 'gitattributes' => '5a62481', } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily @@ -62,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 @@ -79,7 +80,7 @@ module TestEnv end def setup_gitlab_shell - unless File.directory?(Rails.root.join(*%w(tmp tests gitlab-shell))) + unless File.directory?(Gitlab.config.gitlab_shell.path) `rake gitlab:shell:install` end end @@ -126,14 +127,14 @@ module TestEnv def copy_repo(project) base_repo_path = File.expand_path(factory_repo_path_bare) - target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git") + target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.namespace.path}/#{project.path}.git") FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path end def repos_path - Gitlab.config.gitlab_shell.repos_path + Gitlab.config.repositories.storages.default end def backup_path @@ -142,7 +143,7 @@ module TestEnv def copy_forked_repo_with_submodules(project) base_repo_path = File.expand_path(forked_repo_path_bare) - target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git") + target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.namespace.path}/#{project.path}.git") FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 25da0917134..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 @@ -98,67 +97,107 @@ describe 'gitlab:app namespace rake task' do @backup_tar = tars_glob.first end - before do - create_backup - end - - after do - FileUtils.rm(@backup_tar) - end + context 'tar creation' do + before do + create_backup + end - context 'archive file permissions' do - it 'should set correct permissions on the tar file' do - expect(File.exist?(@backup_tar)).to be_truthy - expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600') + after do + FileUtils.rm(@backup_tar) end - context 'with custom archive_permissions' do - before do - allow(Gitlab.config.backup).to receive(:archive_permissions).and_return(0651) - # We created a backup in a before(:all) so it got the default permissions. - # We now need to do some work to create a _new_ backup file using our stub. - FileUtils.rm(@backup_tar) - create_backup + context 'archive file permissions' do + it 'should set correct permissions on the tar file' do + expect(File.exist?(@backup_tar)).to be_truthy + expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600') end - it 'uses the custom permissions' do - expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100651') + context 'with custom archive_permissions' do + before do + allow(Gitlab.config.backup).to receive(:archive_permissions).and_return(0651) + # We created a backup in a before(:all) so it got the default permissions. + # We now need to do some work to create a _new_ backup file using our stub. + FileUtils.rm(@backup_tar) + create_backup + end + + it 'uses the custom permissions' do + expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100651') + end end end - end - it 'should set correct permissions on the tar contents' do - tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} - ) - expect(exit_status).to eq(0) - expect(tar_contents).to match('db/') - expect(tar_contents).to match('uploads.tar.gz') - expect(tar_contents).to match('repositories/') - expect(tar_contents).to match('builds.tar.gz') - expect(tar_contents).to match('artifacts.tar.gz') - expect(tar_contents).to match('lfs.tar.gz') - expect(tar_contents).to match('registry.tar.gz') - expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/) - end + it 'should set correct permissions on the tar contents' do + tar_contents, exit_status = Gitlab::Popen.popen( + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} + ) + expect(exit_status).to eq(0) + expect(tar_contents).to match('db/') + expect(tar_contents).to match('uploads.tar.gz') + expect(tar_contents).to match('repositories/') + expect(tar_contents).to match('builds.tar.gz') + expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).to match('lfs.tar.gz') + expect(tar_contents).to match('registry.tar.gz') + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/) + end - it 'should delete temp directories' do - temp_dirs = Dir.glob( - File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}') - ) + it 'should delete temp directories' do + temp_dirs = Dir.glob( + File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}') + ) + + expect(temp_dirs).to be_empty + end - expect(temp_dirs).to be_empty + context 'registry disabled' do + let(:enable_registry) { false } + + it 'should not create registry.tar.gz' do + tar_contents, exit_status = Gitlab::Popen.popen( + %W{tar -tvf #{@backup_tar}} + ) + expect(exit_status).to eq(0) + expect(tar_contents).not_to match('registry.tar.gz') + end + end end - context 'registry disabled' do - let(:enable_registry) { false } + context 'multiple repository storages' do + let(:project_a) { create(:project, repository_storage: 'default') } + let(:project_b) { create(:project, repository_storage: 'custom') } + + before do + FileUtils.mkdir('tmp/tests/default_storage') + FileUtils.mkdir('tmp/tests/custom_storage') + storages = { + 'default' => 'tmp/tests/default_storage', + 'custom' => 'tmp/tests/custom_storage' + } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + + # Create the projects now, after mocking the settings but before doing the backup + project_a + project_b + + # We only need a backup of the repositories for this test + ENV["SKIP"] = "db,uploads,builds,artifacts,lfs,registry" + create_backup + end + + after do + FileUtils.rm_rf('tmp/tests/default_storage') + FileUtils.rm_rf('tmp/tests/custom_storage') + FileUtils.rm(@backup_tar) + end - it 'should not create registry.tar.gz' do + it 'should include repositories in all repository storages' do tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar}} + %W{tar -tvf #{@backup_tar} repositories} ) expect(exit_status).to eq(0) - expect(tar_contents).not_to match('registry.tar.gz') + expect(tar_contents).to match("repositories/#{project_a.path_with_namespace}.bundle") + expect(tar_contents).to match("repositories/#{project_b.path_with_namespace}.bundle") end end end # backup_create task diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb new file mode 100644 index 00000000000..cd18d19ef5e --- /dev/null +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'projects/builds/show' do + include Devise::TestHelpers + + let(:build) { create(:ci_build) } + let(:project) { build.project } + + before do + assign(:build, build) + assign(:project, project) + + allow(view).to receive(:can?).and_return(true) + end + + context 'when build is running' do + before do + build.run! + render + end + + it 'does not show retry button' do + expect(rendered).not_to have_link('Retry') + end + end + + context 'when build is not running' do + before do + build.success! + render + end + + it 'shows retry button' do + expect(rendered).to have_link('Retry') + end + end +end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index b8e73682c91..20b1a343c27 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -91,6 +91,6 @@ describe PostReceive do end def pwd(project) - File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace) + File.join(Gitlab.config.repositories.storages.default, project.path_with_namespace) end end 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) diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 4ef05eb29d2..5f762282b5e 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -14,6 +14,7 @@ describe RepositoryForkWorker do describe "#perform" do it "creates a new repository from a fork" do expect(shell).to receive(:fork_repository).with( + project.repository_storage_path, project.path_with_namespace, fork_project.namespace.path ).and_return(true) @@ -25,9 +26,11 @@ describe RepositoryForkWorker do end it 'flushes various caches' do - expect(shell).to receive(:fork_repository). - with(project.path_with_namespace, fork_project.namespace.path). - and_return(true) + expect(shell).to receive(:fork_repository).with( + project.repository_storage_path, + project.path_with_namespace, + fork_project.namespace.path + ).and_return(true) expect_any_instance_of(Repository).to receive(:expire_emptiness_caches). and_call_original diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore index f6b286cea98..e5df7b9150e 100644 --- a/vendor/gitignore/Android.gitignore +++ b/vendor/gitignore/Android.gitignore @@ -35,6 +35,7 @@ captures/ # Intellij *.iml .idea/workspace.xml +.idea/libraries # Keystore files *.jks diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index 4581ef2eeef..259148fa18f 100644 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore @@ -1,3 +1,6 @@ +# Prerequisites +*.d + # Compiled Object files *.slo *.lo diff --git a/vendor/gitignore/C.gitignore b/vendor/gitignore/C.gitignore index f805e810e5c..7a065c709c7 100644 --- a/vendor/gitignore/C.gitignore +++ b/vendor/gitignore/C.gitignore @@ -1,3 +1,6 @@ +# Prerequisites +*.d + # Object files *.o *.ko diff --git a/vendor/gitignore/Gradle.gitignore b/vendor/gitignore/Gradle.gitignore index 77617a15c38..a1fc39c070f 100644 --- a/vendor/gitignore/Gradle.gitignore +++ b/vendor/gitignore/Gradle.gitignore @@ -1,5 +1,5 @@ .gradle -build/ +/build/ # Ignore Gradle GUI config gradle-app.setting diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE index b8a103ac9b1..0e259d42c99 100644 --- a/vendor/gitignore/LICENSE +++ b/vendor/gitignore/LICENSE @@ -1,19 +1,121 @@ -Copyright (c) 2016 GitHub, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index 5148e527a7e..aea5294de9d 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -7,6 +7,7 @@ npm-debug.log* pids *.pid *.seed +*.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore index 4123a577c47..3cb097c9d5e 100644 --- a/vendor/gitignore/TeX.gitignore +++ b/vendor/gitignore/TeX.gitignore @@ -152,6 +152,9 @@ pythontex-files-*/ # todonotes *.tdo +# easy-todo +*.lod + # xindy *.xdy diff --git a/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml new file mode 100644 index 00000000000..1678a47f9ac --- /dev/null +++ b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml @@ -0,0 +1,102 @@ +--- +# Build JAVA applications using Apache Maven (http://maven.apache.org) +# For docker image tags see https://hub.docker.com/_/maven/ +# +# For general lifecycle information see https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html +# +# This template will build and test your projects as well as create the documentation. +# +# * Caches downloaded dependencies and plugins between invocation. +# * Does only verify merge requests but deploy built artifacts of the +# master branch. +# * Shows how to use multiple jobs in test stage for verifying functionality +# with multiple JDKs. +# * Uses site:stage to collect the documentation for multi-module projects. +# * Publishes the documentation for `master` branch. + +variables: + # This will supress any download for dependencies and plugins or upload messages which would clutter the console log. + # `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work. + MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true" + # As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used + # when running from the command line. + # `installAtEnd` and `deployAtEnd`are only effective with recent version of the corresponding plugins. + MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true" + +# Cache downloaded dependencies and plugins between builds. +cache: + paths: + - /root/.m2/repository/ + +# This will only validate and compile stuff and run e.g. maven-enforcer-plugin. +# Because some enforcer rules might check dependency convergence and class duplications +# we use `test-compile` here instead of `validate`, so the correct classpath is picked up. +.validate: &validate + stage: build + script: + - 'mvn $MAVEN_CLI_OPTS test-compile' + +# For merge requests do not `deploy` but only run `verify`. +# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html +.verify: &verify + stage: test + script: + - 'mvn $MAVEN_CLI_OPTS verify site site:stage' + except: + - master + +# Validate merge requests using JDK7 +validate:jdk7: + <<: *validate + image: maven:3.3.9-jdk-7 + +# Validate merge requests using JDK8 +validate:jdk8: + <<: *validate + image: maven:3.3.9-jdk-8 + +# Verify merge requests using JDK7 +verify:jdk7: + <<: *verify + image: maven:3.3.9-jdk-7 + +# Verify merge requests using JDK8 +verify:jdk8: + <<: *verify + image: maven:3.3.9-jdk-8 + + +# For `master` branch run `mvn deploy` automatically. +# Here you need to decide whether you want to use JDK7 or 8. +# To get this working you need to define a volume while configuring your gitlab-ci-multi-runner. +# Mount your `settings.xml` as `/root/.m2/settings.xml` which holds your secrets. +# See https://maven.apache.org/settings.html +deploy:jdk8: + # Use stage test here, so the pages job may later pickup the created site. + stage: test + script: + - 'mvn $MAVEN_CLI_OPTS deploy site site:stage' + only: + - master + # Archive up the built documentation site. + artifacts: + paths: + - target/staging + image: maven:3.3.9-jdk-8 + + +pages: + image: busybox:latest + stage: deploy + script: + # Because Maven appends the artifactId automatically to the staging path if you did define a parent pom, + # you might need to use `mv target/staging/YOUR_ARTIFACT_ID public` instead. + - mv target/staging public + dependencies: + - deploy:jdk8 + artifacts: + paths: + - public + only: + - master + diff --git a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml index 7fcc0b436b5..7fcc0b436b5 100644 --- a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml index 791afdd23f1..791afdd23f1 100644 --- a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml index 249a168aa33..249a168aa33 100644 --- a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml index dd3ef149668..dd3ef149668 100644 --- a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml index b468d79bcad..b468d79bcad 100644 --- a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml index 45df6975259..45df6975259 100644 --- a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml index f5b40f2b9f1..f5b40f2b9f1 100644 --- a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml index 36918fc005a..36918fc005a 100644 --- a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml index c5c44a5d86c..c5c44a5d86c 100644 --- a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml index 50e8b7ccd46..50e8b7ccd46 100644 --- a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml index 9f4cc0574d6..9f4cc0574d6 100644 --- a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml index b469b316ba5..b469b316ba5 100644 --- a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml index 4762ec9acfd..4762ec9acfd 100644 --- a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml index c5f3154f587..c5f3154f587 100644 --- a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml index 78f3e39949f..2a761bbd127 100644 --- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -10,12 +10,19 @@ services: - redis:latest - postgres:latest +# Cache gems in between builds +cache: + paths: + - vendor/ruby + # This is a basic example for a gem or script which doesn't use # services such as redis or postgres before_script: - - gem install bundler # Bundler is not installed with the image - - bundle install -j $(nproc) # Install dependencies + - ruby -v # Print out ruby version for debugging + - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image + - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby +# Optional - Delete if not using `rubocop` rubocop: script: - rubocop @@ -26,5 +33,5 @@ rspec: rails: script: - - rake db:migrate - - rspec spec + - bundle exec rake db:migrate + - bundle exec rake test diff --git a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml new file mode 100644 index 00000000000..ae3f7405ea3 --- /dev/null +++ b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml @@ -0,0 +1,23 @@ +# Unofficial language image. Look for the different tagged releases at: +# https://hub.docker.com/r/scorpil/rust/tags/ +image: "scorpil/rust:stable" + +# Optional: Pick zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +#services: +# - mysql:latest +# - redis:latest +# - postgres:latest + +# Optional: Install a C compiler, cmake and git into the container. +# You will often need this when you (or any of your dependencies) depends on C code. +#before_script: +#- apt-get update -yqq +#- apt-get install -yqq --no-install-recommends build-essential + +# Use cargo to test the project +test:cargo: + script: + - rustc --version && cargo --version # Print version info for debugging + - cargo test --verbose --jobs 1 --release # Don't paralize to make errors more readable diff --git a/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml b/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml new file mode 100644 index 00000000000..443ba42e38c --- /dev/null +++ b/vendor/gitlab-ci-yml/Scala.gitlab-ci.yml @@ -0,0 +1,22 @@ +# Official Java image. Look for the different tagged releases at +# https://hub.docker.com/r/library/java/tags/ . A Java image is not required +# but an image with a JVM speeds up the build a bit. +image: java:8 + +before_script: + # Enable the usage of sources over https + - apt-get update -yqq + - apt-get install apt-transport-https -yqq + # Add keyserver for SBT + - echo "deb http://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list + - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 642AC823 + # Install SBT + - apt-get update -yqq + - apt-get install sbt -yqq + # Log the sbt version + - sbt sbt-version + +test: + script: + # Execute your project's tests + - sbt clean test |